early-access version 1611

This commit is contained in:
pineappleEA
2021-04-18 05:35:25 +02:00
parent 16f54e367d
commit 18db69f039
1409 changed files with 545335 additions and 10 deletions

View File

@@ -0,0 +1,396 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file action_configparser.c
*
* Source file for the parser for action config files.
*/
#include <SDL_stdinc.h>
#include <SDL_test.h>
#include <string.h>
#include "SDL_visualtest_action_configparser.h"
#include "SDL_visualtest_rwhelper.h"
#include "SDL_visualtest_parsehelper.h"
static void
FreeAction(SDLVisualTest_Action* action)
{
if(!action)
return;
switch(action->type)
{
case SDL_ACTION_LAUNCH:
{
char* path;
char* args;
path = action->extra.process.path;
args = action->extra.process.args;
if(path)
SDL_free(path);
if(args)
SDL_free(args);
action->extra.process.path = NULL;
action->extra.process.args = NULL;
}
break;
}
}
int
SDLVisualTest_EnqueueAction(SDLVisualTest_ActionQueue* queue,
SDLVisualTest_Action action)
{
SDLVisualTest_ActionNode* node;
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return 0;
}
node = (SDLVisualTest_ActionNode*)SDL_malloc(
sizeof(SDLVisualTest_ActionNode));
if(!node)
{
SDLTest_LogError("malloc() failed");
return 0;
}
node->action = action;
node->next = NULL;
queue->size++;
if(!queue->rear)
queue->rear = queue->front = node;
else
{
queue->rear->next = node;
queue->rear = node;
}
return 1;
}
int
SDLVisualTest_DequeueAction(SDLVisualTest_ActionQueue* queue)
{
SDLVisualTest_ActionNode* node;
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return 0;
}
if(SDLVisualTest_IsActionQueueEmpty(queue))
{
SDLTest_LogError("cannot dequeue from empty queue");
return 0;
}
if(queue->front == queue->rear)
{
FreeAction(&queue->front->action);
SDL_free(queue->front);
queue->front = queue->rear = NULL;
}
else
{
node = queue->front;
queue->front = queue->front->next;
FreeAction(&node->action);
SDL_free(node);
}
queue->size--;
return 1;
}
void
SDLVisualTest_InitActionQueue(SDLVisualTest_ActionQueue* queue)
{
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return;
}
queue->front = NULL;
queue->rear = NULL;
queue->size = 0;
}
SDLVisualTest_Action*
SDLVisualTest_GetQueueFront(SDLVisualTest_ActionQueue* queue)
{
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return NULL;
}
if(!queue->front)
{
SDLTest_LogError("cannot get front of empty queue");
return NULL;
}
return &queue->front->action;
}
int
SDLVisualTest_IsActionQueueEmpty(SDLVisualTest_ActionQueue* queue)
{
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return 1;
}
if(queue->size > 0)
return 0;
return 1;
}
void
SDLVisualTest_EmptyActionQueue(SDLVisualTest_ActionQueue* queue)
{
if(queue)
{
while(!SDLVisualTest_IsActionQueueEmpty(queue))
SDLVisualTest_DequeueAction(queue);
}
}
/* Since the size of the queue is not likely to be larger than 100 elements
we can get away with using insertion sort. */
static void
SortQueue(SDLVisualTest_ActionQueue* queue)
{
SDLVisualTest_ActionNode* head;
SDLVisualTest_ActionNode* tail;
if(!queue || SDLVisualTest_IsActionQueueEmpty(queue))
return;
head = queue->front;
for(tail = head; tail && tail->next;)
{
SDLVisualTest_ActionNode* pos;
SDLVisualTest_ActionNode* element = tail->next;
if(element->action.time < head->action.time)
{
tail->next = tail->next->next;
element->next = head;
head = element;
}
else if(element->action.time >= tail->action.time)
{
tail = tail->next;
}
else
{
for(pos = head;
(pos->next->action.time < element->action.time);
pos = pos->next);
tail->next = tail->next->next;
element->next = pos->next;
pos->next = element;
}
}
queue->front = head;
queue->rear = tail;
}
int
SDLVisualTest_InsertIntoActionQueue(SDLVisualTest_ActionQueue* queue,
SDLVisualTest_Action action)
{
SDLVisualTest_ActionNode* n;
SDLVisualTest_ActionNode* prev;
SDLVisualTest_ActionNode* newnode;
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return 0;
}
if(SDLVisualTest_IsActionQueueEmpty(queue))
{
if(!SDLVisualTest_EnqueueAction(queue, action))
{
SDLTest_LogError("SDLVisualTest_EnqueueAction() failed");
return 0;
}
return 1;
}
newnode = (SDLVisualTest_ActionNode*)malloc(sizeof(SDLVisualTest_ActionNode));
if(!newnode)
{
SDLTest_LogError("malloc() failed");
return 0;
}
newnode->action = action;
queue->size++;
for(n = queue->front, prev = NULL; n; n = n->next)
{
if(action.time < n->action.time)
{
if(prev)
{
prev->next = newnode;
newnode->next = n;
}
else
{
newnode->next = queue->front;
queue->front = newnode;
}
return 1;
}
prev = n;
}
queue->rear->next = newnode;
newnode->next = NULL;
queue->rear = newnode;
return 1;
}
int
SDLVisualTest_ParseActionConfig(char* file, SDLVisualTest_ActionQueue* queue)
{
char line[MAX_ACTION_LINE_LENGTH];
SDLVisualTest_RWHelperBuffer buffer;
char* token_ptr;
int linenum;
SDL_RWops* rw;
if(!file)
{
SDLTest_LogError("file argument cannot be NULL");
return 0;
}
if(!queue)
{
SDLTest_LogError("queue argument cannot be NULL");
return 0;
}
rw = SDL_RWFromFile(file, "r");
if(!rw)
{
SDLTest_LogError("SDL_RWFromFile() failed");
return 0;
}
SDLVisualTest_RWHelperResetBuffer(&buffer);
SDLVisualTest_InitActionQueue(queue);
linenum = 0;
while(SDLVisualTest_RWHelperReadLine(rw, line, MAX_ACTION_LINE_LENGTH,
&buffer, '#'))
{
SDLVisualTest_Action action;
int hr, min, sec;
/* parse time */
token_ptr = strtok(line, " ");
if(!token_ptr ||
(SDL_sscanf(token_ptr, "%d:%d:%d", &hr, &min, &sec) != 3))
{
SDLTest_LogError("Could not parse time token at line: %d",
linenum);
SDLVisualTest_EmptyActionQueue(queue);
SDL_RWclose(rw);
return 0;
}
action.time = (((hr * 60 + min) * 60) + sec) * 1000;
/* parse type */
token_ptr = strtok(NULL, " ");
if(SDL_strcasecmp(token_ptr, "launch") == 0)
action.type = SDL_ACTION_LAUNCH;
else if(SDL_strcasecmp(token_ptr, "kill") == 0)
action.type = SDL_ACTION_KILL;
else if(SDL_strcasecmp(token_ptr, "quit") == 0)
action.type = SDL_ACTION_QUIT;
else if(SDL_strcasecmp(token_ptr, "screenshot") == 0)
action.type = SDL_ACTION_SCREENSHOT;
else if(SDL_strcasecmp(token_ptr, "verify") == 0)
action.type = SDL_ACTION_VERIFY;
else
{
SDLTest_LogError("Could not parse type token at line: %d",
linenum);
SDLVisualTest_EmptyActionQueue(queue);
SDL_RWclose(rw);
return 0;
}
/* parse the extra field */
if(action.type == SDL_ACTION_LAUNCH)
{
int len;
char* args;
char* path;
token_ptr = strtok(NULL, " ");
len = token_ptr ? SDL_strlen(token_ptr) : 0;
if(len <= 0)
{
SDLTest_LogError("Please specify the process to launch at line: %d",
linenum);
SDLVisualTest_EmptyActionQueue(queue);
SDL_RWclose(rw);
return 0;
}
path = (char*)SDL_malloc(sizeof(char) * (len + 1));
if(!path)
{
SDLTest_LogError("malloc() failed");
SDLVisualTest_EmptyActionQueue(queue);
SDL_RWclose(rw);
return 0;
}
SDL_strlcpy(path, token_ptr, len + 1);
token_ptr = strtok(NULL, "");
len = token_ptr ? SDL_strlen(token_ptr) : 0;
if(len > 0)
{
args = (char*)SDL_malloc(sizeof(char) * (len + 1));
if(!args)
{
SDLTest_LogError("malloc() failed");
SDL_free(path);
SDLVisualTest_EmptyActionQueue(queue);
SDL_RWclose(rw);
return 0;
}
SDL_strlcpy(args, token_ptr, len + 1);
}
else
args = NULL;
action.extra.process.path = path;
action.extra.process.args = args;
}
/* add the action to the queue */
if(!SDLVisualTest_EnqueueAction(queue, action))
{
SDLTest_LogError("SDLVisualTest_EnqueueAction() failed");
if(action.type == SDL_ACTION_LAUNCH)
{
SDL_free(action.extra.process.path);
if(action.extra.process.args)
SDL_free(action.extra.process.args);
}
SDLVisualTest_EmptyActionQueue(queue);
SDL_RWclose(rw);
return 0;
}
}
/* sort the queue of actions */
SortQueue(queue);
SDL_RWclose(rw);
return 1;
}

View File

