]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Merge branch 'master' into test-suite
authorDale Weiler <killfieldengine@gmail.com>
Mon, 19 Nov 2012 02:13:46 +0000 (02:13 +0000)
committerDale Weiler <killfieldengine@gmail.com>
Mon, 19 Nov 2012 02:13:46 +0000 (02:13 +0000)
Conflicts:
Makefile
ir.c

63 files changed:
Makefile
con.c
gmqcc.h
main.c
test.c [new file with mode: 0644]
tests/builtin.qc [new file with mode: 0644]
tests/builtin.tmpl [new file with mode: 0644]
tests/calls.qc [new file with mode: 0644]
tests/calls.tmpl [new file with mode: 0644]
tests/equality.qc [new file with mode: 0644]
tests/equality.tmpl [new file with mode: 0644]
tests/fieldparams.qc [new file with mode: 0644]
tests/fieldparams.tmpl [new file with mode: 0644]
tests/functions-as-params.qc [new file with mode: 0644]
tests/functions-as-params.tmpl [new file with mode: 0644]
tests/ifs.qc [new file with mode: 0644]
tests/ifs.tmpl [new file with mode: 0644]
tests/ngraphs.qc [new file with mode: 0644]
tests/ngraphs.tmpl [new file with mode: 0644]
tests/variadic.qc [new file with mode: 0644]
tests/variadic.tmpl [new file with mode: 0644]
testsuite/Makefile [deleted file]
testsuite/builtins/main.qc [deleted file]
testsuite/calls/main.qc [deleted file]
testsuite/equality/0.1.expected [deleted file]
testsuite/equality/1.0.expected [deleted file]
testsuite/equality/1.1.expected [deleted file]
testsuite/equality/main.qc [deleted file]
testsuite/field-parameters/expected [deleted file]
testsuite/field-parameters/main.qc [deleted file]
testsuite/fielddefs/deflist.expected [deleted file]
testsuite/fielddefs/main.qc [deleted file]
testsuite/fields1/expected [deleted file]
testsuite/fields1/main.qc [deleted file]
testsuite/functions-as-parameters/expected [deleted file]
testsuite/functions-as-parameters/main.qc [deleted file]
testsuite/globaldefs/deflist.expected [deleted file]
testsuite/globaldefs/main.qc [deleted file]
testsuite/if1/main.qc [deleted file]
testsuite/invalid-assign/main.qc [deleted file]
testsuite/invalid-types/assign.qc [deleted file]
testsuite/invalid-types/call1.qc [deleted file]
testsuite/invalid-types/call2.qc [deleted file]
testsuite/invalid-types/call3.qc [deleted file]
testsuite/invalid-types/op.qc [deleted file]
testsuite/loops1/0.expected [deleted file]
testsuite/loops1/1.expected [deleted file]
testsuite/loops1/10.expected [deleted file]
testsuite/loops1/4.expected [deleted file]
testsuite/loops1/main.qc [deleted file]
testsuite/maths1/0.0.expected [deleted file]
testsuite/maths1/0.3.expected [deleted file]
testsuite/maths1/3.6.expected [deleted file]
testsuite/maths1/main.qc [deleted file]
testsuite/maths2/main.qc [deleted file]
testsuite/ngraphs/expected [deleted file]
testsuite/ngraphs/main.qc [deleted file]
testsuite/shadow-gmqcc/expected [deleted file]
testsuite/shadow-gmqcc/main.qc [deleted file]
testsuite/shadow-qcc/expected [deleted file]
testsuite/shadow-qcc/main.qc [deleted file]
testsuite/variadic/main.qc [deleted file]
util.c

