X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=blobdiff_plain;f=test.c;h=5e41be36b40c311f6c5c4d7ec4f3cd16da8be3f8;hp=0ddb75a70cbd021bec1c17629f390416f59ad75d;hb=724bca0eeca05a68900e3054e118904b6618dc7c;hpb=d27c06ea7fb9f55ea4014851af2b219732b657ef diff --git a/test.c b/test.c index 0ddb75a..5e41be3 100644 --- a/test.c +++ b/test.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012, 2013 + * Copyright (C) 2012, 2013, 2014, 2015 * Dale Weiler * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -20,47 +20,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#include "gmqcc.h" +#include +#include +#include + #include +#include #include -opts_cmd_t opts; +#include +#include -const char *task_bins[] = { +#include "gmqcc.h" + +static const char *task_bins[] = { "./gmqcc", "./qcvm" }; -/* - * TODO: Windows version - * this implements a unique bi-directional popen-like function that - * allows reading data from both stdout and stderr. And writing to - * stdin :) - * - * Example of use: - * FILE *handles[3] = task_popen("ls", "-l", "r"); - * if (!handles) { perror("failed to open stdin/stdout/stderr to ls"); - * // handles[0] = stdin - * // handles[1] = stdout - * // handles[2] = stderr - * - * task_pclose(handles); // to close - */ -#ifndef _WIN32 -#include -#include -#include -#include typedef struct { FILE *handles[3]; - int pipes [3]; - + int pipes[3]; int stderr_fd; int stdout_fd; int pid; } popen_t; -FILE ** task_popen(const char *command, const char *mode) { +static FILE **task_popen(const char *command, const char *mode) { int inhandle [2]; int outhandle [2]; int errhandle [2]; @@ -84,7 +70,7 @@ FILE ** task_popen(const char *command, const char *mode) { while (*line != '\0' && *line != ' ' && *line != '\t' && *line != '\n') line++; } - vec_push(argv, '\0'); + vec_push(argv, (char *)0); } @@ -94,20 +80,18 @@ FILE ** task_popen(const char *command, const char *mode) { if ((data->pid = fork()) > 0) { /* parent */ - close(inhandle [0]); + close(inhandle [0]); close(outhandle [1]); close(errhandle [1]); - - data->pipes [0] = inhandle [1]; - data->pipes [1] = outhandle[0]; - data->pipes [2] = errhandle[0]; + data->pipes[0] = inhandle [1]; + data->pipes[1] = outhandle[0]; + data->pipes[2] = errhandle[0]; data->handles[0] = fdopen(inhandle [1], "w"); data->handles[1] = fdopen(outhandle[0], mode); data->handles[2] = fdopen(errhandle[0], mode); /* sigh */ - if (argv) - vec_free(argv); + vec_free(argv); return data->handles; } else if (data->pid == 0) { /* child */ @@ -116,9 +100,9 @@ FILE ** task_popen(const char *command, const char *mode) { close(errhandle[0]); /* see piping documentation for this sillyness :P */ - close(0), dup(inhandle [0]); - close(1), dup(outhandle[1]); - close(2), dup(errhandle[1]); + dup2(inhandle [0], 0); + dup2(outhandle[1], 1); + dup2(errhandle[1], 2); execvp(*argv, argv); exit(EXIT_FAILURE); @@ -132,12 +116,11 @@ task_popen_error_2: close(outhandle[0]), close(outhandle[1]); task_popen_error_1: close(inhandle [0]), close(inhandle [1]); task_popen_error_0: - if (argv) - vec_free(argv); + vec_free(argv); return NULL; } -int task_pclose(FILE **handles) { +static int task_pclose(FILE **handles) { popen_t *data = (popen_t*)handles; int status = 0; @@ -151,33 +134,9 @@ int task_pclose(FILE **handles) { return status; } -#else - /* - * Bidirectional piping implementation for windows using CreatePipe and DuplicateHandle + - * other hacks. - */ - typedef struct { - int __dummy; - /* TODO: implement */ - } popen_t; - - FILE **task_popen(const char *command, const char *mode) { - (void)command; - (void)mode; - /* TODO: implement */ - return NULL; - } - - void task_pclose(FILE **files) { - /* TODO: implement */ - (void)files; - return; - } -#endif /*! _WIN32 */ - -#define TASK_COMPILE 0 -#define TASK_EXECUTE 1 +#define TASK_COMPILE 0 +#define TASK_EXECUTE 1 /* * Task template system: * templates are rules for a specific test, used to create a "task" that @@ -210,7 +169,7 @@ int task_pclose(FILE **handles) { * This will perform compilation and execution * -fail * This will perform compilation, but requires - * the compilation to fail in order to succeed. + * the compilation to fail in order to succeed. * * This must be provided, this tag is NOT optional. * @@ -267,8 +226,9 @@ typedef struct { * This is very much like a compiler code generator :-). This generates * a value from some data observed from the compiler. */ -bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) { +static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) { size_t desclen = 0; + size_t filelen = 0; char **destval = NULL; if (!tmpl) @@ -282,7 +242,7 @@ bool task_template_generate(task_template_t *tmpl, char tag, const char *file, s case 'I': destval = &tmpl->sourcefile; break; case 'F': destval = &tmpl->testflags; break; default: - con_printmsg(LVL_ERROR, __FILE__, __LINE__, "internal error", + con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error", "invalid tag `%c:` during code generation\n", tag ); @@ -294,7 +254,7 @@ bool task_template_generate(task_template_t *tmpl, char tag, const char *file, s * assigned value. */ if (*destval) { - con_printmsg(LVL_ERROR, file, line, "compile error", + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error", "tag `%c:` already assigned value: %s\n", tag, *destval ); @@ -307,6 +267,8 @@ bool task_template_generate(task_template_t *tmpl, char tag, const char *file, s */ if (value && *value && (*value == ' ' || *value == '\t')) value++; + else if (!value) + exit(EXIT_FAILURE); /* * Value will contain a newline character at the end, we need to strip @@ -314,8 +276,6 @@ bool task_template_generate(task_template_t *tmpl, char tag, const char *file, s */ if (strchr(value, '\n')) *strrchr(value, '\n')='\0'; - else /* cppcheck: possible nullpointer dereference */ - abort(); /* * Now allocate and set the actual value for the specific tag. Which @@ -328,15 +288,18 @@ bool task_template_generate(task_template_t *tmpl, char tag, const char *file, s /* * Create some padding for the description to align the * printing of the rules file. - */ + */ if ((desclen = strlen(tmpl->description)) > pad[0]) pad[0] = desclen; } + if ((filelen = strlen(file)) > pad[2]) + pad[2] = filelen; + return true; } -bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) { +static bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) { char *data = NULL; char *back = NULL; size_t size = 0; @@ -346,7 +309,7 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size return false; /* top down parsing */ - while (fs_file_getline(&back, &size, fp) != EOF) { + while (util_getline(&back, &size, fp) != EOF) { /* skip whitespace */ data = back; if (*data && (*data == ' ' || *data == '\t')) @@ -359,7 +322,7 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size */ case '/': if (data[1] != '/') { - con_printmsg(LVL_ERROR, file, line, "tmpl parse error", + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", "invalid character `/`, perhaps you meant `//` ?"); mem_d(back); @@ -389,14 +352,14 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size case 'I': case 'F': if (data[1] != ':') { - con_printmsg(LVL_ERROR, file, line, "tmpl parse error", + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", "expected `:` after `%c`", *data ); goto failure; } if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) { - con_printmsg(LVL_ERROR, file, line, "tmpl compile error", + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error", "failed to generate for given task\n" ); goto failure; @@ -411,16 +374,13 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size { char *value = &data[3]; if (data[1] != ':') { - con_printmsg(LVL_ERROR, file, line, "tmpl parse error", + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", "expected `:` after `%c`", *data ); goto failure; } - if (value && *value && (*value == ' ' || *value == '\t')) - value++; - /* * Value will contain a newline character at the end, we need to strip * this otherwise kaboom, seriously, kaboom :P @@ -428,7 +388,7 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size if (strrchr(value, '\n')) *strrchr(value, '\n')='\0'; else /* cppcheck: possible null pointer dereference */ - abort(); + exit(EXIT_FAILURE); vec_push(tmpl->comparematch, util_strdup(value)); @@ -436,7 +396,7 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size } default: - con_printmsg(LVL_ERROR, file, line, "tmpl parse error", + con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error", "invalid tag `%c`", *data ); goto failure; @@ -453,8 +413,7 @@ bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size return true; failure: - if (back) - mem_d (back); + mem_d (back); return false; } @@ -462,7 +421,7 @@ failure: * Nullifies the template data: used during initialization of a new * template and free. */ -void task_template_nullify(task_template_t *tmpl) { +static void task_template_nullify(task_template_t *tmpl) { if (!tmpl) return; @@ -477,23 +436,23 @@ void task_template_nullify(task_template_t *tmpl) { tmpl->testflags = NULL; } -task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) { +static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) { /* a page should be enough */ char fullfile[4096]; size_t filepadd = 0; - FILE *tempfile = NULL; + FILE *tempfile = NULL; task_template_t *tmpl = NULL; - snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file); + util_snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file); - tempfile = fs_file_open(fullfile, "r"); + tempfile = fopen(fullfile, "r"); tmpl = (task_template_t*)mem_a(sizeof(task_template_t)); task_template_nullify(tmpl); /* * Create some padding for the printing to align the * printing of the rules file to the console. - */ + */ if ((filepadd = strlen(fullfile)) > pad[1]) pad[1] = filepadd; @@ -563,14 +522,27 @@ task_template_t *task_template_compile(const char *file, const char *dir, size_t con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file); if (tmpl->comparematch) con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file); - goto success; + } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) { + if (tmpl->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file); + if (!tmpl->comparematch) { + con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); + goto failure; + } + } else if (!strcmp(tmpl->proceduretype, "-pp")) { + if (tmpl->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file); + if (!tmpl->comparematch) { + con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); + goto failure; + } } else { con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype); goto failure; } success: - fs_file_close(tempfile); + fclose(tempfile); return tmpl; failure: @@ -579,23 +551,23 @@ failure: * so the check to see if it's not null here is required. */ if (tempfile) - fs_file_close(tempfile); + fclose(tempfile); mem_d (tmpl); return NULL; } -void task_template_destroy(task_template_t **tmpl) { +static void task_template_destroy(task_template_t *tmpl) { if (!tmpl) return; - if ((*tmpl)->description) mem_d((*tmpl)->description); - if ((*tmpl)->proceduretype) mem_d((*tmpl)->proceduretype); - if ((*tmpl)->compileflags) mem_d((*tmpl)->compileflags); - if ((*tmpl)->executeflags) mem_d((*tmpl)->executeflags); - if ((*tmpl)->sourcefile) mem_d((*tmpl)->sourcefile); - if ((*tmpl)->rulesfile) mem_d((*tmpl)->rulesfile); - if ((*tmpl)->testflags) mem_d((*tmpl)->testflags); + if (tmpl->description) mem_d(tmpl->description); + if (tmpl->proceduretype) mem_d(tmpl->proceduretype); + if (tmpl->compileflags) mem_d(tmpl->compileflags); + if (tmpl->executeflags) mem_d(tmpl->executeflags); + if (tmpl->sourcefile) mem_d(tmpl->sourcefile); + if (tmpl->rulesfile) mem_d(tmpl->rulesfile); + if (tmpl->testflags) mem_d(tmpl->testflags); /* * Delete all allocated string for task tmpl then destroy the @@ -603,17 +575,18 @@ void task_template_destroy(task_template_t **tmpl) { */ { size_t i = 0; - for (; i < vec_size((*tmpl)->comparematch); i++) - mem_d((*tmpl)->comparematch[i]); + for (; i < vec_size(tmpl->comparematch); i++) + mem_d(tmpl->comparematch[i]); - vec_free((*tmpl)->comparematch); + vec_free(tmpl->comparematch); } /* * Nullify all the template members otherwise NULL comparision * checks will fail if tmpl pointer is reused. */ - mem_d(*tmpl); + mem_d(tmpl->tempfilename); + mem_d(tmpl); } /* @@ -622,162 +595,211 @@ void task_template_destroy(task_template_t **tmpl) { */ typedef struct { task_template_t *tmpl; - FILE **runhandles; - FILE *stderrlog; - FILE *stdoutlog; - char *stdoutlogfile; - char *stderrlogfile; - bool compiled; + FILE **runhandles; + FILE *stderrlog; + FILE *stdoutlog; + char *stdoutlogfile; + char *stderrlogfile; + bool compiled; } task_t; -task_t *task_tasks = NULL; +static task_t *task_tasks = NULL; /* * Read a directory and searches for all template files in it * which is later used to run all tests. */ -bool task_propagate(const char *curdir, size_t *pad, const char *defs) { +static bool task_propagate(const char *curdir, size_t *pad, const char *defs) { bool success = true; - DIR *dir; - struct dirent *files; + DIR *dir; + struct dirent *files; struct stat directory; char buffer[4096]; size_t found = 0; + char **directories = NULL; + char *claim = util_strdup(curdir); + size_t i; - dir = opendir(curdir); + vec_push(directories, claim); + dir = opendir(claim); + /* + * Generate a list of subdirectories since we'll be checking them too + * for tmpl files. + */ while ((files = readdir(dir))) { - snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); - - if (stat(buffer, &directory) == -1) { - con_err("internal error: stat failed, aborting\n"); - abort(); + util_asprintf(&claim, "%s/%s", curdir, files->d_name); + if (stat(claim, &directory) == -1) { + closedir(dir); + mem_d(claim); + return false; } - /* skip directories */ - if (S_ISDIR(directory.st_mode)) - continue; + if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') { + vec_push(directories, claim); + } else { + mem_d(claim); + claim = NULL; + } + } + closedir(dir); - /* - * We made it here, which concludes the file/directory is not - * actually a directory, so it must be a file :) - */ - if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) { - task_template_t *tmpl = task_template_compile(files->d_name, curdir, pad); - char buf[4096]; /* one page should be enough */ - char *qcflags = NULL; - task_t task; - - util_debug("TEST", "compiling task template: %s/%s\n", curdir, files->d_name); - found ++; - if (!tmpl) { - con_err("error compiling task template: %s\n", files->d_name); - success = false; - continue; + /* + * Now do all the work, by touching all the directories inside + * test as well and compile the task templates into data we can + * use to run the tests. + */ + for (i = 0; i < vec_size(directories); i++) { + dir = opendir(directories[i]); + + while ((files = readdir(dir))) { + util_snprintf(buffer, sizeof(buffer), "%s/%s", directories[i], files->d_name); + if (stat(buffer, &directory) == -1) { + con_err("internal error: stat failed, aborting\n"); + abort(); } - /* - * Generate a temportary file name for the output binary - * so we don't trample over an existing one. - */ - tmpl->tempfilename = tempnam(curdir, "TMPDAT"); - /* - * Additional QCFLAGS enviroment variable may be used - * to test compile flags for all tests. This needs to be - * BEFORE other flags (so that the .tmpl can override them) - */ - qcflags = getenv("QCFLAGS"); + if (S_ISDIR(directory.st_mode)) + continue; /* - * Generate the command required to open a pipe to a process - * which will be refered to with a handle in the task for - * reading the data from the pipe. + * We made it here, which concludes the file/directory is not + * actually a directory, so it must be a file :) */ - if (qcflags) { - if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { - snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - tmpl->sourcefile, - qcflags, - tmpl->compileflags, - tmpl->tempfilename - ); - } else { - snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - defs, - curdir, - tmpl->sourcefile, - qcflags, - tmpl->compileflags, - tmpl->tempfilename - ); + if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) { + task_template_t *tmpl = task_template_compile(files->d_name, directories[i], pad); + char buf[4096]; /* one page should be enough */ + const char *qcflags = NULL; + task_t task; + + found ++; + if (!tmpl) { + con_err("error compiling task template: %s\n", files->d_name); + success = false; + continue; } - } else { - if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { - snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - tmpl->sourcefile, - tmpl->compileflags, - tmpl->tempfilename - ); + /* + * Generate a temportary file name for the output binary + * so we don't trample over an existing one. + */ + tmpl->tempfilename = NULL; + util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", directories[i], files->d_name); + + /* + * Additional QCFLAGS enviroment variable may be used + * to test compile flags for all tests. This needs to be + * BEFORE other flags (so that the .tmpl can override them) + */ + qcflags = getenv("QCFLAGS"); + + /* + * Generate the command required to open a pipe to a process + * which will be refered to with a handle in the task for + * reading the data from the pipe. + */ + if (strcmp(tmpl->proceduretype, "-pp")) { + if (qcflags) { + if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s", + task_bins[TASK_COMPILE], + directories[i], + tmpl->sourcefile, + qcflags, + tmpl->compileflags, + tmpl->tempfilename + ); + } else { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + directories[i], + tmpl->sourcefile, + qcflags, + tmpl->compileflags, + tmpl->tempfilename + ); + } + } else { + if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } else { + util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } + } } else { - snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - defs, - curdir, - tmpl->sourcefile, - tmpl->compileflags, - tmpl->tempfilename - ); + /* Preprocessing (qcflags mean shit all here we don't allow them) */ + if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) { + util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s", + task_bins[TASK_COMPILE], + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } else { + util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + directories[i], + tmpl->sourcefile, + tmpl->compileflags, + tmpl->tempfilename + ); + } } - } - - /* - * The task template was compiled, now lets create a task from - * the template data which has now been propagated. - */ - task.tmpl = tmpl; - if (!(task.runhandles = task_popen(buf, "r"))) { - con_err("error opening pipe to process for test: %s\n", tmpl->description); - success = false; - continue; - } - util_debug("TEST", "executing test: `%s` [%s]\n", tmpl->description, buf); + /* + * The task template was compiled, now lets create a task from + * the template data which has now been propagated. + */ + task.tmpl = tmpl; + if (!(task.runhandles = task_popen(buf, "r"))) { + con_err("error opening pipe to process for test: %s\n", tmpl->description); + success = false; + continue; + } - /* - * Open up some file desciptors for logging the stdout/stderr - * to our own. - */ - snprintf(buf, sizeof(buf), "%s.stdout", tmpl->tempfilename); - task.stdoutlogfile = util_strdup(buf); - if (!(task.stdoutlog = fs_file_open(buf, "w"))) { - con_err("error opening %s for stdout\n", buf); - continue; - } + /* + * Open up some file desciptors for logging the stdout/stderr + * to our own. + */ + util_snprintf(buf, sizeof(buf), "%s.stdout", tmpl->tempfilename); + task.stdoutlogfile = util_strdup(buf); + if (!(task.stdoutlog = fopen(buf, "w"))) { + con_err("error opening %s for stdout\n", buf); + continue; + } - snprintf(buf, sizeof(buf), "%s.stderr", tmpl->tempfilename); - task.stderrlogfile = util_strdup(buf); - if (!(task.stderrlog = fs_file_open(buf, "w"))) { - con_err("error opening %s for stderr\n", buf); - continue; + util_snprintf(buf, sizeof(buf), "%s.stderr", tmpl->tempfilename); + task.stderrlogfile = util_strdup(buf); + if (!(task.stderrlog = fopen(buf, "w"))) { + con_err("error opening %s for stderr\n", buf); + continue; + } + vec_push(task_tasks, task); } - - vec_push(task_tasks, task); } + closedir(dir); + mem_d(directories[i]); /* free claimed memory */ } + vec_free(directories); - util_debug("TEST", "compiled %d task template files out of %d\n", - vec_size(task_tasks), - found - ); - - closedir(dir); return success; } @@ -785,30 +807,29 @@ bool task_propagate(const char *curdir, size_t *pad, const char *defs) { * Task precleanup removes any existing temporary files or log files * left behind from a previous invoke of the test-suite. */ -void task_precleanup(const char *curdir) { - DIR *dir; - struct dirent *files; - char buffer[4096]; +static void task_precleanup(const char *curdir) { + DIR *dir; + struct dirent *files; + char buffer[4096]; dir = opendir(curdir); while ((files = readdir(dir))) { if (strstr(files->d_name, "TMP") || strstr(files->d_name, ".stdout") || - strstr(files->d_name, ".stderr")) + strstr(files->d_name, ".stderr") || + strstr(files->d_name, ".dat")) { - snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); + util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); if (remove(buffer)) con_err("error removing temporary file: %s\n", buffer); - else - util_debug("TEST", "removed temporary file: %s\n", buffer); } } closedir(dir); } -void task_destroy(void) { +static void task_destroy(void) { /* * Free all the data in the task list and finally the list itself * then proceed to cleanup anything else outside the program like @@ -820,33 +841,28 @@ void task_destroy(void) { * Close any open handles to files or processes here. It's mighty * annoying to have to do all this cleanup work. */ - if (task_tasks[i].runhandles) task_pclose(task_tasks[i].runhandles); - if (task_tasks[i].stdoutlog) fs_file_close (task_tasks[i].stdoutlog); - if (task_tasks[i].stderrlog) fs_file_close (task_tasks[i].stderrlog); + if (task_tasks[i].stdoutlog) fclose(task_tasks[i].stdoutlog); + if (task_tasks[i].stderrlog) fclose(task_tasks[i].stderrlog); /* * Only remove the log files if the test actually compiled otherwise * forget about it (or if it didn't compile, and the procedure type - * was set to -fail (meaning it shouldn't compile) .. stil remove) + * was set to -fail (meaning it shouldn't compile) .. stil remove) */ if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) { if (remove(task_tasks[i].stdoutlogfile)) con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile); - else - util_debug("TEST", "removed stdout log file: %s\n", task_tasks[i].stdoutlogfile); if (remove(task_tasks[i].stderrlogfile)) con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile); - else - util_debug("TEST", "removed stderr log file: %s\n", task_tasks[i].stderrlogfile); - remove(task_tasks[i].tmpl->tempfilename); + (void)!remove(task_tasks[i].tmpl->tempfilename); } /* free util_strdup data for log files */ mem_d(task_tasks[i].stdoutlogfile); mem_d(task_tasks[i].stderrlogfile); - task_template_destroy(&task_tasks[i].tmpl); + task_template_destroy(task_tasks[i].tmpl); } vec_free(task_tasks); } @@ -854,40 +870,59 @@ void task_destroy(void) { /* * This executes the QCVM task for a specificly compiled progs.dat * using the template passed into it for call-flags and user defined - * messages. + * messages IF the procedure type is -execute, otherwise it matches + * the preprocessor output. */ -bool task_execute(task_template_t *tmpl, char ***line) { - bool success = true; - FILE *execute; - char buffer[4096]; +static bool task_trymatch(size_t i, char ***line) { + bool success = true; + bool process = true; + int retval = EXIT_SUCCESS; + FILE *execute; + char buffer[4096]; + task_template_t *tmpl = task_tasks[i].tmpl; + memset (buffer,0,sizeof(buffer)); - /* - * Drop the execution flags for the QCVM if none where - * actually specified. - */ - if (!strcmp(tmpl->executeflags, "$null")) { - snprintf(buffer, sizeof(buffer), "%s %s", - task_bins[TASK_EXECUTE], - tmpl->tempfilename - ); + if (!strcmp(tmpl->proceduretype, "-execute")) { + /* + * Drop the execution flags for the QCVM if none where + * actually specified. + */ + if (!strcmp(tmpl->executeflags, "$null")) { + util_snprintf(buffer, sizeof(buffer), "%s %s", + task_bins[TASK_EXECUTE], + tmpl->tempfilename + ); + } else { + util_snprintf(buffer, sizeof(buffer), "%s %s %s", + task_bins[TASK_EXECUTE], + tmpl->executeflags, + tmpl->tempfilename + ); + } + + execute = popen(buffer, "r"); + if (!execute) + return false; + } else if (!strcmp(tmpl->proceduretype, "-pp")) { + /* + * we're preprocessing, which means we need to read int + * the produced file and do some really weird shit. + */ + if (!(execute = fopen(tmpl->tempfilename, "r"))) + return false; + process = false; } else { - snprintf(buffer, sizeof(buffer), "%s %s %s", - task_bins[TASK_EXECUTE], - tmpl->executeflags, - tmpl->tempfilename - ); + /* + * we're testing diagnostic output, which means it will be + * in runhandles[2] (stderr) since that is where the compiler + * puts it's errors. + */ + if (!(execute = fopen(task_tasks[i].stderrlogfile, "r"))) + return false; + process = false; } - util_debug("TEST", "executing qcvm: `%s` [%s]\n", - tmpl->description, - buffer - ); - - execute = popen(buffer, "r"); - if (!execute) - return false; - /* * Now lets read the lines and compare them to the matches we expect * and handle accordingly. @@ -896,13 +931,17 @@ bool task_execute(task_template_t *tmpl, char ***line) { char *data = NULL; size_t size = 0; size_t compare = 0; - while (fs_file_getline(&data, &size, execute) != EOF) { + + while (util_getline(&data, &size, execute) != EOF) { if (!strcmp(data, "No main function found\n")) { con_err("test failure: `%s` (No main function found) [%s]\n", tmpl->description, tmpl->rulesfile ); - pclose(execute); + if (!process) + fclose(execute); + else + pclose((FILE*)execute); return false; } @@ -913,28 +952,72 @@ bool task_execute(task_template_t *tmpl, char ***line) { if (strrchr(data, '\n')) *strrchr(data, '\n') = '\0'; + /* + * We remove the file/directory and stuff from the error + * match messages when testing diagnostics. + */ + if(!strcmp(tmpl->proceduretype, "-diagnostic")) { + if (strstr(data, "there have been errors, bailing out")) + continue; /* ignore it */ + if (strstr(data, ": error: ")) { + char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9); + mem_d(data); + data = claim; + } + } + + /* + * We need to ignore null lines for when -pp is used (preprocessor), since + * the preprocessor is likely to create empty newlines in certain macro + * instantations, otherwise it's in the wrong nature to ignore empty newlines. + */ + if (!strcmp(tmpl->proceduretype, "-pp") && !*data) + continue; + if (vec_size(tmpl->comparematch) > compare) { - if (strcmp(data, tmpl->comparematch[compare++])) + if (strcmp(data, tmpl->comparematch[compare++])) { success = false; + } } else { - success = false; + success = false; } /* * Copy to output vector for diagnostics if execution match * fails. - */ + */ vec_push(*line, data); /* reset */ data = NULL; size = 0; } + + if (compare != vec_size(tmpl->comparematch)) + success = false; + mem_d(data); data = NULL; } - pclose(execute); - return success; + + if (process) + retval = pclose((FILE*)execute); + else + fclose(execute); + + return success && retval == EXIT_SUCCESS; +} + +static const char *task_type(task_template_t *tmpl) { + if (!strcmp(tmpl->proceduretype, "-pp")) + return "type: preprocessor"; + if (!strcmp(tmpl->proceduretype, "-execute")) + return "type: execution"; + if (!strcmp(tmpl->proceduretype, "-compile")) + return "type: compile"; + if (!strcmp(tmpl->proceduretype, "-diagnostic")) + return "type: diagnostic"; + return "type: fail"; } /* @@ -943,23 +1026,35 @@ bool task_execute(task_template_t *tmpl, char ***line) { * execution this takes more work since a task needs to be generated * from thin air and executed INLINE. */ -void task_schedualize(size_t *pad) { +#include +static size_t task_schedualize(size_t *pad) { + char space[2][64]; bool execute = false; char *data = NULL; char **match = NULL; size_t size = 0; - size_t i; - size_t j; + size_t i = 0; + size_t j = 0; + size_t failed = 0; + int status = 0; - util_debug("TEST", "found %d tasks, preparing to execute\n", vec_size(task_tasks)); + util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks)); + + for (; i < vec_size(task_tasks); i++) { + memset(space[1], 0, sizeof(space[1])); + util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1)); + + con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), ""); - for (i = 0; i < vec_size(task_tasks); i++) { - util_debug("TEST", "executing task: %d: %s\n", i, task_tasks[i].tmpl->description); /* * Generate a task from thin air if it requires execution in * the QCVM. */ - execute = !!(!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")); + + /* diagnostic is not executed, but compare tested instead, like preproessor */ + execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) || + (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) || + (!strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")); /* * We assume it compiled before we actually compiled :). On error @@ -971,17 +1066,15 @@ void task_schedualize(size_t *pad) { * Read data from stdout first and pipe that stuff into a log file * then we do the same for stderr. */ - while (fs_file_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) { - fs_file_puts(task_tasks[i].stdoutlog, data); + while (util_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) { + fputs(data, task_tasks[i].stdoutlog); if (strstr(data, "failed to open file")) { task_tasks[i].compiled = false; execute = false; } - - fs_file_flush(task_tasks[i].stdoutlog); } - while (fs_file_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { + while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { /* * If a string contains an error we just dissalow execution * of it in the vm. @@ -990,46 +1083,77 @@ void task_schedualize(size_t *pad) { * that refers to a variable named error, or something like * that .. then this will blowup :P */ - if (strstr(data, "error")) { + if (strstr(data, "error") && strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) { execute = false; task_tasks[i].compiled = false; } - fs_file_puts (task_tasks[i].stderrlog, data); - fs_file_flush(task_tasks[i].stdoutlog); + fputs(data, task_tasks[i].stderrlog); + fflush(task_tasks[i].stderrlog); /* fast flush for read */ } if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) { - con_err("test failure: `%s` (failed to compile) see %s.stdout and %s.stderr [%s]\n", + con_out("failure: `%s` %*s %*s\n", task_tasks[i].tmpl->description, - task_tasks[i].tmpl->tempfilename, - task_tasks[i].tmpl->tempfilename, - task_tasks[i].tmpl->rulesfile + (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]), + "(failed to compile)" ); + failed++; + continue; + } + + status = task_pclose(task_tasks[i].runhandles); + if ((!strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS) + || ( strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) { + con_out("failure: `%s` %*s %*s\n", + task_tasks[i].tmpl->description, + (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]), + "(compiler didn't return exit success)" + ); + failed++; continue; } if (!execute) { - con_out("test succeeded: `%s` %*s\n", + con_out("succeeded: `%s` %*s %*s\n", task_tasks[i].tmpl->description, - (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + - (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), - task_tasks[i].tmpl->rulesfile - + (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]), + task_type(task_tasks[i].tmpl) + ); continue; } /* * If we made it here that concludes the task is to be executed - * in the virtual machine. + * in the virtual machine (or the preprocessor output needs to + * be matched). */ - if (!task_execute(task_tasks[i].tmpl, &match)) { + if (!task_trymatch(i, &match)) { size_t d = 0; - con_err("test failure: `%s` (invalid results from execution) [%s]\n", + con_out("failure: `%s` %*s %*s\n", task_tasks[i].tmpl->description, - task_tasks[i].tmpl->rulesfile + (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen( + (strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) + ? "(invalid results from execution)" + : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) + ? "(invalid results from preprocessing)" + : "(invalid results from compiler diagnsotics)" + ) - pad[2]), + (strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) + ? "(invalid results from execution)" + : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) + ? "(invalid results from preprocessing)" + : "(invalid results from compiler diagnsotics)" ); /* @@ -1037,52 +1161,56 @@ void task_schedualize(size_t *pad) { * handler for the all the given matches in the template file and * what was actually returned from executing. */ - con_err(" Expected From %u Matches: (got %u Matches)\n", + con_out(" Expected From %u Matches: (got %u Matches)\n", vec_size(task_tasks[i].tmpl->comparematch), vec_size(match) ); for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) { char *select = task_tasks[i].tmpl->comparematch[d]; - size_t length = 40 - strlen(select); + size_t length = 60 - strlen(select); - con_err(" Expected: \"%s\"", select); + con_out(" Expected: \"%s\"", select); while (length --) - con_err(" "); - con_err("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<>" : match[d]); + con_out(" "); + con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<>" : match[d]); } /* * Print the non-expected out (since we are simply not expecting it) * This will help track down bugs in template files that fail to match * something. - */ + */ if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) { for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) { - con_err(" Expected: Nothing | Got: \"%s\"\n", + con_out(" Expected: Nothing | Got: \"%s\"\n", match[d + vec_size(task_tasks[i].tmpl->comparematch)] ); } } - + for (j = 0; j < vec_size(match); j++) mem_d(match[j]); vec_free(match); + failed++; continue; } + for (j = 0; j < vec_size(match); j++) mem_d(match[j]); vec_free(match); - con_out("test succeeded: `%s` %*s\n", + con_out("succeeded: `%s` %*s %*s\n", task_tasks[i].tmpl->description, - (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + - (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), - task_tasks[i].tmpl->rulesfile - + (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]), + task_tasks[i].tmpl->rulesfile, + (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]), + task_type(task_tasks[i].tmpl) + ); } mem_d(data); + return failed; } /* @@ -1099,21 +1227,23 @@ void task_schedualize(size_t *pad) { * * It expects con_init() was called before hand. */ -GMQCC_WARN bool test_perform(const char *curdir, const char *defs) { +static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) { + size_t failed = false; static const char *default_defs = "defs.qh"; size_t pad[] = { - 0, 0 + /* test ### [succeed/fail]: `description` [tests/template.tmpl] [type] */ + 0, 0, 0 }; /* * If the default definition file isn't set to anything. We will * use the default_defs here, which is "defs.qc" - */ + */ if (!defs) { defs = default_defs; } - + task_precleanup(curdir); if (!task_propagate(curdir, pad, defs)) { @@ -1128,10 +1258,12 @@ GMQCC_WARN bool test_perform(const char *curdir, const char *defs) { * it's designed to prevent lock contention, and possible syncronization * issues. */ - task_schedualize(pad); + failed = task_schedualize(pad); + if (failed) + con_out("%u out of %u tests failed\n", failed, vec_size(task_tasks)); task_destroy(); - return true; + return (failed) ? false : true; } /* @@ -1165,12 +1297,11 @@ static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, } int main(int argc, char **argv) { - bool succeed = false; - char *redirout = (char*)stdout; - char *redirerr = (char*)stderr; - char *defs = NULL; + bool succeed = false; + char *defs = NULL; con_init(); + OPTS_OPTION_U16(OPTION_MEMDUMPCOLS) = 16; /* * Command line option parsing commences now We only need to support @@ -1181,15 +1312,9 @@ int main(int argc, char **argv) { --argc; if (argv[0][0] == '-') { - if (parsecmd("redirout", &argc, &argv, &redirout, 1, false)) - continue; - if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false)) - continue; - if (parsecmd("defs", &argc, &argv, &defs, 1, false)) + if (parsecmd("defs", &argc, &argv, &defs, 1, false)) continue; - con_change(redirout, redirerr); - if (!strcmp(argv[0]+1, "debug")) { OPTS_OPTION_BOOL(OPTION_DEBUG) = true; continue; @@ -1207,10 +1332,7 @@ int main(int argc, char **argv) { return -1; } } - con_change(redirout, redirerr); succeed = test_perform("tests", defs); - util_meminfo(); - return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE; }