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