@@ -0,0 +1,358 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file harness_argparser.c
*
* Source file for functions to parse arguments to the test harness.
*/
#include <SDL_test.h>
#include <stdio.h>
#include <string.h>
#include "SDL_visualtest_harness_argparser.h"
#include "SDL_visualtest_rwhelper.h"
/** Maximum length of one line in the config file */
#define MAX_CONFIG_LINE_LEN 400
/** Default value for the timeout after which the SUT is forcefully killed */
#define DEFAULT_SUT_TIMEOUT (60 * 1000)
/* String compare s1 and s2 ignoring leading hyphens */
static int
StrCaseCmpIgnoreHyphen(char* s1, char* s2)
{
/* treat NULL pointer as empty strings */
if(!s1)
s1 = "";
if(!s2)
s2 = "";
while(*s1 == '-')
s1++;
while(*s2 == '-')
s2++;
return SDL_strcasecmp(s1, s2);
}
/* parser an argument, updates the state object and returns the number of
arguments processed; returns -1 on failure */
static int
ParseArg(char** argv, int index, SDLVisualTest_HarnessState* state)
{
if(!argv || !argv[index] || !state)
return 0;
if(StrCaseCmpIgnoreHyphen("sutapp", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for sutapp.");
return -1;
}
SDL_strlcpy(state->sutapp, argv[index], MAX_PATH_LEN);
SDLTest_Log("SUT Application: %s", state->sutapp);
return 2;
}
else if(StrCaseCmpIgnoreHyphen("output-dir", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for output-dir.");
return -1;
}
SDL_strlcpy(state->output_dir, argv[index], MAX_PATH_LEN);
SDLTest_Log("Screenshot Output Directory: %s", state->output_dir);
return 2;
}
else if(StrCaseCmpIgnoreHyphen("verify-dir", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for verify-dir.");
return -1;
}
SDL_strlcpy(state->verify_dir, argv[index], MAX_PATH_LEN);
SDLTest_Log("Screenshot Verification Directory: %s", state->verify_dir);
return 2;
}
else if(StrCaseCmpIgnoreHyphen("sutargs", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for sutargs.");
return -1;
}
SDL_strlcpy(state->sutargs, argv[index], MAX_SUT_ARGS_LEN);
SDLTest_Log("SUT Arguments: %s", state->sutargs);
return 2;
}
else if(StrCaseCmpIgnoreHyphen("timeout", argv[index]) == 0)
{
int hr, min, sec;
index++;
if(!argv[index] || SDL_sscanf(argv[index], "%d:%d:%d", &hr, &min, &sec) != 3)
{
SDLTest_LogError("Arguments parsing error: Invalid argument for timeout.");
return -1;
}
state->timeout = (((hr * 60) + min) * 60 + sec) * 1000;
SDLTest_Log("Maximum Timeout for each SUT run: %d milliseconds",
state->timeout);
return 2;
}
else if(StrCaseCmpIgnoreHyphen("parameter-config", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for parameter-config.");
return -1;
}
SDLTest_Log("SUT Parameters file: %s", argv[index]);
SDLVisualTest_FreeSUTConfig(&state->sut_config);
if(!SDLVisualTest_ParseSUTConfig(argv[index], &state->sut_config))
{
SDLTest_LogError("Failed to parse SUT parameters file");
return -1;
}
return 2;
}
else if(StrCaseCmpIgnoreHyphen("variator", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for variator.");
return -1;
}
SDLTest_Log("Variator: %s", argv[index]);
if(SDL_strcasecmp("exhaustive", argv[index]) == 0)
state->variator_type = SDL_VARIATOR_EXHAUSTIVE;
else if(SDL_strcasecmp("random", argv[index]) == 0)
state->variator_type = SDL_VARIATOR_RANDOM;
else
{
SDLTest_LogError("Arguments parsing error: Invalid variator name.");
return -1;
}
return 2;
}
else if(StrCaseCmpIgnoreHyphen("num-variations", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: Invalid argument for num-variations.");
return -1;
}
state->num_variations = SDL_atoi(argv[index]);
SDLTest_Log("Number of variations to run: %d", state->num_variations);
if(state->num_variations <= 0)
{
SDLTest_LogError("Arguments parsing error: num-variations must be positive.");
return -1;
}
return 2;
}
else if(StrCaseCmpIgnoreHyphen("no-launch", argv[index]) == 0)
{
state->no_launch = SDL_TRUE;
SDLTest_Log("SUT will not be launched.");
return 1;
}
else if(StrCaseCmpIgnoreHyphen("action-config", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: invalid argument for action-config");
return -1;
}
SDLTest_Log("Action Config file: %s", argv[index]);
SDLVisualTest_EmptyActionQueue(&state->action_queue);
if(!SDLVisualTest_ParseActionConfig(argv[index], &state->action_queue))
{
SDLTest_LogError("SDLVisualTest_ParseActionConfig() failed");
return -1;
}
return 2;
}
else if(StrCaseCmpIgnoreHyphen("config", argv[index]) == 0)
{
index++;
if(!argv[index])
{
SDLTest_LogError("Arguments parsing error: invalid argument for config");
return -1;
}
/* do nothing, this option has already been handled */
return 2;
}
return 0;
}
/* TODO: Trailing/leading spaces and spaces between equals sign not supported. */
static int
ParseConfig(char* file, SDLVisualTest_HarnessState* state)
{
SDL_RWops* rw;
SDLVisualTest_RWHelperBuffer buffer;
char line[MAX_CONFIG_LINE_LEN];
rw = SDL_RWFromFile(file, "r");
if(!rw)
{
SDLTest_LogError("SDL_RWFromFile() failed");
return 0;
}
SDLVisualTest_RWHelperResetBuffer(&buffer);
while(SDLVisualTest_RWHelperReadLine(rw, line, MAX_CONFIG_LINE_LEN,
&buffer, '#'))
{
char** argv;
int i, num_params;
/* count number of parameters and replace the trailing newline with 0 */
num_params = 1;
for(i = 0; line[i]; i++)
{
if(line[i] == '=')
{
num_params = 2;
break;
}
}
/* populate argv */
argv = (char**)SDL_malloc((num_params + 1) * sizeof(char*));
if(!argv)
{
SDLTest_LogError("malloc() failed.");
SDL_RWclose(rw);
return 0;
}
argv[num_params] = NULL;
for(i = 0; i < num_params; i++)
{
argv[i] = strtok(i == 0 ? line : NULL, "=");
}
if(ParseArg(argv, 0, state) == -1)
{
SDLTest_LogError("ParseArg() failed");
SDL_free(argv);
SDL_RWclose(rw);
return 0;
}
SDL_free(argv);
}
SDL_RWclose(rw);
if(!state->sutapp[0])
return 0;
return 1;
}
int
SDLVisualTest_ParseHarnessArgs(char** argv, SDLVisualTest_HarnessState* state)
{
int i;
SDLTest_Log("Parsing commandline arguments..");
if(!argv)
{
SDLTest_LogError("argv is NULL");
return 0;
}
if(!state)
{
SDLTest_LogError("state is NULL");
return 0;
}
/* initialize the state object */
state->sutargs[0] = '\0';
state->sutapp[0] = '\0';
state->output_dir[0] = '\0';
state->verify_dir[0] = '\0';
state->timeout = DEFAULT_SUT_TIMEOUT;
SDL_memset(&state->sut_config, 0, sizeof(SDLVisualTest_SUTConfig));
SDL_memset(&state->action_queue, 0, sizeof(SDLVisualTest_ActionQueue));
state->variator_type = SDL_VARIATOR_RANDOM;
state->num_variations = -1;
state->no_launch = SDL_FALSE;
/* parse config file if passed */
for(i = 0; argv[i]; i++)
{
if(StrCaseCmpIgnoreHyphen("config", argv[i]) == 0)
{
if(!argv[i + 1])
{
SDLTest_Log("Arguments parsing error: invalid argument for config.");
return 0;
}
if(!ParseConfig(argv[i + 1], state))
{
SDLTest_LogError("ParseConfig() failed");
return 0;
}
}
}
/* parse the arguments */
for(i = 0; argv[i];)
{
int consumed = ParseArg(argv, i, state);
if(consumed == -1 || consumed == 0)
{
SDLTest_LogError("ParseArg() failed");
return 0;
}
i += consumed;
}
if(state->variator_type == SDL_VARIATOR_RANDOM && state->num_variations == -1)
state->num_variations = 1;
/* check to see if required options have been passed */
if(!state->sutapp[0])
{
SDLTest_LogError("sutapp must be passed.");
return 0;
}
if(!state->sutargs[0] && !state->sut_config.options)
{
SDLTest_LogError("Either sutargs or parameter-config must be passed.");
return 0;
}
if(!state->output_dir[0])
{
SDL_strlcpy(state->output_dir, "./output", MAX_PATH_LEN);
}
if(!state->verify_dir[0])
{
SDL_strlcpy(state->verify_dir, "./verify", MAX_PATH_LEN);
}
return 1;
}
void
SDLVisualTest_FreeHarnessState(SDLVisualTest_HarnessState* state)
{
if(state)
{
SDLVisualTest_EmptyActionQueue(&state->action_queue);
SDLVisualTest_FreeSUTConfig(&state->sut_config);
}
}

View File

@@ -0,0 +1,199 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file linux_process.c
*
* Source file for the process API on linux.
*/
#include <SDL.h>
#include <SDL_test.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include "SDL_visualtest_process.h"
#include "SDL_visualtest_harness_argparser.h"
#include "SDL_visualtest_parsehelper.h"
#if defined(__LINUX__)
static void
LogLastError(char* str)
{
char* error = (char*)strerror(errno);
if(!str || !error)
return;
SDLTest_LogError("%s: %s", str, error);
}
int
SDL_LaunchProcess(char* file, char* args, SDL_ProcessInfo* pinfo)
{
pid_t pid;
char** argv;
if(!file)
{
SDLTest_LogError("file argument cannot be NULL");
return 0;
}
if(!pinfo)
{
SDLTest_LogError("pinfo cannot be NULL");
return 0;
}
pid = fork();
if(pid == -1)
{
LogLastError("fork() failed");
return 0;
}
else if(pid == 0)
{
/* parse the arguments string */
argv = SDLVisualTest_ParseArgsToArgv(args);
argv[0] = file;
execv(file, argv);
LogLastError("execv() failed");
return 0;
}
else
{
pinfo->pid = pid;
return 1;
}
/* never executed */
return 0;
}
int
SDL_GetProcessExitStatus(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
int success, status;
if(!pinfo)
{
SDLTest_LogError("pinfo argument cannot be NULL");
return 0;
}
if(!ps)
{
SDLTest_LogError("ps argument cannot be NULL");
return 0;
}
success = waitpid(pinfo->pid, &status, WNOHANG);
if(success == -1)
{
LogLastError("waitpid() failed");
return 0;
}
else if(success == 0)
{
ps->exit_status = -1;
ps->exit_success = 1;
}
else
{
ps->exit_success = WIFEXITED(status);
ps->exit_status = WEXITSTATUS(status);
}
return 1;
}
int
SDL_IsProcessRunning(SDL_ProcessInfo* pinfo)
{
int success;
if(!pinfo)
{
SDLTest_LogError("pinfo cannot be NULL");
return -1;
}
success = kill(pinfo->pid, 0);
if(success == -1)
{
if(errno == ESRCH) /* process is not running */
return 0;
else
{
LogLastError("kill() failed");
return -1;
}
}
return 1;
}
int
SDL_QuitProcess(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
int success, status;
if(!pinfo)
{
SDLTest_LogError("pinfo argument cannot be NULL");
return 0;
}
if(!ps)
{
SDLTest_LogError("ps argument cannot be NULL");
return 0;
}
success = kill(pinfo->pid, SIGQUIT);
if(success == -1)
{
LogLastError("kill() failed");
return 0;
}
success = waitpid(pinfo->pid, &status, 0);
if(success == -1)
{
LogLastError("waitpid() failed");
return 0;
}
ps->exit_success = WIFEXITED(status);
ps->exit_status = WEXITSTATUS(status);
return 1;
}
int
SDL_KillProcess(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
int success, status;
if(!pinfo)
{
SDLTest_LogError("pinfo argument cannot be NULL");
return 0;
}
if(!ps)
{
SDLTest_LogError("ps argument cannot be NULL");
return 0;
}
success = kill(pinfo->pid, SIGKILL);
if(success == -1)
{
LogLastError("kill() failed");
return 0;
}
success = waitpid(pinfo->pid, &status, 0);
if(success == -1)
{
LogLastError("waitpid() failed");
return 0;
}
ps->exit_success = WIFEXITED(status);
ps->exit_status = WEXITSTATUS(status);
return 1;
}
#endif

