X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=blobdiff_plain;f=test.c;h=5e99df404063de4b7d638cd3e6217107414f5f36;hp=b29a3d4c1b1a7ca9176ed89f1d60128cf43571b2;hb=e11a17b40805b93e1e2ae80129c9d235b6d6119b;hpb=fa401b6f568ef80a40194722e3ddc103f7378adb diff --git a/test.c b/test.c index b29a3d4..5e99df4 100644 --- a/test.c +++ b/test.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 + * Copyright (C) 2012, 2013 * Dale Weiler * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -121,29 +121,12 @@ FILE ** task_popen(const char *command, const char *mode) { close(2), dup(errhandle[1]); execvp(*argv, argv); - exit(1); + exit(EXIT_FAILURE); } else { /* fork failed */ goto task_popen_error_3; } - /* - * clang is stupid, it doesn't understand that yes, this code - * is actually reachable. - */ -# ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wunreachable-code" -# endif - if (argv) - vec_free(argv); - -# ifdef __clang__ -# pragma clang diagnostic pop -# endif - - return data->handles; - task_popen_error_3: close(errhandle[0]), close(errhandle[1]); task_popen_error_2: close(outhandle[0]), close(outhandle[1]); task_popen_error_1: close(inhandle [0]), close(inhandle [1]); @@ -169,12 +152,12 @@ int task_pclose(FILE **handles) { return status; } #else -# define _WIN32_LEAN_AND_MEAN -# define popen _popen -# define pclose _pclose -# include -# include -# include +# define _WIN32_LEAN_AND_MEAN +# define popen _popen +# define pclose _pclose +# include +# include +# include /* * Bidirectional piping implementation for windows using CreatePipe and DuplicateHandle + * other hacks. @@ -199,10 +182,10 @@ int task_pclose(FILE **handles) { return; } -# ifdef __MINGW32__ +# ifdef __MINGW32__ /* mingw32 has dirent.h */ -# include -# elif defined (_MSC_VER) +# include +# elif defined (_WIN32) /* * visual studio lacks dirent.h it's a posix thing * so we emulate it with the WinAPI. @@ -280,9 +263,9 @@ int task_pclose(FILE **handles) { * Visual studio also lacks S_ISDIR for sys/stat.h, so we emulate this as well * which is not hard at all. */ -# undef S_ISDIR /* undef just incase */ -# define S_ISDIR(X) ((X)&_S_IFDIR) -# endif +# undef S_ISDIR /* undef just incase */ +# define S_ISDIR(X) ((X)&_S_IFDIR) +# endif #endif #define TASK_COMPILE 0 @@ -311,14 +294,6 @@ int task_pclose(FILE **handles) { * Used to set a description of the current test, this must be * provided, this tag is NOT optional. * - * F: - * Used to set a failure message, this message will be displayed - * if the test fails, this tag is optional - * - * S: - * Used to set a success message, this message will be displayed - * if the test succeeds, this tag is optional. - * * T: * Used to set the procedure for the given task, there are two * options for this: @@ -336,6 +311,9 @@ int task_pclose(FILE **handles) { * Used to set the compilation flags for the given task, this * must be provided, this tag is NOT optional. * + * F: Used to set some test suite flags, currently the only option + * is -no-defs (to including of defs.qh) + * * E: * Used to set the execution flags for the given task. This tag * must be provided if T == -execute, otherwise it's erroneous @@ -368,21 +346,22 @@ int task_pclose(FILE **handles) { */ typedef struct { char *description; - char *failuremessage; - char *successmessage; char *compileflags; char *executeflags; char *proceduretype; char *sourcefile; char *tempfilename; char **comparematch; + char *rulesfile; + char *testflags; } task_template_t; /* * 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 *template, char tag, const char *file, size_t line, const char *value) { +bool task_template_generate(task_template_t *template, char tag, const char *file, size_t line, const char *value, size_t *pad) { + size_t desclen = 0; char **destval = NULL; if (!template) @@ -390,12 +369,11 @@ bool task_template_generate(task_template_t *template, char tag, const char *fil switch(tag) { case 'D': destval = &template->description; break; - case 'F': destval = &template->failuremessage; break; - case 'S': destval = &template->successmessage; break; case 'T': destval = &template->proceduretype; break; case 'C': destval = &template->compileflags; break; case 'E': destval = &template->executeflags; break; case 'I': destval = &template->sourcefile; break; + case 'F': destval = &template->testflags; break; default: con_printmsg(LVL_ERROR, __FILE__, __LINE__, "internal error", "invalid tag `%c:` during code generation\n", @@ -438,10 +416,20 @@ bool task_template_generate(task_template_t *template, char tag, const char *fil */ *destval = util_strdup(value); + + if (*destval == template->description) { + /* + * Create some padding for the description to align the + * printing of the rules file. + */ + if ((desclen = strlen(template->description)) > pad[0]) + pad[0] = desclen; + } + return true; } -bool task_template_parse(const char *file, task_template_t *template, FILE *fp) { +bool task_template_parse(const char *file, task_template_t *template, FILE *fp, size_t *pad) { char *data = NULL; char *back = NULL; size_t size = 0; @@ -451,7 +439,7 @@ bool task_template_parse(const char *file, task_template_t *template, FILE *fp) return false; /* top down parsing */ - while (util_getline(&back, &size, fp) != EOF) { + while (file_getline(&back, &size, fp) != EOF) { /* skip whitespace */ data = back; if (*data && (*data == ' ' || *data == '\t')) @@ -488,12 +476,11 @@ bool task_template_parse(const char *file, task_template_t *template, FILE *fp) * it to. */ case 'D': - case 'F': - case 'S': case 'T': case 'C': case 'E': case 'I': + case 'F': if (data[1] != ':') { con_printmsg(LVL_ERROR, file, line, "template parse error", "expected `:` after `%c`", @@ -501,7 +488,7 @@ bool task_template_parse(const char *file, task_template_t *template, FILE *fp) ); goto failure; } - if (!task_template_generate(template, *data, file, line, &data[3])) { + if (!task_template_generate(template, *data, file, line, &data[3], pad)) { con_printmsg(LVL_ERROR, file, line, "template compile error", "failed to generate for given task\n" ); @@ -573,29 +560,39 @@ void task_template_nullify(task_template_t *template) { return; template->description = NULL; - template->failuremessage = NULL; - template->successmessage = NULL; template->proceduretype = NULL; template->compileflags = NULL; template->executeflags = NULL; template->comparematch = NULL; template->sourcefile = NULL; template->tempfilename = NULL; + template->rulesfile = NULL; + template->testflags = NULL; } -task_template_t *task_template_compile(const char *file, const char *dir) { +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; task_template_t *template = NULL; memset (fullfile, 0, sizeof(fullfile)); snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file); - tempfile = fopen(fullfile, "r"); - template = mem_a(sizeof(task_template_t)); + tempfile = file_open(fullfile, "r"); + template = mem_a(sizeof(task_template_t)); task_template_nullify(template); + /* + * 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; + + template->rulesfile = util_strdup(fullfile); + /* * Esnure the file even exists for the task, this is pretty useless * to even do. @@ -607,7 +604,7 @@ task_template_t *task_template_compile(const char *file, const char *dir) { goto failure; } - if (!task_template_parse(file, template, tempfile)) { + if (!task_template_parse(file, template, tempfile, pad)) { con_err("template parse error: error during parsing\n"); goto failure; } @@ -667,7 +664,7 @@ task_template_t *task_template_compile(const char *file, const char *dir) { } success: - fclose(tempfile); + file_close(tempfile); return template; failure: @@ -676,7 +673,7 @@ failure: * so the check to see if it's not null here is required. */ if (tempfile) - fclose(tempfile); + file_close(tempfile); mem_d (template); return NULL; @@ -687,12 +684,12 @@ void task_template_destroy(task_template_t **template) { return; if ((*template)->description) mem_d((*template)->description); - if ((*template)->failuremessage) mem_d((*template)->failuremessage); - if ((*template)->successmessage) mem_d((*template)->successmessage); if ((*template)->proceduretype) mem_d((*template)->proceduretype); if ((*template)->compileflags) mem_d((*template)->compileflags); if ((*template)->executeflags) mem_d((*template)->executeflags); if ((*template)->sourcefile) mem_d((*template)->sourcefile); + if ((*template)->rulesfile) mem_d((*template)->rulesfile); + if ((*template)->testflags) mem_d((*template)->testflags); /* * Delete all allocated string for task template then destroy the @@ -733,7 +730,7 @@ 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) { +bool task_propagate(const char *curdir, size_t *pad, const char *defs) { bool success = true; DIR *dir; struct dirent *files; @@ -761,8 +758,9 @@ bool task_propagate(const char *curdir) { * actually a directory, so it must be a file :) */ if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) { - task_template_t *template = task_template_compile(files->d_name, curdir); + task_template_t *template = 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); @@ -778,19 +776,62 @@ bool task_propagate(const char *curdir) { */ template->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"); + /* * 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. */ - memset (buf,0,sizeof(buf)); - snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s", - task_bins[TASK_COMPILE], - curdir, - template->sourcefile, - template->compileflags, - template->tempfilename - ); + memset (buf,0,sizeof(buf)); + if (qcflags) { + if (template->testflags && !strcmp(template->testflags, "-no-defs")) { + snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + template->sourcefile, + qcflags, + template->compileflags, + template->tempfilename + ); + } else { + snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + curdir, + template->sourcefile, + qcflags, + template->compileflags, + template->tempfilename + ); + } + } else { + if (template->testflags && !strcmp(template->testflags, "-no-defs")) { + snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + template->sourcefile, + template->compileflags, + template->tempfilename + ); + } else { + snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s", + task_bins[TASK_COMPILE], + curdir, + defs, + curdir, + template->sourcefile, + template->compileflags, + template->tempfilename + ); + } + } /* * The task template was compiled, now lets create a task from @@ -812,7 +853,7 @@ bool task_propagate(const char *curdir) { memset (buf,0,sizeof(buf)); snprintf(buf, sizeof(buf), "%s.stdout", template->tempfilename); task.stdoutlogfile = util_strdup(buf); - if (!(task.stdoutlog = fopen(buf, "w"))) { + if (!(task.stdoutlog = file_open(buf, "w"))) { con_err("error opening %s for stdout\n", buf); continue; } @@ -820,7 +861,7 @@ bool task_propagate(const char *curdir) { memset (buf,0,sizeof(buf)); snprintf(buf, sizeof(buf), "%s.stderr", template->tempfilename); task.stderrlogfile = util_strdup(buf); - if (!(task.stderrlog = fopen(buf, "w"))) { + if (!(task.stderrlog = file_open(buf, "w"))) { con_err("error opening %s for stderr\n", buf); continue; } @@ -838,31 +879,6 @@ bool task_propagate(const char *curdir) { return success; } -/* - * Removes all temporary 'progs.dat' files created during compilation - * of all tests' - */ -void task_cleanup(const char *curdir) { - DIR *dir; - struct dirent *files; - char buffer[4096]; - - dir = opendir(curdir); - - while ((files = readdir(dir))) { - memset(buffer, 0, sizeof(buffer)); - if (strstr(files->d_name, "TMP")) { - 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); -} - /* * Task precleanup removes any existing temporary files or log files * left behind from a previous invoke of the test-suite. @@ -891,7 +907,7 @@ void task_precleanup(const char *curdir) { closedir(dir); } -void task_destroy(const char *curdir) { +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 @@ -904,8 +920,8 @@ void task_destroy(const char *curdir) { * 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) fclose (task_tasks[i].stdoutlog); - if (task_tasks[i].stderrlog) fclose (task_tasks[i].stderrlog); + if (task_tasks[i].stdoutlog) file_close (task_tasks[i].stdoutlog); + if (task_tasks[i].stderrlog) file_close (task_tasks[i].stderrlog); /* * Only remove the log files if the test actually compiled otherwise @@ -917,11 +933,12 @@ void task_destroy(const char *curdir) { 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].template->tempfilename); } /* free util_strdup data for log files */ @@ -931,11 +948,6 @@ void task_destroy(const char *curdir) { task_template_destroy(&task_tasks[i].template); } vec_free(task_tasks); - - /* - * Cleanup outside stuff like temporary files. - */ - task_cleanup(curdir); } /* @@ -983,12 +995,11 @@ bool task_execute(task_template_t *template, char ***line) { char *data = NULL; size_t size = 0; size_t compare = 0; - while (util_getline(&data, &size, execute) != EOF) { + while (file_getline(&data, &size, execute) != EOF) { if (!strcmp(data, "No main function found\n")) { - con_err("test failure: `%s` [%s] (No main function found)\n", + con_err("test failure: `%s` (No main function found) [%s]\n", template->description, - (template->failuremessage) ? - template->failuremessage : "unknown" + template->rulesfile ); pclose(execute); return false; @@ -1031,7 +1042,7 @@ bool task_execute(task_template_t *template, char ***line) { * execution this takes more work since a task needs to be generated * from thin air and executed INLINE. */ -void task_schedualize() { +void task_schedualize(size_t *pad) { bool execute = false; char *data = NULL; char **match = NULL; @@ -1059,8 +1070,8 @@ void task_schedualize() { * Read data from stdout first and pipe that stuff into a log file * then we do the same for stderr. */ - while (util_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) { - fputs(data, task_tasks[i].stdoutlog); + while (file_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) { + file_puts(task_tasks[i].stdoutlog, data); if (strstr(data, "failed to open file")) { task_tasks[i].compiled = false; @@ -1069,7 +1080,7 @@ void task_schedualize() { fflush(task_tasks[i].stdoutlog); } - while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { + while (file_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { /* * If a string contains an error we just dissalow execution * of it in the vm. @@ -1083,26 +1094,27 @@ void task_schedualize() { task_tasks[i].compiled = false; } - fputs(data, task_tasks[i].stderrlog); + file_puts(task_tasks[i].stderrlog, data); fflush(task_tasks[i].stdoutlog); } if (!task_tasks[i].compiled && strcmp(task_tasks[i].template->proceduretype, "-fail")) { - con_err("test failure: `%s` [%s] (failed to compile) see %s.stdout and %s.stderr\n", + con_err("test failure: `%s` (failed to compile) see %s.stdout and %s.stderr [%s]\n", task_tasks[i].template->description, - (task_tasks[i].template->failuremessage) ? - task_tasks[i].template->failuremessage : "unknown", task_tasks[i].template->tempfilename, - task_tasks[i].template->tempfilename + task_tasks[i].template->tempfilename, + task_tasks[i].template->rulesfile ); continue; } if (!execute) { - con_out("test succeeded: `%s` [%s]\n", - task_tasks[i].template->description, - (task_tasks[i].template->successmessage) ? - task_tasks[i].template->successmessage : "unknown" + con_out("test succeeded: `%s` %*s\n", + task_tasks[i].template->description, + (pad[0] + pad[1] - strlen(task_tasks[i].template->description)) + + (strlen(task_tasks[i].template->rulesfile) - pad[1]), + task_tasks[i].template->rulesfile + ); continue; } @@ -1114,10 +1126,9 @@ void task_schedualize() { if (!task_execute(task_tasks[i].template, &match)) { size_t d = 0; - con_err("test failure: `%s` [%s] (invalid results from execution)\n", - task_tasks[i].template->description, - (task_tasks[i].template->failuremessage) ? - task_tasks[i].template->failuremessage : "unknown" + con_err("test failure: `%s` (invalid results from execution) [%s]\n", + task_tasks[i].template->description, + task_tasks[i].template->rulesfile ); /* @@ -1162,10 +1173,12 @@ void task_schedualize() { mem_d(match[j]); vec_free(match); - con_out("test succeeded: `%s` [%s]\n", - task_tasks[i].template->description, - (task_tasks[i].template->successmessage) ? - task_tasks[i].template->successmessage : "unknown" + con_out("test succeeded: `%s` %*s\n", + task_tasks[i].template->description, + (pad[0] + pad[1] - strlen(task_tasks[i].template->description)) + + (strlen(task_tasks[i].template->rulesfile) - pad[1]), + task_tasks[i].template->rulesfile + ); } mem_d(data); @@ -1185,11 +1198,26 @@ void task_schedualize() { * * It expects con_init() was called before hand. */ -bool test_perform(const char *curdir) { +GMQCC_WARN bool test_perform(const char *curdir, const char *defs) { + static const char *default_defs = "defs.qh"; + + size_t pad[] = { + 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)) { + if (!task_propagate(curdir, pad, defs)) { con_err("error: failed to propagate tasks\n"); - task_destroy(curdir); + task_destroy(); return false; } /* @@ -1199,8 +1227,8 @@ bool test_perform(const char *curdir) { * it's designed to prevent lock contention, and possible syncronization * issues. */ - task_schedualize(); - task_destroy(curdir); + task_schedualize(pad); + task_destroy(); return true; } @@ -1236,8 +1264,10 @@ 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; con_init(); @@ -1254,15 +1284,17 @@ int main(int argc, char **argv) { continue; if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false)) continue; + if (parsecmd("defs", &argc, &argv, &defs, 1, false)) + continue; con_change(redirout, redirerr); if (!strcmp(argv[0]+1, "debug")) { - opts.debug = true; + OPTS_OPTION_BOOL(OPTION_DEBUG) = true; continue; } if (!strcmp(argv[0]+1, "memchk")) { - opts.memchk = true; + OPTS_OPTION_BOOL(OPTION_MEMCHK) = true; continue; } if (!strcmp(argv[0]+1, "nocolor")) { @@ -1275,7 +1307,9 @@ int main(int argc, char **argv) { } } con_change(redirout, redirerr); - test_perform("tests"); + succeed = test_perform("tests", defs); util_meminfo(); - return 0; + + + return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE; }