10 #define GREATER(x,y) ( ((x) > (y)) ? (x) : (y) )
13 int test; /* i.e. the number of the test */
14 char *name; /* name of the test, malloc */
15 int pass; /* if the test passed */
16 struct result *next; /* so we can make a linked list */
27 int todo, todo_pass, todo_fail;
29 struct result *results;
30 struct result *last; /* last result, so we append rather than push */
34 int lookfor(char *needle, char *haystack, size_t n) {
35 return strncasecmp(needle, haystack, n) == 0;
38 int rmatch(const regex_t *preg, const char *string, regmatch_t pmatch[]) {
39 return regexec(preg, string, 4, pmatch, 0) == 0;
42 /* write n backspaces to stdout */
44 while (n--) putchar('\b');
48 while (n--) putchar(' ');
51 int runone(struct testrun *run) {
56 size_t nread, len = 0;
59 if (pipe(pipefd) == -1) {
72 close(pipefd[0]); /* close read end */
73 close(1); /* close stdout */
74 dup(pipefd[1]); /* set stdout to the write end of the pipe */
75 execlp(run->name, run->name, NULL);
76 /* only gets here if exec failed */
77 printf("Bail Out! exec %s failed: %s\n", run->name, strerror(errno));
82 /* parent continue on */
83 tap = fdopen(pipefd[0], "r");
85 regex_t plan, diagnostic, bail, test, directive, version;
88 regcomp(&plan, "^[[:digit:]]+\\.\\.([[:digit:]]+)", REG_EXTENDED);
89 regcomp(&diagnostic, "#[[:space:]]*(.+)", REG_EXTENDED);
90 regcomp(&bail, "^Bail Out![[:space:]]*(.+)", REG_EXTENDED);
91 regcomp(&version, "^TAP Version ([[:digit:]]+)", REG_EXTENDED);
93 regcomp(&test, "^(not )?ok([[:space:]]*([[:digit:]]+))?", REG_EXTENDED);
94 regcomp(&directive, "#[[:space:]]*([^[:space:]]+)([[:space:]]+(.+))?",
95 REG_EXTENDED|REG_ICASE);
98 /* description: ok \d+([^#]+) */
103 /* bail out: ^Bail out! */
104 /* diagnostic: ^#.+ */
105 /* start of yaml: ^\s+--- */
106 /* end of yaml: ^\s+... */
107 /* between yaml lines must be indented */
108 /* anything else is an error */
110 while ((nread = getline(&line, &len, tap)) != -1) {
111 /* version: ^TAP version \d+ */
113 /* plan: ^\d+\.\.\d+ */
114 if (regexec(&plan, line, 2, match, 0) != REG_NOMATCH) {
115 run->plan = atoi(line+match[1].rm_so);
116 /* TODO plans can have a diag skip reason */
120 /* not ok: ^not ok \d+ */
121 if (rmatch(&test, line, match)) {
129 if (run->interactive) {
131 written = printf("%d/%d", run->ran, run->plan);
136 /* if the "not" match fails, the test passed */
137 pass = match[1].rm_so == -1;
140 test = atoi(line+match[3].rm_so);
143 if (rmatch(&directive, line, match)) {
144 todo = lookfor("todo", line+match[1].rm_so, 4);
145 skip = lookfor("skip", line+match[1].rm_so, 4);
148 /* check for diagnostics */
149 if (test != run->ran) {
150 fprintf(stderr, "expected test %d, got %d\n", run->ran+1, test);
153 /* pass, todo, or skip are all pass */
154 if (pass || todo || skip) {
168 if (!pass && !todo) {
170 r = malloc(sizeof *r);
181 /* push test number onto fail list */
185 else if (rmatch(&diagnostic, line, match)) {
188 else if (rmatch(&version, line, match)) {
189 run->version = atoi(line + match[1].rm_so);
191 else if (rmatch(&bail, line, match)) {
195 printf("Unknown line: %s", line);
198 /* not ok: ^not ok \d+ */
199 /* description: ok \d+([^#]+) */
204 /* bail out: ^Bail out! */
205 /* diagnostic: ^#.+ */
206 /* start of yaml: ^\s+--- */
207 /* end of yaml: ^\s+... */
208 /* between yaml lines must be indented */
209 /* anything else is an error */
220 if (run->ran < run->plan) {
221 run->fail += run->plan - run->ran;
224 if (run->interactive) {
234 void json(struct testrun *total, struct testrun *runs, int nruns) {
238 int main(int ac, char *av[]) {
242 struct testrun total = { 0 };
243 struct testrun run = { 0 };
244 struct testrun clear = { 0 };
245 struct testrun *runs;
251 * -i interactive, regardless of tty
256 int interactive = 0, json = 0, summary = 1, progress = 1, detail = 0;
258 interactive = isatty(1);
260 /* TODO -jj turn off everything else? */
261 /* TODO -cC color escapes */
262 while ((opt = getopt(ac, av, "jJpPiIsSdD")) != -1) {
264 case 'j': json = 1; break;
265 case 'J': json = 0; break;
266 case 'p': progress = 1; break;
267 case 'P': progress = 0; break;
268 case 'i': interactive = 1; break;
269 case 'I': interactive = 0; break;
270 case 's': summary = 1; break;
271 case 'S': summary = 0; break;
272 case 'd': detail = 1; break;
273 case 'D': detail = 0; break;
275 fprintf(stderr, "Usage: %s [-jpisd] file...\n",
281 runs = malloc(ac * sizeof *runs);
283 for (i=optind; i<ac; i++) {
285 maxnamelen = GREATER(len, maxnamelen);
288 for (i=optind; i<ac; i++) {
291 run.interactive = interactive;
294 printf("%s ", run.name);
295 len = strlen(run.name);
296 for (j=len;j<maxnamelen+2;j++) {
304 total.plan += run.plan;
305 total.ran += run.ran;
306 total.pass += run.pass;
307 total.fail += run.fail;
308 total.skip += run.skip;
312 printf("Bail out!\n");
314 if (run.fail > 0 ) printf("not ");
319 runs[i-optind] = run;
323 printf("ran: %d/%d, pass: %d, fail: %d, skip: %d",
324 total.ran, total.plan, total.pass, total.fail,
327 if (total.plan > 0) {
328 printf(", %.2f%% ok",
329 100.0*(double)total.pass/(double)total.plan
337 printf("detail here\n");
341 printf("{ \"plan\": %d, \"ran\": %d, \"pass\": %d, \"fail\": %d }\n",
342 total.plan, total.ran, total.pass, total.fail
347 Files=8, Tests=508, 0 wallclock secs ( 0.08 usr + 0.01 sys = 0.09 CPU)
351 return total.fail ? EXIT_FAILURE : EXIT_SUCCESS;