]> git.xonotic.org Git - xonotic/gmqcc.git/blob - test.c
Initial preprocessor procedure option for testsuite implemented.
[xonotic/gmqcc.git] / test.c
1 /*
2  * Copyright (C) 2012, 2013
3  *     Dale Weiler
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of
6  * this software and associated documentation files (the "Software"), to deal in
7  * the Software without restriction, including without limitation the rights to
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9  * of the Software, and to permit persons to whom the Software is furnished to do
10  * so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all
13  * copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 #include "gmqcc.h"
24 #include <sys/types.h>
25 #include <sys/stat.h>
26
27 opts_cmd_t opts;
28
29 const char *task_bins[] = {
30     "./gmqcc",
31     "./qcvm"
32 };
33
34 /*
35  * TODO: Windows version
36  * this implements a unique bi-directional popen-like function that
37  * allows reading data from both stdout and stderr. And writing to
38  * stdin :)
39  *
40  * Example of use:
41  * FILE *handles[3] = task_popen("ls", "-l", "r");
42  * if (!handles) { perror("failed to open stdin/stdout/stderr to ls");
43  * // handles[0] = stdin
44  * // handles[1] = stdout
45  * // handles[2] = stderr
46  *
47  * task_pclose(handles); // to close
48  */
49 #ifndef _WIN32
50 #include <sys/types.h>
51 #include <sys/wait.h>
52 #include <dirent.h>
53 #include <unistd.h>
54 typedef struct {
55     FILE *handles[3];
56     int   pipes  [3];
57
58     int stderr_fd;
59     int stdout_fd;
60     int pid;
61 } popen_t;
62
63 FILE ** task_popen(const char *command, const char *mode) {
64     int     inhandle  [2];
65     int     outhandle [2];
66     int     errhandle [2];
67     int     trypipe;
68
69     popen_t *data = (popen_t*)mem_a(sizeof(popen_t));
70
71     /*
72      * Parse the command now into a list for execv, this is a pain
73      * in the ass.
74      */
75     char  *line = (char*)command;
76     char **argv = NULL;
77     {
78
79         while (*line != '\0') {
80             while (*line == ' ' || *line == '\t' || *line == '\n')
81                 *line++ = '\0';
82             vec_push(argv, line);
83
84             while (*line != '\0' && *line != ' ' &&
85                    *line != '\t' && *line != '\n') line++;
86         }
87         vec_push(argv, '\0');
88     }
89
90
91     if ((trypipe = pipe(inhandle))  < 0) goto task_popen_error_0;
92     if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1;
93     if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2;
94
95     if ((data->pid = fork()) > 0) {
96         /* parent */
97         close(inhandle  [0]);
98         close(outhandle [1]);
99         close(errhandle [1]);
100
101         data->pipes  [0] = inhandle [1];
102         data->pipes  [1] = outhandle[0];
103         data->pipes  [2] = errhandle[0];
104         data->handles[0] = fdopen(inhandle [1], "w");
105         data->handles[1] = fdopen(outhandle[0], mode);
106         data->handles[2] = fdopen(errhandle[0], mode);
107
108         /* sigh */
109         if (argv)
110             vec_free(argv);
111         return data->handles;
112     } else if (data->pid == 0) {
113         /* child */
114         close(inhandle [1]);
115         close(outhandle[0]);
116         close(errhandle[0]);
117
118         /* see piping documentation for this sillyness :P */
119         close(0), dup(inhandle [0]);
120         close(1), dup(outhandle[1]);
121         close(2), dup(errhandle[1]);
122
123         execvp(*argv, argv);
124         exit(EXIT_FAILURE);
125     } else {
126         /* fork failed */
127         goto task_popen_error_3;
128     }
129
130 task_popen_error_3: close(errhandle[0]), close(errhandle[1]);
131 task_popen_error_2: close(outhandle[0]), close(outhandle[1]);
132 task_popen_error_1: close(inhandle [0]), close(inhandle [1]);
133 task_popen_error_0:
134
135     if (argv)
136         vec_free(argv);
137     return NULL;
138 }
139
140 int task_pclose(FILE **handles) {
141     popen_t *data   = (popen_t*)handles;
142     int      status = 0;
143
144     close(data->pipes[0]); /* stdin  */
145     close(data->pipes[1]); /* stdout */
146     close(data->pipes[2]); /* stderr */
147
148     waitpid(data->pid, &status, 0);
149
150     mem_d(data);
151
152     return status;
153 }
154 #else
155     /*
156      * Bidirectional piping implementation for windows using CreatePipe and DuplicateHandle +
157      * other hacks.
158      */
159     typedef struct {
160         int __dummy;
161         /* TODO: implement */
162     } popen_t;
163
164     FILE **task_popen(const char *command, const char *mode) {
165         (void)command;
166         (void)mode;
167
168         /* TODO: implement */
169         return NULL;
170     }
171
172     void task_pclose(FILE **files) {
173         /* TODO: implement */
174         (void)files;
175         return;
176     }
177 #endif /*! _WIN32 */
178
179 #define TASK_COMPILE    0
180 #define TASK_EXECUTE    1
181 /*
182  * Task template system:
183  *  templates are rules for a specific test, used to create a "task" that
184  *  is executed with those set of rules (arguments, and what not). Tests
185  *  that don't have a template with them cannot become tasks, since without
186  *  the information for that test there is no way to properly "test" them.
187  *  Rules for these templates are described in a template file, using a
188  *  task template language.
189  *
190  *  The language is a basic finite statemachine, top-down single-line
191  *  description language.
192  *
193  *  The languge is composed entierly of "tags" which describe a string of
194  *  text for a task.  Think of it much like a configuration file.  Except
195  *  it's been designed to allow flexibility and future support for prodecual
196  *  semantics.
197  *
198  *  The following "tags" are suported by the language
199  *
200  *      D:
201  *          Used to set a description of the current test, this must be
202  *          provided, this tag is NOT optional.
203  *
204  *      T:
205  *          Used to set the procedure for the given task, there are two
206  *          options for this:
207  *              -compile
208  *                  This simply performs compilation only
209  *              -execute
210  *                  This will perform compilation and execution
211  *              -fail
212  *                  This will perform compilation, but requires
213  *                  the compilation to fail in order to succeed.   
214  *
215  *          This must be provided, this tag is NOT optional.
216  *
217  *      C:
218  *          Used to set the compilation flags for the given task, this
219  *          must be provided, this tag is NOT optional.
220  *
221  *      F:  Used to set some test suite flags, currently the only option
222  *          is -no-defs (to including of defs.qh)
223  *
224  *      E:
225  *          Used to set the execution flags for the given task. This tag
226  *          must be provided if T == -execute, otherwise it's erroneous
227  *          as compilation only takes place.
228  *
229  *      M:
230  *          Used to describe a string of text that should be matched from
231  *          the output of executing the task.  If this doesn't match the
232  *          task fails.  This tag must be provided if T == -execute, otherwise
233  *          it's erroneous as compilation only takes place.
234  *
235  *      I:
236  *          Used to specify the INPUT source file to operate on, this must be
237  *          provided, this tag is NOT optional
238  *
239  *
240  *  Notes:
241  *      These tags have one-time use, using them more than once will result
242  *      in template compilation errors.
243  *
244  *      Lines beginning with # or // in the template file are comments and
245  *      are ignored by the template parser.
246  *
247  *      Whitespace is optional, with exception to the colon ':' between the
248  *      tag and it's assignment value/
249  *
250  *      The template compiler will detect erronrous tags (optional tags
251  *      that need not be set), as well as missing tags, and error accordingly
252  *      this will result in the task failing.
253  */
254 typedef struct {
255     char  *description;
256     char  *compileflags;
257     char  *executeflags;
258     char  *proceduretype;
259     char  *sourcefile;
260     char  *tempfilename;
261     char **comparematch;
262     char  *rulesfile;
263     char  *testflags;
264 } task_template_t;
265
266 /*
267  * This is very much like a compiler code generator :-).  This generates
268  * a value from some data observed from the compiler.
269  */
270 bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) {
271     size_t desclen = 0;
272     size_t filelen = 0;
273     char **destval = NULL;
274
275     if (!tmpl)
276         return false;
277
278     switch(tag) {
279         case 'D': destval = &tmpl->description;    break;
280         case 'T': destval = &tmpl->proceduretype;  break;
281         case 'C': destval = &tmpl->compileflags;   break;
282         case 'E': destval = &tmpl->executeflags;   break;
283         case 'I': destval = &tmpl->sourcefile;     break;
284         case 'F': destval = &tmpl->testflags;      break;
285         default:
286             con_printmsg(LVL_ERROR, __FILE__, __LINE__, "internal error",
287                 "invalid tag `%c:` during code generation\n",
288                 tag
289             );
290             return false;
291     }
292
293     /*
294      * Ensure if for the given tag, there already exists a
295      * assigned value.
296      */
297     if (*destval) {
298         con_printmsg(LVL_ERROR, file, line, "compile error",
299             "tag `%c:` already assigned value: %s\n",
300             tag, *destval
301         );
302         return false;
303     }
304
305     /*
306      * Strip any whitespace that might exist in the value for assignments
307      * like "D:      foo"
308      */
309     if (value && *value && (*value == ' ' || *value == '\t'))
310         value++;
311
312     /*
313      * Value will contain a newline character at the end, we need to strip
314      * this otherwise kaboom, seriously, kaboom :P
315      */
316     if (strchr(value, '\n'))
317         *strrchr(value, '\n')='\0';
318     else /* cppcheck: possible nullpointer dereference */
319         exit(EXIT_FAILURE);
320
321     /*
322      * Now allocate and set the actual value for the specific tag. Which
323      * was properly selected and can be accessed with *destval.
324      */
325     *destval = util_strdup(value);
326
327
328     if (*destval == tmpl->description) {
329         /*
330          * Create some padding for the description to align the
331          * printing of the rules file.
332          */  
333         if ((desclen = strlen(tmpl->description)) > pad[0])
334             pad[0] = desclen;
335     }
336
337     if ((filelen = strlen(file)) > pad[2])
338         pad[2] = filelen;
339
340     return true;
341 }
342
343 bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) {
344     char  *data = NULL;
345     char  *back = NULL;
346     size_t size = 0;
347     size_t line = 1;
348
349     if (!tmpl)
350         return false;
351
352     /* top down parsing */
353     while (fs_file_getline(&back, &size, fp) != EOF) {
354         /* skip whitespace */
355         data = back;
356         if (*data && (*data == ' ' || *data == '\t'))
357             data++;
358
359         switch (*data) {
360             /*
361              * Handle comments inside task tmpl files.  We're strict
362              * about the language for fun :-)
363              */
364             case '/':
365                 if (data[1] != '/') {
366                     con_printmsg(LVL_ERROR, file, line, "tmpl parse error",
367                         "invalid character `/`, perhaps you meant `//` ?");
368
369                     mem_d(back);
370                     return false;
371                 }
372             case '#':
373                 break;
374
375             /*
376              * Empty newlines are acceptable as well, so we handle that here
377              * despite being just odd since there should't be that many
378              * empty lines to begin with.
379              */
380             case '\r':
381             case '\n':
382                 break;
383
384
385             /*
386              * Now begin the actual "tag" stuff.  This works as you expect
387              * it to.
388              */
389             case 'D':
390             case 'T':
391             case 'C':
392             case 'E':
393             case 'I':
394             case 'F':
395                 if (data[1] != ':') {
396                     con_printmsg(LVL_ERROR, file, line, "tmpl parse error",
397                         "expected `:` after `%c`",
398                         *data
399                     );
400                     goto failure;
401                 }
402                 if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) {
403                     con_printmsg(LVL_ERROR, file, line, "tmpl compile error",
404                         "failed to generate for given task\n"
405                     );
406                     goto failure;
407                 }
408                 break;
409
410             /*
411              * Match requires it's own system since we allow multiple M's
412              * for multi-line matching.
413              */
414             case 'M':
415             {
416                 char *value = &data[3];
417                 if (data[1] != ':') {
418                     con_printmsg(LVL_ERROR, file, line, "tmpl parse error",
419                         "expected `:` after `%c`",
420                         *data
421                     );
422                     goto failure;
423                 }
424
425                 if (value && *value && (*value == ' ' || *value == '\t'))
426                     value++;
427
428                 /*
429                  * Value will contain a newline character at the end, we need to strip
430                  * this otherwise kaboom, seriously, kaboom :P
431                  */
432                 if (strrchr(value, '\n'))
433                     *strrchr(value, '\n')='\0';
434                 else /* cppcheck: possible null pointer dereference */
435                     exit(EXIT_FAILURE);
436
437                 vec_push(tmpl->comparematch, util_strdup(value));
438
439                 break;
440             }
441
442             default:
443                 con_printmsg(LVL_ERROR, file, line, "tmpl parse error",
444                     "invalid tag `%c`", *data
445                 );
446                 goto failure;
447             /* no break required */
448         }
449
450         /* update line and free old sata */
451         line++;
452         mem_d(back);
453         back = NULL;
454     }
455     if (back)
456         mem_d(back);
457     return true;
458
459 failure:
460     if (back)
461         mem_d (back);
462     return false;
463 }
464
465 /*
466  * Nullifies the template data: used during initialization of a new
467  * template and free.
468  */
469 void task_template_nullify(task_template_t *tmpl) {
470     if (!tmpl)
471         return;
472
473     tmpl->description    = NULL;
474     tmpl->proceduretype  = NULL;
475     tmpl->compileflags   = NULL;
476     tmpl->executeflags   = NULL;
477     tmpl->comparematch   = NULL;
478     tmpl->sourcefile     = NULL;
479     tmpl->tempfilename   = NULL;
480     tmpl->rulesfile      = NULL;
481     tmpl->testflags      = NULL;
482 }
483
484 task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) {
485     /* a page should be enough */
486     char             fullfile[4096];
487     size_t           filepadd = 0;
488     FILE            *tempfile = NULL;
489     task_template_t *tmpl     = NULL;
490
491     snprintf(fullfile,    sizeof(fullfile), "%s/%s", dir, file);
492
493     tempfile = fs_file_open(fullfile, "r");
494     tmpl     = (task_template_t*)mem_a(sizeof(task_template_t));
495     task_template_nullify(tmpl);
496
497     /*
498      * Create some padding for the printing to align the
499      * printing of the rules file to the console.
500      */  
501     if ((filepadd = strlen(fullfile)) > pad[1])
502         pad[1] = filepadd;
503
504     tmpl->rulesfile = util_strdup(fullfile);
505
506     /*
507      * Esnure the file even exists for the task, this is pretty useless
508      * to even do.
509      */
510     if (!tempfile) {
511         con_err("template file: %s does not exist or invalid permissions\n",
512             file
513         );
514         goto failure;
515     }
516
517     if (!task_template_parse(file, tmpl, tempfile, pad)) {
518         con_err("template parse error: error during parsing\n");
519         goto failure;
520     }
521
522     /*
523      * Regardless procedure type, the following tags must exist:
524      *  D
525      *  T
526      *  C
527      *  I
528      */
529     if (!tmpl->description) {
530         con_err("template compile error: %s missing `D:` tag\n", file);
531         goto failure;
532     }
533     if (!tmpl->proceduretype) {
534         con_err("template compile error: %s missing `T:` tag\n", file);
535         goto failure;
536     }
537     if (!tmpl->compileflags) {
538         con_err("template compile error: %s missing `C:` tag\n", file);
539         goto failure;
540     }
541     if (!tmpl->sourcefile) {
542         con_err("template compile error: %s missing `I:` tag\n", file);
543         goto failure;
544     }
545
546     /*
547      * Now lets compile the template, compilation is really just
548      * the process of validating the input.
549      */
550     if (!strcmp(tmpl->proceduretype, "-compile")) {
551         if (tmpl->executeflags)
552             con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file);
553         if (tmpl->comparematch)
554             con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file);
555         goto success;
556     } else if (!strcmp(tmpl->proceduretype, "-execute")) {
557         if (!tmpl->executeflags) {
558             /* default to $null */
559             tmpl->executeflags = util_strdup("$null");
560         }
561         if (!tmpl->comparematch) {
562             con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
563             goto failure;
564         }
565     } else if (!strcmp(tmpl->proceduretype, "-fail")) {
566         if (tmpl->executeflags)
567             con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file);
568         if (tmpl->comparematch)
569             con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file);
570     } else if (!strcmp(tmpl->proceduretype, "-pp")) {
571         if (!tmpl->executeflags)
572             con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file);
573         if (!tmpl->comparematch) {
574             con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
575             goto failure;
576         }
577     } else {
578         con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype);
579         goto failure;
580     }
581
582 success:
583     fs_file_close(tempfile);
584     return tmpl;
585
586 failure:
587     /*
588      * The file might not exist and we jump here when that doesn't happen
589      * so the check to see if it's not null here is required.
590      */
591     if (tempfile)
592         fs_file_close(tempfile);
593     mem_d (tmpl);
594
595     return NULL;
596 }
597
598 void task_template_destroy(task_template_t **tmpl) {
599     if (!tmpl)
600         return;
601
602     if ((*tmpl)->description)    mem_d((*tmpl)->description);
603     if ((*tmpl)->proceduretype)  mem_d((*tmpl)->proceduretype);
604     if ((*tmpl)->compileflags)   mem_d((*tmpl)->compileflags);
605     if ((*tmpl)->executeflags)   mem_d((*tmpl)->executeflags);
606     if ((*tmpl)->sourcefile)     mem_d((*tmpl)->sourcefile);
607     if ((*tmpl)->rulesfile)      mem_d((*tmpl)->rulesfile);
608     if ((*tmpl)->testflags)      mem_d((*tmpl)->testflags);
609
610     /*
611      * Delete all allocated string for task tmpl then destroy the
612      * main vector.
613      */
614     {
615         size_t i = 0;
616         for (; i < vec_size((*tmpl)->comparematch); i++)
617             mem_d((*tmpl)->comparematch[i]);
618
619         vec_free((*tmpl)->comparematch);
620     }
621
622     /*
623      * Nullify all the template members otherwise NULL comparision
624      * checks will fail if tmpl pointer is reused.
625      */
626     mem_d(*tmpl);
627 }
628
629 /*
630  * Now comes the task manager, this system allows adding tasks in and out
631  * of a task list.  This is the executor of the tasks essentially as well.
632  */
633 typedef struct {
634     task_template_t *tmpl;
635     FILE           **runhandles;
636     FILE            *stderrlog;
637     FILE            *stdoutlog;
638     char            *stdoutlogfile;
639     char            *stderrlogfile;
640     bool             compiled;
641 } task_t;
642
643 task_t *task_tasks = NULL;
644
645 /*
646  * Read a directory and searches for all template files in it
647  * which is later used to run all tests.
648  */
649 bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
650     bool             success = true;
651     DIR             *dir;
652     struct dirent   *files;
653     struct stat      directory;
654     char             buffer[4096];
655     size_t           found = 0;
656
657     dir = fs_dir_open(curdir);
658
659     while ((files = fs_dir_read(dir))) {
660         snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
661
662         if (stat(buffer, &directory) == -1) {
663             con_err("internal error: stat failed, aborting\n");
664             abort();
665         }
666
667         /* skip directories */
668         if (S_ISDIR(directory.st_mode))
669             continue;
670
671         /*
672          * We made it here, which concludes the file/directory is not
673          * actually a directory, so it must be a file :)
674          */
675         if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) {
676             task_template_t *tmpl = task_template_compile(files->d_name, curdir, pad);
677             char             buf[4096]; /* one page should be enough */
678             char            *qcflags = NULL;
679             task_t           task;
680
681             util_debug("TEST", "compiling task template: %s/%s\n", curdir, files->d_name);
682             found ++;
683             if (!tmpl) {
684                 con_err("error compiling task template: %s\n", files->d_name);
685                 success = false;
686                 continue;
687             }
688             /*
689              * Generate a temportary file name for the output binary
690              * so we don't trample over an existing one.
691              */
692             tmpl->tempfilename = NULL;
693             util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s", curdir, files->d_name);
694
695             /*
696              * Additional QCFLAGS enviroment variable may be used
697              * to test compile flags for all tests.  This needs to be
698              * BEFORE other flags (so that the .tmpl can override them)
699              */
700             qcflags = getenv("QCFLAGS");
701
702             /*
703              * Generate the command required to open a pipe to a process
704              * which will be refered to with a handle in the task for
705              * reading the data from the pipe.
706              */
707             if (strcmp(tmpl->proceduretype, "-pp")) {
708                 if (qcflags) {
709                     if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
710                         snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
711                             task_bins[TASK_COMPILE],
712                             curdir,
713                             tmpl->sourcefile,
714                             qcflags,
715                             tmpl->compileflags,
716                             tmpl->tempfilename
717                         );
718                     } else {
719                         snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s",
720                             task_bins[TASK_COMPILE],
721                             curdir,
722                             defs,
723                             curdir,
724                             tmpl->sourcefile,
725                             qcflags,
726                             tmpl->compileflags,
727                             tmpl->tempfilename
728                         );
729                     }
730                 } else {
731                     if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
732                         snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
733                             task_bins[TASK_COMPILE],
734                             curdir,
735                             tmpl->sourcefile,
736                             tmpl->compileflags,
737                             tmpl->tempfilename
738                         );
739                     } else {
740                         snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s",
741                             task_bins[TASK_COMPILE],
742                             curdir,
743                             defs,
744                             curdir,
745                             tmpl->sourcefile,
746                             tmpl->compileflags,
747                             tmpl->tempfilename
748                         );
749                     }
750                 }
751             } else {
752                 /* Preprocessing (qcflags mean shit all here we don't allow them) */
753                 if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
754                     snprintf(buf, sizeof(buf), "%s -E %s/%s -o %s",
755                         task_bins[TASK_COMPILE],
756                         curdir,
757                         tmpl->sourcefile,
758                         tmpl->tempfilename
759                     );
760                 } else {
761                     snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s -o %s",
762                         task_bins[TASK_COMPILE],
763                         curdir,
764                         defs,
765                         curdir,
766                         tmpl->sourcefile,
767                         tmpl->tempfilename
768                     );
769                 }
770             }
771
772             /*
773              * The task template was compiled, now lets create a task from
774              * the template data which has now been propagated.
775              */
776             task.tmpl = tmpl;
777             if (!(task.runhandles = task_popen(buf, "r"))) {
778                 con_err("error opening pipe to process for test: %s\n", tmpl->description);
779                 success = false;
780                 continue;
781             }
782
783             util_debug("TEST", "executing test: `%s` [%s]\n", tmpl->description, buf);
784
785             /*
786              * Open up some file desciptors for logging the stdout/stderr
787              * to our own.
788              */
789             snprintf(buf,  sizeof(buf), "%s.stdout", tmpl->tempfilename);
790             task.stdoutlogfile = util_strdup(buf);
791             if (!(task.stdoutlog     = fs_file_open(buf, "w"))) {
792                 con_err("error opening %s for stdout\n", buf);
793                 continue;
794             }
795
796             snprintf(buf,  sizeof(buf), "%s.stderr", tmpl->tempfilename);
797             task.stderrlogfile = util_strdup(buf);
798             if (!(task.stderrlog = fs_file_open(buf, "w"))) {
799                 con_err("error opening %s for stderr\n", buf);
800                 continue;
801             }
802
803             vec_push(task_tasks, task);
804         }
805     }
806
807     util_debug("TEST", "compiled %d task template files out of %d\n",
808         vec_size(task_tasks),
809         found
810     );
811
812     fs_dir_close(dir);
813     return success;
814 }
815
816 /*
817  * Task precleanup removes any existing temporary files or log files
818  * left behind from a previous invoke of the test-suite.
819  */
820 void task_precleanup(const char *curdir) {
821     DIR             *dir;
822     struct dirent   *files;
823     char             buffer[4096];
824
825     dir = fs_dir_open(curdir);
826
827     while ((files = fs_dir_read(dir))) {
828         if (strstr(files->d_name, "TMP")     ||
829             strstr(files->d_name, ".stdout") ||
830             strstr(files->d_name, ".stderr"))
831         {
832             snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
833             if (remove(buffer))
834                 con_err("error removing temporary file: %s\n", buffer);
835             else
836                 util_debug("TEST", "removed temporary file: %s\n", buffer);
837         }
838     }
839
840     fs_dir_close(dir);
841 }
842
843 void task_destroy(void) {
844     /*
845      * Free all the data in the task list and finally the list itself
846      * then proceed to cleanup anything else outside the program like
847      * temporary files.
848      */
849     size_t i;
850     for (i = 0; i < vec_size(task_tasks); i++) {
851         /*
852          * Close any open handles to files or processes here.  It's mighty
853          * annoying to have to do all this cleanup work.
854          */
855         if (task_tasks[i].runhandles) task_pclose(task_tasks[i].runhandles);
856         if (task_tasks[i].stdoutlog)  fs_file_close (task_tasks[i].stdoutlog);
857         if (task_tasks[i].stderrlog)  fs_file_close (task_tasks[i].stderrlog);
858
859         /*
860          * Only remove the log files if the test actually compiled otherwise
861          * forget about it (or if it didn't compile, and the procedure type
862          * was set to -fail (meaning it shouldn't compile) .. stil remove) 
863          */
864         if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
865             if (remove(task_tasks[i].stdoutlogfile))
866                 con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile);
867             else
868                 util_debug("TEST", "removed stdout log file: %s\n", task_tasks[i].stdoutlogfile);
869             if (remove(task_tasks[i].stderrlogfile))
870                 con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile);
871             else
872                 util_debug("TEST", "removed stderr log file: %s\n", task_tasks[i].stderrlogfile);
873
874             remove(task_tasks[i].tmpl->tempfilename);
875         }
876
877         /* free util_strdup data for log files */
878         mem_d(task_tasks[i].stdoutlogfile);
879         mem_d(task_tasks[i].stderrlogfile);
880
881         task_template_destroy(&task_tasks[i].tmpl);
882     }
883     vec_free(task_tasks);
884 }
885
886 /*
887  * This executes the QCVM task for a specificly compiled progs.dat
888  * using the template passed into it for call-flags and user defined
889  * messages.
890  */
891 bool task_execute(task_template_t *tmpl, char ***line) {
892     bool     success = true;
893     FILE    *execute;
894     char     buffer[4096];
895     memset  (buffer,0,sizeof(buffer));
896
897     if (strcmp(tmpl->proceduretype, "-pp")) {
898         /*
899          * Drop the execution flags for the QCVM if none where
900          * actually specified.
901          */
902         if (!strcmp(tmpl->executeflags, "$null")) {
903             snprintf(buffer,  sizeof(buffer), "%s %s",
904                 task_bins[TASK_EXECUTE],
905                 tmpl->tempfilename
906             );
907         } else {
908             snprintf(buffer,  sizeof(buffer), "%s %s %s",
909                 task_bins[TASK_EXECUTE],
910                 tmpl->executeflags,
911                 tmpl->tempfilename
912             );
913         }
914
915         util_debug("TEST", "executing qcvm: `%s` [%s]\n",
916             tmpl->description,
917             buffer
918         );
919
920         execute = popen(buffer, "r");
921         if (!execute)
922             return false;
923     } else {
924         /*
925          * we're preprocessing, which means we need to read int
926          * the produced file and do some really weird shit.
927          */
928         if (!(execute = fs_file_open(tmpl->tempfilename, "r")))
929             return false;
930     }
931
932     /*
933      * Now lets read the lines and compare them to the matches we expect
934      * and handle accordingly.
935      */
936     {
937         char  *data    = NULL;
938         size_t size    = 0;
939         size_t compare = 0;
940         while (fs_file_getline(&data, &size, execute) != EOF) {
941             if (!strcmp(data, "No main function found\n")) {
942                 con_err("test failure: `%s` (No main function found) [%s]\n",
943                     tmpl->description,
944                     tmpl->rulesfile
945                 );
946                 pclose(execute);
947                 return false;
948             }
949
950             /*
951              * Trim newlines from data since they will just break our
952              * ability to properly validate matches.
953              */
954             if  (strrchr(data, '\n'))
955                 *strrchr(data, '\n') = '\0';
956
957             if (vec_size(tmpl->comparematch) > compare) {
958                 if (strcmp(data, tmpl->comparematch[compare++]))
959                     success = false;
960             } else {
961                     success = false;
962             }
963
964             /*
965              * Copy to output vector for diagnostics if execution match
966              * fails.
967              */  
968             vec_push(*line, data);
969
970             /* reset */
971             data = NULL;
972             size = 0;
973         }
974         mem_d(data);
975         data = NULL;
976     }
977
978     if (strcmp(tmpl->proceduretype, "-pp"))
979         pclose(execute);
980     else
981         fs_file_close(execute);
982
983     return success;
984 }
985
986 const char *task_type(task_template_t *tmpl) {
987     if (!strcmp(tmpl->proceduretype, "-pp"))
988         return "type: preprocessor test";
989     if (!strcmp(tmpl->proceduretype, "-execute"))
990         return "type: execution test";
991     if (!strcmp(tmpl->proceduretype, "-compile"))
992         return "type: compile test";
993     return "type: fail test";
994 }
995
996 /*
997  * This schedualizes all tasks and actually runs them individually
998  * this is generally easy for just -compile variants.  For compile and
999  * execution this takes more work since a task needs to be generated
1000  * from thin air and executed INLINE.
1001  */
1002 #include <math.h>
1003 void task_schedualize(size_t *pad) {
1004     char   space[2][64];
1005     bool   execute  = false;
1006     char  *data     = NULL;
1007     char **match    = NULL;
1008     size_t size     = 0;
1009     size_t i        = 0;
1010     size_t j        = 0;
1011
1012     snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
1013
1014     for (; i < vec_size(task_tasks); i++) {
1015         memset(space[1], 0, sizeof(space[1]));
1016         snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1));
1017         
1018         con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), "");
1019
1020         util_debug("TEST", "executing task: %d: %s\n", i, task_tasks[i].tmpl->description);
1021         /*
1022          * Generate a task from thin air if it requires execution in
1023          * the QCVM.
1024          */
1025         execute = !!(!strcmp(task_tasks[i].tmpl->proceduretype, "-execute"));
1026
1027         /*
1028          * We assume it compiled before we actually compiled :).  On error
1029          * we change the value
1030          */
1031         task_tasks[i].compiled = true;
1032
1033         /*
1034          * Read data from stdout first and pipe that stuff into a log file
1035          * then we do the same for stderr.
1036          */
1037         while (fs_file_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) {
1038             fs_file_puts(task_tasks[i].stdoutlog, data);
1039
1040             if (strstr(data, "failed to open file")) {
1041                 task_tasks[i].compiled = false;
1042                 execute                = false;
1043             }
1044         }
1045         while (fs_file_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) {
1046             /*
1047              * If a string contains an error we just dissalow execution
1048              * of it in the vm.
1049              *
1050              * TODO: make this more percise, e.g if we print a warning
1051              * that refers to a variable named error, or something like
1052              * that .. then this will blowup :P
1053              */
1054             if (strstr(data, "error")) {
1055                 execute                = false;
1056                 task_tasks[i].compiled = false;
1057             }
1058
1059             fs_file_puts (task_tasks[i].stderrlog, data);
1060         }
1061
1062         if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
1063             con_err("failure: `%s` (failed to compile) see %s.stdout and %s.stderr [%s]\n",
1064                 task_tasks[i].tmpl->description,
1065                 task_tasks[i].tmpl->tempfilename,
1066                 task_tasks[i].tmpl->tempfilename,
1067                 task_tasks[i].tmpl->rulesfile
1068             );
1069             continue;
1070         }
1071
1072         if (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp")) {
1073             /* this is a pain */
1074         }
1075
1076         if (!execute) {
1077             con_out("succeeded: `%s` %*s %*s\n",
1078                 task_tasks[i].tmpl->description,
1079                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1080                 task_tasks[i].tmpl->rulesfile,
1081                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]),
1082                 task_type(task_tasks[i].tmpl)
1083                 
1084             );
1085             continue;
1086         }
1087
1088         /*
1089          * If we made it here that concludes the task is to be executed
1090          * in the virtual machine.
1091          */
1092         if (!task_execute(task_tasks[i].tmpl, &match)) {
1093             size_t d = 0;
1094
1095             con_err("failure: `%s` (invalid results from execution) [%s]\n",
1096                 task_tasks[i].tmpl->description,
1097                 task_tasks[i].tmpl->rulesfile
1098             );
1099
1100             /*
1101              * Print nicely formatted expected match lists to console error
1102              * handler for the all the given matches in the template file and
1103              * what was actually returned from executing.
1104              */
1105             con_err("    Expected From %u Matches: (got %u Matches)\n",
1106                 vec_size(task_tasks[i].tmpl->comparematch),
1107                 vec_size(match)
1108             );
1109             for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) {
1110                 char  *select = task_tasks[i].tmpl->comparematch[d];
1111                 size_t length = 40 - strlen(select);
1112
1113                 con_err("        Expected: \"%s\"", select);
1114                 while (length --)
1115                     con_err(" ");
1116                 con_err("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<<nothing else to compare>>" : match[d]);
1117             }
1118
1119             /*
1120              * Print the non-expected out (since we are simply not expecting it)
1121              * This will help track down bugs in template files that fail to match
1122              * something.
1123              */  
1124             if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) {
1125                 for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) {
1126                     con_err("        Expected: Nothing                                   | Got: \"%s\"\n",
1127                         match[d + vec_size(task_tasks[i].tmpl->comparematch)]
1128                     );
1129                 }
1130             }
1131                     
1132
1133             for (j = 0; j < vec_size(match); j++)
1134                 mem_d(match[j]);
1135             vec_free(match);
1136             continue;
1137         }
1138         for (j = 0; j < vec_size(match); j++)
1139             mem_d(match[j]);
1140         vec_free(match);
1141
1142         con_out("succeeded: `%s` %*s %*s\n",
1143             task_tasks[i].tmpl->description,
1144             (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1145             task_tasks[i].tmpl->rulesfile,
1146             (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]),
1147             task_type(task_tasks[i].tmpl)
1148             
1149         );
1150     }
1151     mem_d(data);
1152 }
1153
1154 /*
1155  * This is the heart of the whole test-suite process.  This cleans up
1156  * any existing temporary files left behind as well as log files left
1157  * behind.  Then it propagates a list of tests from `curdir` by scaning
1158  * it for template files and compiling them into tasks, in which it
1159  * schedualizes them (executes them) and actually reports errors and
1160  * what not.  It then proceeds to destroy the tasks and return memory
1161  * it's the engine :)
1162  *
1163  * It returns true of tests could be propagated, otherwise it returns
1164  * false.
1165  *
1166  * It expects con_init() was called before hand.
1167  */
1168 GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
1169     static const char *default_defs = "defs.qh";
1170
1171     size_t pad[] = {
1172         /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
1173                     0,                                 0,                        0
1174     };
1175
1176     /*
1177      * If the default definition file isn't set to anything.  We will
1178      * use the default_defs here, which is "defs.qc"
1179      */   
1180     if (!defs) {
1181         defs = default_defs;
1182     }
1183         
1184
1185     task_precleanup(curdir);
1186     if (!task_propagate(curdir, pad, defs)) {
1187         con_err("error: failed to propagate tasks\n");
1188         task_destroy();
1189         return false;
1190     }
1191     /*
1192      * If we made it here all tasks where propagated from their resultant
1193      * template file.  So we can start the FILO scheduler, this has been
1194      * designed in the most thread-safe way possible for future threading
1195      * it's designed to prevent lock contention, and possible syncronization
1196      * issues.
1197      */
1198     task_schedualize(pad);
1199     task_destroy();
1200
1201     return true;
1202 }
1203
1204 /*
1205  * Fancy GCC-like LONG parsing allows things like --opt=param with
1206  * assignment operator.  This is used for redirecting stdout/stderr
1207  * console to specific files of your choice.
1208  */
1209 static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
1210     int  argc   = *argc_;
1211     char **argv = *argv_;
1212
1213     size_t len = strlen(optname);
1214
1215     if (strncmp(argv[0]+ds, optname, len))
1216         return false;
1217
1218     /* it's --optname, check how the parameter is supplied */
1219     if (argv[0][ds+len] == '=') {
1220         *out = argv[0]+ds+len+1;
1221         return true;
1222     }
1223
1224     if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
1225         return false;
1226
1227     /* using --opt param */
1228     *out = argv[1];
1229     --*argc_;
1230     ++*argv_;
1231     return true;
1232 }
1233
1234 int main(int argc, char **argv) {
1235     bool          succeed  = false;
1236     char         *redirout = (char*)stdout;
1237     char         *redirerr = (char*)stderr;
1238     char         *defs     = NULL;
1239
1240     con_init();
1241
1242     /*
1243      * Command line option parsing commences now We only need to support
1244      * a few things in the test suite.
1245      */
1246     while (argc > 1) {
1247         ++argv;
1248         --argc;
1249
1250         if (argv[0][0] == '-') {
1251             if (parsecmd("redirout", &argc, &argv, &redirout, 1, false))
1252                 continue;
1253             if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false))
1254                 continue;
1255             if (parsecmd("defs",     &argc, &argv, &defs,     1, false))
1256                 continue;
1257
1258             con_change(redirout, redirerr);
1259
1260             if (!strcmp(argv[0]+1, "debug")) {
1261                 OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
1262                 continue;
1263             }
1264             if (!strcmp(argv[0]+1, "memchk")) {
1265                 OPTS_OPTION_BOOL(OPTION_MEMCHK) = true;
1266                 continue;
1267             }
1268             if (!strcmp(argv[0]+1, "nocolor")) {
1269                 con_color(0);
1270                 continue;
1271             }
1272
1273             con_err("invalid argument %s\n", argv[0]+1);
1274             return -1;
1275         }
1276     }
1277     con_change(redirout, redirerr);
1278     succeed = test_perform("tests", defs);
1279     util_meminfo();
1280
1281
1282     return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
1283 }