9 #define GREATER(x,y) ( ((x) > (y)) ? (x) : (y) )
12 int test; /* i.e. the number of the test */
13 char *name; /* name of the test, malloc */
14 int pass; /* if the test passed */
15 struct result *next; /* so we can make a linked list */
26 int todo, todo_pass, todo_fail;
28 struct result *results;
29 struct result *last; /* last result, so we append rather than push */
33 int lookfor(char *needle, char *haystack, size_t n) {
34 return strncasecmp(needle, haystack, n) == 0;
37 int rmatch(const regex_t *preg, const char *string, regmatch_t pmatch[]) {
38 return regexec(preg, string, 4, pmatch, 0) == 0;
41 /* write n backspaces to stdout */
43 while (n--) putchar('\b');
47 while (n--) putchar(' ');
50 int runone(struct testrun *run) {
55 size_t nread, len = 0;
58 if (pipe(pipefd) == -1) {
71 close(pipefd[0]); /* close read end */
72 close(1); /* close stdout */
73 dup(pipefd[1]); /* set stdout to the write end of the pipe */
74 execlp(run->name, run->name, NULL);
78 /* parent continue on */
79 tap = fdopen(pipefd[0], "r");
81 regex_t plan, diagnostic, bail, test, directive, version;
84 regcomp(&plan, "^[[:digit:]]+\\.\\.([[:digit:]]+)", REG_EXTENDED);
85 regcomp(&diagnostic, "#[[:space:]]*(.+)", REG_EXTENDED);
86 regcomp(&bail, "^Bail Out![[:space:]]*(.+)", REG_EXTENDED);
87 regcomp(&version, "^TAP Version ([[:digit:]]+)", REG_EXTENDED);
89 regcomp(&test, "^(not )?ok([[:space:]]*([[:digit:]]+))?", REG_EXTENDED);
90 regcomp(&directive, "#[[:space:]]*([^[:space:]]+)([[:space:]]+(.+))?",
91 REG_EXTENDED|REG_ICASE);
94 /* description: ok \d+([^#]+) */
99 /* bail out: ^Bail out! */
100 /* diagnostic: ^#.+ */
101 /* start of yaml: ^\s+--- */
102 /* end of yaml: ^\s+... */
103 /* between yaml lines must be indented */
104 /* anything else is an error */
106 while ((nread = getline(&line, &len, tap)) != -1) {
107 /* version: ^TAP version \d+ */
109 /* plan: ^\d+\.\.\d+ */
110 if (regexec(&plan, line, 2, match, 0) != REG_NOMATCH) {
111 run->plan = atoi(line+match[1].rm_so);
112 /* TODO plans can have a diag skip reason */
116 /* not ok: ^not ok \d+ */
117 if (rmatch(&test, line, match)) {
123 /* if the "not" match fails, the test passed */
124 pass = match[1].rm_so == -1;
127 test = atoi(line+match[3].rm_so);
130 if (rmatch(&directive, line, match)) {
131 todo = lookfor("todo", line+match[1].rm_so, 4);
132 skip = lookfor("skip", line+match[1].rm_so, 4);
135 /* check for diagnostics */
136 if (test != run->ran+1) {
137 fprintf(stderr, "expected test %d, got %d\n", run->ran+1, test);
157 r = malloc(sizeof *r);
168 /* push test number onto fail list */
171 if (run->interactive) {
173 written = printf("%d/%d", run->ran, run->plan);
177 else if (rmatch(&diagnostic, line, match)) {
180 else if (rmatch(&version, line, match)) {
181 run->version = atoi(line + match[1].rm_so);
183 else if (rmatch(&bail, line, match)) {
187 printf("Unknown line: %s", line);
190 /* not ok: ^not ok \d+ */
191 /* description: ok \d+([^#]+) */
196 /* bail out: ^Bail out! */
197 /* diagnostic: ^#.+ */
198 /* start of yaml: ^\s+--- */
199 /* end of yaml: ^\s+... */
200 /* between yaml lines must be indented */
201 /* anything else is an error */
212 if (run->ran < run->plan) {
213 run->fail += run->plan - run->ran;
216 if (run->interactive) {
226 void json(struct testrun *total, struct testrun *runs, int nruns) {
230 int main(int ac, char *av[]) {
234 struct testrun total = { 0 };
235 struct testrun run = { 0 };
236 struct testrun clear = { 0 };
237 struct testrun *runs;
243 * -i interactive, regardless of tty
248 int interactive = 0, json = 0, summary = 1, progress = 1, detail = 0;
250 interactive = isatty(1);
252 /* TODO -jj turn off everything else? */
253 /* TODO -cC color escapes */
254 while ((opt = getopt(ac, av, "jJpPiIsSdD")) != -1) {
256 case 'j': json = 1; break;
257 case 'J': json = 0; break;
258 case 'p': progress = 1; break;
259 case 'P': progress = 0; break;
260 case 'i': interactive = 1; break;
261 case 'I': interactive = 0; break;
262 case 's': summary = 1; break;
263 case 'S': summary = 0; break;
264 case 'd': detail = 1; break;
265 case 'D': detail = 0; break;
267 fprintf(stderr, "Usage: %s [-jpisd] file...\n",
273 runs = malloc(ac * sizeof *runs);
275 for (i=optind; i<ac; i++) {
277 maxnamelen = GREATER(len, maxnamelen);
280 for (i=optind; i<ac; i++) {
284 run.interactive = interactive;
287 printf("%s ", run.name);
288 len = strlen(run.name);
289 for (j=len;j<maxnamelen+2;j++) {
297 total.plan += run.plan;
298 total.ran += run.ran;
299 total.pass += run.pass;
300 total.fail += run.fail;
303 if (run.fail > 0 ) printf("not ");
307 runs[i-optind] = run;
311 printf("ran: %d/%d, pass: %d, fail: %d, %.2f%% ok\n",
312 total.ran, total.plan, total.pass, total.fail,
313 100.0*(double)total.pass/(double)total.plan
319 printf("detail here\n");
323 printf("{ \"plan\": %d, \"ran\": %d, \"pass\": %d, \"fail\": %d }\n",
324 total.plan, total.ran, total.pass, total.fail
329 Files=8, Tests=508, 0 wallclock secs ( 0.08 usr + 0.01 sys = 0.09 CPU)
333 return total.fail ? EXIT_FAILURE : EXIT_SUCCESS;