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