]> git.xonotic.org Git - xonotic/gmqcc.git/blob - test.c
test for the simple __FUNC__ case
[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     util_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         util_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             #ifdef _MSC_VER
701             {
702                 char   buffer[4096];
703                 size_t size;
704                 getenv_s(&size, buffer, sizeof(buffer), "QCFLAGS");
705                 qcflags = buffer;
706             }
707             #else
708             qcflags = getenv("QCFLAGS");
709             #endif
710
711             /*
712              * Generate the command required to open a pipe to a process
713              * which will be refered to with a handle in the task for
714              * reading the data from the pipe.
715              */
716             if (strcmp(tmpl->proceduretype, "-pp")) {
717                 if (qcflags) {
718                     if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
719                         util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
720                             task_bins[TASK_COMPILE],
721                             curdir,
722                             tmpl->sourcefile,
723                             qcflags,
724                             tmpl->compileflags,
725                             tmpl->tempfilename
726                         );
727                     } else {
728                         util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s",
729                             task_bins[TASK_COMPILE],
730                             curdir,
731                             defs,
732                             curdir,
733                             tmpl->sourcefile,
734                             qcflags,
735                             tmpl->compileflags,
736                             tmpl->tempfilename
737                         );
738                     }
739                 } else {
740                     if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
741                         util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
742                             task_bins[TASK_COMPILE],
743                             curdir,
744                             tmpl->sourcefile,
745                             tmpl->compileflags,
746                             tmpl->tempfilename
747                         );
748                     } else {
749                         util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s",
750                             task_bins[TASK_COMPILE],
751                             curdir,
752                             defs,
753                             curdir,
754                             tmpl->sourcefile,
755                             tmpl->compileflags,
756                             tmpl->tempfilename
757                         );
758                     }
759                 }
760             } else {
761                 /* Preprocessing (qcflags mean shit all here we don't allow them) */
762                 if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
763                     util_snprintf(buf, sizeof(buf), "%s -E %s/%s -o %s",
764                         task_bins[TASK_COMPILE],
765                         curdir,
766                         tmpl->sourcefile,
767                         tmpl->tempfilename
768                     );
769                 } else {
770                     util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s -o %s",
771                         task_bins[TASK_COMPILE],
772                         curdir,
773                         defs,
774                         curdir,
775                         tmpl->sourcefile,
776                         tmpl->tempfilename
777                     );
778                 }
779             }
780
781             /*
782              * The task template was compiled, now lets create a task from
783              * the template data which has now been propagated.
784              */
785             task.tmpl = tmpl;
786             if (!(task.runhandles = task_popen(buf, "r"))) {
787                 con_err("error opening pipe to process for test: %s\n", tmpl->description);
788                 success = false;
789                 continue;
790             }
791
792             util_debug("TEST", "executing test: `%s` [%s]\n", tmpl->description, buf);
793
794             /*
795              * Open up some file desciptors for logging the stdout/stderr
796              * to our own.
797              */
798             util_snprintf(buf,  sizeof(buf), "%s.stdout", tmpl->tempfilename);
799             task.stdoutlogfile = util_strdup(buf);
800             if (!(task.stdoutlog     = fs_file_open(buf, "w"))) {
801                 con_err("error opening %s for stdout\n", buf);
802                 continue;
803             }
804
805             util_snprintf(buf,  sizeof(buf), "%s.stderr", tmpl->tempfilename);
806             task.stderrlogfile = util_strdup(buf);
807             if (!(task.stderrlog = fs_file_open(buf, "w"))) {
808                 con_err("error opening %s for stderr\n", buf);
809                 continue;
810             }
811
812             vec_push(task_tasks, task);
813         }
814     }
815
816     util_debug("TEST", "compiled %d task template files out of %d\n",
817         vec_size(task_tasks),
818         found
819     );
820
821     fs_dir_close(dir);
822     return success;
823 }
824
825 /*
826  * Task precleanup removes any existing temporary files or log files
827  * left behind from a previous invoke of the test-suite.
828  */
829 void task_precleanup(const char *curdir) {
830     DIR             *dir;
831     struct dirent   *files;
832     char             buffer[4096];
833
834     dir = fs_dir_open(curdir);
835
836     while ((files = fs_dir_read(dir))) {
837         if (strstr(files->d_name, "TMP")     ||
838             strstr(files->d_name, ".stdout") ||
839             strstr(files->d_name, ".stderr"))
840         {
841             util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
842             if (remove(buffer))
843                 con_err("error removing temporary file: %s\n", buffer);
844             else
845                 util_debug("TEST", "removed temporary file: %s\n", buffer);
846         }
847     }
848
849     fs_dir_close(dir);
850 }
851
852 void task_destroy(void) {
853     /*
854      * Free all the data in the task list and finally the list itself
855      * then proceed to cleanup anything else outside the program like
856      * temporary files.
857      */
858     size_t i;
859     for (i = 0; i < vec_size(task_tasks); i++) {
860         /*
861          * Close any open handles to files or processes here.  It's mighty
862          * annoying to have to do all this cleanup work.
863          */
864         if (task_tasks[i].runhandles) task_pclose(task_tasks[i].runhandles);
865         if (task_tasks[i].stdoutlog)  fs_file_close (task_tasks[i].stdoutlog);
866         if (task_tasks[i].stderrlog)  fs_file_close (task_tasks[i].stderrlog);
867
868         /*
869          * Only remove the log files if the test actually compiled otherwise
870          * forget about it (or if it didn't compile, and the procedure type
871          * was set to -fail (meaning it shouldn't compile) .. stil remove)
872          */
873         if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
874             if (remove(task_tasks[i].stdoutlogfile))
875                 con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile);
876             else
877                 util_debug("TEST", "removed stdout log file: %s\n", task_tasks[i].stdoutlogfile);
878             if (remove(task_tasks[i].stderrlogfile))
879                 con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile);
880             else
881                 util_debug("TEST", "removed stderr log file: %s\n", task_tasks[i].stderrlogfile);
882
883             remove(task_tasks[i].tmpl->tempfilename);
884         }
885
886         /* free util_strdup data for log files */
887         mem_d(task_tasks[i].stdoutlogfile);
888         mem_d(task_tasks[i].stderrlogfile);
889
890         task_template_destroy(&task_tasks[i].tmpl);
891     }
892     vec_free(task_tasks);
893 }
894
895 /*
896  * This executes the QCVM task for a specificly compiled progs.dat
897  * using the template passed into it for call-flags and user defined
898  * messages IF the procedure type is -execute, otherwise it matches
899  * the preprocessor output.
900  */
901 bool task_trymatch(task_template_t *tmpl, char ***line) {
902     bool     success = true;
903     bool     preprocessing = false;
904     FILE    *execute;
905     char     buffer[4096];
906     memset  (buffer,0,sizeof(buffer));
907
908     if (strcmp(tmpl->proceduretype, "-pp")) {
909         /*
910          * Drop the execution flags for the QCVM if none where
911          * actually specified.
912          */
913         if (!strcmp(tmpl->executeflags, "$null")) {
914             util_snprintf(buffer,  sizeof(buffer), "%s %s",
915                 task_bins[TASK_EXECUTE],
916                 tmpl->tempfilename
917             );
918         } else {
919             util_snprintf(buffer,  sizeof(buffer), "%s %s %s",
920                 task_bins[TASK_EXECUTE],
921                 tmpl->executeflags,
922                 tmpl->tempfilename
923             );
924         }
925
926         util_debug("TEST", "executing qcvm: `%s` [%s]\n",
927             tmpl->description,
928             buffer
929         );
930
931         execute = popen(buffer, "r");
932         if (!execute)
933             return false;
934     } else {
935         /*
936          * we're preprocessing, which means we need to read int
937          * the produced file and do some really weird shit.
938          */
939         if (!(execute = fs_file_open(tmpl->tempfilename, "r")))
940             return false;
941
942         preprocessing = true;
943     }
944
945     /*
946      * Now lets read the lines and compare them to the matches we expect
947      * and handle accordingly.
948      */
949     {
950         char  *data    = NULL;
951         size_t size    = 0;
952         size_t compare = 0;
953         while (fs_file_getline(&data, &size, execute) != EOF) {
954             if (!strcmp(data, "No main function found\n")) {
955                 con_err("test failure: `%s` (No main function found) [%s]\n",
956                     tmpl->description,
957                     tmpl->rulesfile
958                 );
959                 if (preprocessing)
960                     fs_file_close(execute);
961                 else
962                     pclose(execute);
963                 return false;
964             }
965
966             /*
967              * Trim newlines from data since they will just break our
968              * ability to properly validate matches.
969              */
970             if  (strrchr(data, '\n'))
971                 *strrchr(data, '\n') = '\0';
972
973             /*
974              * If data is just null now, that means the line was an empty
975              * one and for that, we just ignore it.
976              */
977             if (!*data)
978                 continue;
979
980             if (vec_size(tmpl->comparematch) > compare) {
981                 if (strcmp(data, tmpl->comparematch[compare++]))
982                     success = false;
983             } else {
984                     success = false;
985             }
986
987             /*
988              * Copy to output vector for diagnostics if execution match
989              * fails.
990              */
991             vec_push(*line, data);
992
993             /* reset */
994             data = NULL;
995             size = 0;
996         }
997         mem_d(data);
998         data = NULL;
999     }
1000
1001     if (!preprocessing)
1002         pclose(execute);
1003     else
1004         fs_file_close(execute);
1005
1006     return success;
1007 }
1008
1009 const char *task_type(task_template_t *tmpl) {
1010     if (!strcmp(tmpl->proceduretype, "-pp"))
1011         return "type: preprocessor";
1012     if (!strcmp(tmpl->proceduretype, "-execute"))
1013         return "type: execution";
1014     if (!strcmp(tmpl->proceduretype, "-compile"))
1015         return "type: compile";
1016     return "type: fail";
1017 }
1018
1019 /*
1020  * This schedualizes all tasks and actually runs them individually
1021  * this is generally easy for just -compile variants.  For compile and
1022  * execution this takes more work since a task needs to be generated
1023  * from thin air and executed INLINE.
1024  */
1025 #include <math.h>
1026 void task_schedualize(size_t *pad) {
1027     char   space[2][64];
1028     bool   execute  = false;
1029     char  *data     = NULL;
1030     char **match    = NULL;
1031     size_t size     = 0;
1032     size_t i        = 0;
1033     size_t j        = 0;
1034
1035     util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
1036
1037     for (; i < vec_size(task_tasks); i++) {
1038         memset(space[1], 0, sizeof(space[1]));
1039         util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1));
1040
1041         con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), "");
1042
1043         util_debug("TEST", "executing task: %d: %s\n", i, task_tasks[i].tmpl->description);
1044         /*
1045          * Generate a task from thin air if it requires execution in
1046          * the QCVM.
1047          */
1048         execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) ||
1049                      (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp"));
1050
1051         /*
1052          * We assume it compiled before we actually compiled :).  On error
1053          * we change the value
1054          */
1055         task_tasks[i].compiled = true;
1056
1057         /*
1058          * Read data from stdout first and pipe that stuff into a log file
1059          * then we do the same for stderr.
1060          */
1061         while (fs_file_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) {
1062             fs_file_puts(task_tasks[i].stdoutlog, data);
1063
1064             if (strstr(data, "failed to open file")) {
1065                 task_tasks[i].compiled = false;
1066                 execute                = false;
1067             }
1068         }
1069         while (fs_file_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) {
1070             /*
1071              * If a string contains an error we just dissalow execution
1072              * of it in the vm.
1073              *
1074              * TODO: make this more percise, e.g if we print a warning
1075              * that refers to a variable named error, or something like
1076              * that .. then this will blowup :P
1077              */
1078             if (strstr(data, "error")) {
1079                 execute                = false;
1080                 task_tasks[i].compiled = false;
1081             }
1082
1083             fs_file_puts (task_tasks[i].stderrlog, data);
1084         }
1085
1086         if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
1087             con_out("failure:   `%s` %*s %*s\n",
1088                 task_tasks[i].tmpl->description,
1089                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1090                 task_tasks[i].tmpl->rulesfile,
1091                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]),
1092                 "(failed to compile)"
1093             );
1094             continue;
1095         }
1096
1097         if (!execute) {
1098             con_out("succeeded: `%s` %*s %*s\n",
1099                 task_tasks[i].tmpl->description,
1100                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1101                 task_tasks[i].tmpl->rulesfile,
1102                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]),
1103                 task_type(task_tasks[i].tmpl)
1104
1105             );
1106             continue;
1107         }
1108
1109         /*
1110          * If we made it here that concludes the task is to be executed
1111          * in the virtual machine (or the preprocessor output needs to
1112          * be matched).
1113          */
1114         if (!task_trymatch(task_tasks[i].tmpl, &match)) {
1115             size_t d = 0;
1116
1117             con_out("failure:   `%s` %*s %*s\n",
1118                 task_tasks[i].tmpl->description,
1119                 (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1120                 task_tasks[i].tmpl->rulesfile,
1121                 (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(
1122                     (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
1123                         ? "(invalid results from execution)"
1124                         : "(invalid results from preprocessing)"
1125                 ) - pad[2]),
1126                 (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
1127                     ? "(invalid results from execution)"
1128                     : "(invalid results from preprocessing)"
1129             );
1130
1131             /*
1132              * Print nicely formatted expected match lists to console error
1133              * handler for the all the given matches in the template file and
1134              * what was actually returned from executing.
1135              */
1136             con_out("    Expected From %u Matches: (got %u Matches)\n",
1137                 vec_size(task_tasks[i].tmpl->comparematch),
1138                 vec_size(match)
1139             );
1140             for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) {
1141                 char  *select = task_tasks[i].tmpl->comparematch[d];
1142                 size_t length = 40 - strlen(select);
1143
1144                 con_out("        Expected: \"%s\"", select);
1145                 while (length --)
1146                     con_out(" ");
1147                 con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<<nothing else to compare>>" : match[d]);
1148             }
1149
1150             /*
1151              * Print the non-expected out (since we are simply not expecting it)
1152              * This will help track down bugs in template files that fail to match
1153              * something.
1154              */
1155             if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) {
1156                 for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) {
1157                     con_out("        Expected: Nothing                                   | Got: \"%s\"\n",
1158                         match[d + vec_size(task_tasks[i].tmpl->comparematch)]
1159                     );
1160                 }
1161             }
1162
1163
1164             for (j = 0; j < vec_size(match); j++)
1165                 mem_d(match[j]);
1166             vec_free(match);
1167             continue;
1168         }
1169         for (j = 0; j < vec_size(match); j++)
1170             mem_d(match[j]);
1171         vec_free(match);
1172
1173         con_out("succeeded: `%s` %*s %*s\n",
1174             task_tasks[i].tmpl->description,
1175             (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
1176             task_tasks[i].tmpl->rulesfile,
1177             (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]),
1178             task_type(task_tasks[i].tmpl)
1179
1180         );
1181     }
1182     mem_d(data);
1183 }
1184
1185 /*
1186  * This is the heart of the whole test-suite process.  This cleans up
1187  * any existing temporary files left behind as well as log files left
1188  * behind.  Then it propagates a list of tests from `curdir` by scaning
1189  * it for template files and compiling them into tasks, in which it
1190  * schedualizes them (executes them) and actually reports errors and
1191  * what not.  It then proceeds to destroy the tasks and return memory
1192  * it's the engine :)
1193  *
1194  * It returns true of tests could be propagated, otherwise it returns
1195  * false.
1196  *
1197  * It expects con_init() was called before hand.
1198  */
1199 GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
1200     static const char *default_defs = "defs.qh";
1201
1202     size_t pad[] = {
1203         /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
1204                     0,                                 0,                        0
1205     };
1206
1207     /*
1208      * If the default definition file isn't set to anything.  We will
1209      * use the default_defs here, which is "defs.qc"
1210      */
1211     if (!defs) {
1212         defs = default_defs;
1213     }
1214
1215
1216     task_precleanup(curdir);
1217     if (!task_propagate(curdir, pad, defs)) {
1218         con_err("error: failed to propagate tasks\n");
1219         task_destroy();
1220         return false;
1221     }
1222     /*
1223      * If we made it here all tasks where propagated from their resultant
1224      * template file.  So we can start the FILO scheduler, this has been
1225      * designed in the most thread-safe way possible for future threading
1226      * it's designed to prevent lock contention, and possible syncronization
1227      * issues.
1228      */
1229     task_schedualize(pad);
1230     task_destroy();
1231
1232     return true;
1233 }
1234
1235 /*
1236  * Fancy GCC-like LONG parsing allows things like --opt=param with
1237  * assignment operator.  This is used for redirecting stdout/stderr
1238  * console to specific files of your choice.
1239  */
1240 static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
1241     int  argc   = *argc_;
1242     char **argv = *argv_;
1243
1244     size_t len = strlen(optname);
1245
1246     if (strncmp(argv[0]+ds, optname, len))
1247         return false;
1248
1249     /* it's --optname, check how the parameter is supplied */
1250     if (argv[0][ds+len] == '=') {
1251         *out = argv[0]+ds+len+1;
1252         return true;
1253     }
1254
1255     if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
1256         return false;
1257
1258     /* using --opt param */
1259     *out = argv[1];
1260     --*argc_;
1261     ++*argv_;
1262     return true;
1263 }
1264
1265 int main(int argc, char **argv) {
1266     bool          succeed  = false;
1267     char         *redirout = (char*)stdout;
1268     char         *redirerr = (char*)stderr;
1269     char         *defs     = NULL;
1270
1271     con_init();
1272
1273     /*
1274      * Command line option parsing commences now We only need to support
1275      * a few things in the test suite.
1276      */
1277     while (argc > 1) {
1278         ++argv;
1279         --argc;
1280
1281         if (argv[0][0] == '-') {
1282             if (parsecmd("redirout", &argc, &argv, &redirout, 1, false))
1283                 continue;
1284             if (parsecmd("redirerr", &argc, &argv, &redirerr, 1, false))
1285                 continue;
1286             if (parsecmd("defs",     &argc, &argv, &defs,     1, false))
1287                 continue;
1288
1289             con_change(redirout, redirerr);
1290
1291             if (!strcmp(argv[0]+1, "debug")) {
1292                 OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
1293                 continue;
1294             }
1295             if (!strcmp(argv[0]+1, "memchk")) {
1296                 OPTS_OPTION_BOOL(OPTION_MEMCHK) = true;
1297                 continue;
1298             }
1299             if (!strcmp(argv[0]+1, "nocolor")) {
1300                 con_color(0);
1301                 continue;
1302             }
1303
1304             con_err("invalid argument %s\n", argv[0]+1);
1305             return -1;
1306         }
1307     }
1308     con_change(redirout, redirerr);
1309     succeed = test_perform("tests", defs);
1310     util_meminfo();
1311
1312
1313     return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
1314 }