3 * Test vector output management
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
44 /*----- Common machinery --------------------------------------------------*/
46 enum { INPUT, OUTPUT, MATCH, EXPECT, FOUND };
48 void (*report_status)(unsigned /*disp*/, int /*st*/,
49 struct tvec_state */*tv*/);
50 void (*report_register)(unsigned /*disp*/,
51 const struct tvec_reg */*r*/,
52 const struct tvec_regdef */*rd*/,
53 struct tvec_state */*tv*/);
56 static const char *stdisp(unsigned disp)
59 case MATCH: return "final";
60 case EXPECT: return "expected";
61 case FOUND: return "actual";
66 static const char *regdisp(unsigned disp)
69 case INPUT: return "input";
70 case OUTPUT: return "output";
71 case MATCH: return "matched";
72 case EXPECT: return "expected";
73 case FOUND: return "computed";
78 static int getenv_boolean(const char *var, int dflt)
85 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
86 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
87 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
90 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
91 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
95 moan("unexpected value `%s' for boolean environment variable `%s'",
101 static void basic_report_status(unsigned disp, int st, struct tvec_state *tv)
102 { tvec_write(tv, " %8s status = `%c'\n", stdisp(disp), st); }
104 static void basic_report_register(unsigned disp,
105 const struct tvec_reg *r,
106 const struct tvec_regdef *rd,
107 struct tvec_state *tv)
109 tvec_write(tv, " %8s %s = ", regdisp(disp), rd->name);
110 if (r->f&TVRF_LIVE) rd->ty->dump(&r->v, rd, tv, 0);
111 else tvec_write(tv, "#<unset>");
112 tvec_write(tv, "\n");
115 static const struct mismatchfns basic_mismatchfns =
116 { basic_report_status, basic_report_register };
118 static void dump_inreg(const struct tvec_regdef *rd,
119 const struct mismatchfns *fns, struct tvec_state *tv)
120 { fns->report_register(INPUT, TVEC_REG(tv, in, rd->i), rd, tv); }
122 static void dump_outreg(const struct tvec_regdef *rd,
123 const struct mismatchfns *fns, struct tvec_state *tv)
125 const struct tvec_reg
126 *rin = TVEC_REG(tv, in, rd->i), *rout = TVEC_REG(tv, out, rd->i);
129 if (!(rout->f&TVRF_LIVE)) {
130 if (!(rin->f&TVRF_LIVE))
131 fns->report_register(INPUT, rin, rd, tv);
133 fns->report_register(FOUND, rout, rd, tv);
134 fns->report_register(EXPECT, rin, rd, tv);
137 if (!(rin->f&TVRF_LIVE)) fns->report_register(OUTPUT, rout, rd, tv);
138 else if (rd->ty->eq(&rin->v, &rout->v, rd))
139 fns->report_register(MATCH, rin, rd, tv);
141 fns->report_register(FOUND, rout, rd, tv);
142 fns->report_register(EXPECT, rin, rd, tv);
148 static void mismatch(const struct mismatchfns *fns, struct tvec_state *tv)
150 const struct tvec_regdef *rd;
152 if (tv->st != tv->expst) {
153 fns->report_status(FOUND, tv->st, tv);
154 fns->report_status(EXPECT, tv->expst, tv);
155 } else if (tv->st != '.')
156 fns->report_status(MATCH, tv->st, tv);
158 for (rd = tv->test->regs; rd->name; rd++) {
159 if (rd->i < tv->nrout) dump_outreg(rd, fns, tv);
160 else dump_inreg(rd, fns, tv);
164 static void bench_summary(struct tvec_state *tv)
166 const struct tvec_regdef *rd;
170 for (rd = tv->test->regs; rd->name; rd++)
172 if (f&f_any) tvec_write(tv, ", ");
174 tvec_write(tv, "%s = ", rd->name);
175 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, tv, TVSF_COMPACT);
181 static void normalize(double *x_inout, const char **unit_out, double scale)
185 *const big[] = { "k", "M", "G", "T", "P", "E", 0 },
186 *const little[] = { "m", "ยต", "n", "p", "f", "a", 0 };
187 const char *const *u;
191 for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale);
193 for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale);
197 *x_inout = x; *unit_out = *u;
200 static void bench_report(struct tvec_state *tv,
201 const struct bench_timing *tm)
203 const struct tvec_bench *b = tv->test->arg.p;
204 double n = (double)tm->n*b->niter;
206 const char *u, *what, *whats;
208 assert(tm->f&BTF_TIMEOK);
211 tvec_write(tv, " -- %.0f iterations ", n);
212 what = "op"; whats = "ops"; scale = 1000;
214 n *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz;
215 x = n; normalize(&x, &u, 1024); tvec_write(tv, " -- %.3f %sB ", x, u);
216 what = whats = "B"; scale = 1024;
218 x = tm->t; normalize(&x, &u, 1000);
219 tvec_write(tv, "in %.3f %ss", x, u);
220 if (tm->f&BTF_CYOK) {
221 x = tm->cy; normalize(&x, &u, 1000);
222 tvec_write(tv, " (%.3f %scy)", x, u);
224 tvec_write(tv, ": ");
226 x = n/tm->t; normalize(&x, &u, scale);
227 tvec_write(tv, "%.3f %s%s/s", x, u, whats);
228 x = tm->t/n; normalize(&x, &u, 1000);
229 tvec_write(tv, ", %.3f %ss/%s", x, u, what);
230 if (tm->f&BTF_CYOK) {
231 x = tm->cy/n; normalize(&x, &u, 1000);
232 tvec_write(tv, " (%.3f %scy/%s)", x, u, what);
234 tvec_write(tv, "\n");
237 /*----- Skeleton ----------------------------------------------------------*/
239 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
240 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
241 static void ..._write(struct tvec_output *o, const char *p, size_t sz)
242 static void ..._bsession(struct tvec_output *o)
243 static int ..._esession(struct tvec_output *o)
244 static void ..._bgroup(struct tvec_output *o)
245 static void ..._egroup(struct tvec_output *o, unsigned outcome)
246 static void ..._skipgroup(struct tvec_output *o,
247 const char *excuse, va_list *ap)
248 static void ..._btest(struct tvec_output *o)
249 static void ..._skip(struct tvec_output *o, const char *detail, va_list *ap)
250 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
251 static void ..._mismatch(struct tvec_output *o)
252 static void ..._etest(struct tvec_output *o, unsigned outcome)
253 static void ..._bbench(struct tvec_output *o)
254 static void ..._ebench(struct tvec_output *o, const struct tvec_timing *t)
255 static void ..._destroy(struct tvec_output *o)
257 static const struct tvec_outops ..._ops = {
258 ..._error, ..._notice, ..._write,
259 ..._bsession, ..._esession,
260 ..._bgroup, ..._egroup, ..._skip,
261 ..._btest, ..._skip, ..._fail, ..._mismatch, ..._etest,
262 ..._bbench, ..._ebench,
266 /*----- Human-readable output ---------------------------------------------*/
268 #define HAF_FGMASK 0x0f
269 #define HAF_FGSHIFT 0
270 #define HAF_BGMASK 0xf0
271 #define HAF_BGSHIFT 4
274 #define HAF_BOLD 1024u
275 #define HCOL_BLACK 0u
277 #define HCOL_GREEN 2u
278 #define HCOL_YELLOW 3u
280 #define HCOL_MAGENTA 5u
282 #define HCOL_WHITE 7u
283 #define HCF_BRIGHT 8u
284 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
285 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
287 #define HA_WIN (HFG(GREEN))
288 #define HA_LOSE (HFG(RED) | HAF_BOLD)
289 #define HA_SKIP (HFG(YELLOW))
291 struct human_output {
292 struct tvec_output _o;
298 #define HOF_DUPERR 2u
299 #define HOF_COLOUR 4u
300 #define HOF_PROGRESS 8u
303 static void set_colour(FILE *fp, int *sep_inout,
304 const char *norm, const char *bright,
307 if (*sep_inout) putc(*sep_inout, fp);
308 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
312 static void setattr(struct human_output *h, unsigned attr)
314 unsigned diff = h->attr ^ attr;
317 if (!diff || !(h->f&HOF_COLOUR)) return;
318 fputs("\x1b[", h->fp);
321 if (attr&HAF_BOLD) putc('1', h->fp);
322 else { putc('0', h->fp); diff = h->attr; }
325 if (diff&(HAF_FG | HAF_FGMASK)) {
327 set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT);
329 { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; }
331 if (diff&(HAF_BG | HAF_BGMASK)) {
333 set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT);
335 { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; }
338 putc('m', h->fp); h->attr = attr;
343 static void clear_progress(struct human_output *h)
347 if (h->f&HOF_PROGRESS) {
348 n = strlen(h->_o.tv->test->name) + 2 + h->scoreboard.len;
349 for (i = 0; i < n; i++) fputs("\b \b", h->fp);
350 h->f &= ~HOF_PROGRESS;
354 static void write_scoreboard_char(struct human_output *h, int ch)
357 case 'x': setattr(h, HA_LOSE); break;
358 case '_': setattr(h, HA_SKIP); break;
359 default: setattr(h, 0); break;
361 putc(ch, h->fp); setattr(h, 0);
364 static void show_progress(struct human_output *h)
368 if ((h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
369 fprintf(h->fp, "%s: ", h->_o.tv->test->name);
370 if (!(h->f&HOF_COLOUR))
371 dstr_write(&h->scoreboard, h->fp);
372 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
373 write_scoreboard_char(h, *p);
374 fflush(h->fp); h->f |= HOF_PROGRESS;
378 static void report_location(struct human_output *h, FILE *fp,
379 const char *file, unsigned lno)
384 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
386 if (fp != h->fp) f |= f_flush;
389 setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp);
390 setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
391 setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp);
392 setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
393 setattr(h, 0); FLUSH(h->fp); fputc(' ', fp);
400 static void human_report(struct human_output *h,
401 const char *msg, va_list *ap)
403 struct tvec_state *tv = h->_o.tv;
405 fprintf(stderr, "%s: ", QUIS);
406 report_location(h, stderr, tv->infile, tv->lno);
407 vfprintf(stderr, msg, *ap);
410 if (h->f&HOF_DUPERR) {
411 report_location(h, stderr, tv->infile, tv->lno);
412 vfprintf(h->fp, msg, *ap);
417 static void human_error(struct tvec_output *o, const char *msg, va_list *ap)
419 struct human_output *h = (struct human_output *)o;
421 if (h->f&HOF_PROGRESS) fputc('\n', h->fp);
422 human_report(h, msg, ap);
425 static void human_notice(struct tvec_output *o, const char *msg, va_list *ap)
427 struct human_output *h = (struct human_output *)o;
428 clear_progress(h); human_report(h, msg, ap); show_progress(h);
431 static void human_write(struct tvec_output *o, const char *p, size_t sz)
433 struct human_output *h = (struct human_output *)o;
434 fwrite(p, 1, sz, h->fp);
437 static void human_bsession(struct tvec_output *o) { ; }
439 static void report_skipped(struct human_output *h, unsigned n)
442 fprintf(h->fp, " (%u ", n);
443 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
448 static int human_esession(struct tvec_output *o)
450 struct human_output *h = (struct human_output *)o;
451 struct tvec_state *tv = h->_o.tv;
453 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
454 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
455 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
456 all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
459 setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0);
460 fprintf(h->fp, " %s%u %s",
461 !(all_skip || grps_skip) ? "all " : "",
462 all_win, all_win == 1 ? "test" : "tests");
463 report_skipped(h, all_skip);
464 fprintf(h->fp, " in %u %s",
465 grps_win, grps_win == 1 ? "group" : "groups");
466 report_skipped(h, grps_skip);
468 setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
469 fprintf(h->fp, " %u out of %u %s",
470 all_lose, all_run, all_run == 1 ? "test" : "tests");
471 report_skipped(h, all_skip);
472 fprintf(h->fp, " in %u out of %u %s",
473 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
474 report_skipped(h, grps_skip);
478 return (tv->all[TVOUT_LOSE] ? 1 : 0);
481 static void human_bgroup(struct tvec_output *o)
483 struct human_output *h = (struct human_output *)o;
484 dstr_reset(&h->scoreboard); show_progress(h);
487 static void human_grpsumm(struct human_output *h, unsigned outcome)
489 struct tvec_state *tv = h->_o.tv;
490 unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
491 skip = tv->curr[TVOUT_SKIP], run = win + lose;
494 assert(outcome == TVOUT_LOSE);
495 fprintf(h->fp, " %u/%u ", lose, run);
496 setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
497 report_skipped(h, skip);
499 assert(outcome == TVOUT_WIN);
500 fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0);
501 report_skipped(h, skip);
506 static void human_egroup(struct tvec_output *o, unsigned outcome)
508 struct human_output *h = (struct human_output *)o;
510 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
511 else fprintf(h->fp, "%s:", h->_o.tv->test->name);
512 human_grpsumm(h, outcome);
515 static void human_skipgroup(struct tvec_output *o,
516 const char *excuse, va_list *ap)
518 struct human_output *h = (struct human_output *)o;
520 if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
521 h->f &= ~HOF_PROGRESS;
522 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
524 fprintf(h->fp, "%s: ", h->_o.tv->test->name);
525 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
527 if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
531 static void human_btest(struct tvec_output *o)
532 { struct human_output *h = (struct human_output *)o; show_progress(h); }
534 static void human_skip(struct tvec_output *o,
535 const char *excuse, va_list *ap)
537 struct human_output *h = (struct human_output *)o;
538 struct tvec_state *tv = h->_o.tv;
541 report_location(h, h->fp, tv->infile, tv->test_lno);
542 fprintf(h->fp, "`%s' ", tv->test->name);
543 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
544 if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
548 static void human_fail(struct tvec_output *o,
549 const char *detail, va_list *ap)
551 struct human_output *h = (struct human_output *)o;
552 struct tvec_state *tv = h->_o.tv;
555 report_location(h, h->fp, tv->infile, tv->test_lno);
556 fprintf(h->fp, "`%s' ", tv->test->name);
557 setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
558 if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); }
562 static void set_dispattr(struct human_output *h, unsigned disp)
565 case EXPECT: setattr(h, HFG(GREEN)); break;
566 case FOUND: setattr(h, HFG(RED)); break;
567 default: setattr(h, 0); break;
571 static void human_report_status(unsigned disp, int st, struct tvec_state *tv)
573 struct human_output *h = (struct human_output *)tv->output;
575 fprintf(h->fp, " %8s status = ", stdisp(disp));
576 set_dispattr(h, disp); fprintf(h->fp, "`%c'", st); setattr(h, 0);
580 static void human_report_register(unsigned disp,
581 const struct tvec_reg *r,
582 const struct tvec_regdef *rd,
583 struct tvec_state *tv)
585 struct human_output *h = (struct human_output *)tv->output;
587 fprintf(h->fp, " %8s %s = ", regdisp(disp), rd->name);
588 if (!(r->f&TVRF_LIVE))
589 tvec_write(tv, "#<unset>");
591 set_dispattr(h, disp);
592 rd->ty->dump(&r->v, rd, tv, 0);
595 tvec_write(tv, "\n");
598 static const struct mismatchfns human_mismatchfns =
599 { human_report_status, human_report_register };
601 static void human_mismatch(struct tvec_output *o)
603 struct human_output *h = (struct human_output *)o;
605 if (h->f&HOF_COLOUR) mismatch(&human_mismatchfns, h->_o.tv);
606 else mismatch(&basic_mismatchfns, h->_o.tv);
609 static void human_etest(struct tvec_output *o, unsigned outcome)
611 struct human_output *h = (struct human_output *)o;
617 case TVOUT_WIN: ch = '.'; break;
618 case TVOUT_LOSE: ch = 'x'; break;
619 case TVOUT_SKIP: ch = '_'; break;
622 dstr_putc(&h->scoreboard, ch);
623 write_scoreboard_char(h, ch); fflush(h->fp);
627 static void human_bbench(struct tvec_output *o)
629 struct human_output *h = (struct human_output *)o;
630 struct tvec_state *tv = h->_o.tv;
633 fprintf(h->fp, "%s: ", tv->test->name);
634 bench_summary(tv); fflush(h->fp);
637 static void human_ebench(struct tvec_output *o,
638 const struct bench_timing *tm)
640 struct human_output *h = (struct human_output *)o;
641 bench_report(h->_o.tv, tm);
644 static void human_destroy(struct tvec_output *o)
646 struct human_output *h = (struct human_output *)o;
648 if (h->f&HOF_DUPERR) fclose(h->fp);
649 dstr_destroy(&h->scoreboard);
653 static const struct tvec_outops human_ops = {
654 human_error, human_notice, human_write,
655 human_bsession, human_esession,
656 human_bgroup, human_egroup, human_skipgroup,
657 human_btest, human_skip, human_fail, human_mismatch, human_etest,
658 human_bbench, human_ebench,
662 struct tvec_output *tvec_humanoutput(FILE *fp)
664 struct human_output *h;
667 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
668 h->f = 0; h->attr = 0;
671 if (fp != stdout && fp != stderr) h->f |= HOF_DUPERR;
673 switch (getenv_boolean("TVEC_TTY", -1)) {
674 case 1: h->f |= HOF_TTY; break;
677 if (isatty(fileno(fp))) h->f |= HOF_TTY;
680 switch (getenv_boolean("TVEC_COLOUR", -1)) {
681 case 1: h->f |= HOF_COLOUR; break;
686 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
691 dstr_create(&h->scoreboard);
695 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
698 struct tvec_output _o;
701 #define TOF_FRESHLINE 1u
704 static void tap_report(struct tap_output *t, const char *msg, va_list *ap)
706 struct tvec_state *tv = t->_o.tv;
708 if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno);
709 vfprintf(t->fp, msg, *ap); fputc('\n', t->fp);
712 static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
714 struct tap_output *t = (struct tap_output *)o;
715 fputs("Bail out! ", t->fp); tap_report(t, msg, ap);
718 static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
720 struct tap_output *t = (struct tap_output *)o;
721 fputs("## ", t->fp); tap_report(t, msg, ap);
724 static void tap_write(struct tvec_output *o, const char *p, size_t sz)
726 struct tap_output *t = (struct tap_output *)o;
727 const char *q, *l = p + sz;
730 if (t->f&TOF_FRESHLINE) fputs("## ", t->fp);
732 q = memchr(p, '\n', l - p); if (!q) break;
733 fwrite(p, 1, q + 1 - p, t->fp); p = q + 1;
734 if (p == l) { t->f |= TOF_FRESHLINE; return; }
737 fwrite(p, 1, l - p, t->fp); t->f &= ~TOF_FRESHLINE;
740 static void tap_bsession(struct tvec_output *o) { ; }
742 static unsigned tap_grpix(struct tap_output *t)
744 struct tvec_state *tv = t->_o.tv;
746 return (tv->grps[TVOUT_WIN] +
747 tv->grps[TVOUT_LOSE] +
748 tv->grps[TVOUT_SKIP]);
751 static int tap_esession(struct tvec_output *o)
753 struct tap_output *t = (struct tap_output *)o;
755 fprintf(t->fp, "1..%u\n", tap_grpix(t));
759 static void tap_bgroup(struct tvec_output *o) { ; }
761 static void tap_egroup(struct tvec_output *o, unsigned outcome)
763 struct tap_output *t = (struct tap_output *)o;
764 struct tvec_state *tv = t->_o.tv;
766 grpix = tap_grpix(t),
767 win = tv->curr[TVOUT_WIN],
768 lose = tv->curr[TVOUT_LOSE],
769 skip = tv->curr[TVOUT_SKIP];
772 assert(outcome == TVOUT_LOSE);
773 fprintf(t->fp, "not ok %u %s: FAILED %u/%u",
774 grpix, tv->test->name, lose, win + lose);
775 if (skip) fprintf(t->fp, " (skipped %u)", skip);
777 assert(outcome == TVOUT_WIN);
778 fprintf(t->fp, "ok %u %s: passed %u", grpix, tv->test->name, win);
779 if (skip) fprintf(t->fp, " (skipped %u)", skip);
784 static void tap_skipgroup(struct tvec_output *o,
785 const char *excuse, va_list *ap)
787 struct tap_output *t = (struct tap_output *)o;
789 fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->_o.tv->test->name);
791 { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); }
795 static void tap_btest(struct tvec_output *o) { ; }
797 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
799 struct tap_output *t = (struct tap_output *)o;
800 struct tvec_state *tv = t->_o.tv;
802 fprintf(t->fp, "## %s:%u: `%s' skipped",
803 tv->infile, tv->test_lno, tv->test->name);
804 if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); }
808 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
810 struct tap_output *t = (struct tap_output *)o;
811 struct tvec_state *tv = t->_o.tv;
813 fprintf(t->fp, "## %s:%u: `%s' FAILED",
814 tv->infile, tv->test_lno, tv->test->name);
815 if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); }
819 static void tap_mismatch(struct tvec_output *o)
820 { mismatch(&basic_mismatchfns, o->tv); }
822 static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
824 static void tap_bbench(struct tvec_output *o) { ; }
826 static void tap_ebench(struct tvec_output *o,
827 const struct bench_timing *tm)
829 struct tap_output *t = (struct tap_output *)o;
830 struct tvec_state *tv = t->_o.tv;
832 tvec_write(tv, "%s: ", tv->test->name); bench_summary(tv);
833 bench_report(tv, tm);
836 static void tap_destroy(struct tvec_output *o)
838 struct tap_output *t = (struct tap_output *)o;
840 if (t->fp != stdout && t->fp != stderr) fclose(t->fp);
844 static const struct tvec_outops tap_ops = {
845 tap_error, tap_notice, tap_write,
846 tap_bsession, tap_esession,
847 tap_bgroup, tap_egroup, tap_skipgroup,
848 tap_btest, tap_skip, tap_fail, tap_mismatch, tap_etest,
849 tap_bbench, tap_ebench,
853 struct tvec_output *tvec_tapoutput(FILE *fp)
855 struct tap_output *t;
857 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
858 t->f = TOF_FRESHLINE;
863 /*----- Default output ----------------------------------------------------*/
865 struct tvec_output *tvec_dfltout(FILE *fp)
867 int ttyp = getenv_boolean("TVEC_TTY", -1);
869 if (ttyp == -1) ttyp = isatty(fileno(fp));
870 if (ttyp) return (tvec_humanoutput(fp));
871 else return (tvec_tapoutput(fp));
874 /*----- That's all, folks -------------------------------------------------*/