X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=blobdiff_plain;f=test.c;h=495b547183f3f2530c89d325ae30ba305c2e3c4d;hp=34b3744952b5320ff8e79ccdb7407dba78a0c996;hb=13003bf6af9cf8c76cd024b0bf11695b92fba093;hpb=ca52ecc20af641a20b3d3cdb0921f55c27541491 diff --git a/test.c b/test.c index 34b3744..495b547 100644 --- a/test.c +++ b/test.c @@ -23,10 +23,9 @@ #include "gmqcc.h" #include #include -#include -bool opts_memchk = false; -bool opts_debug = false; +opts_cmd_t opts; + char *task_bins[] = { "./gmqcc", "./qcvm" @@ -50,7 +49,7 @@ char *task_bins[] = { #ifndef _WIN32 #include #include - +#include #include typedef struct { FILE *handles[3]; @@ -128,8 +127,21 @@ FILE ** task_popen(const char *command, const char *mode) { 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]); @@ -157,7 +169,120 @@ int task_pclose(FILE **handles) { return status; } #else +# 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. + */ + + 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; + } +# ifdef __MINGW32__ + /* mingw32 has dirent.h */ +# include +# elif defined (_MSC_VER) + /* + * visual studio lacks dirent.h it's a posix thing + * so we emulate it with the WinAPI. + */ + + struct dirent { + long d_ino; + unsigned short d_reclen; + unsigned short d_namlen; + char d_name[FILENAME_MAX]; + }; + + typedef struct { + struct _finddata_t dd_dta; + struct dirent dd_dir; + long dd_handle; + int dd_stat; + char dd_name[1]; + } DIR; + + DIR *opendir(const char *name) { + DIR *dir = (DIR*)mem_a(sizeof(DIR) + strlen(name)); + if (!dir) + return NULL; + + strcpy(dir->dd_name, name); + return dir; + } + + int closedir(DIR *dir) { + FindClose((HANDLE)dir->dd_handle); + mem_d ((void*)dir); + return 0; + } + + struct dirent *readdir(DIR *dir) { + WIN32_FIND_DATA info; + struct dirent *data; + int rets; + + if (!dir->dd_handle) { + char *dirname; + if (*dir->dd_name) { + size_t n = strlen(dir->dd_name); + if ((dirname = (char*)mem_a(n + 5) /* 4 + 1 */)) { + strcpy(dirname, dir->dd_name); + strcpy(dirname + n, "\\*.*"); /* 4 + 1 */ + } + } else { + if (!(dirname = util_strdup("\\*.*"))) + return NULL; + } + + dir->dd_handle = (long)FindFirstFile(dirname, &info); + mem_d(dirname); + rets = !(!dir->dd_handle); + } else if (dir->dd_handle != -11) { + rets = FindNextFile ((HANDLE)dir->dd_handle, &info); + } else { + rets = 0; + } + + if (!rets) + return NULL; + + if ((data = (struct dirent*)mem_a(sizeof(struct dirent)))) { + strncpy(data->d_name, info.cFileName, FILENAME_MAX - 1); + data->d_name[FILENAME_MAX - 1] = '\0'; /* terminate */ + data->d_namlen = strlen(data->d_name); + } + return data; + } + + /* + * 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 #endif #define TASK_COMPILE 0 @@ -201,6 +326,9 @@ int task_pclose(FILE **handles) { * This simply performs compilation only * -execute * This will perform compilation and execution + * -fail + * This will perform compilation, but requires + * the compilation to fail in order to succeed. * * This must be provided, this tag is NOT optional. * @@ -299,7 +427,10 @@ bool task_template_generate(task_template_t *template, char tag, const char *fil * Value will contain a newline character at the end, we need to strip * this otherwise kaboom, seriously, kaboom :P */ - *strrchr(value, '\n')='\0'; + 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 @@ -320,7 +451,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')) @@ -400,7 +531,10 @@ bool task_template_parse(const char *file, task_template_t *template, FILE *fp) * Value will contain a newline character at the end, we need to strip * this otherwise kaboom, seriously, kaboom :P */ - *strrchr(value, '\n')='\0'; + if (strrchr(value, '\n')) + *strrchr(value, '\n')='\0'; + else /* cppcheck: possible null pointer dereference */ + abort(); vec_push(template->comparematch, util_strdup(value)); @@ -458,7 +592,7 @@ task_template_t *task_template_compile(const char *file, const char *dir) { memset (fullfile, 0, sizeof(fullfile)); snprintf(fullfile, sizeof(fullfile), "%s/%s", dir, file); - tempfile = fopen(fullfile, "r"); + tempfile = file_open(fullfile, "r"); template = mem_a(sizeof(task_template_t)); task_template_nullify(template); @@ -521,13 +655,19 @@ task_template_t *task_template_compile(const char *file, const char *dir) { con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file); goto failure; } + } else if (!strcmp(template->proceduretype, "-fail")) { + if (template->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file); + if (template->comparematch) + con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file); + goto success; } else { con_err("template compile error: %s invalid procedure type: %s\n", file, template->proceduretype); goto failure; } success: - fclose(tempfile); + file_close(tempfile); return template; failure: @@ -536,7 +676,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; @@ -672,7 +812,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; } @@ -680,7 +820,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; } @@ -764,14 +904,15 @@ 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 - * forget about it. + * forget about it (or if it didn't compile, and the procedure type + * was set to -fail (meaning it shouldn't compile) .. stil remove) */ - if (task_tasks[i].compiled) { + if (task_tasks[i].compiled || !strcmp(task_tasks[i].template->proceduretype, "-fail")) { if (remove(task_tasks[i].stdoutlogfile)) con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile); else @@ -802,8 +943,8 @@ void task_destroy(const char *curdir) { * using the template passed into it for call-flags and user defined * messages. */ -bool task_execute(task_template_t *template) { - bool success = false; +bool task_execute(task_template_t *template, char ***line) { + bool success = true; FILE *execute; char buffer[4096]; memset (buffer,0,sizeof(buffer)); @@ -842,7 +983,7 @@ bool task_execute(task_template_t *template) { 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", template->description, @@ -860,12 +1001,22 @@ bool task_execute(task_template_t *template) { if (strrchr(data, '\n')) *strrchr(data, '\n') = '\0'; + if (vec_size(template->comparematch) > compare) { + if (strcmp(data, template->comparematch[compare++])) + success = false; + } else { + success = false; + } /* - * We only care about the last line from the output for now - * implementing multi-line match is TODO. - */ - success = !!!(strcmp(data, template->comparematch[compare++])); + * Copy to output vector for diagnostics if execution match + * fails. + */ + vec_push(*line, data); + + /* reset */ + data = NULL; + size = 0; } mem_d(data); data = NULL; @@ -883,8 +1034,10 @@ bool task_execute(task_template_t *template) { void task_schedualize() { bool execute = false; char *data = NULL; + char **match = NULL; size_t size = 0; size_t i; + size_t j; util_debug("TEST", "found %d tasks, preparing to execute\n", vec_size(task_tasks)); @@ -894,8 +1047,7 @@ void task_schedualize() { * Generate a task from thin air if it requires execution in * the QCVM. */ - if (!strcmp(task_tasks[i].template->proceduretype, "-execute")) - execute = true; + execute = !!(!strcmp(task_tasks[i].template->proceduretype, "-execute")); /* * We assume it compiled before we actually compiled :). On error @@ -907,15 +1059,17 @@ 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")) - execute = false; + if (strstr(data, "failed to open file")) { + task_tasks[i].compiled = false; + execute = false; + } 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. @@ -929,16 +1083,12 @@ 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 we made it here that concludes the task is to be executed - * in the virtual machine. - */ - if (!execute || !task_execute(task_tasks[i].template)) { - con_err("test failure: `%s` [%s] see %s.stdout and %s.stderr\n", + 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", task_tasks[i].template->description, (task_tasks[i].template->failuremessage) ? task_tasks[i].template->failuremessage : "unknown", @@ -948,10 +1098,74 @@ void task_schedualize() { 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" + ); + continue; + } + + /* + * If we made it here that concludes the task is to be executed + * in the virtual machine. + */ + 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" + ); + + /* + * Print nicely formatted expected match lists to console error + * 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", + vec_size(task_tasks[i].template->comparematch), + vec_size(match) + ); + for (; d < vec_size(task_tasks[i].template->comparematch); d++) { + char *select = task_tasks[i].template->comparematch[d]; + size_t length = 40 - strlen(select); + + con_err(" Expected: \"%s\"", select); + while (length --) + con_err(" "); + con_err("| 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].template->comparematch)) { + for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].template->comparematch); d++) { + con_err(" Expected: Nothing | Got: \"%s\"\n", + match[d + vec_size(task_tasks[i].template->comparematch)] + ); + } + } + + + for (j = 0; j < vec_size(match); j++) + mem_d(match[j]); + vec_free(match); + continue; + } + for (j = 0; j < vec_size(match); j++) + mem_d(match[j]); + vec_free(match); + con_out("test succeeded: `%s` [%s]\n", - task_tasks[i].template->description, + task_tasks[i].template->description, (task_tasks[i].template->successmessage) ? - task_tasks[i].template->successmessage : "unknown" + task_tasks[i].template->successmessage : "unknown" ); } mem_d(data); @@ -1044,11 +1258,11 @@ int main(int argc, char **argv) { con_change(redirout, redirerr); if (!strcmp(argv[0]+1, "debug")) { - opts_debug = true; + opts.debug = true; continue; } if (!strcmp(argv[0]+1, "memchk")) { - opts_memchk = true; + opts.memchk = true; continue; } if (!strcmp(argv[0]+1, "nocolor")) {