1 #define _POSIX_C_SOURCE 200809L
13 #define GREATER(x,y) ( ((x) > (y)) ? (x) : (y) )
14 #define WARN(x) fprintf(stderr, "%s:%d %s\n", __FILE__, __LINE__, x)
17 int test; /* i.e. the number of the test */
18 char *name; /* name of the test, malloc */
19 int pass; /* if the test passed */
20 struct result *next; /* so we can make a linked list */
32 int todo, todo_pass, todo_fail;
34 struct result *results;
35 struct result *last; /* last result, so we append rather than push */
39 int lookfor(char *needle, char *haystack, size_t n) {
40 return strncasecmp(needle, haystack, n) == 0;
43 int rmatch(const regex_t *preg, const char *string, regmatch_t pmatch[]) {
44 return regexec(preg, string, 4, pmatch, 0) == 0;
47 /* write n backspaces to stdout */
49 while (n--) putchar('\b');
53 while (n--) putchar(' ');
56 int runone(struct testrun *run) {
65 if (!run->readstdin) {
66 if (pipe(pipefd) == -1) {
79 close(pipefd[0]); /* close read end */
80 close(1); /* close stdout */
81 dup(pipefd[1]); /* set stdout to the write end of the pipe */
82 execlp(run->name, run->name, NULL);
83 /* only gets here if exec failed */
84 printf("Bail Out! exec %s failed: %s\n", run->name, strerror(errno));
89 /* parent continue on */
90 tap = fdopen(pipefd[0], "r");
92 perror("tap is null:");
99 regex_t plan, diagnostic, bail, test, directive, version;
102 regcomp(&plan, "^[[:digit:]]+\\.\\.([[:digit:]]+)", REG_EXTENDED);
103 regcomp(&diagnostic, "#[[:space:]]*(.+)", REG_EXTENDED);
104 regcomp(&bail, "^Bail Out![[:space:]]*(.+)", REG_EXTENDED|REG_ICASE);
105 regcomp(&version, "^TAP Version ([[:digit:]]+)", REG_EXTENDED);
107 regcomp(&test, "^(not )?ok([[:space:]]*([[:digit:]]+))?", REG_EXTENDED);
108 regcomp(&directive, "#[[:space:]]*([^[:space:]]+)([[:space:]]+(.+))?",
109 REG_EXTENDED|REG_ICASE);
112 /* description: ok \d+([^#]+) */
117 /* bail out: ^Bail out! */
118 /* diagnostic: ^#.+ */
119 /* start of yaml: ^\s+--- */
120 /* end of yaml: ^\s+... */
121 /* between yaml lines must be indented */
122 /* anything else is an error */
124 while ((nread = getline(&line, &len, tap)) != -1) {
125 /* version: ^TAP version \d+ */
127 /* plan: ^\d+\.\.\d+ */
128 if (regexec(&plan, line, 2, match, 0) != REG_NOMATCH) {
129 run->plan = atoi(line+match[1].rm_so);
130 /* TODO plans can have a diag skip reason */
134 /* not ok: ^not ok \d+ */
135 if (rmatch(&test, line, match)) {
143 if (run->interactive) {
145 written = printf("%d/%d", run->ran, run->plan);
150 /* if the "not" match fails, the test passed */
151 pass = match[1].rm_so == -1;
154 test = atoi(line+match[3].rm_so);
157 if (rmatch(&directive, line, match)) {
158 todo = lookfor("todo", line+match[1].rm_so, 4);
159 skip = lookfor("skip", line+match[1].rm_so, 4);
162 /* check for diagnostics */
163 if (test != run->ran) {
164 fprintf(stderr, "expected test %d, got %d\n", run->ran+1, test);
167 /* pass, todo, or skip are all pass */
168 if (pass || todo || skip) {
182 if (!pass && !todo) {
184 r = malloc(sizeof *r);
195 /* push test number onto fail list */
199 else if (rmatch(&diagnostic, line, match)) {
202 else if (rmatch(&version, line, match)) {
203 run->version = atoi(line + match[1].rm_so);
205 else if (rmatch(&bail, line, match)) {
209 printf("Unknown line: %s", line);
212 /* not ok: ^not ok \d+ */
213 /* description: ok \d+([^#]+) */
218 /* bail out: ^Bail out! */
219 /* diagnostic: ^#.+ */
220 /* start of yaml: ^\s+--- */
221 /* end of yaml: ^\s+... */
222 /* between yaml lines must be indented */
223 /* anything else is an error */
231 if (!run->readstdin) {
236 if (run->ran < run->plan) {
237 run->fail += run->plan - run->ran;
240 if (run->interactive) {
250 void json(struct testrun *total, struct testrun *runs, int nruns) {
251 if (total && runs && nruns) {
257 int main(int ac, char *av[]) {
261 struct testrun total = { 0 };
262 struct testrun run = { 0 };
263 struct testrun clear = { 0 };
264 struct testrun *runs;
266 char *stdinname = "<stdin>";
267 int stdinnamelen = 0;
272 * -i interactive, regardless of tty
277 int interactive = 0, json = 0, summary = 1, progress = 1, detail = 0;
279 interactive = isatty(1);
281 /* TODO -jj turn off everything else? */
282 /* TODO -cC color escapes */
283 while ((opt = getopt(ac, av, "jJpPiIsSdDn:")) != -1) {
285 case 'j': json = 1; break;
286 case 'J': json = 0; break;
287 case 'p': progress = 1; break;
288 case 'P': progress = 0; break;
289 case 'i': interactive = 1; break;
290 case 'I': interactive = 0; break;
291 case 's': summary = 1; break;
292 case 'S': summary = 0; break;
293 case 'd': detail = 1; break;
294 case 'D': detail = 0; break;
295 case 'n': stdinname = optarg;
298 fprintf(stderr, "Usage: %s [-jpisd] file...\n",
303 stdinnamelen = strlen(stdinname);
305 runs = malloc(ac * sizeof *runs);
307 for (i=optind; i<ac; i++) {
308 if (!strcmp(av[i], "-")) {
309 maxnamelen = GREATER(stdinnamelen, maxnamelen);
312 maxnamelen = GREATER(len, maxnamelen);
316 for (i=optind; i<ac; i++) {
318 run.interactive = interactive;
320 if (!strcmp(av[i], "-")) {
322 run.name = stdinname;
329 printf("%s ", run.name);
330 len = strlen(run.name);
331 for (j=len;j<maxnamelen+2;j++) {
339 total.plan += run.plan;
340 total.ran += run.ran;
341 total.pass += run.pass;
342 total.fail += run.fail;
343 total.skip += run.skip;
347 printf("Bail out!\n");
349 if (run.fail > 0 ) printf("not ");
354 runs[i-optind] = run;
358 printf("ran: %d/%d, pass: %d, fail: %d, skip: %d",
359 total.ran, total.plan, total.pass, total.fail,
362 if (total.plan > 0) {
363 printf(", %.2f%% ok",
364 100.0*(double)total.pass/(double)total.plan
372 printf("detail here\n");
376 printf("{ \"plan\": %d, \"ran\": %d, \"pass\": %d, \"fail\": %d }\n",
377 total.plan, total.ran, total.pass, total.fail
382 Files=8, Tests=508, 0 wallclock secs ( 0.08 usr + 0.01 sys = 0.09 CPU)
386 return total.fail ? EXIT_FAILURE : EXIT_SUCCESS;