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