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