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 */
31 int todo, todo_pass, todo_fail;
33 struct result *results;
34 struct result *last; /* last result, so we append rather than push */
38 int lookfor(char *needle, char *haystack, size_t n) {
39 return strncasecmp(needle, haystack, n) == 0;
42 int rmatch(const regex_t *preg, const char *string, regmatch_t pmatch[]) {
43 return regexec(preg, string, 4, pmatch, 0) == 0;
46 /* write n backspaces to stdout */
48 while (n--) putchar('\b');
52 while (n--) putchar(' ');
55 int runone(struct testrun *run) {
64 if (!run->readstdin) {
65 if (pipe(pipefd) == -1) {
78 close(pipefd[0]); /* close read end */
79 close(1); /* close stdout */
80 dup(pipefd[1]); /* set stdout to the write end of the pipe */
81 execlp(run->name, run->name, NULL);
82 /* only gets here if exec failed */
83 printf("Bail Out! exec %s failed: %s\n", run->name, strerror(errno));
88 /* parent continue on */
89 tap = fdopen(pipefd[0], "r");
94 regex_t plan, diagnostic, bail, test, directive, version;
97 regcomp(&plan, "^[[:digit:]]+\\.\\.([[:digit:]]+)", REG_EXTENDED);
98 regcomp(&diagnostic, "#[[:space:]]*(.+)", REG_EXTENDED);
99 regcomp(&bail, "^Bail Out![[:space:]]*(.+)", REG_EXTENDED);
100 regcomp(&version, "^TAP Version ([[:digit:]]+)", REG_EXTENDED);
102 regcomp(&test, "^(not )?ok([[:space:]]*([[:digit:]]+))?", REG_EXTENDED);
103 regcomp(&directive, "#[[:space:]]*([^[:space:]]+)([[:space:]]+(.+))?",
104 REG_EXTENDED|REG_ICASE);
107 /* description: ok \d+([^#]+) */
112 /* bail out: ^Bail out! */
113 /* diagnostic: ^#.+ */
114 /* start of yaml: ^\s+--- */
115 /* end of yaml: ^\s+... */
116 /* between yaml lines must be indented */
117 /* anything else is an error */
119 while ((nread = getline(&line, &len, tap)) != -1) {
120 /* version: ^TAP version \d+ */
122 /* plan: ^\d+\.\.\d+ */
123 if (regexec(&plan, line, 2, match, 0) != REG_NOMATCH) {
124 run->plan = atoi(line+match[1].rm_so);
125 /* TODO plans can have a diag skip reason */
129 /* not ok: ^not ok \d+ */
130 if (rmatch(&test, line, match)) {
138 if (run->interactive) {
140 written = printf("%d/%d", run->ran, run->plan);
145 /* if the "not" match fails, the test passed */
146 pass = match[1].rm_so == -1;
149 test = atoi(line+match[3].rm_so);
152 if (rmatch(&directive, line, match)) {
153 todo = lookfor("todo", line+match[1].rm_so, 4);
154 skip = lookfor("skip", line+match[1].rm_so, 4);
157 /* check for diagnostics */
158 if (test != run->ran) {
159 fprintf(stderr, "expected test %d, got %d\n", run->ran+1, test);
162 /* pass, todo, or skip are all pass */
163 if (pass || todo || skip) {
177 if (!pass && !todo) {
179 r = malloc(sizeof *r);
190 /* push test number onto fail list */
194 else if (rmatch(&diagnostic, line, match)) {
197 else if (rmatch(&version, line, match)) {
198 run->version = atoi(line + match[1].rm_so);
200 else if (rmatch(&bail, line, match)) {
204 printf("Unknown line: %s", line);
207 /* not ok: ^not ok \d+ */
208 /* description: ok \d+([^#]+) */
213 /* bail out: ^Bail out! */
214 /* diagnostic: ^#.+ */
215 /* start of yaml: ^\s+--- */
216 /* end of yaml: ^\s+... */
217 /* between yaml lines must be indented */
218 /* anything else is an error */
226 if (!run->readstdin) {
231 if (run->ran < run->plan) {
232 run->fail += run->plan - run->ran;
235 if (run->interactive) {
245 void json(struct testrun *total, struct testrun *runs, int nruns) {
246 if (total && runs && nruns) {
252 int main(int ac, char *av[]) {
256 struct testrun total = { 0 };
257 struct testrun run = { 0 };
258 struct testrun clear = { 0 };
259 struct testrun *runs;
261 char *stdinname = "<stdin>";
262 int stdinnamelen = 0;
267 * -i interactive, regardless of tty
272 int interactive = 0, json = 0, summary = 1, progress = 1, detail = 0;
274 interactive = isatty(1);
276 /* TODO -jj turn off everything else? */
277 /* TODO -cC color escapes */
278 while ((opt = getopt(ac, av, "jJpPiIsSdDn:")) != -1) {
280 case 'j': json = 1; break;
281 case 'J': json = 0; break;
282 case 'p': progress = 1; break;
283 case 'P': progress = 0; break;
284 case 'i': interactive = 1; break;
285 case 'I': interactive = 0; break;
286 case 's': summary = 1; break;
287 case 'S': summary = 0; break;
288 case 'd': detail = 1; break;
289 case 'D': detail = 0; break;
290 case 'n': stdinname = optarg;
293 fprintf(stderr, "Usage: %s [-jpisd] file...\n",
298 stdinnamelen = strlen(stdinname);
300 runs = malloc(ac * sizeof *runs);
302 for (i=optind; i<ac; i++) {
303 if (!strcmp(av[i], "-")) {
304 maxnamelen = GREATER(stdinnamelen, maxnamelen);
307 maxnamelen = GREATER(len, maxnamelen);
311 for (i=optind; i<ac; i++) {
313 run.interactive = interactive;
315 if (!strcmp(av[i], "-")) {
317 run.name = stdinname;
324 printf("%s ", run.name);
325 len = strlen(run.name);
326 for (j=len;j<maxnamelen+2;j++) {
334 total.plan += run.plan;
335 total.ran += run.ran;
336 total.pass += run.pass;
337 total.fail += run.fail;
338 total.skip += run.skip;
342 printf("Bail out!\n");
344 if (run.fail > 0 ) printf("not ");
349 runs[i-optind] = run;
353 printf("ran: %d/%d, pass: %d, fail: %d, skip: %d",
354 total.ran, total.plan, total.pass, total.fail,
357 if (total.plan > 0) {
358 printf(", %.2f%% ok",
359 100.0*(double)total.pass/(double)total.plan
367 printf("detail here\n");
371 printf("{ \"plan\": %d, \"ran\": %d, \"pass\": %d, \"fail\": %d }\n",
372 total.plan, total.ran, total.pass, total.fail
377 Files=8, Tests=508, 0 wallclock secs ( 0.08 usr + 0.01 sys = 0.09 CPU)
381 return total.fail ? EXIT_FAILURE : EXIT_SUCCESS;