2 ** Copyright (C) 2002 by Kevin L. Mitchell <klmitch@mit.edu>
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.
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.
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,
30 #include <sys/types.h>
34 #define BUFSIZE 512 /* buffer size */
35 #define ARGBUF 8 /* number of arguments to start with */
37 #define CONTINUELINE 0x1000 /* line continuation "character" */
39 #define Q_D -3 /* quote decimal */
40 #define Q_X -2 /* quote hexadecimal */
41 #define Q_S -1 /* quote self */
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 */
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 */
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 */
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 */
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 */
98 struct test_dep *td_next; /* next dependency */
99 struct test_prog *td_prog; /* program we're dependent upon */
103 const char *m_name; /* name for value */
104 enum test_result m_value; /* value for name */
106 #define M(name) { #name, TR_ ## name }
107 M(NOTRUN), M(PASS), M(UPASS), M(FAIL), M(XFAIL), M(UNRESOLVED), M(UNTESTED),
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
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 } };
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 */
149 /* If allocation of memory fails, we must exit */
155 if (!(ptr = malloc(size))) { /* get some memory */
156 perror(glob_data.prog_name); /* error occurred, bail out */
160 return ptr; /* return the memory allocated */
163 /* Similar function to realloc memory */
165 xrealloc(void *ptr, size_t size)
169 if (!(nptr = realloc(ptr, size))) { /* reallocate the memory */
170 perror(glob_data.prog_name); /* error occurred, bail out */
174 return nptr; /* return new memory allocation */
177 /* Duplicate a string */
179 xstrdup(const char *str)
183 if (!str) /* if no string to allocate, allocate none */
186 if (!(ptr = strdup(str))) { /* duplicate the string */
187 perror(glob_data.prog_name); /* error occurred, bail out */
191 return ptr; /* return the new string */
194 /* Duplicate a parameter list */
196 argvdup(char * const *params, int count)
201 if (!params) /* no parameters to duplicate? */
204 if (count <= 0) /* must count it ourselves */
205 for (count = 0; params[count]; count++) /* count through params */
206 ; /* empty for loop */
208 nparams = xmalloc(sizeof(char *) * (count + 1)); /* allocate memory */
210 for (i = 0; i < count; i++) /* go through params */
211 nparams[i] = xstrdup(params[i]); /* and duplicate each one */
213 nparams[count] = 0; /* zero out end of vector */
215 return nparams; /* and return the vector */
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)
223 struct test_prog *tp;
225 tp = xmalloc(sizeof(struct test_prog)); /* allocate memory */
227 tp->tp_next = 0; /* initialize the struct test_prog */
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);
237 tp->tp_status = TP_PENDING;
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;
249 glob_data.prog_last = tp;
250 glob_data.prog_count++;
252 return tp; /* return it */
255 /* Create a new test */
257 add_test(struct test_prog *tp, const char *name, const char *desc,
258 enum test_result expect)
262 t = xmalloc(sizeof(struct test)); /* allocate memory */
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;
272 if (!tp->tp_tests) /* add it to the program's data list */
274 else if (tp->tp_last)
275 tp->tp_last->t_next = t;
277 tp->tp_count++; /* keep count */
279 glob_data.test_total++; /* total number of tests */
280 glob_data.result_count[TR_NOTRUN]++; /* keep a count */
282 return t; /* return it */
285 /* Add a dependency */
286 static struct test_dep *
287 add_dep(struct test_prog *tp, struct test_prog *dep)
291 td = xmalloc(sizeof(struct test_dep)); /* allocate memory */
293 td->td_next = tp->tp_deps; /* initialize dependency structure */
296 tp->tp_deps = td; /* add it to the list */
298 return td; /* return dependency */
301 /* Close all test program input file descriptors before forking */
305 struct test_prog *tp;
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! */
312 /* Find a test program given its name */
313 static struct test_prog *
314 find_prog(const char *name)
316 struct test_prog *tp;
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 */
322 return 0; /* nothing found */
325 /* Find a test given its name */
327 find_test(struct test_prog *tp, const char *name)
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 */
335 return 0; /* nothing found */
338 /* Get a value given a name */
339 static enum test_result
340 find_result(const char *name)
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 */
348 return TR_UNRESOLVED; /* couldn't map name to value; must be resolved */
351 /* Get a name given a value */
352 #define name_result(val) (result_map[(val)].m_name)
354 /* Set the result of a given test */
356 set_result(struct test *t, enum test_result result, const char *info)
358 glob_data.result_count[t->t_result]--; /* decrement one count */
360 switch (result) { /* check result */
361 case TR_NOTRUN: /* these should never be reported by a test program */
364 t->t_result = TR_UNRESOLVED; /* so mark it unresolved due to test error */
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 */
371 t->t_result = TR_PASS; /* normal pass */
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 */
378 t->t_result = TR_FAIL; /* wasn't expected to fail! */
382 t->t_result = result; /* some other result */
386 glob_data.result_count[t->t_result]++; /* increment another count */
388 t->t_info = xstrdup(info); /* remember extra information */
390 result = t->t_result;
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);
397 if (!(glob_data.flags & FLAG_VERBOSE)) /* only output if verbose */
400 if (glob_data.flags & FLAG_POSIX) { /* adjust for POSIX */
401 if (result == TR_UPASS)
403 else if (result == TR_XFAIL)
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);
414 /* Mark all tests for a given program unresolved */
416 mark_all(struct test_prog *tp, enum test_result result, const char *info)
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 */
425 /* Find the test program */
427 locate_file(const char *prog, char *buf, int mode)
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];
436 sprintf(buf, "%s/%s", dir, prog); /* form program file name */
438 if (!access(buf, mode)) /* check access */
439 return buf; /* Ok, return program name */
442 sprintf(buf, "./%s", prog); /* form program file name */
444 if (!access(buf, mode)) /* check access */
445 return buf; /* Ok, return program name */
448 return 0; /* failed to find the program */
451 /* Build an argument list for executing a program */
453 build_argv(char *name, char **params)
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 */
462 count += i; /* now add them to the count */
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 */
469 count += i; /* now add them to the count */
472 nparams = xmalloc(sizeof(char *) * (count + 1)); /* allocate memory */
474 nparams[0] = name; /* program name, first */
475 count = 1; /* next place to put an argument */
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 */
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 */
485 nparams[count] = 0; /* end with a 0 value */
487 return nparams; /* return parameters */
490 /* Execute a test program */
492 execute_prog(struct test_prog *tp)
494 int fds[2], stat, err;
495 char progbuf[BUFSIZE], *prog;
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;
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;
509 switch ((tp->tp_pid = fork())) {
510 case -1: /* couldn't fork! */
511 err = errno; /* save errno */
512 close(fds[0]); /* close file descriptors */
514 mark_all(tp, TR_UNRESOLVED, strerror(err));
515 tp->tp_status = TP_FAILED;
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 */
526 dup2(fds[1], 1); /* make stdout point to write end of pipe */
527 close(fds[1]); /* close redundant file descriptor */
529 dup2(glob_data.log_fd, 2); /* make stderr go to log file */
530 close(glob_data.log_fd); /* close redundant file descriptor */
532 execv(prog, build_argv(prog, tp->tp_argv)); /* execute program */
534 printf("UNRESOLVED/ALL:Couldn't execute test: %s\n", strerror(errno));
535 exit(1); /* report error and exit child */
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;
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];
561 /* Find a test program to execute */
562 static struct test_prog *
563 next_test(struct test_prog *tp)
567 if (!tp) /* start at beginning of prog list if no test prog passed in */
568 tp = glob_data.prog_list;
570 for (; tp; tp = tp->tp_next) {
571 if (tp->tp_status != TP_PENDING) /* skip non-pending tests */
574 if (!tp->tp_deps) /* no dependencies? just return it */
577 for (td = tp->tp_deps; td; td = td->td_next) {
578 if (!td->td_prog) /* broken dependency, ignore it */
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 */
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 */
591 case TP_COMPLETE: /* dependent test is complete, cool */
592 continue; /* Go check next dependency */
599 if (!td) /* checked all dependencies, everything checks out. */
606 /* Start some test programs */
610 struct test_prog *tp;
612 if (glob_data.flags & FLAG_FINISHED) /* are we already done? */
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 */
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 */
633 /* Inform user about dependancy loop */
634 fprintf(stderr, "Dependencies for %s failed due to cycle\n",
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;
643 glob_data.flags |= FLAG_FINISHED; /* we're done! */
647 /* Place the results of the test in a file */
649 report_test_fp(FILE *fp, int posix)
653 fprintf(fp, "================== Test Results Summary ==================\n");
655 fprintf(fp, "Total tests: %d\n", glob_data.test_total);
657 if (glob_data.result_count[TR_NOTRUN] > 0)
658 fprintf(fp, "Tests not run: %d\n", glob_data.result_count[TR_NOTRUN]);
660 count = glob_data.result_count[TR_PASS] + glob_data.result_count[TR_UPASS];
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]);
670 count = glob_data.result_count[TR_FAIL] + glob_data.result_count[TR_XFAIL];
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]);
680 if (glob_data.result_count[TR_UNRESOLVED] > 0)
681 fprintf(fp, "Unresolved tests: %d\n",
682 glob_data.result_count[TR_UNRESOLVED]);
684 if (glob_data.result_count[TR_UNTESTED] > 0)
685 fprintf(fp, "Untested test cases: %d\n",
686 glob_data.result_count[TR_UNTESTED]);
688 if (glob_data.result_count[TR_UNSUPPORTED] > 0)
689 fprintf(fp, "Unsupported tests: %d\n",
690 glob_data.result_count[TR_UNSUPPORTED]);
692 fprintf(fp, "==========================================================\n");
695 /* Report the results of the test */
699 report_test_fp(glob_data.log_fp, 0);
700 report_test_fp(stdout, glob_data.flags & FLAG_POSIX);
703 /* Read data from a test program */
705 read_test(struct test_prog *tp)
709 enum test_result result;
710 char buf[BUFSIZE], *rescode = 0, *testcode = 0, *info = 0, *tmp;
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;
723 mark_all(tp, TR_UNRESOLVED, "Test program exited with unknown code");
724 tp->tp_status = TP_FAILED;
727 close(tp->tp_fd); /* close file descriptor */
728 fclose(tp->tp_file); /* close file object */
730 FD_CLR(tp->tp_fd, &glob_data.read_fds); /* clear fd bit in read set */
732 tp->tp_fd = -1; /* clear out the struct test_prog object's run status */
736 if (--glob_data.prog_running < glob_data.prog_max)
737 run_test(); /* freed up a slot for a new test program */
739 if ((tmp = strchr(buf, '\n'))) /* remove newline */
742 rescode = buf; /* find result code */
744 if (!(testcode = strchr(buf, '/'))) /* locate test code */
747 *(testcode++) = '\0'; /* nul-terminate and advance in one swell foop */
749 if ((info = strchr(testcode, ':'))) /* locate extra info */
750 *(info++) = '\0'; /* nul-terminate and advance in one swell foop */
752 result = find_result(rescode); /* get the result */
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 */
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);
771 /* Wait for data from test programs */
777 struct test_prog *tp;
779 while (glob_data.prog_running || !(glob_data.flags & FLAG_FINISHED)) {
780 read_set = glob_data.read_fds; /* all hail structure copy! */
782 nfds = select(glob_data.high_fd + 1, &read_set, 0, 0, 0);
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? */
793 /* Retrieves a \ quoted sequence from a file and produces the correct
799 int c = 0, i, cnt = 0;
801 if ((i = getc(fp)) == EOF) /* retrieve the character after the \ */
804 if (i == '\n') /* just a line continuation */
806 if (i >= 'a' && i <= 'z') { /* special lower-case letters... */
807 switch (xform[i - 'a']) {
808 case Q_S: /* insert the character itself */
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 */
816 return cnt ? c : EOF; /* return error if no chars */
818 if (isdigit(i)) /* calculate character code */
819 c = (c << 4) | (i - '0');
821 c = (c << 4) | ((i - 'A') + 10);
823 c = (c << 4) | ((i - 'a') + 10);
827 return cnt ? c : EOF; /* return error if no chars */
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 */
834 return cnt ? c : EOF; /* return error if no chars */
836 c = (c * 10) + (i - '0'); /* calculate character code */
837 if (c > 255) { /* oops, char overflow, backup */
844 return cnt ? c : EOF; /* return error if no chars */
847 default: /* insert the specified code */
848 return xform[i - 'a'];
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 */
858 c = (c << 3) | (i - '0'); /* accumulate the next code */
864 return i; /* return the character itself */
867 /* Create a program */
869 create_prog(const char *name, const char *prog, const char *desc,
870 char * const *params, int count)
872 struct test_prog *tp;
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);
878 add_prog(name, prog, desc, params, count);
881 /* Create a test case */
883 create_test(const char *name, const char *prog, const char *expect_code,
886 struct test_prog *tp;
888 enum test_result expect;
890 switch ((expect = find_result(expect_code))) {
891 case TR_NOTRUN: /* invalid values for expectation */
897 fprintf(stderr, "%s: Invalid expectation code \"%s\" for test \"%s\"; "
898 "ignoring\n", glob_data.prog_name, expect_code, name);
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);
916 /* Create a dependency */
918 create_dep(const char *prog, char * const *deps, int count)
920 struct test_prog *tp, *dep;
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,
935 /* Walk through the dependencies to weed out duplicates */
936 for (td = tp->tp_deps; ; td = td->td_next)
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 */
945 /* Report too few arguments problems */
947 check_args(int count, int min, char *directive)
949 if (count >= min) /* Check argument count */
952 fprintf(stderr, "%s: Too few arguments for %s directive\n",
953 glob_data.prog_name, directive);
957 /* Read the configuration file */
959 read_conf(char *filename)
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];
966 int buflen = BUFSIZE, pos = 0, arglen = ARGBUF, argidx = 0, c, flags = 0;
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);
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);
982 buf = xmalloc(buflen); /* get some memory for the buffers */
983 args = xmalloc(sizeof(char *) * arglen);
985 while (!(flags & STOP)) {
986 c = getc(fp); /* get a character */
987 flags &= ~(QUOTED | EOS | EOD); /* reset quoted and end-of-* flags */
989 if (state == s_comment) {
990 if (c != '\n' && c != EOF)
991 continue; /* skip comment... */
993 state = save; /* but preserve ending character and process */
996 if (flags & SAVE_QUOTE)
997 flags = (flags & ~SAVE_QUOTE) | QUOTED;
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 */
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);
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]);
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 */
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);
1034 } else if (c == CONTINUELINE) /* continue line--not a quoted char */
1037 flags |= QUOTED; /* mark character as quoted */
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!
1044 if (flags & (QUOTE_SINGLE | QUOTE_DOUBLE | QUOTE_FILE)) {
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);
1052 } else if (!(flags & QUOTE_FILE)) /* file quotes aren't real quotes */
1053 flags |= QUOTED; /* mark character as quoted */
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 */
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 */
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 */
1079 if (c == '\n') /* hit end of line? */
1080 flags |= EOD; /* end of the directive, then */
1082 continue; /* move on to next character */
1085 case s_comment: /* should never be in this state here */
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.
1095 flags |= EOS | (c == '\n' ? EOD : 0);
1100 if (pos + 1 > buflen) /* get more memory for buffer if needed */
1101 buf = xrealloc(buf, buflen <<= 1);
1103 if (flags & EOS) /* end of string? */
1104 buf[pos++] = '\0'; /* end the string in the buffer */
1106 if (flags & EOD) { /* end of directive? */
1107 if (pos == 0) /* nothing's actually been accumulated */
1108 continue; /* so go on */
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]);
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);
1124 fprintf(stderr, "%s: Unknown directive \"%s\"\n", glob_data.prog_name,
1129 if (flags & STOP) /* if we were told to stop, well, STOP! */
1132 pos = 0; /* prepare for the next round through */
1137 continue; /* go on */
1140 if (!(flags & (EOS | EOD))) /* insert character into buffer */
1144 fclose(fp); /* clean up */
1149 /* Add another include directory to the list */
1151 add_include(char *dir)
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;
1160 /* Output a standard usage message and exit */
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 "
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 "
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");
1186 /* Here's what drives it all */
1188 main(int argc, char **argv)
1192 FD_ZERO(&glob_data.read_fds); /* first, zero out the fd set */
1194 if (!(glob_data.prog_name = strrchr(argv[0], '/'))) /* set program name */
1195 glob_data.prog_name = argv[0];
1197 glob_data.prog_name++;
1199 while ((c = getopt(argc, argv, "c:l:I:i:j:vph")) != EOF)
1201 case 'c': /* set the config file */
1202 glob_data.conf_file = optarg;
1205 case 'l': /* set the log file */
1206 glob_data.log_file = optarg;
1209 case 'I': /* add an include dir */
1211 if (!glob_data.include_dirs)
1212 glob_data.include_dirs =
1213 xmalloc(sizeof(char *) * (glob_data.include_size = ARGBUF));
1214 add_include(optarg);
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);
1225 case 'v': /* requesting verbosity */
1226 if (glob_data.flags & FLAG_VERBOSE)
1227 glob_data.flags |= FLAG_VVERBOSE; /* double-verbosity! */
1229 glob_data.flags |= FLAG_VERBOSE;
1232 case 'p': /* requesting POSIX-compliant output */
1233 glob_data.flags |= FLAG_POSIX;
1236 case 'h': /* requesting a usage message */
1240 case '?': /* unknown option */
1246 if (optind < argc) {
1247 glob_data.argv = argv + optind; /* save trailing arguments */
1248 glob_data.argc = argc - optind;
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,
1257 } else if (!(glob_data.log_fp = fdopen(glob_data.log_fd, "a"))) {
1258 perror(glob_data.prog_name);
1262 setvbuf(glob_data.log_fp, 0, _IOLBF, 0);
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 */
1268 report_test(); /* issue final report */