Accept topic changes from servers that do not send topic-set timestamps (fixes SF...
[ircu2.10.12-pk.git] / libs / dbprim / tests / test-harness.c
1 /*
2 ** Copyright (C) 2002 by Kevin L. Mitchell <klmitch@mit.edu>
3 **
4 ** This library is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU Library General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2 of the License, or (at your option) any later version.
8 **
9 ** This library is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 ** Library General Public License for more details.
13 **
14 ** You should have received a copy of the GNU Library General Public
15 ** License along with this library; if not, write to the Free
16 ** Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
17 ** MA 02111-1307, USA
18 **
19 ** @(#)$Id$
20 */
21 #include <ctype.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33
34 #define BUFSIZE 512 /* buffer size */
35 #define ARGBUF  8   /* number of arguments to start with */
36
37 #define CONTINUELINE  0x1000 /* line continuation "character" */
38
39 #define Q_D -3 /* quote decimal */
40 #define Q_X -2 /* quote hexadecimal */
41 #define Q_S -1 /* quote self */
42
43 #define QUOTE_SINGLE  0x0010 /* we're in single-quote mode */
44 #define QUOTE_DOUBLE  0x0020 /* we're in double-quote mode */
45 #define QUOTE_FILE    0x0040 /* we're in file quote mode */
46 #define QUOTED        0x0080 /* it's a quoted character */
47 #define SAVE_QUOTE    0x0100 /* preserve quote on ungetc'd char */
48 #define EOS           0x0200 /* end of string */
49 #define EOD           0x0400 /* end of directive */
50 #define STOP          0x0800 /* stop processing the file */
51
52 enum test_result {
53   TR_NOTRUN,     /* test has not yet been run */
54   TR_PASS,       /* test passed */
55   TR_UPASS,      /* test passed, but was not expected to pass */
56   TR_FAIL,       /* test failed */
57   TR_XFAIL,      /* test failed, but was expected to fail */
58   TR_UNRESOLVED, /* unknown whether test passed or not */
59   TR_UNTESTED,   /* test case not yet ready */
60   TR_UNSUPPORTED /* tested feature unsupported here */
61 };
62
63 enum test_status {
64   TP_PENDING, /* test program is not yet running */
65   TP_RUNNING, /* test program has been started */
66   TP_FAILED,  /* test program errored out */
67   TP_COMPLETE /* test program has been completed */
68 };
69
70 struct test {
71   struct test      *t_next;   /* next test for program */
72   char             *t_name;   /* test's name */
73   char             *t_desc;   /* description of test */
74   enum test_result  t_result; /* result of test */
75   enum test_result  t_expect; /* expected result of test */
76   struct test_prog *t_prog;   /* structure describing program for test */
77   char             *t_info;   /* any extra information from test program */
78 };
79
80 struct test_prog {
81   struct test_prog  *tp_next;   /* next test program */
82   struct test_prog **tp_prev_p; /* what points to us */
83   char              *tp_name;   /* program invocation name */
84   char              *tp_prog;   /* file name of testing program */
85   char             **tp_argv;   /* arguments to feed to test program */
86   char              *tp_desc;   /* description of program */
87   struct test       *tp_tests;  /* linked list of tests run by program */
88   struct test       *tp_last;   /* last test added so far */
89   int                tp_count;  /* count of number of tests run by program */
90   struct test_dep   *tp_deps;   /* program dependencies */
91   enum test_status   tp_status; /* status of test program--has it run yet? */
92   int                tp_fd;     /* file descriptor for test program */
93   FILE              *tp_file;   /* FILE for test program */
94   int                tp_pid;    /* process ID for test program */
95 };
96
97 struct test_dep {
98   struct test_dep  *td_next; /* next dependency */
99   struct test_prog *td_prog; /* program we're dependent upon */
100 };
101
102 static struct {
103   const char      *m_name;  /* name for value */
104   enum test_result m_value; /* value for name */
105 } result_map[] = {
106 #define M(name) { #name, TR_ ## name }
107   M(NOTRUN), M(PASS), M(UPASS), M(FAIL), M(XFAIL), M(UNRESOLVED), M(UNTESTED),
108   M(UNSUPPORTED),
109 #undef M
110   { 0, 0 }
111 };
112
113 static int xform[] = {
114   '\a',  '\b',   Q_S,   Q_D,'\033',  '\f',   Q_S,   Q_S,   Q_S,
115    Q_S,   Q_S,   Q_S,   Q_S,  '\n',   Q_S,   Q_S,   Q_S,   Q_S,
116    Q_S,  '\t',   Q_S,  '\v',   Q_S,   Q_X,   Q_S,   Q_S
117 };
118
119 static struct {
120   char             *conf_file;       /* file name of config file */
121   char             *log_file;        /* file name of log file */
122   char             *prog_name;       /* program's name */
123   struct test_prog *prog_list;       /* list of test programs */
124   struct test_prog *prog_last;       /* last program in list */
125   int               prog_count;      /* count of test programs */
126   int               prog_running;    /* count of running programs */
127   int               prog_max;        /* maximum simultaneous programs */
128   char            **include_dirs;    /* directories to be searched for progs */
129   int               include_cnt;     /* count of include directories */
130   int               include_size;    /* total memory for include_dirs */
131   char            **argv;            /* argument vector */
132   int               argc;            /* argument count */
133   unsigned int      flags;           /* any special flags */
134   int               test_total;      /* total number of tests */
135   FILE             *log_fp;          /* log FILE object */
136   int               log_fd;          /* log file file descriptor */
137   int               high_fd;         /* highest fd so far allocated */
138   int               result_count[8]; /* count of all results */
139   fd_set            read_fds;        /* descriptors to be read */
140 } glob_data = { "test-harness.dat", "test-harness.log", 0, 0, 0, 0, 0, 1, 0, 0,
141                 0, 0, 0, 0, 0, 0, -1, -1,
142                 { 0, 0, 0, 0, 0, 0, 0, 0 } };
143
144 #define FLAG_VERBOSE  0x0001 /* verbose output enabled */
145 #define FLAG_VVERBOSE 0x0002 /* very verbose output enabled */
146 #define FLAG_POSIX    0x0004 /* POSIX-compliant output only */
147 #define FLAG_FINISHED 0x1000 /* finished starting all tests */
148
149 /* If allocation of memory fails, we must exit */
150 static void *
151 xmalloc(size_t size)
152 {
153   void *ptr;
154
155   if (!(ptr = malloc(size))) { /* get some memory */
156     perror(glob_data.prog_name); /* error occurred, bail out */
157     exit(1);
158   }
159
160   return ptr; /* return the memory allocated */
161 }
162
163 /* Similar function to realloc memory */
164 static void *
165 xrealloc(void *ptr, size_t size)
166 {
167   void *nptr;
168
169   if (!(nptr = realloc(ptr, size))) { /* reallocate the memory */
170     perror(glob_data.prog_name); /* error occurred, bail out */
171     exit(1);
172   }
173
174   return nptr; /* return new memory allocation */
175 }
176
177 /* Duplicate a string */
178 static char *
179 xstrdup(const char *str)
180 {
181   char *ptr;
182
183   if (!str) /* if no string to allocate, allocate none */
184     return 0;
185
186   if (!(ptr = strdup(str))) { /* duplicate the string */
187     perror(glob_data.prog_name); /* error occurred, bail out */
188     exit(1);
189   }
190
191   return ptr; /* return the new string */
192 }
193
194 /* Duplicate a parameter list */
195 static char **
196 argvdup(char * const *params, int count)
197 {
198   char **nparams;
199   int i;
200
201   if (!params) /* no parameters to duplicate? */
202     return 0;
203
204   if (count <= 0) /* must count it ourselves */
205     for (count = 0; params[count]; count++) /* count through params */
206       ; /* empty for loop */
207
208   nparams = xmalloc(sizeof(char *) * (count + 1)); /* allocate memory */
209
210   for (i = 0; i < count; i++) /* go through params */
211     nparams[i] = xstrdup(params[i]); /* and duplicate each one */
212
213   nparams[count] = 0; /* zero out end of vector */
214
215   return nparams; /* and return the vector */
216 }
217
218 /* Create a new program */
219 static struct test_prog *
220 add_prog(const char *name, const char *prog, const char *desc,
221          char * const *params, int count)
222 {
223   struct test_prog *tp;
224
225   tp = xmalloc(sizeof(struct test_prog)); /* allocate memory */
226
227   tp->tp_next = 0; /* initialize the struct test_prog */
228   tp->tp_prev_p = 0;
229   tp->tp_name = xstrdup(name);
230   tp->tp_prog = xstrdup(prog);
231   tp->tp_argv = argvdup(params, count);
232   tp->tp_desc = xstrdup(desc);
233   tp->tp_tests = 0;
234   tp->tp_last = 0;
235   tp->tp_count = 0;
236   tp->tp_deps = 0;
237   tp->tp_status = TP_PENDING;
238   tp->tp_fd = -1;
239   tp->tp_file = 0;
240   tp->tp_pid = 0;
241
242   if (!glob_data.prog_list) { /* add it to the global data list */
243     glob_data.prog_list = tp;
244     tp->tp_prev_p = &glob_data.prog_list;
245   } else if (glob_data.prog_last) {
246     glob_data.prog_last->tp_next = tp;
247     tp->tp_prev_p = &glob_data.prog_last->tp_next;
248   }
249   glob_data.prog_last = tp;
250   glob_data.prog_count++;
251
252   return tp; /* return it */
253 }
254
255 /* Create a new test */
256 static struct test *
257 add_test(struct test_prog *tp, const char *name, const char *desc,
258          enum test_result expect)
259 {
260   struct test *t;
261
262   t = xmalloc(sizeof(struct test)); /* allocate memory */
263
264   t->t_next = 0; /* initialize the struct test */
265   t->t_name = xstrdup(name);
266   t->t_desc = xstrdup(desc);
267   t->t_result = TR_NOTRUN;
268   t->t_expect = expect;
269   t->t_prog = tp;
270   t->t_info = 0;
271
272   if (!tp->tp_tests) /* add it to the program's data list */
273     tp->tp_tests = t;
274   else if (tp->tp_last)
275     tp->tp_last->t_next = t;
276   tp->tp_last = t;
277   tp->tp_count++; /* keep count */
278
279   glob_data.test_total++; /* total number of tests */
280   glob_data.result_count[TR_NOTRUN]++; /* keep a count */
281
282   return t; /* return it */
283 }
284
285 /* Add a dependency */
286 static struct test_dep *
287 add_dep(struct test_prog *tp, struct test_prog *dep)
288 {
289   struct test_dep *td;
290
291   td = xmalloc(sizeof(struct test_dep)); /* allocate memory */
292
293   td->td_next = tp->tp_deps; /* initialize dependency structure */
294   td->td_prog = dep;
295
296   tp->tp_deps = td; /* add it to the list */
297
298   return td; /* return dependency */
299 }
300
301 /* Close all test program input file descriptors before forking */
302 static void
303 close_progs(void)
304 {
305   struct test_prog *tp;
306
307   for (tp = glob_data.prog_list; tp; tp = tp->tp_next) /* walk linked list */
308     if (tp->tp_fd >= 0) /* if fd is open... */
309       close(tp->tp_fd); /* close it! */
310 }
311
312 /* Find a test program given its name */
313 static struct test_prog *
314 find_prog(const char *name)
315 {
316   struct test_prog *tp;
317
318   for (tp = glob_data.prog_list; tp; tp = tp->tp_next) /* walk linked list */
319     if (!strcmp(tp->tp_name, name))
320       return tp; /* found it, return it */
321
322   return 0; /* nothing found */
323 }
324
325 /* Find a test given its name */
326 static struct test *
327 find_test(struct test_prog *tp, const char *name)
328 {
329   struct test *t;
330
331   for (t = tp->tp_tests; t; t = t->t_next) /* walk linked list */
332     if (!strcmp(t->t_name, name))
333       return t; /* found it, return it */
334
335   return 0; /* nothing found */
336 }
337
338 /* Get a value given a name */
339 static enum test_result
340 find_result(const char *name)
341 {
342   int i;
343
344   for (i = 0; result_map[i].m_name; i++) /* walk through array */
345     if (!strcasecmp(result_map[i].m_name, name))
346       return result_map[i].m_value; /* found it, return it */
347
348   return TR_UNRESOLVED; /* couldn't map name to value; must be resolved */
349 }
350
351 /* Get a name given a value */
352 #define name_result(val)        (result_map[(val)].m_name)
353
354 /* Set the result of a given test */
355 static void
356 set_result(struct test *t, enum test_result result, const char *info)
357 {
358   glob_data.result_count[t->t_result]--; /* decrement one count */
359
360   switch (result) { /* check result */
361   case TR_NOTRUN: /* these should never be reported by a test program */
362   case TR_UPASS:
363   case TR_XFAIL:
364     t->t_result = TR_UNRESOLVED; /* so mark it unresolved due to test error */
365     break;
366
367   case TR_PASS: /* a test passed */
368     if (t->t_expect == TR_FAIL)
369       t->t_result = TR_UPASS; /* expected to fail!  mark unexpected pass */
370     else
371       t->t_result = TR_PASS; /* normal pass */
372     break;
373
374   case TR_FAIL: /* a test failed */
375     if (t->t_expect == TR_FAIL)
376       t->t_result = TR_XFAIL; /* expected to fail; mark it as such */
377     else
378       t->t_result = TR_FAIL; /* wasn't expected to fail! */
379     break;
380
381   default:
382     t->t_result = result; /* some other result */
383     break;
384   }
385
386   glob_data.result_count[t->t_result]++; /* increment another count */
387
388   t->t_info = xstrdup(info); /* remember extra information */
389
390   result = t->t_result;
391
392   /* save the result to the log file */
393   fprintf(glob_data.log_fp, "%s: (%s/%s) %s\n", name_result(result),
394           t->t_prog->tp_name, t->t_name, t->t_desc);
395   fprintf(glob_data.log_fp, "INFO: %s\n", t->t_info);
396
397   if (!(glob_data.flags & FLAG_VERBOSE)) /* only output if verbose */
398     return;
399
400   if (glob_data.flags & FLAG_POSIX) { /* adjust for POSIX */
401     if (result == TR_UPASS)
402       result = TR_PASS;
403     else if (result == TR_XFAIL)
404       result = TR_FAIL;
405   }
406
407   /* print the result */
408   printf("%s: (%s/%s) %s\n", name_result(result), t->t_prog->tp_name,
409          t->t_name, t->t_desc);
410   if ((glob_data.flags & (FLAG_POSIX | FLAG_VVERBOSE)) == FLAG_VVERBOSE)
411     printf("INFO: %s\n", t->t_info);
412 }
413
414 /* Mark all tests for a given program unresolved */
415 static void
416 mark_all(struct test_prog *tp, enum test_result result, const char *info)
417 {
418   struct test *t;
419
420   for (t = tp->tp_tests; t; t = t->t_next) /* walk linked list */
421     if (t->t_result == TR_NOTRUN)
422       set_result(t, result, info); /* mark it unresolved */
423 }
424
425 /* Find the test program */
426 static char *
427 locate_file(const char *prog, char *buf, int mode)
428 {
429   int i;
430   char *dir;
431
432   if (glob_data.include_cnt) /* search include directories */
433     for (i = 0; i < glob_data.include_cnt; i++) {
434       dir = glob_data.include_dirs[i];
435
436       sprintf(buf, "%s/%s", dir, prog); /* form program file name */
437
438       if (!access(buf, mode)) /* check access */
439         return buf; /* Ok, return program name */
440     }
441   else {
442     sprintf(buf, "./%s", prog); /* form program file name */
443
444     if (!access(buf, mode)) /* check access */
445       return buf; /* Ok, return program name */
446   }
447
448   return 0; /* failed to find the program */
449 }
450
451 /* Build an argument list for executing a program */
452 static char **
453 build_argv(char *name, char **params)
454 {
455   char **nparams;
456   int i, count = 1;
457
458   if (glob_data.argv) { /* global arguments to pass */
459     for (i = 0; glob_data.argv[i]; i++) /* step through those arguments... */
460       ; /* but do nothing other than count them */
461
462     count += i; /* now add them to the count */
463   }
464
465   if (params) { /* arguments for this specific test program */
466     for (i = 0; params[i]; i++) /* step through them... */
467       ; /* but do nothing other than count them */
468
469     count += i; /* now add them to the count */
470   }
471
472   nparams = xmalloc(sizeof(char *) * (count + 1)); /* allocate memory */
473
474   nparams[0] = name; /* program name, first */
475   count = 1; /* next place to put an argument */
476
477   if (glob_data.argv) /* now global arguments... */
478     for (i = 0; glob_data.argv[i]; i++) /* step through the arguments... */
479       nparams[count++] = glob_data.argv[i]; /* set argument value */
480
481   if (params) /* next test program arguments */
482     for (i = 0; params[i]; i++) /* step through them */
483       nparams[count++] = params[i]; /* set argument value */
484
485   nparams[count] = 0; /* end with a 0 value */
486
487   return nparams; /* return parameters */
488 }
489
490 /* Execute a test program */
491 static int
492 execute_prog(struct test_prog *tp)
493 {
494   int fds[2], stat, err;
495   char progbuf[BUFSIZE], *prog;
496
497   if (!(prog = locate_file(tp->tp_prog, progbuf, X_OK))) { /* find program */
498     mark_all(tp, TR_UNTESTED, "Unable to locate test program");
499     tp->tp_status = TP_FAILED;
500     return 0;
501   }
502
503   if (pipe(fds)) { /* now set up the pipe for it */
504     mark_all(tp, TR_UNRESOLVED, strerror(errno));
505     tp->tp_status = TP_FAILED;
506     return 0;
507   }
508
509   switch ((tp->tp_pid = fork())) {
510   case -1: /* couldn't fork! */
511     err = errno; /* save errno */
512     close(fds[0]); /* close file descriptors */
513     close(fds[1]);
514     mark_all(tp, TR_UNRESOLVED, strerror(err));
515     tp->tp_status = TP_FAILED;
516     return 0;
517     break;
518
519   case 0: /* we're in the child */
520     close(fds[0]); /* close read end of the pipe */
521     close(0); /* close stdin */
522     close(1); /* close stdout */
523     close(2); /* close stderr */
524     close_progs(); /* close all test programs' input descriptors */
525
526     dup2(fds[1], 1); /* make stdout point to write end of pipe */
527     close(fds[1]); /* close redundant file descriptor */
528
529     dup2(glob_data.log_fd, 2); /* make stderr go to log file */
530     close(glob_data.log_fd); /* close redundant file descriptor */
531
532     execv(prog, build_argv(prog, tp->tp_argv)); /* execute program */
533
534     printf("UNRESOLVED/ALL:Couldn't execute test: %s\n", strerror(errno));
535     exit(1); /* report error and exit child */
536     break;
537
538   default: /* we're in the parent */
539     close(fds[1]); /* close write end of the pipe */
540     if (!(tp->tp_file = fdopen(fds[0], "r"))) { /* open FILE object */
541       err = errno; /* save value of errno */
542       close(fds[0]); /* it failed; close the file descriptor */
543       kill(tp->tp_pid, SIGKILL); /* kill the child */
544       waitpid(tp->tp_pid, &stat, 0); /* wait for child */
545       tp->tp_pid = 0; /* clear PID */
546       mark_all(tp, TR_UNRESOLVED, strerror(err));
547       tp->tp_status = TP_FAILED;
548       return 0;
549     }
550     tp->tp_fd = fds[0]; /* store the fd */
551     tp->tp_status = TP_RUNNING;
552     FD_SET(fds[0], &glob_data.read_fds); /* record interest in fd */
553     if (fds[0] > glob_data.high_fd) /* remember the highest fd so far */
554       glob_data.high_fd = fds[0];
555     break;
556   }
557
558   return 1;
559 }
560
561 /* Find a test program to execute */
562 static struct test_prog *
563 next_test(struct test_prog *tp)
564 {
565   struct test_dep *td;
566
567   if (!tp) /* start at beginning of prog list if no test prog passed in */
568     tp = glob_data.prog_list;
569
570   for (; tp; tp = tp->tp_next) {
571     if (tp->tp_status != TP_PENDING) /* skip non-pending tests */
572       continue;
573
574     if (!tp->tp_deps) /* no dependencies?  just return it */
575       return tp;
576
577     for (td = tp->tp_deps; td; td = td->td_next) {
578       if (!td->td_prog) /* broken dependency, ignore it */
579         continue;
580
581       switch (td->td_prog->tp_status) {
582       case TP_FAILED: /* dependent test failed... */
583         mark_all(tp, TR_UNTESTED, "Test dependency failed");
584         tp->tp_status = TP_FAILED; /* mark us failed */
585         *tp->tp_prev_p = tp->tp_next; /* clip us out of the list */
586         /*FALLTHROUGH*/
587       case TP_PENDING: /* hasn't been executed yet */
588       case TP_RUNNING: /* dependent test is currently executing... */
589         break; /* bail out and process the next test program */
590
591       case TP_COMPLETE: /* dependent test is complete, cool */
592         continue; /* Go check next dependency */
593         break;
594       }
595
596       break;
597     }
598
599     if (!td) /* checked all dependencies, everything checks out. */
600       return tp;
601   }
602
603   return 0;
604 }
605
606 /* Start some test programs */
607 static void
608 run_test(void)
609 {
610   struct test_prog *tp;
611
612   if (glob_data.flags & FLAG_FINISHED) /* are we already done? */
613     return;
614
615   /* start some test programs */
616   for (tp = next_test(0); tp && !(glob_data.flags & FLAG_FINISHED);
617        tp = next_test(tp)) {
618     if (glob_data.prog_running >= glob_data.prog_max) /* too many running? */
619       return; /* just wait for a bit */
620     else { /* execute and update count of how many are running */
621       if (execute_prog(tp) && ++glob_data.prog_running >= glob_data.prog_max)
622         return; /* if we've hit the limit, don't look for another */
623     }
624   }
625
626   /* If no programs are running, we're either done or have a cycle */
627   if (!tp && glob_data.prog_running == 0) {
628     /* mark any pending tests as failed */
629     for (tp = glob_data.prog_list; tp; tp = tp->tp_next) {
630       if (tp->tp_status != TP_PENDING) /* skip non-pending entries */
631         continue;
632
633       /* Inform user about dependancy loop */
634       fprintf(stderr, "Dependencies for %s failed due to cycle\n",
635               tp->tp_name);
636
637       /* Fail everything */
638       mark_all(tp, TR_UNTESTED, "Test dependency failed--cycle");
639       tp->tp_status = TP_FAILED;
640       *tp->tp_prev_p = tp->tp_next;
641     }
642
643     glob_data.flags |= FLAG_FINISHED; /* we're done! */
644   }
645 }
646
647 /* Place the results of the test in a file */
648 static void
649 report_test_fp(FILE *fp, int posix)
650 {
651   int count;
652
653   fprintf(fp, "================== Test Results Summary ==================\n");
654
655   fprintf(fp, "Total tests: %d\n", glob_data.test_total);
656
657   if (glob_data.result_count[TR_NOTRUN] > 0)
658     fprintf(fp, "Tests not run: %d\n", glob_data.result_count[TR_NOTRUN]);
659
660   count = glob_data.result_count[TR_PASS] + glob_data.result_count[TR_UPASS];
661   if (count > 0)
662     fprintf(fp, "Tests passed: %d\n", count);
663   if (!posix && glob_data.result_count[TR_PASS] != count) {
664     if (glob_data.result_count[TR_PASS] > 0)
665       fprintf(fp, "  Expected: %d\n", glob_data.result_count[TR_PASS]);
666     if (glob_data.result_count[TR_UPASS] > 0)
667       fprintf(fp, "  Unexpected: %d\n", glob_data.result_count[TR_UPASS]);
668   }
669
670   count = glob_data.result_count[TR_FAIL] + glob_data.result_count[TR_XFAIL];
671   if (count > 0)
672     fprintf(fp, "Tests failed: %d\n", count);
673   if (!posix && glob_data.result_count[TR_XFAIL] != count) {
674     if (glob_data.result_count[TR_XFAIL] > 0)
675       fprintf(fp, "  Expected: %d\n", glob_data.result_count[TR_XFAIL]);
676     if (glob_data.result_count[TR_FAIL] > 0)
677       fprintf(fp, "  Unexpected: %d\n", glob_data.result_count[TR_FAIL]);
678   }
679
680   if (glob_data.result_count[TR_UNRESOLVED] > 0)
681     fprintf(fp, "Unresolved tests: %d\n",
682             glob_data.result_count[TR_UNRESOLVED]);
683
684   if (glob_data.result_count[TR_UNTESTED] > 0)
685     fprintf(fp, "Untested test cases: %d\n",
686             glob_data.result_count[TR_UNTESTED]);
687
688   if (glob_data.result_count[TR_UNSUPPORTED] > 0)
689     fprintf(fp, "Unsupported tests: %d\n",
690             glob_data.result_count[TR_UNSUPPORTED]);
691
692   fprintf(fp, "==========================================================\n");
693 }
694
695 /* Report the results of the test */
696 static void
697 report_test(void)
698 {
699   report_test_fp(glob_data.log_fp, 0);
700   report_test_fp(stdout, glob_data.flags & FLAG_POSIX);
701 }
702
703 /* Read data from a test program */
704 static void
705 read_test(struct test_prog *tp)
706 {
707   int stat;
708   struct test *t = 0;
709   enum test_result result;
710   char buf[BUFSIZE], *rescode = 0, *testcode = 0, *info = 0, *tmp;
711
712   if (!fgets(buf, sizeof(buf), tp->tp_file)) { /* child exited */
713     waitpid(tp->tp_pid, &stat, 0); /* wait for child */
714     if (WIFEXITED(stat)) {
715       sprintf(buf, "Test program exited with status %d", WEXITSTATUS(stat));
716       mark_all(tp, WEXITSTATUS(stat) ? TR_UNRESOLVED : TR_UNTESTED, buf);
717       tp->tp_status = WEXITSTATUS(stat) ? TP_FAILED : TP_COMPLETE;
718     } else if (WIFSIGNALED(stat)) {
719       sprintf(buf, "Test program terminated by signal %d", WTERMSIG(stat));
720       mark_all(tp, TR_UNRESOLVED, buf);
721       tp->tp_status = TP_FAILED;
722     } else {
723       mark_all(tp, TR_UNRESOLVED, "Test program exited with unknown code");
724       tp->tp_status = TP_FAILED;
725     }
726
727     close(tp->tp_fd); /* close file descriptor */
728     fclose(tp->tp_file); /* close file object */
729
730     FD_CLR(tp->tp_fd, &glob_data.read_fds); /* clear fd bit in read set */
731
732     tp->tp_fd = -1; /* clear out the struct test_prog object's run status */
733     tp->tp_file = 0;
734     tp->tp_pid = 0;
735
736     if (--glob_data.prog_running < glob_data.prog_max)
737       run_test(); /* freed up a slot for a new test program */
738   } else {
739     if ((tmp = strchr(buf, '\n'))) /* remove newline */
740       *tmp = '\0';
741
742     rescode = buf; /* find result code */
743
744     if (!(testcode = strchr(buf, '/'))) /* locate test code */
745       return;
746
747     *(testcode++) = '\0'; /* nul-terminate and advance in one swell foop */
748
749     if ((info = strchr(testcode, ':'))) /* locate extra info */
750       *(info++) = '\0'; /* nul-terminate and advance in one swell foop */
751
752     result = find_result(rescode); /* get the result */
753
754     if (!strcmp(testcode, "ALL")) /* special test code marks every test */
755       mark_all(tp, result, info);
756     else if ((t = find_test(tp, testcode)))
757       set_result(t, result, info); /* set the result if we found test */
758     else {
759       fprintf(glob_data.log_fp, "%s: (%s/%s) Unknown test (will not be "
760               "counted)\n", name_result(result), tp->tp_name, testcode);
761       fprintf(glob_data.log_fp, "INFO: %s\n", info);
762       if (glob_data.flags & FLAG_VERBOSE)
763         printf("%s: (%s/%s) Unknown test (will not be counted)\n",
764                name_result(result), tp->tp_name, testcode);
765       if ((glob_data.flags & (FLAG_POSIX | FLAG_VVERBOSE)) == FLAG_VVERBOSE)
766         printf("INFO: %s\n", info);
767     }
768   }
769 }
770
771 /* Wait for data from test programs */
772 static void
773 run_select(void)
774 {
775   int nfds;
776   fd_set read_set;
777   struct test_prog *tp;
778
779   while (glob_data.prog_running || !(glob_data.flags & FLAG_FINISHED)) {
780     read_set = glob_data.read_fds; /* all hail structure copy! */
781
782     nfds = select(glob_data.high_fd + 1, &read_set, 0, 0, 0);
783
784     for (tp = glob_data.prog_list; tp; tp = tp->tp_next) /* walk linked list */
785       if (tp->tp_fd >= 0 && FD_ISSET(tp->tp_fd, &read_set)) { /* found one */
786         read_test(tp); /* read the data... */
787         if (--nfds) /* are we done yet? */
788           break;
789       }
790   }
791 }
792
793 /* Retrieves a \ quoted sequence from a file and produces the correct
794  * output character
795  */
796 static int
797 unquote(FILE *fp)
798 {
799   int c = 0, i, cnt = 0;
800
801   if ((i = getc(fp)) == EOF) /* retrieve the character after the \ */
802     return EOF;
803
804   if (i == '\n') /* just a line continuation */
805     return CONTINUELINE;
806   if (i >= 'a' && i <= 'z') { /* special lower-case letters... */
807     switch (xform[i - 'a']) {
808     case Q_S: /* insert the character itself */
809       return i;
810       break;
811
812     case Q_X: /* \xHH--introduces a hex char code */
813       while (cnt < 2 && (i = getc(fp)) != EOF) {
814         if (!isxdigit(i)) { /* wasn't a hex digit? put it back */
815           ungetc(i, fp);
816           return cnt ? c : EOF; /* return error if no chars */
817         } else {
818           if (isdigit(i)) /* calculate character code */
819             c = (c << 4) | (i - '0');
820           else if (isupper(i))
821             c = (c << 4) | ((i - 'A') + 10);
822           else
823             c = (c << 4) | ((i - 'a') + 10);
824         }
825         cnt++;
826       }
827       return cnt ? c : EOF; /* return error if no chars */
828       break;
829
830     case Q_D: /* \dNNN--introduces a decimal char code */
831       while (cnt < 3 && (i = getc(fp)) != EOF) {
832         if (!isdigit(i)) { /* wasn't a digit? put it back */
833           ungetc(i, fp);
834           return cnt ? c : EOF; /* return error if no chars */
835         } else {
836           c = (c * 10) + (i - '0'); /* calculate character code */
837           if (c > 255) { /* oops, char overflow, backup */
838             ungetc(i, fp);
839             return c / 10;
840           }
841         }
842         cnt++;
843       }
844       return cnt ? c : EOF; /* return error if no chars */
845       break;
846
847     default: /* insert the specified code */
848       return xform[i - 'a'];
849       break;
850     }
851   } else if (i >= '0' && i <= '7') { /* octal number */
852     c = (i - '0'); /* accumulate first code... */
853     while (cnt < 2 && (i = getc(fp)) != EOF) { /* get the rest */
854       if (i < '0' || i > '7') { /* not a valid octal number, put it back */
855         ungetc(i, fp);
856         return c;
857       } else
858         c = (c << 3) | (i - '0'); /* accumulate the next code */
859       cnt++;
860     }
861     return c;
862   }
863
864   return i; /* return the character itself */
865 }
866
867 /* Create a program */
868 static void
869 create_prog(const char *name, const char *prog, const char *desc,
870             char * const *params, int count)
871 {
872   struct test_prog *tp;
873
874   if ((tp = find_prog(name))) /* don't add duplicate programs */
875     fprintf(stderr, "%s: Duplicate \"program\" directive for \"%s\" found; "
876             "ignoring\n", glob_data.prog_name, name);
877   else
878     add_prog(name, prog, desc, params, count);
879 }
880
881 /* Create a test case */
882 static void
883 create_test(const char *name, const char *prog, const char *expect_code,
884             const char *desc)
885 {
886   struct test_prog *tp;
887   struct test *t;
888   enum test_result expect;
889
890   switch ((expect = find_result(expect_code))) {
891   case TR_NOTRUN: /* invalid values for expectation */
892   case TR_UPASS:
893   case TR_XFAIL:
894   case TR_UNRESOLVED:
895   case TR_UNTESTED:
896   case TR_UNSUPPORTED:
897     fprintf(stderr, "%s: Invalid expectation code \"%s\" for test \"%s\"; "
898             "ignoring\n", glob_data.prog_name, expect_code, name);
899     return;
900     break;
901
902   default:
903     break;
904   }
905
906   if (!(tp = find_prog(prog))) /* must have a program directive */
907     fprintf(stderr, "%s: No \"program\" directive for test \"%s\"; ignoring\n",
908             glob_data.prog_name, name);
909   else if ((t = find_test(tp, name))) /* no duplicate tests */
910     fprintf(stderr, "%s: Duplicate \"test\" directive for \"%s\" in program "
911             "\"%s\" found; ignoring\n", glob_data.prog_name, name, prog);
912   else /* create the test */
913     add_test(tp, name, desc, expect);
914 }
915
916 /* Create a dependency */
917 static void
918 create_dep(const char *prog, char * const *deps, int count)
919 {
920   struct test_prog *tp, *dep;
921   struct test_dep *td;
922
923   if (!(tp = find_prog(prog))) /* must have a program directive */
924     fprintf(stderr, "%s: No \"program\" directive for dependency \"%s\"; "
925             "ignoring\n", glob_data.prog_name, prog);
926   else /* create the dependencies */
927     for (; count; count--, deps++) {
928       if (!(dep = find_prog(*deps))) { /* does the dependency exist? */
929         fprintf(stderr, "%s: Program \"%s\" dependent on non-existant "
930                 "program \"%s\"; ignoring\n", glob_data.prog_name, prog,
931                 *deps);
932         continue;
933       }
934
935       /* Walk through the dependencies to weed out duplicates */
936       for (td = tp->tp_deps; ; td = td->td_next)
937         if (!td) {
938           add_dep(tp, dep); /* add the dependency */
939           break; /* explicitly exit the loop */
940         } else if (td->td_prog == dep)
941           break; /* silently ignore identical dependencies */
942     }
943 }
944
945 /* Report too few arguments problems */
946 static void
947 check_args(int count, int min, char *directive)
948 {
949   if (count >= min) /* Check argument count */
950     return;
951
952   fprintf(stderr, "%s: Too few arguments for %s directive\n",
953           glob_data.prog_name, directive);
954   exit(1);
955 }
956
957 /* Read the configuration file */
958 static void
959 read_conf(char *filename)
960 {
961   enum {
962     s_space, s_comment, s_string
963   } state = s_space, save = s_space;
964   char *buf = 0, **args = 0, filebuf[BUFSIZE], *file, file2buf[BUFSIZE];
965   char *file2, *s;
966   int buflen = BUFSIZE, pos = 0, arglen = ARGBUF, argidx = 0, c, flags = 0;
967   int file2pos = 0;
968   FILE *fp;
969
970   if (!(file = locate_file(filename, filebuf, R_OK))) { /* find file */
971     fprintf(stderr, "%s: Unable to locate config file %s for reading\n",
972             glob_data.prog_name, filename);
973     exit(1);
974   }
975
976   if (!(fp = fopen(file, "r"))) { /* open config file */
977     fprintf(stderr, "%s: Unable to open config file %s for reading\n",
978             glob_data.prog_name, file);
979     exit(1);
980   }
981
982   buf = xmalloc(buflen); /* get some memory for the buffers */
983   args = xmalloc(sizeof(char *) * arglen);
984
985   while (!(flags & STOP)) {
986     c = getc(fp); /* get a character */
987     flags &= ~(QUOTED | EOS | EOD); /* reset quoted and end-of-* flags */
988
989     if (state == s_comment) {
990       if (c != '\n' && c != EOF)
991         continue; /* skip comment... */
992       else
993         state = save; /* but preserve ending character and process */
994     }
995
996     if (flags & SAVE_QUOTE)
997       flags = (flags & ~SAVE_QUOTE) | QUOTED;
998     else {
999       if (!(flags & QUOTE_SINGLE) && c == '"') { /* process double quote */
1000         flags ^= QUOTE_DOUBLE;
1001         continue; /* get next character */
1002       } else if (!(flags & QUOTE_DOUBLE) && c == '\'') { /* single quote */
1003         flags ^= QUOTE_SINGLE;
1004         continue; /* get next character */
1005       } else if (!(flags & QUOTE_SINGLE) && c == '<') { /* open file quote */
1006         file2pos = pos;
1007         flags |= QUOTE_FILE;
1008         continue; /* skip the < */
1009       } else if (!(flags & QUOTE_SINGLE) && c == '>') { /* close file quote */
1010         if (!(flags & QUOTE_FILE)) { /* close file quote with no open? */
1011           fprintf(stderr, "%s: Mismatched closing file quote ('>') while "
1012                   "parsing config file %s\n", glob_data.prog_name, file);
1013           exit(1);
1014         }
1015         flags &= ~QUOTE_FILE; /* turn off file quote flag */
1016         buf[pos] = '\0'; /* terminate buffer temporarily */
1017         if (!(file2 = locate_file(&buf[file2pos], file2buf, R_OK)))
1018           fprintf(stderr, "%s: WARNING: Unable to find file %s\n",
1019                   glob_data.prog_name, &buf[file2pos]);
1020         else {
1021           pos = file2pos; /* rewind in buffer... */
1022           while (*file2) { /* copy filename into buffer */
1023             if (pos + 1 > buflen) /* get more memory for buffer if needed */
1024               buf = xrealloc(buf, buflen <<= 1);
1025             buf[pos++] = *(file2++); /* copy the character */
1026           }
1027         }
1028         continue; /* skip the > */
1029       } else if (!(flags & QUOTE_SINGLE) && c == '\\') { /* character quote */
1030         if ((c = unquote(fp)) == EOF) { /* unquote the character */
1031           fprintf(stderr, "%s: Hanging \\ while parsing config file %s\n",
1032                   glob_data.prog_name, file);
1033           exit(1);
1034         } else if (c == CONTINUELINE) /* continue line--not a quoted char */
1035           c = ' ';
1036         else
1037           flags |= QUOTED; /* mark character as quoted */
1038       }
1039       /* Check to see if the character appears in quotes; this is not part
1040        * of an if-else chain because a '\' followed by a newline inside a
1041        * double-quoted string should produce a _quoted_ space, and the
1042        * test above gives us an _unquoted_ space!
1043        */
1044       if (flags & (QUOTE_SINGLE | QUOTE_DOUBLE | QUOTE_FILE)) {
1045         if (c == EOF) {
1046           fprintf(stderr, "%s: Unmatched %s quote (%s) while parsing config "
1047                   "file %s\n", glob_data.prog_name, flags & QUOTE_SINGLE ?
1048                   "single" : (flags & QUOTE_DOUBLE ? "double" : "file"),
1049                   flags & QUOTE_SINGLE ? "\"'\"" : (flags & QUOTE_DOUBLE ?
1050                                                     "'\"'" : "'<'"), file);
1051           exit(1);
1052         } else if (!(flags & QUOTE_FILE)) /* file quotes aren't real quotes */
1053           flags |= QUOTED; /* mark character as quoted */
1054       }
1055     }
1056
1057     if (!(flags & QUOTED) && c == '#') { /* found beginning of a comment */
1058       save = state; /* save current state--we'll restore to here */
1059       state = s_comment; /* switch to comment state */
1060       continue; /* skip all characters in the comment */
1061     }
1062
1063     /* Now let's figure out what to do with this thing... */
1064     if (c == EOF) /* end of file? */
1065       flags |= (state == s_string ? EOS : 0) | EOD | STOP; /* ends directive */
1066     else /* ok, switch on state */
1067       switch (state) {
1068       case s_space: /* Looking for the *end* of a string of spaces */
1069         if ((flags & QUOTED) || !isspace(c)) { /* non-space character! */
1070           state = s_string; /* Switch to string state */
1071           ungetc(c, fp); /* push character back onto stream */
1072           if (flags & QUOTED) /* save quote flag */
1073             flags |= SAVE_QUOTE;
1074           if (argidx + 1 > arglen) /* get more memory for args if needed */
1075             args = xrealloc(args, sizeof(char *) * (arglen <<= 1));
1076           args[argidx++] = buf + pos; /* point to starting point of next arg */
1077           args[argidx] = 0; /* make sure to terminate the array */
1078         }
1079         if (c == '\n') /* hit end of line? */
1080           flags |= EOD; /* end of the directive, then */
1081         else
1082           continue; /* move on to next character */
1083         break;
1084
1085       case s_comment: /* should never be in this state here */
1086         break;
1087
1088       case s_string: /* part of a string */
1089         /* unquoted space or end of file ends string */
1090         if ((!(flags & QUOTED) && isspace(c))) {
1091           state = s_space; /* will need to search for next non-space */
1092           /* finished accumulating this string--if it's \n, done with the
1093            * directive, as well.
1094            */
1095           flags |= EOS | (c == '\n' ? EOD : 0);
1096         }
1097         break;
1098       }
1099
1100     if (pos + 1 > buflen) /* get more memory for buffer if needed */
1101       buf = xrealloc(buf, buflen <<= 1);
1102
1103     if (flags & EOS) /* end of string? */
1104       buf[pos++] = '\0'; /* end the string in the buffer */
1105
1106     if (flags & EOD) { /* end of directive? */
1107       if (pos == 0) /* nothing's actually been accumulated */
1108         continue; /* so go on */
1109
1110       if (!strcmp(args[0], "program")) { /* program directive */
1111         check_args(argidx, 3, args[0]);
1112         create_prog(args[1], args[2], argidx > 3 ? args[3] : "",
1113                     argidx > 4 ? args + 4 : 0, argidx > 4 ? argidx - 4 : 0);
1114       } else if (!strcmp(args[0], "test")) { /* test directive */
1115         check_args(argidx, 4, args[0]);
1116         create_test(args[1], args[2], args[3], argidx > 4 ? args[4] : "");
1117       } else if (!strcmp(args[0], "include")) { /* include directive */
1118         check_args(argidx, 2, args[0]);
1119         read_conf(args[1]);
1120       } else if ((s = strchr(args[0], ':')) && *(s + 1) == '\0') {
1121         *s = '\0'; /* it's a dependency specification */
1122         create_dep(args[0], args + 1, argidx - 1);
1123       } else {
1124         fprintf(stderr, "%s: Unknown directive \"%s\"\n", glob_data.prog_name,
1125                 args[0]);
1126         exit(1);
1127       }
1128
1129       if (flags & STOP) /* if we were told to stop, well, STOP! */
1130         break;
1131
1132       pos = 0; /* prepare for the next round through */
1133       argidx = 0;
1134       flags = 0;
1135       args[0] = buf;
1136       state = s_space;
1137       continue; /* go on */
1138     }
1139
1140     if (!(flags & (EOS | EOD))) /* insert character into buffer */
1141       buf[pos++] = c;
1142   }
1143
1144   fclose(fp); /* clean up */
1145   free(buf);
1146   free(args);
1147 }
1148
1149 /* Add another include directory to the list */
1150 static void
1151 add_include(char *dir)
1152 {
1153   if (glob_data.include_cnt + 1 > glob_data.include_size) /* get more memory */
1154     glob_data.include_dirs =
1155       xrealloc(glob_data.include_dirs,
1156                sizeof(char *) * (glob_data.include_size <<= 1));
1157   glob_data.include_dirs[glob_data.include_cnt++] = dir;
1158 }
1159
1160 /* Output a standard usage message and exit */
1161 static void
1162 usage(int ret)
1163 {
1164   fprintf(stderr, "Usage: %s -hvp [-c <conf>] [-l <log>] [-i <include>]\n",
1165           glob_data.prog_name);
1166   fprintf(stderr, "       %*s [-I <include>] [-j <max>]\n",
1167           (int)strlen(glob_data.prog_name), "");
1168   fprintf(stderr, "\n  -h\t\tPrint this help message\n");
1169   fprintf(stderr, "  -v\t\tVerbose output; multiple usages increase verbosity "
1170           "level\n");
1171   fprintf(stderr, "  -p\t\tRequest POSIX-compliant test output\n");
1172   fprintf(stderr, "  -c <conf>\tUse <conf> as the configuration file\n");
1173   fprintf(stderr, "  -l <log>\tUse <log> as the log file for test programs\n");
1174   fprintf(stderr, "  -i <include>\tAdd <include> to list of directories "
1175           "searched for test programs.\n");
1176   fprintf(stderr, "  -I <include>\tSynonymous with \"-i\"\n");
1177   fprintf(stderr, "  -j <max>\tSpecifies the maximum number of test programs "
1178           "that may be\n");
1179   fprintf(stderr, "\t\texecuted simultaneously\n");
1180   fprintf(stderr, "\nArguments following a \"--\" are passed to all "
1181           "test programs run by this program.\n");
1182
1183   exit(ret);
1184 }
1185
1186 /* Here's what drives it all */
1187 int
1188 main(int argc, char **argv)
1189 {
1190   int c;
1191
1192   FD_ZERO(&glob_data.read_fds); /* first, zero out the fd set */
1193
1194   if (!(glob_data.prog_name = strrchr(argv[0], '/'))) /* set program name */
1195     glob_data.prog_name = argv[0];
1196   else
1197     glob_data.prog_name++;
1198
1199   while ((c = getopt(argc, argv, "c:l:I:i:j:vph")) != EOF)
1200     switch (c) {
1201     case 'c': /* set the config file */
1202       glob_data.conf_file = optarg;
1203       break;
1204
1205     case 'l': /* set the log file */
1206       glob_data.log_file = optarg;
1207       break;
1208
1209     case 'I': /* add an include dir */
1210     case 'i':
1211       if (!glob_data.include_dirs)
1212         glob_data.include_dirs =
1213           xmalloc(sizeof(char *) * (glob_data.include_size = ARGBUF));
1214       add_include(optarg);
1215       break;
1216
1217     case 'j': /* maximum number of simultaneous test programs to run */
1218       if ((glob_data.prog_max = atoi(optarg)) < 1) {
1219         fprintf(stderr, "%s: Must be able to run a test program!\n",
1220                 glob_data.prog_name);
1221         usage(1);
1222       }
1223       break;
1224
1225     case 'v': /* requesting verbosity */
1226       if (glob_data.flags & FLAG_VERBOSE)
1227         glob_data.flags |= FLAG_VVERBOSE; /* double-verbosity! */
1228       else
1229         glob_data.flags |= FLAG_VERBOSE;
1230       break;
1231
1232     case 'p': /* requesting POSIX-compliant output */
1233       glob_data.flags |= FLAG_POSIX;
1234       break;
1235
1236     case 'h': /* requesting a usage message */
1237       usage(0);
1238       break;
1239
1240     case '?': /* unknown option */
1241     default:
1242       usage(1);
1243       break;
1244     }
1245
1246   if (optind < argc) {
1247     glob_data.argv = argv + optind; /* save trailing arguments */
1248     glob_data.argc = argc - optind;
1249   }
1250
1251   if ((glob_data.log_fd =
1252        open(glob_data.log_file, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
1253             S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) {
1254     fprintf(stderr, "%s: Cannot open log file: %s\n", glob_data.prog_name,
1255             strerror(errno));
1256     exit(1);
1257   } else if (!(glob_data.log_fp = fdopen(glob_data.log_fd, "a"))) {
1258     perror(glob_data.prog_name);
1259     exit(1);
1260   }
1261
1262   setvbuf(glob_data.log_fp, 0, _IOLBF, 0);
1263
1264   read_conf(glob_data.conf_file); /* read the configuration file */
1265   run_test(); /* start some tests */
1266   run_select(); /* now wait for the test results */
1267
1268   report_test(); /* issue final report */
1269
1270   return 0;
1271 }