1 #define _POSIX_C_SOURCE 200809L
13 #define GREATER(x,y) ( ((x) > (y)) ? (x) : (y) )
16 int test; /* i.e. the number of the test */
17 char *name; /* name of the test, malloc */
18 int pass; /* if the test passed */
19 struct result *next; /* so we can make a linked list */
30 int todo, todo_pass, todo_fail;
32 struct result *results;
33 struct result *last; /* last result, so we append rather than push */
37 int lookfor(char *needle, char *haystack, size_t n) {
38 return strncasecmp(needle, haystack, n) == 0;
41 int rmatch(const regex_t *preg, const char *string, regmatch_t pmatch[]) {
42 return regexec(preg, string, 4, pmatch, 0) == 0;
45 /* write n backspaces to stdout */
47 while (n--) putchar('\b');
51 while (n--) putchar(' ');
54 int runone(struct testrun *run) {
63 if (pipe(pipefd) == -1) {
76 close(pipefd[0]); /* close read end */
77 close(1); /* close stdout */
78 dup(pipefd[1]); /* set stdout to the write end of the pipe */
79 execlp(run->name, run->name, NULL);
80 /* only gets here if exec failed */
81 printf("Bail Out! exec %s failed: %s\n", run->name, strerror(errno));
86 /* parent continue on */
87 tap = fdopen(pipefd[0], "r");
89 regex_t plan, diagnostic, bail, test, directive, version;
92 regcomp(&plan, "^[[:digit:]]+\\.\\.([[:digit:]]+)", REG_EXTENDED);
93 regcomp(&diagnostic, "#[[:space:]]*(.+)", REG_EXTENDED);
94 regcomp(&bail, "^Bail Out![[:space:]]*(.+)", REG_EXTENDED);
95 regcomp(&version, "^TAP Version ([[:digit:]]+)", REG_EXTENDED);
97 regcomp(&test, "^(not )?ok([[:space:]]*([[:digit:]]+))?", REG_EXTENDED);
98 regcomp(&directive, "#[[:space:]]*([^[:space:]]+)([[:space:]]+(.+))?",
99 REG_EXTENDED|REG_ICASE);
102 /* description: ok \d+([^#]+) */
107 /* bail out: ^Bail out! */
108 /* diagnostic: ^#.+ */
109 /* start of yaml: ^\s+--- */
110 /* end of yaml: ^\s+... */
111 /* between yaml lines must be indented */
112 /* anything else is an error */
114 while ((nread = getline(&line, &len, tap)) != -1) {
115 /* version: ^TAP version \d+ */
117 /* plan: ^\d+\.\.\d+ */
118 if (regexec(&plan, line, 2, match, 0) != REG_NOMATCH) {
119 run->plan = atoi(line+match[1].rm_so);
120 /* TODO plans can have a diag skip reason */
124 /* not ok: ^not ok \d+ */
125 if (rmatch(&test, line, match)) {
133 if (run->interactive) {
135 written = printf("%d/%d", run->ran, run->plan);
140 /* if the "not" match fails, the test passed */
141 pass = match[1].rm_so == -1;
144 test = atoi(line+match[3].rm_so);
147 if (rmatch(&directive, line, match)) {
148 todo = lookfor("todo", line+match[1].rm_so, 4);
149 skip = lookfor("skip", line+match[1].rm_so, 4);
152 /* check for diagnostics */
153 if (test != run->ran) {
154 fprintf(stderr, "expected test %d, got %d\n", run->ran+1, test);
157 /* pass, todo, or skip are all pass */
158 if (pass || todo || skip) {
172 if (!pass && !todo) {
174 r = malloc(sizeof *r);
185 /* push test number onto fail list */
189 else if (rmatch(&diagnostic, line, match)) {
192 else if (rmatch(&version, line, match)) {
193 run->version = atoi(line + match[1].rm_so);
195 else if (rmatch(&bail, line, match)) {
199 printf("Unknown line: %s", line);
202 /* not ok: ^not ok \d+ */
203 /* description: ok \d+([^#]+) */
208 /* bail out: ^Bail out! */
209 /* diagnostic: ^#.+ */
210 /* start of yaml: ^\s+--- */
211 /* end of yaml: ^\s+... */
212 /* between yaml lines must be indented */
213 /* anything else is an error */
224 if (run->ran < run->plan) {
225 run->fail += run->plan - run->ran;
228 if (run->interactive) {
238 void json(struct testrun *total, struct testrun *runs, int nruns) {
239 if (total && runs && nruns) {
245 int main(int ac, char *av[]) {
249 struct testrun total = { 0 };
250 struct testrun run = { 0 };
251 struct testrun clear = { 0 };
252 struct testrun *runs;
258 * -i interactive, regardless of tty
263 int interactive = 0, json = 0, summary = 1, progress = 1, detail = 0;
265 interactive = isatty(1);
267 /* TODO -jj turn off everything else? */
268 /* TODO -cC color escapes */
269 while ((opt = getopt(ac, av, "jJpPiIsSdD")) != -1) {
271 case 'j': json = 1; break;
272 case 'J': json = 0; break;
273 case 'p': progress = 1; break;
274 case 'P': progress = 0; break;
275 case 'i': interactive = 1; break;
276 case 'I': interactive = 0; break;
277 case 's': summary = 1; break;
278 case 'S': summary = 0; break;
279 case 'd': detail = 1; break;
280 case 'D': detail = 0; break;
282 fprintf(stderr, "Usage: %s [-jpisd] file...\n",
288 runs = malloc(ac * sizeof *runs);
290 for (i=optind; i<ac; i++) {
292 maxnamelen = GREATER(len, maxnamelen);
295 for (i=optind; i<ac; i++) {
298 run.interactive = interactive;
301 printf("%s ", run.name);
302 len = strlen(run.name);
303 for (j=len;j<maxnamelen+2;j++) {
311 total.plan += run.plan;
312 total.ran += run.ran;
313 total.pass += run.pass;
314 total.fail += run.fail;
315 total.skip += run.skip;
319 printf("Bail out!\n");
321 if (run.fail > 0 ) printf("not ");
326 runs[i-optind] = run;
330 printf("ran: %d/%d, pass: %d, fail: %d, skip: %d",
331 total.ran, total.plan, total.pass, total.fail,
334 if (total.plan > 0) {
335 printf(", %.2f%% ok",
336 100.0*(double)total.pass/(double)total.plan
344 printf("detail here\n");
348 printf("{ \"plan\": %d, \"ran\": %d, \"pass\": %d, \"fail\": %d }\n",
349 total.plan, total.ran, total.pass, total.fail
354 Files=8, Tests=508, 0 wallclock secs ( 0.08 usr + 0.01 sys = 0.09 CPU)
358 return total.fail ? EXIT_FAILURE : EXIT_SUCCESS;