From: Dale Weiler Date: Mon, 19 Nov 2012 02:13:46 +0000 (+0000) Subject: Merge branch 'master' into test-suite X-Git-Tag: 0.1.9~404 X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fgmqcc.git;a=commitdiff_plain;h=61fa54318c3aa1ce0c2099f9793d830e5896c016;hp=19e82883ea29ce43ff45f830bc2c8cfbc8f49007 Merge branch 'master' into test-suite Conflicts: Makefile ir.c --- diff --git a/Makefile b/Makefile index ad8c941..aa5ef11 100644 --- a/Makefile +++ b/Makefile @@ -23,17 +23,19 @@ ifeq ($(track), no) CFLAGS += -DNOTRACK endif -OBJ = \ +OBJ = \ util.o \ code.o \ ast.o \ ir.o \ con.o \ ftepp.o + +OBJ_T = test.o util.o con.o OBJ_C = main.o lexer.o parser.o OBJ_X = exec-standalone.o util.o con.o -#default is compiler only + default: gmqcc %.o: %.c $(CC) -c $< -o $@ $(CFLAGS) @@ -41,17 +43,23 @@ default: gmqcc exec-standalone.o: exec.c $(CC) -c $< -o $@ $(CFLAGS) -DQCVM_EXECUTOR=1 -qcvm: $(OBJ_X) +qcvm: $(OBJ_X) $(CC) -o $@ $^ $(CFLAGS) -lm -# compiler target gmqcc: $(OBJ_C) $(OBJ) $(CC) -o $@ $^ $(CFLAGS) -all: gmqcc qcvm +test: $(OBJ_T) + $(CC) -o $@ $^ $(CFLAGS) + +runtests: + ./test + +all: gmqcc qcvm test clean: - rm -f *.o gmqcc qcvm + rm -f *.o gmqcc qcvm test *.dat + $(OBJ) $(OBJ_C) $(OBJ_X): gmqcc.h main.o: lexer.h diff --git a/con.c b/con.c index bfe3a23..8073a57 100644 --- a/con.c +++ b/con.c @@ -272,7 +272,7 @@ int con_change(const char *out, const char *err) { con_close(); if (GMQCC_IS_DEFINE((FILE*)out)) { - console.handle_out = (((FILE*)err) == stdout) ? stdout : stderr; + console.handle_out = (((FILE*)out) == stdout) ? stdout : stderr; con_enablecolor(); } else if (!(console.handle_out = fopen(out, "w"))) return 0; @@ -281,6 +281,10 @@ int con_change(const char *out, const char *err) { con_enablecolor(); } else if (!(console.handle_err = fopen(err, "w"))) return 0; + // no buffering + setvbuf(console.handle_out, NULL, _IONBF, 0); + setvbuf(console.handle_err, NULL, _IONBF, 0); + return 1; } diff --git a/gmqcc.h b/gmqcc.h index 1ea2cef..265bee5 100644 --- a/gmqcc.h +++ b/gmqcc.h @@ -196,6 +196,7 @@ void util_memory_d (void *, unsigned int, const char *); void *util_memory_r (void *, size_t, unsigned int, const char *); void util_meminfo (); +bool util_filexists (const char *); bool util_strupper (const char *); bool util_strdigit (const char *); bool util_strncmpexact (const char *, const char *, size_t); diff --git a/main.c b/main.c index 990acf2..4c8062d 100644 --- a/main.c +++ b/main.c @@ -226,6 +226,8 @@ static bool options_parse(int argc, char **argv) { if (options_long_gcc("redirerr", &argc, &argv, &redirerr)) { continue; } + + con_change(redirout, redirerr); if (!strcmp(argv[0]+1, "debug")) { opts_debug = true; @@ -383,7 +385,6 @@ static bool options_parse(int argc, char **argv) { vec_push(items, item); } } - con_change(redirout, redirerr); return true; } diff --git a/test.c b/test.c new file mode 100644 index 0000000..b59826a --- /dev/null +++ b/test.c @@ -0,0 +1,1022 @@ +/* + * Copyright (C) 2012 + * Dale Weiler + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "gmqcc.h" +#include +#include +#include + +bool opts_memchk = false; +bool opts_debug = false; +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 +typedef struct { + FILE *handles[3]; + int pipes [3]; + + int stderr_fd; + int stdout_fd; + int pid; +} popen_t; + +FILE ** task_popen(const char *command, const char *mode) { + int inhandle [2]; + int outhandle [2]; + int errhandle [2]; + int trypipe; + + popen_t *data = mem_a(sizeof(popen_t)); + + /* + * Parse the command now into a list for execv, this is a pain + * in the ass. + */ + char *line = (char*)command; + char **argv = NULL; + { + + while (*line != '\0') { + while (*line == ' ' || *line == '\t' || *line == '\n') + *line++ = '\0'; + vec_push(argv, line); + + while (*line != '\0' && *line != ' ' && + *line != '\t' && *line != '\n') line++; + } + vec_push(argv, '\0'); + } + + + if ((trypipe = pipe(inhandle)) < 0) goto task_popen_error_0; + if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1; + if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2; + + if ((data->pid = fork()) > 0) { + /* parent */ + 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->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); + return data->handles; + } else if (data->pid == 0) { + /* child */ + close(inhandle [1]); + close(outhandle[0]); + 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]); + + execvp(*argv, argv); + exit(1); + } else { + /* fork failed */ + goto task_popen_error_3; + } + + if (argv) + vec_free(argv); + 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]); +task_popen_error_0: + + if (argv) + vec_free(argv); + return NULL; +} + +int task_pclose(FILE **handles) { + popen_t *data = (popen_t*)handles; + int status = 0; + + close(data->pipes[0]); /* stdin */ + close(data->pipes[1]); /* stdout */ + close(data->pipes[2]); /* stderr */ + + waitpid(data->pid, &status, 0); + + mem_d(data); + + return status; +} +#else + +#endif + +#define TASK_COMPILE 0 +#define TASK_EXECUTE 1 + +/* + * Task template system: + * templates are rules for a specific test, used to create a "task" that + * is executed with those set of rules (arguments, and what not). Tests + * that don't have a template with them cannot become tasks, since without + * the information for that test there is no way to properly "test" them. + * Rules for these templates are described in a template file, using a + * task template language. + * + * The language is a basic finite statemachine, top-down single-line + * description language. + * + * The languge is composed entierly of "tags" which describe a string of + * text for a task. Think of it much like a configuration file. Except + * it's been designed to allow flexibility and future support for prodecual + * semantics. + * + * The following "tags" are suported by the language + * + * D: + * 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: + * -compile + * This simply performs compilation only + * -execute + * This will perform compilation and execution + * + * This must be provided, this tag is NOT optional. + * + * C: + * Used to set the compilation flags for the given task, this + * must be provided, this tag is NOT optional. + * + * E: + * Used to set the execution flags for the given task. This tag + * must be provided if T == -execute, otherwise it's erroneous + * as compilation only takes place. + * + * M: + * Used to describe a string of text that should be matched from + * the output of executing the task. If this doesn't match the + * task fails. This tag must be provided if T == -execute, otherwise + * it's erroneous as compilation only takes place. + * + * I: + * Used to specify the INPUT source file to operate on, this must be + * provided, this tag is NOT optional + * + * + * Notes: + * These tags have one-time use, using them more than once will result + * in template compilation errors. + * + * Lines beginning with # or // in the template file are comments and + * are ignored by the template parser. + * + * Whitespace is optional, with exception to the colon ':' between the + * tag and it's assignment value/ + * + * The template compiler will detect erronrous tags (optional tags + * that need not be set), as well as missing tags, and error accordingly + * this will result in the task failing. + */ +typedef struct { + char *description; + char *failuremessage; + char *successmessage; + char *compileflags; + char *executeflags; + char *comparematch; + char *proceduretype; + char *sourcefile; + char *tempfilename; +} 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) { + char **destval = NULL; + + if (!template) + return false; + + 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 'M': destval = &template->comparematch; break; + case 'I': destval = &template->sourcefile; break; + default: + con_printmsg(LVL_ERROR, __FILE__, __LINE__, "internal error", + "invalid tag `%c:` during code generation\n", + tag + ); + return false; + } + + /* + * Ensure if for the given tag, there already exists a + * assigned value. + */ + if (*destval) { + con_printmsg(LVL_ERROR, file, line, "compile error", + "tag `%c:` already assigned value: %s\n", + tag, *destval + ); + return false; + } + + /* + * Strip any whitespace that might exist in the value for assignments + * like "D: foo" + */ + 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 + */ + *strrchr(value, '\n')='\0'; + + /* + * Now allocate and set the actual value for the specific tag. Which + * was properly selected and can be accessed with *destval. + */ + *destval = util_strdup(value); + + return true; +} + +bool task_template_parse(const char *file, task_template_t *template, FILE *fp) { + char *data = NULL; + char *back = NULL; + size_t size = 0; + size_t line = 1; + + if (!template) + return false; + + /* top down parsing */ + while (util_getline(&back, &size, fp) != EOF) { + /* skip whitespace */ + data = back; + if (*data && (*data == ' ' || *data == '\t')) + data++; + + switch (*data) { + /* + * Handle comments inside task template files. We're strict + * about the language for fun :-) + */ + case '/': + if (data[1] != '/') { + con_printmsg(LVL_ERROR, file, line, "template parse error", + "invalid character `/`, perhaps you meant `//` ?"); + + mem_d(back); + return false; + } + case '#': + break; + + /* + * Empty newlines are acceptable as well, so we handle that here + * despite being just odd since there should't be that many + * empty lines to begin with. + */ + case '\r': + case '\n': + break; + + + /* + * Now begin the actual "tag" stuff. This works as you expect + * it to. + */ + case 'D': + case 'F': + case 'S': + case 'T': + case 'C': + case 'E': + case 'M': + case 'I': + if (data[1] != ':') { + con_printmsg(LVL_ERROR, file, line, "template parse error", + "expected `:` after `%c`", + *data + ); + goto failure; + } + if (!task_template_generate(template, *data, file, line, &data[3])) { + con_printmsg(LVL_ERROR, file, line, "template compile error", + "failed to generate for given task\n" + ); + goto failure; + } + break; + + default: + con_printmsg(LVL_ERROR, file, line, "template parse error", + "invalid tag `%c`", *data + ); + goto failure; + /* no break required */ + } + + /* update line and free old sata */ + line++; + mem_d(back); + back = NULL; + } + if (back) + mem_d(back); + return true; + +failure: + if (back) + mem_d (back); + return false; +} + +/* + * Nullifies the template data: used during initialization of a new + * template and free. + */ +void task_template_nullify(task_template_t *template) { + if (!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; +} + +task_template_t *task_template_compile(const char *file, const char *dir) { + /* a page should be enough */ + char fullfile[4096]; + 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)); + task_template_nullify(template); + + /* + * Esnure the file even exists for the task, this is pretty useless + * to even do. + */ + if (!tempfile) { + con_err("template file: %s does not exist or invalid permissions\n", + file + ); + goto failure; + } + + if (!task_template_parse(file, template, tempfile)) { + con_err("template parse error: error during parsing\n"); + goto failure; + } + + /* + * Regardless procedure type, the following tags must exist: + * D + * T + * C + * I + */ + if (!template->description) { + con_err("template compile error: %s missing `D:` tag\n", file); + goto failure; + } + if (!template->proceduretype) { + con_err("template compile error: %s missing `T:` tag\n", file); + goto failure; + } + if (!template->compileflags) { + con_err("template compile error: %s missing `C:` tag\n", file); + goto failure; + } + if (!template->sourcefile) { + con_err("template compile error: %s missing `I:` tag\n", file); + goto failure; + } + + /* + * Now lets compile the template, compilation is really just + * the process of validating the input. + */ + if (!strcmp(template->proceduretype, "-compile")) { + if (template->executeflags) + con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file); + if (template->comparematch) + con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file); + goto success; + } else if (!strcmp(template->proceduretype, "-execute")) { + if (!template->executeflags) { + con_err("template compile error: %s missing `E:` tag (use `$null` for exclude)\n", file); + goto failure; + } + if (!template->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, template->proceduretype); + goto failure; + } + +success: + fclose(tempfile); + return template; + +failure: + /* + * The file might not exist and we jump here when that doesn't happen + * so the check to see if it's not null here is required. + */ + if (tempfile) + fclose(tempfile); + mem_d (template); + + return NULL; +} + +void task_template_destroy(task_template_t **template) { + if (!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)->comparematch) mem_d((*template)->comparematch); + if ((*template)->sourcefile) mem_d((*template)->sourcefile); + + /* + * Nullify all the template members otherwise NULL comparision + * checks will fail if template pointer is reused. + */ + mem_d(*template); + task_template_nullify(*template); + *template = NULL; +} + +/* + * Now comes the task manager, this system allows adding tasks in and out + * of a task list. This is the executor of the tasks essentially as well. + */ +typedef struct { + task_template_t *template; + FILE **runhandles; + FILE *stderrlog; + FILE *stdoutlog; + char *stdoutlogfile; + char *stderrlogfile; + bool compiled; +} task_t; + +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_propogate(const char *curdir) { + bool success = true; + DIR *dir; + struct dirent *files; + struct stat directory; + char buffer[4096]; + + dir = opendir(curdir); + + while ((files = readdir(dir))) { + memset (buffer, 0,sizeof(buffer)); + snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); + + if (stat(buffer, &directory) == -1) { + con_err("internal error: stat failed, aborting\n"); + abort(); + } + + /* skip directories */ + if (S_ISDIR(directory.st_mode)) + continue; + + /* + * We made it here, which concludes the file/directory is not + * actually a directory, so it must be a file :) + */ + if (strstr(files->d_name, ".tmpl")) { + con_out("compiling task template: %s/%s\n", curdir, files->d_name); + task_template_t *template = task_template_compile(files->d_name, curdir); + if (!template) { + con_err("error compiling task template: %s\n", files->d_name); + success = false; + continue; + } + /* + * Generate a temportary file name for the output binary + * so we don't trample over an existing one. + */ + template->tempfilename = tempnam(curdir, "TMPDAT"); + + /* + * 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. + */ + char buf[4096]; /* one page should be enough */ + 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 + ); + + /* + * The task template was compiled, now lets create a task from + * the template data which has now been propogated. + */ + task_t task; + task.template = template; + if (!(task.runhandles = task_popen(buf, "r"))) { + con_err("error opening pipe to process for test: %s\n", template->description); + success = false; + continue; + } + + con_out("executing test: `%s` [%s]\n", template->description, buf); + + /* + * Open up some file desciptors for logging the stdout/stderr + * to our own. + */ + memset (buf,0,sizeof(buf)); + snprintf(buf, sizeof(buf), "%s/%s.stdout", curdir, template->sourcefile); + task.stdoutlogfile = util_strdup(buf); + task.stdoutlog = fopen(buf, "w"); + + memset (buf,0,sizeof(buf)); + snprintf(buf, sizeof(buf), "%s/%s.stderr", curdir, template->sourcefile); + task.stderrlogfile = util_strdup(buf); + task.stderrlog = fopen(buf, "w"); + + vec_push(task_tasks, task); + } + } + + closedir(dir); + 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 + con_out("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. + */ +void task_precleanup(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") || + strstr(files->d_name, ".stdout") || + strstr(files->d_name, ".stderr")) + { + snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name); + if (remove(buffer)) + con_err("error removing temporary file: %s\n", buffer); + else + con_out("removed temporary file: %s\n", buffer); + } + } + + closedir(dir); +} + +void task_destroy(const char *curdir) { + /* + * Free all the data in the task list and finally the list itself + * then proceed to cleanup anything else outside the program like + * temporary files. + */ + size_t i; + for (i = 0; i < vec_size(task_tasks); i++) { + /* + * 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) 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. + */ + if (task_tasks[i].compiled) { + if (remove(task_tasks[i].stdoutlogfile)) + con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile); + else + con_out("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 + con_out("removed stderr log file: %s\n", task_tasks[i].stderrlogfile); + } + + /* 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].template); + } + vec_free(task_tasks); + + /* + * Cleanup outside stuff like temporary files. + */ + task_cleanup(curdir); +} + +/* + * This executes the QCVM task for a specificly compiled progs.dat + * using the template passed into it for call-flags and user defined + * messages. + */ +bool task_execute(task_template_t *template) { + bool success = false; + FILE *execute; + char buffer[4096]; + memset (buffer,0,sizeof(buffer)); + + /* + * Drop the execution flags for the QCVM if none where + * actually specified. + */ + if (!strcmp(template->executeflags, "$null")) { + snprintf(buffer, sizeof(buffer), "%s %s", + task_bins[TASK_EXECUTE], + template->tempfilename + ); + } else { + snprintf(buffer, sizeof(buffer), "%s %s %s", + task_bins[TASK_EXECUTE], + template->executeflags, + template->tempfilename + ); + } + + con_out("executing qcvm: `%s` [%s]\n", + template->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. + */ + { + char *data = NULL; + size_t size = 0; + while (util_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, + (template->failuremessage) ? + template->failuremessage : "unknown" + ); + pclose(execute); + return false; + } + + /* + * Trim newlines from data since they will just break our + * ability to properly validate matches. + */ + if (strrchr(data, '\n')) + *strrchr(data, '\n') = '\0'; + + /* null list */ + if (!strcmp(template->comparematch, "$null")) + success = true; + + /* + * We only care about the last line from the output for now + * implementing multi-line match is TODO. + */ + if (!strcmp(data, template->comparematch)) + success = true; + } + pclose(execute); + return success; +} + +/* + * This schedualizes all tasks and actually runs them individually + * this is generally easy for just -compile variants. For compile and + * execution this takes more work since a task needs to be generated + * from thin air and executed INLINE. + */ +void task_schedualize(const char *curdir) { + bool execute = false; + char *back = NULL; + char *data = NULL; + size_t size = 0; + size_t i; + + for (i = 0; i < vec_size(task_tasks); i++) { + /* + * Generate a task from thin air if it requires execution in + * the QCVM. + */ + if (!strcmp(task_tasks[i].template->proceduretype, "-execute")) + execute = true; + + /* + * We assume it compiled before we actually compiled :). On error + * we change the value + */ + task_tasks[i].compiled = true; + + /* + * 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) { + back = data; + fputs(data, task_tasks[i].stdoutlog); + fflush(task_tasks[i].stdoutlog); + } + while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) { + back = data; + /* + * If a string contains an error we just dissalow execution + * of it in the vm. + * + * TODO: make this more percise, e.g if we print a warning + * that refers to a variable named error, or something like + * that .. then this will blowup :P + */ + if (strstr(data, "error")) { + execute = false; + task_tasks[i].compiled = false; + } + + fputs(data, task_tasks[i].stderrlog); + fflush(task_tasks[i].stdoutlog); + } + + if (back) + mem_d(back); + + /* + * If we can execute we do so after all data has been read and + * this paticular task has coupled execution in its procedure type + */ + if (!execute) + 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)) { + con_err("test failure: `%s` [%s]\n", + task_tasks[i].template->description, + (task_tasks[i].template->failuremessage) ? + task_tasks[i].template->failuremessage : "unknown" + ); + continue; + } + + con_out("test succeed: `%s` [%s]\n", + task_tasks[i].template->description, + (task_tasks[i].template->successmessage) ? + task_tasks[i].template->successmessage : "unknown" + ); + } + if (back) + mem_d(back); +} + +/* + * This is the heart of the whole test-suite process. This cleans up + * any existing temporary files left behind as well as log files left + * behind. Then it propogates a list of tests from `curdir` by scaning + * it for template files and compiling them into tasks, in which it + * schedualizes them (executes them) and actually reports errors and + * what not. It then proceeds to destroy the tasks and return memory + * it's the engine :) + * + * It returns true of tests could be propogated, otherwise it returns + * false. + * + * It expects con_init() was called before hand. + */ +bool test_perform(const char *curdir) { + task_precleanup(curdir); + if (!task_propogate(curdir)) { + con_err("error: failed to propogate tasks\n"); + task_destroy(curdir); + return false; + } + /* + * If we made it here all tasks where propogated from their resultant + * template file. So we can start the FILO scheduler, this has been + * designed in the most thread-safe way possible for future threading + * it's designed to prevent lock contention, and possible syncronization + * issues. + */ + task_schedualize(curdir); + task_destroy(curdir); + + return true; +} + +/* + * Fancy GCC-like LONG parsing allows things like --opt=param with + * assignment operator. This is used for redirecting stdout/stderr + * console to specific files of your choice. + */ +static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) { + int argc = *argc_; + char **argv = *argv_; + + size_t len = strlen(optname); + + if (strncmp(argv[0]+ds, optname, len)) + return false; + + /* it's --optname, check how the parameter is supplied */ + if (argv[0][ds+len] == '=') { + *out = argv[0]+ds+len+1; + return true; + } + + if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */ + return false; + + /* using --opt param */ + *out = argv[1]; + --*argc_; + ++*argv_; + return true; +} + +int main(int argc, char **argv) { + char *redirout = (char*)stdout; + char *redirerr = (char*)stderr; + con_init(); + + /* + * Command line option parsing commences now We only need to support + * a few things in the test suite. + */ + while (argc > 1) { + ++argv; + --argc; + + if (argv[0][0] == '-') { + if (parsecmd("redirout", &argc, &argv, &redirout, 1, false)) + continue; + if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false)) + continue; + + con_change(redirout, redirerr); + + if (!strcmp(argv[0]+1, "debug")) { + opts_debug = true; + continue; + } + if (!strcmp(argv[0]+1, "memchk")) { + opts_memchk = true; + continue; + } + if (!strcmp(argv[0]+1, "nocolor")) { + con_color(0); + continue; + } + + con_err("invalid argument %s\n", argv[0]+1); + return -1; + } + } + con_change(redirout, redirerr); + test_perform("tests"); + util_meminfo(); + return 0; +} diff --git a/tests/builtin.qc b/tests/builtin.qc new file mode 100644 index 0000000..6e58644 --- /dev/null +++ b/tests/builtin.qc @@ -0,0 +1,5 @@ +void(string) print = #1; + +void() main = { + print("hello world"); +} diff --git a/tests/builtin.tmpl b/tests/builtin.tmpl new file mode 100644 index 0000000..d81cad9 --- /dev/null +++ b/tests/builtin.tmpl @@ -0,0 +1,9 @@ +# used to test the builtins +I: builtin.qc +D: test builtin functions +T: -execute +C: -std=gmqcc +E: $null +F: builtins failed +S: builtins worked +M: hello world diff --git a/tests/calls.qc b/tests/calls.qc new file mode 100644 index 0000000..d55598e --- /dev/null +++ b/tests/calls.qc @@ -0,0 +1,14 @@ +void(string, ...) print = #1; +string(float) ftos = #2; + +float(float x, float y, float z) sum = { + return x + y + z; +}; + +void(float a, float b, float c) main = { + local float f; + f = sum(sum(a, sum(a, b, c), c), + sum(sum(sum(a, b, c), b, sum(a, b, c)), b, sum(a, b, sum(a, b, c))), + sum(sum(a, b, c), b, c)); + print(ftos(f), "\n"); +}; diff --git a/tests/calls.tmpl b/tests/calls.tmpl new file mode 100644 index 0000000..3c551af --- /dev/null +++ b/tests/calls.tmpl @@ -0,0 +1,8 @@ +I: calls.qc +D: test calls +T: -execute +C: -std=gmqcc +E: -float 100 -float 200 -float 300 +F: calls failed +S: calls worked +M: 4600 diff --git a/tests/equality.qc b/tests/equality.qc new file mode 100644 index 0000000..bc134b0 --- /dev/null +++ b/tests/equality.qc @@ -0,0 +1,11 @@ +void(string, ...) print = #1; +string(float) ftos = #2; + +void(float a, float b) main = { + if (a == b) print("eq,"); + if (a != b) print("ne,"); + if (a > b) print("gt,"); + if (a < b) print("lt,"); + if (a >= b) print("ge,"); + if (a <= b) print("le,"); +}; diff --git a/tests/equality.tmpl b/tests/equality.tmpl new file mode 100644 index 0000000..18c3750 --- /dev/null +++ b/tests/equality.tmpl @@ -0,0 +1,8 @@ +I: equality.qc +D: test equality +T: -execute +C: -std=gmqcc +E: -float 100 -float 200 +F: equality failed +S: equality worked +M: ne,lt,le, diff --git a/tests/fieldparams.qc b/tests/fieldparams.qc new file mode 100644 index 0000000..9ca71d6 --- /dev/null +++ b/tests/fieldparams.qc @@ -0,0 +1,17 @@ +void(string, string) print = #1; +entity() spawn = #3; + +.string a; +.string b; + +void(entity e, .string s) callout = { + print(e.s, "\n"); +}; + +void() main = { + local entity e; + e = spawn(); + e.a = "foo"; + e.b = "bar"; + callout(e, b); +}; diff --git a/tests/fieldparams.tmpl b/tests/fieldparams.tmpl new file mode 100644 index 0000000..7d5217f --- /dev/null +++ b/tests/fieldparams.tmpl @@ -0,0 +1,8 @@ +I: fieldparams.qc +D: test field paramaters +T: -execute +C: -std=qcc +E: $null +F: field paramaters fail +S: field paramaters work +M: bar diff --git a/tests/functions-as-params.qc b/tests/functions-as-params.qc new file mode 100644 index 0000000..7790e64 --- /dev/null +++ b/tests/functions-as-params.qc @@ -0,0 +1,13 @@ +void(string, string) print = #1; + +string() getter = { + return "correct"; +}; + +void(string() f) printer = { + print(f(), "\n"); +}; + +void() main = { + printer(getter); +}; diff --git a/tests/functions-as-params.tmpl b/tests/functions-as-params.tmpl new file mode 100644 index 0000000..2c4504c --- /dev/null +++ b/tests/functions-as-params.tmpl @@ -0,0 +1,8 @@ +I: functions-as-params.qc +D: test functions as paramaters +T: -execute +C: -std=gmqcc +E: $null +F: functions as paramaters failed +S: functions as paramaters passed +M: correct diff --git a/tests/ifs.qc b/tests/ifs.qc new file mode 100644 index 0000000..d3089ce --- /dev/null +++ b/tests/ifs.qc @@ -0,0 +1,12 @@ +void(string, ...) print = #1; + +void(float c) main = { + if (c == 1) + print("One\n"); + else if (c == 2) + print("Two\n"); + else if (c == 3) + print("Three\n"); + else + print("Else\n"); +}; diff --git a/tests/ifs.tmpl b/tests/ifs.tmpl new file mode 100644 index 0000000..8ba64e3 --- /dev/null +++ b/tests/ifs.tmpl @@ -0,0 +1,8 @@ +I: ifs.qc +D: test if statement +T: -execute +C: -std=gmqcc +E: -float 2 +F: if statement failed +S: if statement passed +M: Two diff --git a/tests/ngraphs.qc b/tests/ngraphs.qc new file mode 100644 index 0000000..330d625 --- /dev/null +++ b/tests/ngraphs.qc @@ -0,0 +1,6 @@ +void(...) print = %:1; + +void() main = ??< + print("??=??'??(??)??!????-??/??/%>|"); + print("#^[]|{}~\\%>\n"); +%>; diff --git a/tests/ngraphs.tmpl b/tests/ngraphs.tmpl new file mode 100644 index 0000000..f0c6f94 --- /dev/null +++ b/tests/ngraphs.tmpl @@ -0,0 +1,8 @@ +I: ngraphs.qc +D: test digraphs and trigraphs +T: -execute +C: -std=gmqcc +E: $null +F: digraphs and trigraphs failed +S: digraphs and trigraphs passed +M: #^[]|{}~\%>|#^[]|{}~\%> diff --git a/tests/variadic.qc b/tests/variadic.qc new file mode 100644 index 0000000..27d938f --- /dev/null +++ b/tests/variadic.qc @@ -0,0 +1,5 @@ +void(...) print = #1; + +void() main = { + print("hello", " world"); +} diff --git a/tests/variadic.tmpl b/tests/variadic.tmpl new file mode 100644 index 0000000..bba4920 --- /dev/null +++ b/tests/variadic.tmpl @@ -0,0 +1,8 @@ +I: variadic.qc +D: test variadic arguments +T: -execute +C: -std=gmqcc +E: $null +F: variadic arguments failed +S: variadic arguments worked +M: hello world diff --git a/testsuite/Makefile b/testsuite/Makefile deleted file mode 100644 index 6eafd4c..0000000 --- a/testsuite/Makefile +++ /dev/null @@ -1,191 +0,0 @@ -QCC = ../gmqcc -VM = ../qcvm - -TESTLIST = \ - globaldefs \ - fielddefs \ - builtins \ - variadic \ - calls \ - if1 \ - loops1 \ - maths1 \ - maths2 \ - equality \ - fields1 \ - invalid-types \ - ngraphs \ - invalid-assign \ - field-parameters \ - functions-as-parameters \ - shadow-qcc shadow-gmqcc - -.PHONY: clean test - -clean: - rm -f gmqcc qcvm - rm -f */deflist */*.gm.dat */*.qcc.dat */output - rm -rf obj - -test: $(TESTLIST) - -obj/%.gm.dat: %/main.qc obj - @echo "Testing:" $(subst obj/,,$(subst .gm.dat,,$@)) - @$(QCC) -std=gmqcc -o $@ $< > $@.out 2> $@.err - -obj/%.qcc.dat: %/main.qc obj - @echo "Testing:" $(subst obj/,,$(subst .qcc.dat,,$@)) - @$(QCC) -std=qcc -o $@ $< > $@.out 2> $@.err - -####################################################################### - -# Macro which causes something to be compiled either with -std=qcc or without... -# this may at some point be extended to also store information about the progs.dat -# somewhere but for now we only need to build the object. -define maketest -$(eval $dat = obj/${1}.${2}.dat) -$1: obj/$1.$2.dat -endef - -####################################################################### - -globaldefs: obj/globaldefs.gm.dat - @$(VM) -printdefs $< > $@/deflist - @diff $@/deflist $@/deflist.expected - -$(eval $(call maketest,fielddefs,gm)) -fielddefs: - @$(VM) -printfields $< > $@/deflist - @diff $@/deflist $@/deflist.expected - -$(eval $(call maketest,builtins,qcc)) -builtins: - @$(VM) -string "Hello 1" $< > $@/output - @test "`wc -l $@/output | awk '{ print $$1 }'`" = "1" - @grep -qE '^Hello 1$$' $@/output - @$(VM) -string "A test message Yeah" $< > $@/output - @test "`wc -l $@/output | awk '{ print $$1 }'`" = "1" - @grep -qE '^A test message Yeah$$' $@/output - -$(eval $(call maketest,variadic,qcc)) -variadic: - @$(VM) -string "Hello 1" $< > $@/output - @test "`wc -l $@/output | awk '{ print $$1 }'`" = "1" - @grep -qE '^Hello 1$$' $@/output - @$(VM) -string "A test message Yeah" $< > $@/output - @test "`wc -l $@/output | awk '{ print $$1 }'`" = "1" - @grep -qE '^A test message Yeah$$' $@/output - -$(eval $(call maketest,calls,qcc)) -calls: - @$(VM) -float 1 -float 100 -float 10000 $< > $@/output - @grep -qE '^70907$$' $@/output - @$(VM) -float 3 -float 201 -float 90127 $< > $@/output - @grep -qE '^632719$$' $@/output - -$(eval $(call maketest,if1,qcc)) -if1: - @$(VM) -float 1 -float 100 -float 10000 $< > $@/output - @grep -qE '^One$$' $@/output - @$(VM) -float 2 -float 100 -float 10000 $< > $@/output - @grep -qE '^Two$$' $@/output - @$(VM) -float 3 -float 100 -float 10000 $< > $@/output - @grep -qE '^Three$$' $@/output - @$(VM) -float 4 -float 100 -float 10000 $< > $@/output - @grep -qE '^Else$$' $@/output - -$(eval $(call maketest,loops1,qcc)) -loops1: - @$(VM) -float 0 $< > $@/output - @diff $@/output $@/0.expected - @$(VM) -float 1 $< > $@/output - @diff $@/output $@/1.expected - @$(VM) -float 4 $< > $@/output - @diff $@/output $@/4.expected - @$(VM) -float 10 $< > $@/output - @diff $@/output $@/10.expected - -$(eval $(call maketest,maths1,qcc)) -maths1: - @$(VM) -float 0 -float 3 $< > $@/output - @diff $@/output $@/0.3.expected - @$(VM) -float 3 -float 6 $< > $@/output - @diff $@/output $@/3.6.expected - @$(VM) -float 0 -float 0 $< > $@/output - @diff $@/output $@/0.0.expected - -$(eval $(call maketest,maths2,qcc)) -maths2: - @$(VM) -vector '1 2 3' -vector '4 5 6' $< > $@/output - @grep -qE '^dot = 32$$' $@/output - @$(VM) -vector '-5 12 5.5' -vector '4 -5 1' $< > $@/output - @grep -qE '^dot = -74.5$$' $@/output - @$(VM) -vector '-5 12 5.5' -vector '0 0 0' $< > $@/output - @grep -qE '^dot = 0$$' $@/output - -$(eval $(call maketest,equality,qcc)) -equality: - @$(VM) -float 1 -float 1 $< > $@/output - @diff $@/output $@/1.1.expected - @$(VM) -float 1 -float 0 $< > $@/output - @diff $@/output $@/1.0.expected - @$(VM) -float 0 -float 1 $< > $@/output - @diff $@/output $@/0.1.expected - -$(eval $(call maketest,fields1,qcc)) -fields1: - @$(VM) -vector '150 2000 150' -vector '220 1300 -200' $< > $@/output - @diff $@/output $@/expected - -invalid-types-ok: obj invalid-types/assign.qc invalid-types/op.qc invalid-types/call1.qc invalid-types/call2.qc invalid-types/call3.qc - @echo "Testing: invalid-types" - @if $(QCC) -std=qcc -o obj/invalid.dat invalid-types/op.qc > obj/invalid.out 2>&1 ; then echo "Successfully compiled a file which was supposed to fail: op.qc" ; false ; else true ; fi - @if $(QCC) -std=qcc -o obj/invalid.dat invalid-types/call1.qc > obj/invalid.out 2>&1 ; then echo "Successfully compiled a file which was supposed to fail: call1.qc" ; false ; else true ; fi - @if $(QCC) -std=qcc -o obj/invalid.dat invalid-types/call2.qc > obj/invalid.out 2>&1 ; then echo "Successfully compiled a file which was supposed to fail: call2.qc" ; false ; else true ; fi - @if $(QCC) -std=qcc -o obj/invalid.dat invalid-types/call3.qc > obj/invalid.out 2>&1 ; then echo "Successfully compiled a file which was supposed to fail: call3.qc" ; false ; else true ; fi - @if $(QCC) -std=qcc -o obj/invalid.dat invalid-types/assign.qc > obj/invalid.out 2>&1 ; then echo "Successfully compiled a file which was supposed to fail: assign.qc" ; false ; else true ; fi - @touch obj/invalid-types-ok - -invalid-types: invalid-types-ok - -$(eval $(call maketest,ngraphs,qcc)) -ngraphs: - @$(VM) $< > $@/output - @diff $@/output $@/expected - -invalid-assign-ok: obj invalid-assign/main.qc - @echo "Testing: invalid-assign" - @if $(QCC) -std=qcc -o obj/invalid.dat invalid-assign/main.qc > obj/invalid.out 2>&1 ; then echo "Successfully compiled a file which was supposed to fail: invalid-assign/main.qc" ; false ; else true ; fi - -invalid-assign: invalid-assign-ok - -$(eval $(call maketest,field-parameters,qcc)) -field-parameters: - @$(VM) $< > $@/output - @diff $@/output $@/expected - -$(eval $(call maketest,functions-as-parameters,qcc)) -functions-as-parameters: - @$(VM) $< > $@/output - @diff $@/output $@/expected - -$(eval $(call maketest,shadow-qcc,qcc)) -shadow-qcc: - @$(VM) -vector '33 44 55' $< > $@/output - @diff $@/output $@/expected - -$(eval $(call maketest,shadow-gmqcc,gm)) -shadow-gmqcc: - @$(VM) -vector '33 44 55' $< > $@/output - @diff $@/output $@/expected - -####################################################################### -obj: - mkdir obj - -../gmqcc: - $(MAKE) -C .. - -../qcvm: - $(MAKE) -C .. qcvm - diff --git a/testsuite/builtins/main.qc b/testsuite/builtins/main.qc deleted file mode 100644 index dd2013e..0000000 --- a/testsuite/builtins/main.qc +++ /dev/null @@ -1,6 +0,0 @@ -void(string) print = #1; - -void(string what) main = { - print(what); - print("\n"); -}; diff --git a/testsuite/calls/main.qc b/testsuite/calls/main.qc deleted file mode 100644 index c6061e0..0000000 --- a/testsuite/calls/main.qc +++ /dev/null @@ -1,14 +0,0 @@ -void(string, ...) print = #1; -string(float) ftos = #2; - -float(float x, float y, float z) sum = { - return x + y + z; -}; - -void(float a, float b, float c) main = { - local float f; - f = sum(sum(a, sum(a, b, c), c), - sum(sum(sum(a, b, c), b, sum(a, b, c)), b, sum(a, b, sum(a, b, c))), - sum(sum(a, b, c), b, c)); - print(ftos(f), "\n"); -}; diff --git a/testsuite/equality/0.1.expected b/testsuite/equality/0.1.expected deleted file mode 100644 index 1356fb7..0000000 --- a/testsuite/equality/0.1.expected +++ /dev/null @@ -1,3 +0,0 @@ -ne -lt -le diff --git a/testsuite/equality/1.0.expected b/testsuite/equality/1.0.expected deleted file mode 100644 index 6a3416e..0000000 --- a/testsuite/equality/1.0.expected +++ /dev/null @@ -1,3 +0,0 @@ -ne -gt -ge diff --git a/testsuite/equality/1.1.expected b/testsuite/equality/1.1.expected deleted file mode 100644 index e332b2c..0000000 --- a/testsuite/equality/1.1.expected +++ /dev/null @@ -1,3 +0,0 @@ -eq -ge -le diff --git a/testsuite/equality/main.qc b/testsuite/equality/main.qc deleted file mode 100644 index b21f8ff..0000000 --- a/testsuite/equality/main.qc +++ /dev/null @@ -1,11 +0,0 @@ -void(string, ...) print = #1; -string(float) ftos = #2; - -void(float a, float b) main = { - if (a == b) print("eq\n"); - if (a != b) print("ne\n"); - if (a > b) print("gt\n"); - if (a < b) print("lt\n"); - if (a >= b) print("ge\n"); - if (a <= b) print("le\n"); -}; diff --git a/testsuite/field-parameters/expected b/testsuite/field-parameters/expected deleted file mode 100644 index 5716ca5..0000000 --- a/testsuite/field-parameters/expected +++ /dev/null @@ -1 +0,0 @@ -bar diff --git a/testsuite/field-parameters/main.qc b/testsuite/field-parameters/main.qc deleted file mode 100644 index 86a31c6..0000000 --- a/testsuite/field-parameters/main.qc +++ /dev/null @@ -1,17 +0,0 @@ -void(string, string) print = #1; -entity() spawn = #3; - -.string a; -.string b; - -void(entity e, .string s) callout = { - print(e.s, "\n"); -}; - -void() main = { - local entity e; - e = spawn(); - e.a = "foo"; - e.b = "bar"; - callout(e, b); -}; diff --git a/testsuite/fielddefs/deflist.expected b/testsuite/fielddefs/deflist.expected deleted file mode 100644 index 54ca670..0000000 --- a/testsuite/fielddefs/deflist.expected +++ /dev/null @@ -1,5 +0,0 @@ -Field: void at 0 -Field: float globf at 0 -Field: vector globv at 1 -Field: string globs at 4 -Field: function globfunc at 5 diff --git a/testsuite/fielddefs/main.qc b/testsuite/fielddefs/main.qc deleted file mode 100644 index e2d2380..0000000 --- a/testsuite/fielddefs/main.qc +++ /dev/null @@ -1,4 +0,0 @@ -.float globf; -.vector globv; -.string globs; -.void() globfunc; diff --git a/testsuite/fields1/expected b/testsuite/fields1/expected deleted file mode 100644 index 585213f..0000000 --- a/testsuite/fields1/expected +++ /dev/null @@ -1,3 +0,0 @@ -spot1 = '150 2000 175' -spot2 = '220 1300 -175' -vis: 0 diff --git a/testsuite/fields1/main.qc b/testsuite/fields1/main.qc deleted file mode 100644 index fc64743..0000000 --- a/testsuite/fields1/main.qc +++ /dev/null @@ -1,43 +0,0 @@ -void(string, ...) print = #1; -string(float) ftos = #2; -entity() spawn = #3; -string(vector) vtos = #5; -void(string, ...) error = #6; - -entity self; - -.vector origin; -.vector view; - -entity() make = { - local entity e; - e = spawn(); - e.view = '0 0 25'; - return e; -}; - -float(entity targ) visible = { - local vector spot1, spot2; - spot1 = self.origin + self.view; - spot2 = targ.origin + targ.view; - - print("spot1 = ", vtos(spot1), "\n"); - print("spot2 = ", vtos(spot2), "\n"); - // This was part of some QC code which had a bug - // we don't actually return anything important here. - return 0; -}; - -void(vector a, vector b) main = { - local entity targ; - - self = make(); - targ = make(); - if (self == targ) - error("ERROR, self == targ\n"); - - self.origin = a; - targ.origin = b; - - print("vis: ", ftos(visible(targ)), "\n"); -}; diff --git a/testsuite/functions-as-parameters/expected b/testsuite/functions-as-parameters/expected deleted file mode 100644 index 818e321..0000000 --- a/testsuite/functions-as-parameters/expected +++ /dev/null @@ -1 +0,0 @@ -correct diff --git a/testsuite/functions-as-parameters/main.qc b/testsuite/functions-as-parameters/main.qc deleted file mode 100644 index 7790e64..0000000 --- a/testsuite/functions-as-parameters/main.qc +++ /dev/null @@ -1,13 +0,0 @@ -void(string, string) print = #1; - -string() getter = { - return "correct"; -}; - -void(string() f) printer = { - print(f(), "\n"); -}; - -void() main = { - printer(getter); -}; diff --git a/testsuite/globaldefs/deflist.expected b/testsuite/globaldefs/deflist.expected deleted file mode 100644 index edee426..0000000 --- a/testsuite/globaldefs/deflist.expected +++ /dev/null @@ -1,5 +0,0 @@ -Global: void at 0 -Global: float globf at 28 -Global: vector globv at 29 -Global: string globs at 32 -Global: function globfunc at 33 diff --git a/testsuite/globaldefs/main.qc b/testsuite/globaldefs/main.qc deleted file mode 100644 index 3cc2dc1..0000000 --- a/testsuite/globaldefs/main.qc +++ /dev/null @@ -1,4 +0,0 @@ -float globf; -vector globv; -string globs; -void() globfunc; diff --git a/testsuite/if1/main.qc b/testsuite/if1/main.qc deleted file mode 100644 index d3089ce..0000000 --- a/testsuite/if1/main.qc +++ /dev/null @@ -1,12 +0,0 @@ -void(string, ...) print = #1; - -void(float c) main = { - if (c == 1) - print("One\n"); - else if (c == 2) - print("Two\n"); - else if (c == 3) - print("Three\n"); - else - print("Else\n"); -}; diff --git a/testsuite/invalid-assign/main.qc b/testsuite/invalid-assign/main.qc deleted file mode 100644 index 264ea5a..0000000 --- a/testsuite/invalid-assign/main.qc +++ /dev/null @@ -1,4 +0,0 @@ -void() main = { - local float x; - x + 3 = 5; -}; diff --git a/testsuite/invalid-types/assign.qc b/testsuite/invalid-types/assign.qc deleted file mode 100644 index d884201..0000000 --- a/testsuite/invalid-types/assign.qc +++ /dev/null @@ -1,5 +0,0 @@ -void() main = { - local float x; - local entity e; - x = e; -}; diff --git a/testsuite/invalid-types/call1.qc b/testsuite/invalid-types/call1.qc deleted file mode 100644 index 07d6217..0000000 --- a/testsuite/invalid-types/call1.qc +++ /dev/null @@ -1,6 +0,0 @@ -void(float, string, entity) fun = #1; - -void() main = { - local float x; - fun(x, x, x); -}; diff --git a/testsuite/invalid-types/call2.qc b/testsuite/invalid-types/call2.qc deleted file mode 100644 index cb84fcd..0000000 --- a/testsuite/invalid-types/call2.qc +++ /dev/null @@ -1,6 +0,0 @@ -void(float, string, entity) fun = #1; - -void() main = { - local string x; - fun(x, x, x); -}; diff --git a/testsuite/invalid-types/call3.qc b/testsuite/invalid-types/call3.qc deleted file mode 100644 index 4ad110b..0000000 --- a/testsuite/invalid-types/call3.qc +++ /dev/null @@ -1,6 +0,0 @@ -void(float, string, entity) fun = #1; - -void() main = { - local entity x; - fun(x, x, x); -}; diff --git a/testsuite/invalid-types/op.qc b/testsuite/invalid-types/op.qc deleted file mode 100644 index bee4452..0000000 --- a/testsuite/invalid-types/op.qc +++ /dev/null @@ -1,5 +0,0 @@ -void() main = { - local float x, y; - local entity e; - x = y + e; -}; diff --git a/testsuite/loops1/0.expected b/testsuite/loops1/0.expected deleted file mode 100644 index bc0c271..0000000 --- a/testsuite/loops1/0.expected +++ /dev/null @@ -1 +0,0 @@ -do 0 diff --git a/testsuite/loops1/1.expected b/testsuite/loops1/1.expected deleted file mode 100644 index 8220dfd..0000000 --- a/testsuite/loops1/1.expected +++ /dev/null @@ -1,3 +0,0 @@ -for 0 -while 0 -do 0 diff --git a/testsuite/loops1/10.expected b/testsuite/loops1/10.expected deleted file mode 100644 index 3bfa4d2..0000000 --- a/testsuite/loops1/10.expected +++ /dev/null @@ -1,30 +0,0 @@ -for 0 -for 1 -for 2 -for 3 -for 4 -for 5 -for 6 -for 7 -for 8 -for 9 -while 0 -while 1 -while 2 -while 3 -while 4 -while 5 -while 6 -while 7 -while 8 -while 9 -do 0 -do 1 -do 2 -do 3 -do 4 -do 5 -do 6 -do 7 -do 8 -do 9 diff --git a/testsuite/loops1/4.expected b/testsuite/loops1/4.expected deleted file mode 100644 index 18efd30..0000000 --- a/testsuite/loops1/4.expected +++ /dev/null @@ -1,12 +0,0 @@ -for 0 -for 1 -for 2 -for 3 -while 0 -while 1 -while 2 -while 3 -do 0 -do 1 -do 2 -do 3 diff --git a/testsuite/loops1/main.qc b/testsuite/loops1/main.qc deleted file mode 100644 index 4b4dcea..0000000 --- a/testsuite/loops1/main.qc +++ /dev/null @@ -1,22 +0,0 @@ -void(string, ...) print = #1; -string(float) ftos = #2; - -void(float n) main = { - local float i; - - for (i = 0; i < n; i += 1) { - print("for ", ftos(i), "\n"); - } - - i = 0; - while (i < n) { - print("while ", ftos(i), "\n"); - i += 1; - } - - i = 0; - do { - print("do ", ftos(i), "\n"); - i += 1; - } while (i < n); -}; diff --git a/testsuite/maths1/0.0.expected b/testsuite/maths1/0.0.expected deleted file mode 100644 index 2423970..0000000 --- a/testsuite/maths1/0.0.expected +++ /dev/null @@ -1,8 +0,0 @@ -input: 0 and 0 -+ 0 -* 0 -/ 0 -& 0 -| 0 -&& 0 -|| 0 diff --git a/testsuite/maths1/0.3.expected b/testsuite/maths1/0.3.expected deleted file mode 100644 index dca5e6f..0000000 --- a/testsuite/maths1/0.3.expected +++ /dev/null @@ -1,8 +0,0 @@ -input: 0 and 3 -+ 3 -* 0 -/ 0 -& 0 -| 3 -&& 0 -|| 1 diff --git a/testsuite/maths1/3.6.expected b/testsuite/maths1/3.6.expected deleted file mode 100644 index 370a09a..0000000 --- a/testsuite/maths1/3.6.expected +++ /dev/null @@ -1,8 +0,0 @@ -input: 3 and 6 -+ 9 -* 18 -/ 0.5 -& 2 -| 7 -&& 1 -|| 1 diff --git a/testsuite/maths1/main.qc b/testsuite/maths1/main.qc deleted file mode 100644 index 58abf54..0000000 --- a/testsuite/maths1/main.qc +++ /dev/null @@ -1,14 +0,0 @@ -void(string, ...) print = #1; -string(float) ftos = #2; -string(vector) vtos = #5; - -void(float a, float b) main = { - print("input: ", ftos(a), " and ", ftos(b), "\n"); - print("+ ", ftos(a+b), "\n"); - print("* ", ftos(a*b), "\n"); - print("/ ", ftos(a/b), "\n"); - print("& ", ftos(a&b), "\n"); - print("| ", ftos(a|b), "\n"); - print("&& ", ftos(a&&b), "\n"); - print("|| ", ftos(a||b), "\n"); -}; diff --git a/testsuite/maths2/main.qc b/testsuite/maths2/main.qc deleted file mode 100644 index 4d8358f..0000000 --- a/testsuite/maths2/main.qc +++ /dev/null @@ -1,6 +0,0 @@ -void(string, ...) print = #1; -string(float) ftos = #2; - -void(vector a, vector b) main = { - print("dot = ", ftos(a*b), "\n"); -}; diff --git a/testsuite/ngraphs/expected b/testsuite/ngraphs/expected deleted file mode 100644 index d51a1ba..0000000 --- a/testsuite/ngraphs/expected +++ /dev/null @@ -1,2 +0,0 @@ -#^[]|{}~\%> -#^[]|{}~\%> diff --git a/testsuite/ngraphs/main.qc b/testsuite/ngraphs/main.qc deleted file mode 100644 index ef2d38b..0000000 --- a/testsuite/ngraphs/main.qc +++ /dev/null @@ -1,6 +0,0 @@ -void(string, string) print = %:1; - -void() main = ??< - print("??=??'??(??)??!????-??/??/%>", "??/n"); - print("#^[]|{}~\\%>", "\n"); -%>; diff --git a/testsuite/shadow-gmqcc/expected b/testsuite/shadow-gmqcc/expected deleted file mode 100644 index 89f4331..0000000 --- a/testsuite/shadow-gmqcc/expected +++ /dev/null @@ -1 +0,0 @@ -'0 0 0' diff --git a/testsuite/shadow-gmqcc/main.qc b/testsuite/shadow-gmqcc/main.qc deleted file mode 100644 index 08bc917..0000000 --- a/testsuite/shadow-gmqcc/main.qc +++ /dev/null @@ -1,7 +0,0 @@ -void(string, string) print = #1; -string(vector) vtos = #5; - -void(vector org) main = { - local vector org; - print(vtos(org), "\n"); -}; diff --git a/testsuite/shadow-qcc/expected b/testsuite/shadow-qcc/expected deleted file mode 100644 index 9ec2a0c..0000000 --- a/testsuite/shadow-qcc/expected +++ /dev/null @@ -1 +0,0 @@ -'33 44 55' diff --git a/testsuite/shadow-qcc/main.qc b/testsuite/shadow-qcc/main.qc deleted file mode 100644 index 08bc917..0000000 --- a/testsuite/shadow-qcc/main.qc +++ /dev/null @@ -1,7 +0,0 @@ -void(string, string) print = #1; -string(vector) vtos = #5; - -void(vector org) main = { - local vector org; - print(vtos(org), "\n"); -}; diff --git a/testsuite/variadic/main.qc b/testsuite/variadic/main.qc deleted file mode 100644 index 6c53eb2..0000000 --- a/testsuite/variadic/main.qc +++ /dev/null @@ -1,5 +0,0 @@ -void(string, ...) print = #1; - -void(string what) main = { - print(what, "\n"); -}; diff --git a/util.c b/util.c index 77753a9..a900889 100644 --- a/util.c +++ b/util.c @@ -521,6 +521,15 @@ FILE *util_fopen(const char *filename, const char *mode) #endif } +bool util_filexists(const char *file) { + FILE *fp = fopen(file, "rb"); + if (!fp) return false; + + /* it exists */ + fclose(fp); + return true; +} + void _util_vec_grow(void **a, size_t i, size_t s) { size_t m = *a ? 2*_vec_beg(*a)+i : i+1; void *p = mem_r((*a ? _vec_raw(*a) : NULL), s * m + sizeof(size_t)*2);