index ad8c9411c7467b647c7e3ad2cb14de64bf07e0ff..aa5ef11f11b60b2f9955c596c60b522e78c4dde4 100644 (file)
--- 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 bfe3a23c72650a60b7285f3eaba6618e2a1ede4c..8073a5723d71286568e5d0c164d3e34059af924f 100644 (file)
--- 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 1ea2ceff8bb472286c8bc3982d1f785fdb4dcc9e..265bee56d1dc28e2acb7c742cc12513429ae57a0 100644 (file)
--- 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 990acf2c1b51565ba2fe1fe3a9deb8edceb23e2b..4c8062d7dcdb1b125101c77348c26952c9ac592c 100644 (file)
--- 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 (file)
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 <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+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 <sys/types.h>
+#include <sys/wait.h>
+
+#include <unistd.h>
+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 (file)
index 0000000..6e58644
--- /dev/null
@@ -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 (file)
index 0000000..d81cad9
--- /dev/null
@@ -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 (file)
index 0000000..d55598e
--- /dev/null
@@ -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 (file)
index 0000000..3c551af
--- /dev/null
@@ -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 (file)
index 0000000..bc134b0
--- /dev/null
@@ -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 (file)
index 0000000..18c3750
--- /dev/null
@@ -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 (file)
index 0000000..9ca71d6
--- /dev/null
@@ -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 (file)
index 0000000..7d5217f
--- /dev/null
@@ -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 (file)
index 0000000..7790e64
--- /dev/null
@@ -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 (file)
index 0000000..2c4504c
--- /dev/null
@@ -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 (file)
index 0000000..d3089ce
--- /dev/null
@@ -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 (file)
index 0000000..8ba64e3
--- /dev/null
@@ -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 (file)
index 0000000..330d625
--- /dev/null
@@ -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 (file)
index 0000000..f0c6f94
--- /dev/null
@@ -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 (file)
index 0000000..27d938f
--- /dev/null
@@ -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 (file)
index 0000000..bba4920
--- /dev/null
@@ -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 (file)
index 6eafd4c..0000000
+++ /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 (file)
index dd2013e..0000000
+++ /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 (file)
index c6061e0..0000000
+++ /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 (file)
index 1356fb7..0000000
+++ /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 (file)
index 6a3416e..0000000
+++ /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 (file)
index e332b2c..0000000
+++ /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 (file)
index b21f8ff..0000000
+++ /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 (file)
index 5716ca5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-bar
diff --git a/testsuite/field-parameters/main.qc b/testsuite/field-parameters/main.qc
deleted file mode 100644 (file)
index 86a31c6..0000000
+++ /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 (file)
index 54ca670..0000000
+++ /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 (file)
index e2d2380..0000000
+++ /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 (file)
index 585213f..0000000
+++ /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 (file)
index fc64743..0000000
+++ /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 (file)
index 818e321..0000000
+++ /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 (file)
index 7790e64..0000000
+++ /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 (file)
index edee426..0000000
+++ /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 (file)
index 3cc2dc1..0000000
+++ /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 (file)
index d3089ce..0000000
+++ /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 (file)
index 264ea5a..0000000
+++ /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 (file)
index d884201..0000000
+++ /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 (file)
index 07d6217..0000000
+++ /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 (file)
index cb84fcd..0000000
+++ /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 (file)
index 4ad110b..0000000
+++ /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 (file)
index bee4452..0000000
+++ /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 (file)
index bc0c271..0000000
+++ /dev/null
@@ -1 +0,0 @@
-do 0
diff --git a/testsuite/loops1/1.expected b/testsuite/loops1/1.expected
deleted file mode 100644 (file)
index 8220dfd..0000000
+++ /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 (file)
index 3bfa4d2..0000000
+++ /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 (file)
index 18efd30..0000000
+++ /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 (file)
index 4b4dcea..0000000
+++ /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 (file)
index 2423970..0000000
+++ /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 (file)
index dca5e6f..0000000
+++ /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 (file)
index 370a09a..0000000
+++ /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 (file)
index 58abf54..0000000
+++ /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 (file)
index 4d8358f..0000000
+++ /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 (file)
index d51a1ba..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-#^[]|{}~\%>
-#^[]|{}~\%>
diff --git a/testsuite/ngraphs/main.qc b/testsuite/ngraphs/main.qc
deleted file mode 100644 (file)
index ef2d38b..0000000
+++ /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 (file)
index 89f4331..0000000
+++ /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 (file)
index 08bc917..0000000
+++ /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 (file)
index 9ec2a0c..0000000
+++ /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 (file)
index 08bc917..0000000
+++ /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 (file)
index 6c53eb2..0000000
+++ /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 77753a9be006d3add446d1a3bde7c9d9924091fc..a900889e57b90f983d776c44326e02935d9eee7b 100644 (file)
--- 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);