]> git.xonotic.org Git - xonotic/gmqcc.git/blob - test.c
Merge branch 'master' into cooking
[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 IF the procedure type is -execute, otherwise it matches
890  * the preprocessor output.
891  */
892 bool task_trymatch(task_template_t *tmpl, char ***line) {
893     bool     success = true;
894     FILE    *execute;
895     char     buffer[4096];
896     memset  (buffer,0,sizeof(buffer));
897
898     if (strcmp(tmpl->proceduretype, "-pp")) {
899         /*
900          * Drop the execution flags for the QCVM if none where
901          * actually specified.
902          */
903         if (!strcmp(tmpl->executeflags, "$null")) {
904             snprintf(buffer,  sizeof(buffer), "%s %s",
905                 task_bins[TASK_EXECUTE],
906                 tmpl->tempfilename
907             );
908         } else {
909             snprintf(buffer,  sizeof(buffer), "%s %s %s",
910                 task_bins[TASK_EXECUTE],
911                 tmpl->executeflags,
912                 tmpl->tempfilename
913             );
914         }
915
916         util_debug("TEST", "executing qcvm: `%s` [%s]\n",
917             tmpl->description,
918             buffer
919         );
920
921         execute = popen(buffer, "r");
922         if (!execute)
923             return false;
924     } else {
925         /*
926          * we're preprocessing, which means we need to read int
927          * the produced file and do some really weird shit.
928          */
929         if (!(execute = fs_file_open(tmpl->tempfilename, "r")))
930             return false;
931     }
932
933     /*
934      * Now lets read the lines and compare them to the matches we expect
935      * and handle accordingly.
936      */
937     {
938         char  *data    = NULL;
939         size_t size    = 0;
940         size_t compare = 0;
941         while (fs_file_getline(&data, &size, execute) != EOF) {
942             if (!strcmp(data, "No main function found\n")) {
943                 con_err("test failure: `%s` (No main function found) [%s]\n",
944                     tmpl->description,
945                     tmpl->rulesfile
946                 );
947                 pclose(execute);
948                 return false;
949             }
950
951             /*
952              * Trim newlines from data since they will just break our
953              * ability to properly validate matches.
954              */
955             if  (strrchr(data, '\n'))
956                 *strrchr(data, '\n') = '\0';
957
958             /*
959              * If data is just null now, that means the line was an empty
960              * one and for that, we just ignore it.
961              */
962             if (!*data)
963                 continue;
964
965             if (vec_size(tmpl->comparematch) > compare) {
966                 if (strcmp(data, tmpl->comparematch[compare++]))
967                     success = false;
968             } else {
969                     success = false;
970             }
971
972             /*
973              * Copy to output vector for diagnostics if execution match
974              * fails.
975              */
976             vec_push(*line, data);
977
978             /* reset */
979             data = NULL;
980             size = 0;
981         }
982         mem_d(data);
983         data = NULL;
984     }
985
986     if (strcmp(tmpl->proceduretype, "-pp"))
987         pclose(execute);
988     else
989         fs_file_close(execute);
990
991     return success;
992 }
993
994 const char *task_type(task_template_t *tmpl) {
995     if (!strcmp(tmpl->proceduretype, "-pp"))
996         return "type: preprocessor";
997     if (!strcmp(tmpl->proceduretype, "-execute"))
998         return "type: execution";
999     if (!strcmp(tmpl->proceduretype, "-compile"))
1000         return "type: compile";
1001     return "type: fail";
1002 }
1003
1004 /*
1005  * This schedualizes all tasks and actually runs them individually
1006  * this is generally easy for just -compile variants.  For compile and
1007  * execution this takes more work since a task needs to be generated
1008  * from thin air and executed INLINE.
1009  */
1010 #include <math.h>
1011 void task_schedualize(size_t *pad) {
1012     char   space[2][64];
1013     bool   execute  = false;
1014     char  *data     = NULL;
1015     char **match    = NULL;
1016     size_t size     = 0;
1017     size_t i        = 0;
1018     size_t j        = 0;
1019
1020     snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
1021
1022     for (; i < vec_size(task_tasks); i++) {
1023         memset(space[1], 0, sizeof(space[1]));
1024         snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1));
1025
1026         con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), "");
1027
1028         util_debug("TEST", "executing task: %d: %s\n", i, task_tasks[i].tmpl->description);
1029         /*
1030          * Generate a task from thin air if it requires execution in
1031          * the QCVM.
1032          */
1033         execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) ||
1034                      (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp"));
1035
1036         /*
1037          * We assume it compiled before we actually compiled :).  On error
1038          * we change the value
1039          */
1040         task_tasks[i].compiled = true;
1041
1042         /*
1043          * Read data from stdout first and pipe that stuff into a log file
1044          * then we do the same for stderr.
1045          */
1046         while (fs_file_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) {
1047             fs_file_puts(task_tasks[i].stdoutlog, data);
1048
1049             if (strstr(data, "failed to open file")) {
1050                 task_tasks[i].compiled = false;
1051                 execute                = false;
1052             }
1053         }
1054         while (fs_file_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) {
1055             /*
1056              * If a string contains an error we just dissalow execution
1057              * of it in the vm.
1058              *
1059              * TODO: make this more percise, e.g if we print a warning
1060              * that refers to a variable named error, or something like
1061              * that .. then this will blowup :P
1062              */
1063             if (strstr(data, "error")) {
1064                 execute                = false;
1065                 task_tasks[i].compiled = false;
1066             }
1067
1068             fs_file_puts (task_tasks[i].stderrlog, data);
1069         }
1070
1071         if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
1072             con_out("failure:   `%s` %*s %*s\n",
1073                 task_tasks[i].tmpl->description,
1074                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1075                 task_tasks[i].tmpl->rulesfile,
1076                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]),
1077                 "(failed to compile)"
1078             );
1079             continue;
1080         }
1081
1082         if (!execute) {
1083             con_out("succeeded: `%s` %*s %*s\n",
1084                 task_tasks[i].tmpl->description,
1085                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1086                 task_tasks[i].tmpl->rulesfile,
1087                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]),
1088                 task_type(task_tasks[i].tmpl)
1089
1090             );
1091             continue;
1092         }
1093
1094         /*
1095          * If we made it here that concludes the task is to be executed
1096          * in the virtual machine (or the preprocessor output needs to
1097          * be matched).
1098          */
1099         if (!task_trymatch(task_tasks[i].tmpl, &match)) {
1100             size_t d = 0;
1101
1102             con_out("failure:   `%s` %*s %*s\n",
1103                 task_tasks[i].tmpl->description,
1104                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1105                 task_tasks[i].tmpl->rulesfile,
1106                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(
1107                     (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
1108                         ? "(invalid results from execution)"
1109                         : "(invalid results from preprocessing)"
1110                 ) - pad[2]),
1111                 (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
1112                     ? "(invalid results from execution)"
1113                     : "(invalid results from preprocessing)"
1114             );
1115
1116             /*
1117              * Print nicely formatted expected match lists to console error
1118              * handler for the all the given matches in the template file and
1119              * what was actually returned from executing.
1120              */
1121             con_out("    Expected From %u Matches: (got %u Matches)\n",
1122                 vec_size(task_tasks[i].tmpl->comparematch),
1123                 vec_size(match)
1124             );
1125             for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) {
1126                 char  *select = task_tasks[i].tmpl->comparematch[d];
1127                 size_t length = 40 - strlen(select);
1128
1129                 con_out("        Expected: \"%s\"", select);
1130                 while (length --)
1131                     con_out(" ");
1132                 con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<<nothing else to compare>>" : match[d]);
1133             }
1134
1135             /*
1136              * Print the non-expected out (since we are simply not expecting it)
1137              * This will help track down bugs in template files that fail to match
1138              * something.
1139              */
1140             if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) {
1141                 for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) {
1142                     con_out("        Expected: Nothing                                   | Got: \"%s\"\n",
1143                         match[d + vec_size(task_tasks[i].tmpl->comparematch)]
1144                     );
1145                 }
1146             }
1147
1148
1149             for (j = 0; j < vec_size(match); j++)
1150                 mem_d(match[j]);
1151             vec_free(match);
1152             continue;
1153         }
1154         for (j = 0; j < vec_size(match); j++)
1155             mem_d(match[j]);
1156         vec_free(match);
1157
1158         con_out("succeeded: `%s` %*s %*s\n",
1159             task_tasks[i].tmpl->description,
1160             (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1161             task_tasks[i].tmpl->rulesfile,
1162             (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]),
1163             task_type(task_tasks[i].tmpl)
1164
1165         );
1166     }
1167     mem_d(data);
1168 }
1169
1170 /*
1171  * This is the heart of the whole test-suite process.  This cleans up
1172  * any existing temporary files left behind as well as log files left
1173  * behind.  Then it propagates a list of tests from `curdir` by scaning
1174  * it for template files and compiling them into tasks, in which it
1175  * schedualizes them (executes them) and actually reports errors and
1176  * what not.  It then proceeds to destroy the tasks and return memory
1177  * it's the engine :)
1178  *
1179  * It returns true of tests could be propagated, otherwise it returns
1180  * false.
1181  *
1182  * It expects con_init() was called before hand.
1183  */
1184 GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
1185     static const char *default_defs = "defs.qh";
1186
1187     size_t pad[] = {
1188         /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
1189                     0,                                 0,                        0
1190     };
1191
1192     /*
1193      * If the default definition file isn't set to anything.  We will
1194      * use the default_defs here, which is "defs.qc"
1195      */
1196     if (!defs) {
1197         defs = default_defs;
1198     }
1199
1200
1201     task_precleanup(curdir);
1202     if (!task_propagate(curdir, pad, defs)) {
1203         con_err("error: failed to propagate tasks\n");
1204         task_destroy();
1205         return false;
1206     }
1207     /*
1208      * If we made it here all tasks where propagated from their resultant
1209      * template file.  So we can start the FILO scheduler, this has been
1210      * designed in the most thread-safe way possible for future threading
1211      * it's designed to prevent lock contention, and possible syncronization
1212      * issues.
1213      */
1214     task_schedualize(pad);
1215     task_destroy();
1216
1217     return true;
1218 }
1219
1220 /*
1221  * Fancy GCC-like LONG parsing allows things like --opt=param with
1222  * assignment operator.  This is used for redirecting stdout/stderr
1223  * console to specific files of your choice.
1224  */
1225 static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
1226     int  argc   = *argc_;
1227     char **argv = *argv_;
1228
1229     size_t len = strlen(optname);
1230
1231     if (strncmp(argv[0]+ds, optname, len))
1232         return false;
1233
1234     /* it's --optname, check how the parameter is supplied */
1235     if (argv[0][ds+len] == '=') {
1236         *out = argv[0]+ds+len+1;
1237         return true;
1238     }
1239
1240     if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
1241         return false;
1242
1243     /* using --opt param */
1244     *out = argv[1];
1245     --*argc_;
1246     ++*argv_;
1247     return true;
1248 }
1249
1250 int main(int argc, char **argv) {
1251     bool          succeed  = false;
1252     char         *redirout = (char*)stdout;
1253     char         *redirerr = (char*)stderr;
1254     char         *defs     = NULL;
1255
1256     con_init();
1257
1258     /*
1259      * Command line option parsing commences now We only need to support
1260      * a few things in the test suite.
1261      */
1262     while (argc > 1) {
1263         ++argv;
1264         --argc;
1265
1266         if (argv[0][0] == '-') {
1267             if (parsecmd("redirout", &argc, &argv, &redirout, 1, false))
1268                 continue;
1269             if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false))
1270                 continue;
1271             if (parsecmd("defs",     &argc, &argv, &defs,     1, false))
1272                 continue;
1273
1274             con_change(redirout, redirerr);
1275
1276             if (!strcmp(argv[0]+1, "debug")) {
1277                 OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
1278                 continue;
1279             }
1280             if (!strcmp(argv[0]+1, "memchk")) {
1281                 OPTS_OPTION_BOOL(OPTION_MEMCHK) = true;
1282                 continue;
1283             }
1284             if (!strcmp(argv[0]+1, "nocolor")) {
1285                 con_color(0);
1286                 continue;
1287             }
1288
1289             con_err("invalid argument %s\n", argv[0]+1);
1290             return -1;
1291         }
1292     }
1293     con_change(redirout, redirerr);
1294     succeed = test_perform("tests", defs);
1295     util_meminfo();
1296
1297
1298     return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
1299 }