28
externals/SDL/visualtest/src/mischelper.c vendored Executable file
View File

@@ -0,0 +1,28 @@
/**
* \file mischelper.c
*
* Source file with miscellaneous helper functions.
*/
#include <SDL_test.h>
void
SDLVisualTest_HashString(char* str, char hash[33])
{
SDLTest_Md5Context md5c;
int i;
if(!str)
{
SDLTest_LogError("str argument cannot be NULL");
return;
}
SDLTest_Md5Init(&md5c);
SDLTest_Md5Update(&md5c, (unsigned char*)str, SDL_strlen(str));
SDLTest_Md5Final(&md5c);
/* convert the md5 hash to an array of hexadecimal digits */
for(i = 0; i < 16; i++)
SDL_snprintf(hash + 2 * i, 33 - 2 * i, "%02x", (int)md5c.digest[i]);
}

231
externals/SDL/visualtest/src/parsehelper.c vendored Executable file
View File

@@ -0,0 +1,231 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file parsehelper.c
*
* Source file with some helper functions for parsing strings.
*/
#include <SDL_test.h>
#include "SDL_visualtest_harness_argparser.h"
/* this function uses a DFA to count the number of tokens in an agruments string.
state 0 is taken to be the start and end state. State 1 handles a double quoted
argument and state 2 handles unquoted arguments. */
static int
CountTokens(char* args)
{
int index, num_tokens;
int state; /* current state of the DFA */
if(!args)
return -1;
index = 0;
state = 0;
num_tokens = 0;
while(args[index])
{
char ch = args[index];
switch(state)
{
case 0:
if(ch == '\"')
{
state = 1;
num_tokens++;
}
else if(!SDL_isspace(ch))
{
state = 2;
num_tokens++;
}
break;
case 1:
if(ch == '\"')
{
state = 0;
}
break;
case 2:
if(SDL_isspace(ch))
{
state = 0;
}
break;
}
index++;
}
return num_tokens;
}
/* - size of tokens is num_tokens + 1
- uses the same DFA used in CountTokens() to split args into an array of strings */
static int
TokenizeHelper(char* str, char** tokens, int num_tokens, int max_token_len)
{
int index, state, done, st_index, token_index;
if(!str)
{
SDLTest_LogError("str argument cannot be NULL");
return 0;
}
if(!tokens)
{
SDLTest_LogError("tokens argument cannot be NULL");
return 0;
}
if(num_tokens <= 0)
{
SDLTest_LogError("num_tokens argument must be positive");
return 0;
}
if(max_token_len <= 0)
{
SDLTest_LogError("max_token_len argument must be positive");
return 0;
}
/* allocate memory for the tokens */
tokens[num_tokens] = NULL;
for(index = 0; index < num_tokens; index++)
{
tokens[index] = (char*)SDL_malloc(max_token_len);
if(!tokens[index])
{
int i;
SDLTest_LogError("malloc() failed.");
for(i = 0; i < index; i++)
SDL_free(tokens[i]);
return 0;
}
tokens[index][0] = '\0';
}
/* copy the tokens into the array */
st_index = 0;
index = 0;
token_index = 0;
state = 0;
done = 0;
while(!done)
{
char ch = str[index];
switch(state)
{
case 0:
if(ch == '\"')
{
state = 1;
st_index = index + 1;
}
else if(!ch)
done = 1;
else if(ch && !SDL_isspace(ch))
{
state = 2;
st_index = index;
}
break;
case 1:
if(ch == '\"')
{
int i;
state = 0;
for(i = st_index; i < index; i++)
{
tokens[token_index][i - st_index] = str[i];
}
tokens[token_index][i - st_index] = '\0';
token_index++;
}
else if(!ch)
{
SDLTest_LogError("Parsing Error!");
done = 1;
}
break;
case 2:
if(!ch)
done = 1;
if(SDL_isspace(ch) || !ch)
{
int i;
state = 0;
for(i = st_index; i < index; i++)
{
tokens[token_index][i - st_index] = str[i];
}
tokens[token_index][i - st_index] = '\0';
token_index++;
}
break;
}
index++;
}
return 1;
}
char**
SDLVisualTest_Tokenize(char* str, int max_token_len)
{
int num_tokens;
char** tokens;
if(!str)
{
SDLTest_LogError("str argument cannot be NULL");
return NULL;
}
if(max_token_len <= 0)
{
SDLTest_LogError("max_token_len argument must be positive");
return NULL;
}
num_tokens = CountTokens(str);
if(num_tokens == 0)
return NULL;
tokens = (char**)SDL_malloc(sizeof(char*) * (num_tokens + 1));
if(!TokenizeHelper(str, tokens, num_tokens, max_token_len))
{
SDLTest_LogError("TokenizeHelper() failed");
SDL_free(tokens);
return NULL;
}
return tokens;
}
char**
SDLVisualTest_ParseArgsToArgv(char* args)
{
char** argv;
int num_tokens;
num_tokens = CountTokens(args);
if(num_tokens == 0)
return NULL;
/* allocate space for arguments */
argv = (char**)SDL_malloc((num_tokens + 2) * sizeof(char*));
if(!argv)
{
SDLTest_LogError("malloc() failed.");
return NULL;
}
/* tokenize */
if(!TokenizeHelper(args, argv + 1, num_tokens, MAX_SUT_ARGS_LEN))
{
SDLTest_LogError("TokenizeHelper() failed");
SDL_free(argv);
return NULL;
}
argv[0] = NULL;
return argv;
}

131
externals/SDL/visualtest/src/rwhelper.c vendored Executable file
View File

@@ -0,0 +1,131 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file rwhelper.c
*
* Source file with some helper functions for working with SDL_RWops.
*/
#include <SDL_test.h>
#include "SDL_visualtest_sut_configparser.h"
#include "SDL_visualtest_rwhelper.h"
void
SDLVisualTest_RWHelperResetBuffer(SDLVisualTest_RWHelperBuffer* buffer)
{
if(!buffer)
{
SDLTest_LogError("buffer argument cannot be NULL");
return;
}
buffer->buffer_pos = 0;
buffer->buffer_width = 0;
}
char
SDLVisualTest_RWHelperReadChar(SDL_RWops* rw, SDLVisualTest_RWHelperBuffer* buffer)
{
if(!rw || !buffer)
return 0;
/* if the buffer has been consumed, we fill it up again */
if(buffer->buffer_pos == buffer->buffer_width)
{
buffer->buffer_width = SDL_RWread(rw, buffer->buffer, 1, RWOPS_BUFFER_LEN);
buffer->buffer_pos = 0;
if(buffer->buffer_width == 0)
return 0;
}
buffer->buffer_pos++;
return buffer->buffer[buffer->buffer_pos - 1];
}
/* does not include new lines in the buffer and adds a trailing null character */
char*
SDLVisualTest_RWHelperReadLine(SDL_RWops* rw, char* str, int size,
SDLVisualTest_RWHelperBuffer* buffer,
char comment_char)
{
char ch;
int current_pos, done;
if(!rw)
{
SDLTest_LogError("rw argument cannot be NULL");
return NULL;
}
if(!str)
{
SDLTest_LogError("str argument cannot be NULL");
return NULL;
}
if(!buffer)
{
SDLTest_LogError("buffer argument cannot be NULL");
return NULL;
}
if(size <= 0)
{
SDLTest_LogError("size argument should be positive");
return NULL;
}
done = 0;
while(!done)
{
/* ignore leading whitespace */
for(ch = SDLVisualTest_RWHelperReadChar(rw, buffer); ch && SDL_isspace(ch);
ch = SDLVisualTest_RWHelperReadChar(rw, buffer));
for(current_pos = 0;
ch && ch != '\n' && ch != '\r' && ch != comment_char;
current_pos++)
{
str[current_pos] = ch;
if(current_pos >= size - 2)
{
current_pos++;
break;
}
ch = SDLVisualTest_RWHelperReadChar(rw, buffer);
}
done = 1;
if(ch == comment_char) /* discard all characters until the next line */
{
do
{
ch = SDLVisualTest_RWHelperReadChar(rw, buffer);
}while(ch && ch != '\n' && ch != '\r');
if(current_pos == 0)
done = 0;
}
}
if(current_pos == 0)
return NULL;
str[current_pos] = '\0';
return str;
}
/* Lines with all whitespace are ignored */
int
SDLVisualTest_RWHelperCountNonEmptyLines(SDL_RWops* rw,
SDLVisualTest_RWHelperBuffer* buffer,
char comment_char)
{
int num_lines = 0;
char str[MAX_SUTOPTION_LINE_LENGTH];
if(!rw)
{
SDLTest_LogError("rw argument cannot be NULL");
return -1;
}
if(!buffer)
{
SDLTest_LogError("buffer argument cannot be NULL");
return -1;
}
while(SDLVisualTest_RWHelperReadLine(rw, str, MAX_SUTOPTION_LINE_LENGTH,
buffer, comment_char))
num_lines++;
return num_lines;
}

136
externals/SDL/visualtest/src/screenshot.c vendored Executable file
View File

@@ -0,0 +1,136 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file screenshot.c
*
* Source file for the screenshot API.
*/
#include "SDL_visualtest_mischelper.h"
#include <SDL_test.h>
int
SDLVisualTest_VerifyScreenshots(char* args, char* test_dir, char* verify_dir)
{
int i, verify_len, return_code, test_len;
char hash[33];
char* verify_path; /* path to the bmp file used for verification */
char* test_path; /* path to the bmp file to be verified */
SDL_RWops* rw;
SDL_Surface* verifybmp;
return_code = 1;
if(!args)
{
SDLTest_LogError("args argument cannot be NULL");
return_code = -1;
goto verifyscreenshots_cleanup_generic;
}
if(!test_dir)
{
SDLTest_LogError("test_dir argument cannot be NULL");
return_code = -1;
goto verifyscreenshots_cleanup_generic;
}
if(!verify_dir)
{
SDLTest_LogError("verify_dir argument cannot be NULL");
return_code = -1;
goto verifyscreenshots_cleanup_generic;
}
/* generate the MD5 hash */
SDLVisualTest_HashString(args, hash);
/* find the verification image */
/* path_len + hash_len + some number of extra characters */
verify_len = SDL_strlen(verify_dir) + 32 + 10;
verify_path = (char*)SDL_malloc(verify_len * sizeof(char));
if(!verify_path)
{
SDLTest_LogError("malloc() failed");
return_code = -1;
goto verifyscreenshots_cleanup_generic;
}
SDL_snprintf(verify_path, verify_len - 1,
"%s/%s.bmp", verify_dir, hash);
rw = SDL_RWFromFile(verify_path, "rb");
if(!rw)
{
SDLTest_Log("Verification image does not exist."
" Please manually verify that the SUT is working correctly.");
return_code = 2;
goto verifyscreenshots_cleanup_verifypath;
}
/* load the verification image */
verifybmp = SDL_LoadBMP_RW(rw, 1);
if(!verifybmp)
{
SDLTest_LogError("SDL_LoadBMP_RW() failed");
return_code = -1;
goto verifyscreenshots_cleanup_verifypath;
}
/* load the test images and compare with the verification image */
/* path_len + hash_len + some number of extra characters */
test_len = SDL_strlen(test_dir) + 32 + 10;
test_path = (char*)SDL_malloc(test_len * sizeof(char));
if(!test_path)
{
SDLTest_LogError("malloc() failed");
return_code = -1;
goto verifyscreenshots_cleanup_verifybmp;
}
for(i = 1; ; i++)
{
SDL_RWops* testrw;
SDL_Surface* testbmp;
if(i == 1)
SDL_snprintf(test_path, test_len - 1, "%s/%s.bmp", test_dir, hash);
else
SDL_snprintf(test_path, test_len - 1, "%s/%s_%d.bmp", test_dir, hash, i);
testrw = SDL_RWFromFile(test_path, "rb");
/* we keep going until we've iterated through the screenshots each
SUT window */
if(!testrw)
break;
/* load the test screenshot */
testbmp = SDL_LoadBMP_RW(testrw, 1);
if(!testbmp)
{
SDLTest_LogError("SDL_LoadBMP_RW() failed");
return_code = -1;
goto verifyscreenshots_cleanup_verifybmp;
}
/* compare with the verification image */
if(SDLTest_CompareSurfaces(testbmp, verifybmp, 0) != 0)
{
return_code = 0;
SDL_FreeSurface(testbmp);
goto verifyscreenshots_cleanup_verifybmp;
}
SDL_FreeSurface(testbmp);
}
if(i == 1)
{
SDLTest_LogError("No verification images found");
return_code = -1;
}
verifyscreenshots_cleanup_verifybmp:
SDL_FreeSurface(verifybmp);
verifyscreenshots_cleanup_verifypath:
SDL_free(verify_path);
verifyscreenshots_cleanup_generic:
return return_code;
}

View File

@@ -0,0 +1,232 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file sut_configparser.c
*
* Source file for the parser for SUT config files.
*/
#include <limits.h>
#include <string.h>
#include <SDL_test.h>
#include <SDL_rwops.h>
#include "SDL_visualtest_sut_configparser.h"
#include "SDL_visualtest_parsehelper.h"
#include "SDL_visualtest_rwhelper.h"
int
SDLVisualTest_ParseSUTConfig(char* file, SDLVisualTest_SUTConfig* config)
{
char line[MAX_SUTOPTION_LINE_LENGTH];
SDLVisualTest_RWHelperBuffer buffer;
char* token_ptr;
char* token_end;
int num_lines, i, token_len;
SDL_RWops* rw;
if(!file)
{
SDLTest_LogError("file argument cannot be NULL");
return 0;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return 0;
}
/* count the number of lines */
rw = SDL_RWFromFile(file, "r");
if(!rw)
{
SDLTest_LogError("SDL_RWFromFile() failed");
return 0;
}
SDLVisualTest_RWHelperResetBuffer(&buffer);
num_lines = SDLVisualTest_RWHelperCountNonEmptyLines(rw, &buffer, '#');
if(num_lines == -1)
return 0;
else if(num_lines == 0)
{
config->options = NULL;
config->num_options = 0;
SDL_RWclose(rw);
return 1;
}
/* allocate memory */
SDL_RWseek(rw, 0, RW_SEEK_SET);
SDLVisualTest_RWHelperResetBuffer(&buffer);
config->num_options = num_lines;
config->options = (SDLVisualTest_SUTOption*)SDL_malloc(num_lines *
sizeof(SDLVisualTest_SUTOption));
if(!config->options)
{
SDLTest_LogError("malloc() failed");
SDL_RWclose(rw);
return 0;
}
/* actually parse the options */
for(i = 0; i < num_lines; i++)
{
if(!SDLVisualTest_RWHelperReadLine(rw, line, MAX_SUTOPTION_LINE_LENGTH,
&buffer, '#'))
{
SDLTest_LogError("SDLVisualTest_RWHelperReadLine() failed");
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
/* parse name */
token_ptr = strtok(line, ", ");
if(!token_ptr)
{
SDLTest_LogError("Could not parse line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
token_len = SDL_strlen(token_ptr) + 1;
SDL_strlcpy(config->options[i].name, token_ptr, token_len);
/* parse type */
token_ptr = strtok(NULL, ", ");
if(!token_ptr)
{
SDLTest_LogError("Could not parse line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
if(SDL_strcmp(token_ptr, "string") == 0)
config->options[i].type = SDL_SUT_OPTIONTYPE_STRING;
else if(SDL_strcmp(token_ptr, "integer") == 0)
config->options[i].type = SDL_SUT_OPTIONTYPE_INT;
else if(SDL_strcmp(token_ptr, "enum") == 0)
config->options[i].type = SDL_SUT_OPTIONTYPE_ENUM;
else if(SDL_strcmp(token_ptr, "boolean") == 0)
config->options[i].type = SDL_SUT_OPTIONTYPE_BOOL;
else
{
SDLTest_LogError("Could not parse type token at line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
/* parse values */
token_ptr = strtok(NULL, "]");
if(!token_ptr)
{
SDLTest_LogError("Could not parse line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
token_ptr = SDL_strchr(token_ptr, '[');
if(!token_ptr)
{
SDLTest_LogError("Could not parse enum token at line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
token_ptr++;
if(config->options[i].type == SDL_SUT_OPTIONTYPE_INT)
{
if(SDL_sscanf(token_ptr, "%d %d", &config->options[i].data.range.min,
&config->options[i].data.range.max) != 2)
{
config->options[i].data.range.min = INT_MIN;
config->options[i].data.range.max = INT_MAX;
}
}
else if(config->options[i].type == SDL_SUT_OPTIONTYPE_ENUM)
{
config->options[i].data.enum_values = SDLVisualTest_Tokenize(token_ptr,
MAX_SUTOPTION_ENUMVAL_LEN);
if(!config->options[i].data.enum_values)
{
SDLTest_LogError("Could not parse enum token at line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
}
/* parse required */
token_ptr = strtok(NULL, ", ");
if(!token_ptr)
{
SDLTest_LogError("Could not parse line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
if(SDL_strcmp(token_ptr, "true") == 0)
config->options[i].required = SDL_TRUE;
else if(SDL_strcmp(token_ptr, "false") == 0)
config->options[i].required = SDL_FALSE;
else
{
SDLTest_LogError("Could not parse required token at line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
/* parse categories */
token_ptr = strtok(NULL, ",");
if(!token_ptr)
{
SDLTest_LogError("Could not parse line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
token_ptr = SDL_strchr(token_ptr, '[');
if(!token_ptr)
{
SDLTest_LogError("Could not parse enum token at line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
token_ptr++;
token_end = SDL_strchr(token_ptr, ']');
*token_end = '\0';
if(!token_end)
{
SDLTest_LogError("Could not parse enum token at line %d", i + 1);
SDL_free(config->options);
SDL_RWclose(rw);
return 0;
}
config->options[i].categories = SDLVisualTest_Tokenize(token_ptr,
MAX_SUTOPTION_CATEGORY_LEN);
}
SDL_RWclose(rw);
return 1;
}
void
SDLVisualTest_FreeSUTConfig(SDLVisualTest_SUTConfig* config)
{
if(config && config->options)
{
SDLVisualTest_SUTOption* option;
for(option = config->options;
option != config->options + config->num_options; option++)
{
if(option->categories)
SDL_free(option->categories);
if(option->type == SDL_SUT_OPTIONTYPE_ENUM && option->data.enum_values)
SDL_free(option->data.enum_values);
}
SDL_free(config->options);
config->options = NULL;
config->num_options = 0;
}
}

533
externals/SDL/visualtest/src/testharness.c vendored Executable file
View File

@@ -0,0 +1,533 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file testharness.c
*
* Source file for the test harness.
*/
#include <stdlib.h>
#include <SDL_test.h>
#include <SDL.h>
#include <SDL_assert.h>
#include "SDL_visualtest_harness_argparser.h"
#include "SDL_visualtest_process.h"
#include "SDL_visualtest_variators.h"
#include "SDL_visualtest_screenshot.h"
#include "SDL_visualtest_mischelper.h"
#if defined(__WIN32__) && !defined(__CYGWIN__)
#include <direct.h>
#elif defined(__WIN32__) && defined(__CYGWIN__)
#include <signal.h>
#elif defined(__LINUX__)
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#else
#error "Unsupported platform"
#endif
/** Code for the user event triggered when a new action is to be executed */
#define ACTION_TIMER_EVENT 0
/** Code for the user event triggered when the maximum timeout is reached */
#define KILL_TIMER_EVENT 1
/** FPS value used for delays in the action loop */
#define ACTION_LOOP_FPS 10
/** Value returned by RunSUTAndTest() when the test has passed */
#define TEST_PASSED 1
/** Value returned by RunSUTAndTest() when the test has failed */
#define TEST_FAILED 0
/** Value returned by RunSUTAndTest() on a fatal error */
#define TEST_ERROR -1
static SDL_ProcessInfo pinfo;
static SDL_ProcessExitStatus sut_exitstatus;
static SDLVisualTest_HarnessState state;
static SDLVisualTest_Variator variator;
static SDLVisualTest_ActionNode* current; /* the current action being performed */
static SDL_TimerID action_timer, kill_timer;
/* returns a char* to be passed as the format argument of a printf-style function. */
static char*
usage()
{
return "Usage: \n%s --sutapp xyz"
" [--sutargs abc | --parameter-config xyz.parameters"
" [--variator exhaustive|random]"
" [--num-variations N] [--no-launch]] [--timeout hh:mm:ss]"
" [--action-config xyz.actions]"
" [--output-dir /path/to/output]"
" [--verify-dir /path/to/verify]"
" or --config app.config";
}
/* register Ctrl+C handlers */
#if defined(__LINUX__) || defined(__CYGWIN__)
static void
CtrlCHandlerCallback(int signum)
{
SDL_Event event;
SDLTest_Log("Ctrl+C received");
event.type = SDL_QUIT;
SDL_PushEvent(&event);
}
#endif
static Uint32
ActionTimerCallback(Uint32 interval, void* param)
{
SDL_Event event;
SDL_UserEvent userevent;
Uint32 next_action_time;
/* push an event to handle the action */
userevent.type = SDL_USEREVENT;
userevent.code = ACTION_TIMER_EVENT;
userevent.data1 = &current->action;
userevent.data2 = NULL;
event.type = SDL_USEREVENT;
event.user = userevent;
SDL_PushEvent(&event);
/* calculate the new interval and return it */
if(current->next)
next_action_time = current->next->action.time - current->action.time;
else
{
next_action_time = 0;
action_timer = 0;
}
current = current->next;
return next_action_time;
}
static Uint32
KillTimerCallback(Uint32 interval, void* param)
{
SDL_Event event;
SDL_UserEvent userevent;
userevent.type = SDL_USEREVENT;
userevent.code = KILL_TIMER_EVENT;
userevent.data1 = NULL;
userevent.data2 = NULL;
event.type = SDL_USEREVENT;
event.user = userevent;
SDL_PushEvent(&event);
kill_timer = 0;
return 0;
}
static int
ProcessAction(SDLVisualTest_Action* action, int* sut_running, char* args)
{
if(!action || !sut_running)
return TEST_ERROR;
switch(action->type)
{
case SDL_ACTION_KILL:
SDLTest_Log("Action: Kill SUT");
if(SDL_IsProcessRunning(&pinfo) == 1 &&
!SDL_KillProcess(&pinfo, &sut_exitstatus))
{
SDLTest_LogError("SDL_KillProcess() failed");
return TEST_ERROR;
}
*sut_running = 0;
break;
case SDL_ACTION_QUIT:
SDLTest_Log("Action: Quit SUT");
if(SDL_IsProcessRunning(&pinfo) == 1 &&
!SDL_QuitProcess(&pinfo, &sut_exitstatus))
{
SDLTest_LogError("SDL_QuitProcess() failed");
return TEST_FAILED;
}
*sut_running = 0;
break;
case SDL_ACTION_LAUNCH:
{
char* path;
char* args;
SDL_ProcessInfo action_process;
SDL_ProcessExitStatus ps;
path = action->extra.process.path;
args = action->extra.process.args;
if(args)
{
SDLTest_Log("Action: Launch process: %s with arguments: %s",
path, args);
}
else
SDLTest_Log("Action: Launch process: %s", path);
if(!SDL_LaunchProcess(path, args, &action_process))
{
SDLTest_LogError("SDL_LaunchProcess() failed");
return TEST_ERROR;
}
/* small delay so that the process can do its job */
SDL_Delay(1000);
if(SDL_IsProcessRunning(&action_process) > 0)
{
SDLTest_LogError("Process %s took too long too complete."
" Force killing...", action->extra);
if(!SDL_KillProcess(&action_process, &ps))
{
SDLTest_LogError("SDL_KillProcess() failed");
return TEST_ERROR;
}
}
}
break;
case SDL_ACTION_SCREENSHOT:
{
char path[MAX_PATH_LEN], hash[33];
SDLTest_Log("Action: Take screenshot");
/* can't take a screenshot if the SUT isn't running */
if(SDL_IsProcessRunning(&pinfo) != 1)
{
SDLTest_LogError("SUT has quit.");
*sut_running = 0;
return TEST_FAILED;
}
/* file name for the screenshot image */
SDLVisualTest_HashString(args, hash);
SDL_snprintf(path, MAX_PATH_LEN, "%s/%s", state.output_dir, hash);
if(!SDLVisualTest_ScreenshotProcess(&pinfo, path))
{
SDLTest_LogError("SDLVisualTest_ScreenshotProcess() failed");
return TEST_ERROR;
}
}
break;
case SDL_ACTION_VERIFY:
{
int ret;
SDLTest_Log("Action: Verify screenshot");
ret = SDLVisualTest_VerifyScreenshots(args, state.output_dir,
state.verify_dir);
if(ret == -1)
{
SDLTest_LogError("SDLVisualTest_VerifyScreenshots() failed");
return TEST_ERROR;
}
else if(ret == 0)
{
SDLTest_Log("Verification failed: Images were not equal.");
return TEST_FAILED;
}
else if(ret == 1)
SDLTest_Log("Verification successful.");
else
{
SDLTest_Log("Verfication skipped.");
return TEST_FAILED;
}
}
break;
default:
SDLTest_LogError("Invalid action type");
return TEST_ERROR;
break;
}
return TEST_PASSED;
}
static int
RunSUTAndTest(char* sutargs, int variation_num)
{
int success, sut_running, return_code;
char hash[33];
SDL_Event event;
return_code = TEST_PASSED;
if(!sutargs)
{
SDLTest_LogError("sutargs argument cannot be NULL");
return_code = TEST_ERROR;
goto runsutandtest_cleanup_generic;
}
SDLVisualTest_HashString(sutargs, hash);
SDLTest_Log("Hash: %s", hash);
success = SDL_LaunchProcess(state.sutapp, sutargs, &pinfo);
if(!success)
{
SDLTest_Log("Could not launch SUT.");
return_code = TEST_ERROR;
goto runsutandtest_cleanup_generic;
}
SDLTest_Log("SUT launch successful.");
SDLTest_Log("Process will be killed in %d milliseconds", state.timeout);
sut_running = 1;
/* launch the timers */
SDLTest_Log("Performing actions..");
current = state.action_queue.front;
action_timer = 0;
kill_timer = 0;
if(current)
{
action_timer = SDL_AddTimer(current->action.time, ActionTimerCallback, NULL);
if(!action_timer)
{
SDLTest_LogError("SDL_AddTimer() failed");
return_code = TEST_ERROR;
goto runsutandtest_cleanup_timer;
}
}
kill_timer = SDL_AddTimer(state.timeout, KillTimerCallback, NULL);
if(!kill_timer)
{
SDLTest_LogError("SDL_AddTimer() failed");
return_code = TEST_ERROR;
goto runsutandtest_cleanup_timer;
}
/* the timer stops running if the actions queue is empty, and the
SUT stops running if it crashes or if we encounter a KILL/QUIT action */
while(sut_running)
{
/* process the actions by using an event queue */
while(SDL_PollEvent(&event))
{
if(event.type == SDL_USEREVENT)
{
if(event.user.code == ACTION_TIMER_EVENT)
{
SDLVisualTest_Action* action;
action = (SDLVisualTest_Action*)event.user.data1;
switch(ProcessAction(action, &sut_running, sutargs))
{
case TEST_PASSED:
break;
case TEST_FAILED:
return_code = TEST_FAILED;
goto runsutandtest_cleanup_timer;
break;
default:
SDLTest_LogError("ProcessAction() failed");
return_code = TEST_ERROR;
goto runsutandtest_cleanup_timer;
}
}
else if(event.user.code == KILL_TIMER_EVENT)
{
SDLTest_LogError("Maximum timeout reached. Force killing..");
return_code = TEST_FAILED;
goto runsutandtest_cleanup_timer;
}
}
else if(event.type == SDL_QUIT)
{
SDLTest_LogError("Received QUIT event. Testharness is quitting..");
return_code = TEST_ERROR;
goto runsutandtest_cleanup_timer;
}
}
SDL_Delay(1000/ACTION_LOOP_FPS);
}
SDLTest_Log("SUT exit code was: %d", sut_exitstatus.exit_status);
if(sut_exitstatus.exit_status == 0)
{
return_code = TEST_PASSED;
goto runsutandtest_cleanup_timer;
}
else
{
return_code = TEST_FAILED;
goto runsutandtest_cleanup_timer;
}
return_code = TEST_ERROR;
goto runsutandtest_cleanup_generic;
runsutandtest_cleanup_timer:
if(action_timer && !SDL_RemoveTimer(action_timer))
{
SDLTest_Log("SDL_RemoveTimer() failed");
return_code = TEST_ERROR;
}
if(kill_timer && !SDL_RemoveTimer(kill_timer))
{
SDLTest_Log("SDL_RemoveTimer() failed");
return_code = TEST_ERROR;
}
/* runsutandtest_cleanup_process: */
if(SDL_IsProcessRunning(&pinfo) && !SDL_KillProcess(&pinfo, &sut_exitstatus))
{
SDLTest_Log("SDL_KillProcess() failed");
return_code = TEST_ERROR;
}
runsutandtest_cleanup_generic:
return return_code;
}
/** Entry point for testharness */
int
main(int argc, char* argv[])
{
int i, passed, return_code, failed;
/* freeing resources, linux style! */
return_code = 0;
if(argc < 2)
{
SDLTest_Log(usage(), argv[0]);
goto cleanup_generic;
}
#if defined(__LINUX__) || defined(__CYGWIN__)
signal(SIGINT, CtrlCHandlerCallback);
#endif
/* parse arguments */
if(!SDLVisualTest_ParseHarnessArgs(argv + 1, &state))
{
SDLTest_Log(usage(), argv[0]);
return_code = 1;
goto cleanup_generic;
}
SDLTest_Log("Parsed harness arguments successfully.");
/* initialize SDL */
if(SDL_Init(SDL_INIT_TIMER) == -1)
{
SDLTest_LogError("SDL_Init() failed.");
SDLVisualTest_FreeHarnessState(&state);
return_code = 1;
goto cleanup_harness_state;
}
/* create an output directory if none exists */
#if defined(__LINUX__) || defined(__CYGWIN__)
mkdir(state.output_dir, 0777);
#elif defined(__WIN32__)
_mkdir(state.output_dir);
#else
#error "Unsupported platform"
#endif
/* test with sutargs */
if(SDL_strlen(state.sutargs))
{
SDLTest_Log("Running: %s %s", state.sutapp, state.sutargs);
if(!state.no_launch)
{
switch(RunSUTAndTest(state.sutargs, 0))
{
case TEST_PASSED:
SDLTest_Log("Status: PASSED");
break;
case TEST_FAILED:
SDLTest_Log("Status: FAILED");
break;
case TEST_ERROR:
SDLTest_LogError("Some error occurred while testing.");
return_code = 1;
goto cleanup_sdl;
break;
}
}
}
if(state.sut_config.num_options > 0)
{
char* variator_name = state.variator_type == SDL_VARIATOR_RANDOM ?
"RANDOM" : "EXHAUSTIVE";
if(state.num_variations > 0)
SDLTest_Log("Testing SUT with variator: %s for %d variations",
variator_name, state.num_variations);
else
SDLTest_Log("Testing SUT with variator: %s and ALL variations",
variator_name);
/* initialize the variator */
if(!SDLVisualTest_InitVariator(&variator, &state.sut_config,
state.variator_type, 0))
{
SDLTest_LogError("Could not initialize variator");
return_code = 1;
goto cleanup_sdl;
}
/* iterate through all the variations */
passed = 0;
failed = 0;
for(i = 0; state.num_variations > 0 ? (i < state.num_variations) : 1; i++)
{
char* args = SDLVisualTest_GetNextVariation(&variator);
if(!args)
break;
SDLTest_Log("\nVariation number: %d\nArguments: %s", i + 1, args);
if(!state.no_launch)
{
switch(RunSUTAndTest(args, i + 1))
{
case TEST_PASSED:
SDLTest_Log("Status: PASSED");
passed++;
break;
case TEST_FAILED:
SDLTest_Log("Status: FAILED");
failed++;
break;
case TEST_ERROR:
SDLTest_LogError("Some error occurred while testing.");
goto cleanup_variator;
break;
}
}
}
if(!state.no_launch)
{
/* report stats */
SDLTest_Log("Testing complete.");
SDLTest_Log("%d/%d tests passed.", passed, passed + failed);
}
goto cleanup_variator;
}
goto cleanup_sdl;
cleanup_variator:
SDLVisualTest_FreeVariator(&variator);
cleanup_sdl:
SDL_Quit();
cleanup_harness_state:
SDLVisualTest_FreeHarnessState(&state);
cleanup_generic:
return return_code;
}

225
externals/SDL/visualtest/src/variator_common.c vendored Executable file
View File

@@ -0,0 +1,225 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file variator_common.c
*
* Source file for some common functionality used by variators.
*/
#include <SDL_test.h>
#include "SDL_visualtest_variator_common.h"
int
SDLVisualTest_NextValue(SDLVisualTest_SUTOptionValue* var,
SDLVisualTest_SUTOption* opt)
{
if(!var)
{
SDLTest_LogError("var argument cannot be NULL");
return -1;
}
if(!opt)
{
SDLTest_LogError("opt argument cannot be NULL");
return -1;
}
switch(opt->type)
{
case SDL_SUT_OPTIONTYPE_BOOL:
if(var->bool_value)
{
var->bool_value = SDL_FALSE;
return 1;
}
else
{
var->bool_value = SDL_TRUE;
return 0;
}
break;
case SDL_SUT_OPTIONTYPE_ENUM:
var->enumerated.index++;
if(!opt->data.enum_values[var->enumerated.index])
{
var->enumerated.index = 0;
return 1;
}
return 0;
break;
case SDL_SUT_OPTIONTYPE_INT:
{
int increment = (opt->data.range.max - opt->data.range.min) /
SDL_SUT_INTEGER_OPTION_TEST_STEPS;
/* prevents infinite loop when rounding */
if(increment == 0)
increment = 1;
var->integer.value += increment;
if(var->integer.value > opt->data.range.max)
{
var->integer.value = opt->data.range.min;
return 1;
}
return 0;
}
break;
case SDL_SUT_OPTIONTYPE_STRING:
return 1;
break;
}
return -1;
}
int
SDLVisualTest_MakeStrFromVariation(SDLVisualTest_Variation* variation,
SDLVisualTest_SUTConfig* config,
char* buffer, int size)
{
int i, index;
SDLVisualTest_SUTOptionValue* vars;
SDLVisualTest_SUTOption* options;
if(!variation)
{
SDLTest_LogError("variation argument cannot be NULL");
return 0;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return 0;
}
if(!buffer)
{
SDLTest_LogError("buffer argument cannot be NULL");
return 0;
}
if(size <= 0)
{
SDLTest_LogError("size argument should be positive");
return 0;
}
index = 0;
buffer[0] = '\0';
options = config->options;
vars = variation->vars;
for(i = 0; i < variation->num_vars; i++)
{
int n, enum_index;
if(index >= size - 1)
{
SDLTest_LogError("String did not fit in buffer size");
return 0;
}
switch(options[i].type)
{
case SDL_SUT_OPTIONTYPE_BOOL:
if(vars[i].bool_value)
{
n = SDL_snprintf(buffer + index, size - index, "%s ",
options[i].name);
if(n <= 0)
{
SDLTest_LogError("SDL_snprintf() failed");
return 0;
}
index += n;
}
break;
case SDL_SUT_OPTIONTYPE_ENUM:
if(vars[i].enumerated.on)
{
enum_index = vars[i].enumerated.index;
n = SDL_snprintf(buffer + index, size - index, "%s %s ",
options[i].name, options[i].data.enum_values[enum_index]);
index += n;
}
break;
case SDL_SUT_OPTIONTYPE_INT:
if(vars[i].integer.on)
{
n = SDL_snprintf(buffer + index, size - index, "%s %d ",
options[i].name, vars[i].integer.value);
index += n;
}
break;
case SDL_SUT_OPTIONTYPE_STRING:
if(vars[i].string.on)
{
n = SDL_snprintf(buffer + index, size - index, "%s %s ",
options[i].name, vars[i].string.value);
index += n;
}
break;
}
}
return 1;
}
int
SDLVisualTest_InitVariation(SDLVisualTest_Variation* variation,
SDLVisualTest_SUTConfig* config)
{
int i;
SDLVisualTest_SUTOptionValue* vars;
SDLVisualTest_SUTOption* options;
if(!variation)
{
SDLTest_LogError("variation argument cannot be NULL");
return 0;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return 0;
}
/* initialize the first variation */
if(config->num_options <= 0)
{
SDLTest_LogError("config->num_options must be positive");
return 0;
}
variation->vars = (SDLVisualTest_SUTOptionValue*)SDL_malloc(config->num_options *
sizeof(SDLVisualTest_SUTOptionValue));
if(!variation->vars)
{
SDLTest_LogError("malloc() failed");
return 0;
}
variation->num_vars = config->num_options;
vars = variation->vars;
options = config->options;
for(i = 0; i < variation->num_vars; i++)
{
switch(options[i].type)
{
case SDL_SUT_OPTIONTYPE_BOOL:
vars[i].bool_value = SDL_FALSE;
break;
case SDL_SUT_OPTIONTYPE_ENUM:
vars[i].enumerated.on = SDL_TRUE;
vars[i].enumerated.index = 0;
break;
case SDL_SUT_OPTIONTYPE_INT:
{
vars[i].integer.on = SDL_TRUE;
vars[i].integer.value = options[i].data.range.min;
}
break;
case SDL_SUT_OPTIONTYPE_STRING:
vars[i].string.on = SDL_TRUE;
vars[i].string.value = options[i].name;
break;
}
}
return 1;
}

View File

@@ -0,0 +1,132 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file variator_exhaustive.c
*
* Source file for the variator that tests the SUT with all the different
* variations of input parameters that are valid.
*/
#include <time.h>
#include <SDL_test.h>
#include "SDL_visualtest_sut_configparser.h"
#include "SDL_visualtest_exhaustive_variator.h"
static int
NextVariation(SDLVisualTest_Variation* variation,
SDLVisualTest_SUTConfig* config)
{
int i, carry;
if(!variation)
{
SDLTest_LogError("variation argument cannot be NULL");
return -1;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return -1;
}
carry = 1;
for(i = 0; i < variation->num_vars; i++)
{
carry = SDLVisualTest_NextValue(&variation->vars[i], &config->options[i]);
if(carry != 1)
break;
}
if(carry == 1) /* we're done, we've tried all possible variations */
return 0;
if(carry == 0)
return 1;
SDLTest_LogError("NextVariation() failed");
return -1;
}
int
SDLVisualTest_InitExhaustiveVariator(SDLVisualTest_ExhaustiveVariator* variator,
SDLVisualTest_SUTConfig* config)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return 0;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return 0;
}
SDLTest_FuzzerInit(time(NULL));
variator->config = *config;
variator->variation.num_vars = 0;
variator->variation.vars = NULL;
return 1;
}
/* TODO: Right now variations where an option is not specified at all are not
tested for. This can be implemented by switching the on attribute for integer,
enum and string options to true and false. */
char*
SDLVisualTest_GetNextExhaustiveVariation(SDLVisualTest_ExhaustiveVariator* variator)
{
int success;
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return NULL;
}
if(!variator->variation.vars) /* the first time this function is called */
{
success = SDLVisualTest_InitVariation(&variator->variation,
&variator->config);
if(!success)
{
SDLTest_LogError("SDLVisualTest_InitVariation() failed");
return NULL;
}
success = SDLVisualTest_MakeStrFromVariation(&variator->variation,
&variator->config, variator->buffer, MAX_SUT_ARGS_LEN);
if(!success)
{
SDLTest_LogError("SDLVisualTest_MakeStrFromVariation() failed");
return NULL;
}
return variator->buffer;
}
else
{
success = NextVariation(&variator->variation, &variator->config);
if(success == 1)
{
success = SDLVisualTest_MakeStrFromVariation(&variator->variation,
&variator->config, variator->buffer, MAX_SUT_ARGS_LEN);
if(!success)
{
SDLTest_LogError("SDLVisualTest_MakeStrFromVariation() failed");
return NULL;
}
return variator->buffer;
}
else if(success == -1)
SDLTest_LogError("NextVariation() failed.");
return NULL;
}
return NULL;
}
void
SDLVisualTest_FreeExhaustiveVariator(SDLVisualTest_ExhaustiveVariator* variator)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return;
}
SDL_free(variator->variation.vars);
variator->variation.vars = NULL;
}

111
externals/SDL/visualtest/src/variator_random.c vendored Executable file
View File

@@ -0,0 +1,111 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file variator_random.c
*
* Source file for the variator that tests the SUT with random variations to the
* input parameters.
*/
#include <time.h>
#include <SDL_test.h>
#include "SDL_visualtest_random_variator.h"
int
SDLVisualTest_InitRandomVariator(SDLVisualTest_RandomVariator* variator,
SDLVisualTest_SUTConfig* config, Uint64 seed)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return 0;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return 0;
}
if(seed)
SDLTest_FuzzerInit(seed);
else
SDLTest_FuzzerInit(time(NULL));
variator->config = *config;
if(!SDLVisualTest_InitVariation(&variator->variation, &variator->config))
{
SDLTest_LogError("SDLVisualTest_InitVariation() failed");
return 0;
}
return 1;
}
char*
SDLVisualTest_GetNextRandomVariation(SDLVisualTest_RandomVariator* variator)
{
SDLVisualTest_SUTOptionValue* vars;
SDLVisualTest_SUTOption* options;
int i;
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return NULL;
}
/* to save typing */
vars = variator->variation.vars;
options = variator->config.options;
/* generate a random variation */
for(i = 0; i < variator->variation.num_vars; i++)
{
switch(options[i].type)
{
case SDL_SUT_OPTIONTYPE_BOOL:
vars[i].bool_value = SDLTest_RandomIntegerInRange(0, 1) ? SDL_FALSE :
SDL_TRUE;
break;
case SDL_SUT_OPTIONTYPE_ENUM:
{
int emx = 0;
while(options[i].data.enum_values[emx])
emx++;
vars[i].enumerated.index = SDLTest_RandomIntegerInRange(0, emx - 1);
}
break;
case SDL_SUT_OPTIONTYPE_INT:
vars[i].integer.value = SDLTest_RandomIntegerInRange(
options[i].data.range.min,
options[i].data.range.max);
break;
case SDL_SUT_OPTIONTYPE_STRING:
// String values are left unchanged
break;
}
}
/* convert variation to an arguments string */
if(!SDLVisualTest_MakeStrFromVariation(&variator->variation, &variator->config,
variator->buffer, MAX_SUT_ARGS_LEN))
{
SDLTest_LogError("SDLVisualTest_MakeStrFromVariation() failed");
return NULL;
}
return variator->buffer;
}
void SDLVisualTest_FreeRandomVariator(SDLVisualTest_RandomVariator* variator)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return;
}
SDL_free(variator->variation.vars);
variator->variation.vars = NULL;
}

93
externals/SDL/visualtest/src/variators.c vendored Executable file
View File

@@ -0,0 +1,93 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file variators.c
*
* Source file for the operations that act on variators.
*/
#include <SDL_test.h>
#include "SDL_visualtest_variators.h"
int
SDLVisualTest_InitVariator(SDLVisualTest_Variator* variator,
SDLVisualTest_SUTConfig* config,
SDLVisualTest_VariatorType type,
Uint64 seed)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return 0;
}
if(!config)
{
SDLTest_LogError("config argument cannot be NULL");
return 0;
}
variator->type = type;
switch(type)
{
case SDL_VARIATOR_EXHAUSTIVE:
return SDLVisualTest_InitExhaustiveVariator(&variator->data.exhaustive,
config);
break;
case SDL_VARIATOR_RANDOM:
return SDLVisualTest_InitRandomVariator(&variator->data.random,
config, seed);
break;
default:
SDLTest_LogError("Invalid value for variator type");
return 0;
}
return 0;
}
char*
SDLVisualTest_GetNextVariation(SDLVisualTest_Variator* variator)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return NULL;
}
switch(variator->type)
{
case SDL_VARIATOR_EXHAUSTIVE:
return SDLVisualTest_GetNextExhaustiveVariation(&variator->data.exhaustive);
break;
case SDL_VARIATOR_RANDOM:
return SDLVisualTest_GetNextRandomVariation(&variator->data.random);
break;
default:
SDLTest_LogError("Invalid value for variator type");
return NULL;
}
return NULL;
}
void SDLVisualTest_FreeVariator(SDLVisualTest_Variator* variator)
{
if(!variator)
{
SDLTest_LogError("variator argument cannot be NULL");
return;
}
switch(variator->type)
{
case SDL_VARIATOR_EXHAUSTIVE:
SDLVisualTest_FreeExhaustiveVariator(&variator->data.exhaustive);
break;
case SDL_VARIATOR_RANDOM:
SDLVisualTest_FreeRandomVariator(&variator->data.random);
break;
default:
SDLTest_LogError("Invalid value for variator type");
}
}

View File

@@ -0,0 +1,284 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file windows_process.c
*
* Source file for the process API on windows.
*/
#include <SDL.h>
#include <SDL_test.h>
#include <string.h>
#include <stdlib.h>
#include "SDL_visualtest_process.h"
#if defined(__WIN32__)
void
LogLastError(char* str)
{
LPVOID buffer;
DWORD dw = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&buffer,
0, NULL);
SDLTest_LogError("%s: %s", str, (char*)buffer);
LocalFree(buffer);
}
int
SDL_LaunchProcess(char* file, char* args, SDL_ProcessInfo* pinfo)
{
BOOL success;
char* working_directory;
char* command_line;
int path_length, args_length;
STARTUPINFO sui = {0};
sui.cb = sizeof(sui);
if(!file)
{
SDLTest_LogError("Path to executable to launched cannot be NULL.");
return 0;
}
if(!pinfo)
{
SDLTest_LogError("pinfo cannot be NULL.");
return 0;
}
/* get the working directory of the process being launched, so that
the process can load any resources it has in it's working directory */
path_length = SDL_strlen(file);
if(path_length == 0)
{
SDLTest_LogError("Length of the file parameter is zero.");
return 0;
}
working_directory = (char*)SDL_malloc(path_length + 1);
if(!working_directory)
{
SDLTest_LogError("Could not allocate working_directory - malloc() failed.");
return 0;
}
SDL_memcpy(working_directory, file, path_length + 1);
PathRemoveFileSpec(working_directory);
if(SDL_strlen(working_directory) == 0)
{
SDL_free(working_directory);
working_directory = NULL;
}
/* join the file path and the args string together */
if(!args)
args = "";
args_length = SDL_strlen(args);
command_line = (char*)SDL_malloc(path_length + args_length + 2);
if(!command_line)
{
SDLTest_LogError("Could not allocate command_line - malloc() failed.");
return 0;
}
SDL_memcpy(command_line, file, path_length);
command_line[path_length] = ' ';
SDL_memcpy(command_line + path_length + 1, args, args_length + 1);
/* create the process */
success = CreateProcess(NULL, command_line, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
NULL, working_directory, &sui, &pinfo->pi);
if(working_directory)
{
SDL_free(working_directory);
working_directory = NULL;
}
SDL_free(command_line);
if(!success)
{
LogLastError("CreateProcess() failed");
return 0;
}
return 1;
}
int
SDL_GetProcessExitStatus(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
DWORD exit_status;
BOOL success;
if(!pinfo)
{
SDLTest_LogError("pinfo cannot be NULL");
return 0;
}
if(!ps)
{
SDLTest_LogError("ps cannot be NULL");
return 0;
}
/* get the exit code */
success = GetExitCodeProcess(pinfo->pi.hProcess, &exit_status);
if(!success)
{
LogLastError("GetExitCodeProcess() failed");
return 0;
}
if(exit_status == STILL_ACTIVE)
ps->exit_status = -1;
else
ps->exit_status = exit_status;
ps->exit_success = 1;
return 1;
}
int
SDL_IsProcessRunning(SDL_ProcessInfo* pinfo)
{
DWORD exit_status;
BOOL success;
if(!pinfo)
{
SDLTest_LogError("pinfo cannot be NULL");
return -1;
}
success = GetExitCodeProcess(pinfo->pi.hProcess, &exit_status);
if(!success)
{
LogLastError("GetExitCodeProcess() failed");
return -1;
}
if(exit_status == STILL_ACTIVE)
return 1;
return 0;
}
static BOOL CALLBACK
CloseWindowCallback(HWND hwnd, LPARAM lparam)
{
DWORD pid;
SDL_ProcessInfo* pinfo;
pinfo = (SDL_ProcessInfo*)lparam;
GetWindowThreadProcessId(hwnd, &pid);
if(pid == pinfo->pi.dwProcessId)
{
DWORD result;
if(!SendMessageTimeout(hwnd, WM_CLOSE, 0, 0, SMTO_BLOCK,
1000, &result))
{
if(GetLastError() != ERROR_TIMEOUT)
{
LogLastError("SendMessageTimeout() failed");
return FALSE;
}
}
}
return TRUE;
}
int
SDL_QuitProcess(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
DWORD wait_result;
if(!pinfo)
{
SDLTest_LogError("pinfo argument cannot be NULL");
return 0;
}
if(!ps)
{
SDLTest_LogError("ps argument cannot be NULL");
return 0;
}
/* enumerate through all the windows, trying to close each one */
if(!EnumWindows(CloseWindowCallback, (LPARAM)pinfo))
{
SDLTest_LogError("EnumWindows() failed");
return 0;
}
/* wait until the process terminates */
wait_result = WaitForSingleObject(pinfo->pi.hProcess, 1000);
if(wait_result == WAIT_FAILED)
{
LogLastError("WaitForSingleObject() failed");
return 0;
}
if(wait_result != WAIT_OBJECT_0)
{
SDLTest_LogError("Process did not quit.");
return 0;
}
/* get the exit code */
if(!SDL_GetProcessExitStatus(pinfo, ps))
{
SDLTest_LogError("SDL_GetProcessExitStatus() failed");
return 0;
}
return 1;
}
int
SDL_KillProcess(SDL_ProcessInfo* pinfo, SDL_ProcessExitStatus* ps)
{
BOOL success;
DWORD exit_status, wait_result;
if(!pinfo)
{
SDLTest_LogError("pinfo argument cannot be NULL");
return 0;
}
if(!ps)
{
SDLTest_LogError("ps argument cannot be NULL");
return 0;
}
/* initiate termination of the process */
success = TerminateProcess(pinfo->pi.hProcess, 0);
if(!success)
{
LogLastError("TerminateProcess() failed");
return 0;
}
/* wait until the process terminates */
wait_result = WaitForSingleObject(pinfo->pi.hProcess, INFINITE);
if(wait_result == WAIT_FAILED)
{
LogLastError("WaitForSingleObject() failed");
return 0;
}
/* get the exit code */
success = GetExitCodeProcess(pinfo->pi.hProcess, &exit_status);
if(!success)
{
LogLastError("GetExitCodeProcess() failed");
return 0;
}
ps->exit_status = exit_status;
ps->exit_success = 1;
return 1;
}
#endif

View File

@@ -0,0 +1,349 @@
/* See COPYING.txt for the full license governing this code. */
/**
* \file windows_screenshot.c
*
* Source file for the screenshot API on windows.
*/
#include "SDL_visualtest_process.h"
#include <SDL.h>
#include <SDL_test.h>
#if defined(__CYGWIN__)
#include <sys/stat.h>
#endif
#if defined(__WIN32__)
#include <Windows.h>
void LogLastError(char* str);
static int img_num;
static SDL_ProcessInfo screenshot_pinfo;
/* Saves a bitmap to a file using hdc as a device context */
static int
SaveBitmapToFile(HDC hdc, HBITMAP hbitmap, char* filename)
{
BITMAP bitmap;
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
DWORD bmpsize, bytes_written;
HANDLE hdib, hfile;
char* bmpdata;
int return_code = 1;
if(!hdc)
{
SDLTest_LogError("hdc argument is NULL");
return 0;
}
if(!hbitmap)
{
SDLTest_LogError("hbitmap argument is NULL");
return 0;
}
if(!filename)
{
SDLTest_LogError("filename argument is NULL");
return 0;
}
if(!GetObject(hbitmap, sizeof(BITMAP), (void*)&bitmap))
{
SDLTest_LogError("GetObject() failed");
return_code = 0;
goto savebitmaptofile_cleanup_generic;
}
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = bitmap.bmWidth;
bih.biHeight = bitmap.bmHeight;
bih.biPlanes = 1;
bih.biBitCount = 32;
bih.biCompression = BI_RGB;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
bmpsize = ((bitmap.bmWidth * bih.biBitCount + 31) / 32) * 4 * bitmap.bmHeight;
hdib = GlobalAlloc(GHND, bmpsize);
if(!hdib)
{
LogLastError("GlobalAlloc() failed");
return_code = 0;
goto savebitmaptofile_cleanup_generic;
}
bmpdata = (char*)GlobalLock(hdib);
if(!bmpdata)
{
LogLastError("GlobalLock() failed");
return_code = 0;
goto savebitmaptofile_cleanup_hdib;
}
if(!GetDIBits(hdc, hbitmap, 0, (UINT)bitmap.bmHeight, bmpdata,
(LPBITMAPINFO)&bih, DIB_RGB_COLORS))
{
SDLTest_LogError("GetDIBits() failed");
return_code = 0;
goto savebitmaptofile_cleanup_unlockhdib;
}
hfile = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if(hfile == INVALID_HANDLE_VALUE)
{
LogLastError("CreateFile()");
return_code = 0;
goto savebitmaptofile_cleanup_unlockhdib;
}
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfSize = bmpsize + bfh.bfOffBits;
bfh.bfType = 0x4D42;
bytes_written = 0;
if(!WriteFile(hfile, (void*)&bfh, sizeof(BITMAPFILEHEADER), &bytes_written, NULL) ||
!WriteFile(hfile, (void*)&bih, sizeof(BITMAPINFOHEADER), &bytes_written, NULL) ||
!WriteFile(hfile, (void*)bmpdata, bmpsize, &bytes_written, NULL))
{
LogLastError("WriteFile() failed");
return_code = 0;
goto savebitmaptofile_cleanup_hfile;
}
savebitmaptofile_cleanup_hfile:
CloseHandle(hfile);
/* make the screenshot file writable on cygwin, since it could be overwritten later */
#if defined(__CYGWIN__)
if(chmod(filename, 0777) == -1)
{
SDLTest_LogError("chmod() failed");
return_code = 0;
}
#endif
savebitmaptofile_cleanup_unlockhdib:
GlobalUnlock(hdib);
savebitmaptofile_cleanup_hdib:
GlobalFree(hdib);
savebitmaptofile_cleanup_generic:
return return_code;
}
/* Takes the screenshot of a window and saves it to a file. If only_client_area
is true, then only the client area of the window is considered */
static int
ScreenshotWindow(HWND hwnd, char* filename, SDL_bool only_client_area)
{
int width, height;
RECT dimensions;
HDC windowdc, capturedc;
HBITMAP capturebitmap;
HGDIOBJ select_success;
BOOL blt_success;
int return_code = 1;
if(!filename)
{
SDLTest_LogError("filename argument cannot be NULL");
return_code = 0;
goto screenshotwindow_cleanup_generic;
}
if(!hwnd)
{
SDLTest_LogError("hwnd argument cannot be NULL");
return_code = 0;
goto screenshotwindow_cleanup_generic;
}
if(!GetWindowRect(hwnd, &dimensions))
{
LogLastError("GetWindowRect() failed");
return_code = 0;
goto screenshotwindow_cleanup_generic;
}
if(only_client_area)
{
RECT crect;
if(!GetClientRect(hwnd, &crect))
{
SDLTest_LogError("GetClientRect() failed");
return_code = 0;
goto screenshotwindow_cleanup_generic;
}
width = crect.right;
height = crect.bottom;
windowdc = GetDC(hwnd);
if(!windowdc)
{
SDLTest_LogError("GetDC() failed");
return_code = 0;
goto screenshotwindow_cleanup_generic;
}
}
else
{
width = dimensions.right - dimensions.left;
height = dimensions.bottom - dimensions.top;
windowdc = GetWindowDC(hwnd);
if(!windowdc)
{
SDLTest_LogError("GetWindowDC() failed");
return_code = 0;
goto screenshotwindow_cleanup_generic;
}
}
capturedc = CreateCompatibleDC(windowdc);
if(!capturedc)
{
SDLTest_LogError("CreateCompatibleDC() failed");
return_code = 0;
goto screenshotwindow_cleanup_windowdc;
}
capturebitmap = CreateCompatibleBitmap(windowdc, width, height);
if(!capturebitmap)
{
SDLTest_LogError("CreateCompatibleBitmap() failed");
return_code = 0;
goto screenshotwindow_cleanup_capturedc;
}
select_success = SelectObject(capturedc, capturebitmap);
if(!select_success || select_success == HGDI_ERROR)
{
SDLTest_LogError("SelectObject() failed");
return_code = 0;
goto screenshotwindow_cleanup_capturebitmap;
}
blt_success = BitBlt(capturedc, 0, 0, width, height, windowdc,
0, 0, SRCCOPY|CAPTUREBLT);
if(!blt_success)
{
LogLastError("BitBlt() failed");
return_code = 0;
goto screenshotwindow_cleanup_capturebitmap;
}
/* save bitmap as file */
if(!SaveBitmapToFile(windowdc, capturebitmap, filename))
{
SDLTest_LogError("SaveBitmapToFile() failed");
return_code = 0;
goto screenshotwindow_cleanup_capturebitmap;
}
/* free resources */
screenshotwindow_cleanup_capturebitmap:
if(!DeleteObject(capturebitmap))
{
SDLTest_LogError("DeleteObjectFailed");
return_code = 0;
}
screenshotwindow_cleanup_capturedc:
if(!DeleteDC(capturedc))
{
SDLTest_LogError("DeleteDC() failed");
return_code = 0;
}
screenshotwindow_cleanup_windowdc:
if(!ReleaseDC(hwnd, windowdc))
{
SDLTest_LogError("ReleaseDC() failed");
return_code = 0;;
}
screenshotwindow_cleanup_generic:
return return_code;
}
/* Takes the screenshot of the entire desktop and saves it to a file */
int SDLVisualTest_ScreenshotDesktop(char* filename)
{
HWND hwnd;
hwnd = GetDesktopWindow();
return ScreenshotWindow(hwnd, filename, SDL_FALSE);
}
/* take screenshot of a window and save it to a file */
static BOOL CALLBACK
ScreenshotHwnd(HWND hwnd, LPARAM lparam)
{
int len;
DWORD pid;
char* prefix;
char* filename;
GetWindowThreadProcessId(hwnd, &pid);
if(pid != screenshot_pinfo.pi.dwProcessId)
return TRUE;
if(!IsWindowVisible(hwnd))
return TRUE;
prefix = (char*)lparam;
len = SDL_strlen(prefix) + 100;
filename = (char*)SDL_malloc(len * sizeof(char));
if(!filename)
{
SDLTest_LogError("malloc() failed");
return FALSE;
}
/* restore the window and bring it to the top */
ShowWindowAsync(hwnd, SW_RESTORE);
/* restore is not instantaneous */
SDL_Delay(500);
/* take a screenshot of the client area */
if(img_num == 1)
SDL_snprintf(filename, len, "%s.bmp", prefix);
else
SDL_snprintf(filename, len, "%s_%d.bmp", prefix, img_num);
img_num++;
ScreenshotWindow(hwnd, filename, SDL_TRUE);
SDL_free(filename);
return TRUE;
}
/* each window of the process will have a screenshot taken. The file name will be
prefix-i.png for the i'th window. */
int
SDLVisualTest_ScreenshotProcess(SDL_ProcessInfo* pinfo, char* prefix)
{
if(!pinfo)
{
SDLTest_LogError("pinfo argument cannot be NULL");
return 0;
}
if(!prefix)
{
SDLTest_LogError("prefix argument cannot be NULL");
return 0;
}
img_num = 1;
screenshot_pinfo = *pinfo;
if(!EnumWindows(ScreenshotHwnd, (LPARAM)prefix))
{
SDLTest_LogError("EnumWindows() failed");
return 0;
}
return 1;
}
#endif