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 ------------------------------------------------------*/
46 /*----- Common machinery --------------------------------------------------*/
48 static const char *regdisp(unsigned disp)
51 case TVRD_INPUT: return "input";
52 case TVRD_OUTPUT: return "output";
53 case TVRD_MATCH: return "matched";
54 case TVRD_EXPECT: return "expected";
55 case TVRD_FOUND: return "found";
60 static int getenv_boolean(const char *var, int dflt)
67 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
68 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
69 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
72 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
73 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
74 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
78 moan("unexpected value `%s' for boolean environment variable `%s'",
84 static int register_maxnamelen(const struct tvec_state *tv)
86 const struct tvec_regdef *rd;
89 for (rd = tv->test->regs; rd->name; rd++)
90 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
94 /*----- Skeleton ----------------------------------------------------------*/
96 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
97 static int ..._esession(struct tvec_output *o)
98 static void ..._bgroup(struct tvec_output *o)
99 static void ..._skipgroup(struct tvec_output *o,
100 const char *excuse, va_list *ap)
101 static void ..._egroup(struct tvec_output *o)
102 static void ..._btest(struct tvec_output *o)
103 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
104 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
105 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
106 union tvec_regval *rv, const struct tvec_regdef *rd)
107 static void ..._etest(struct tvec_output *o, unsigned outcome)
108 static void ..._bbench(struct tvec_output *o,
109 const char *ident, unsigned unit)
110 static void ..._ebench(struct tvec_output *o,
111 const char *ident, unsigned unit,
112 const struct tvec_timing *t)
113 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
114 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
115 static void ..._destroy(struct tvec_output *o)
117 static const struct tvec_outops ..._ops = {
118 ..._bsession, ..._esession,
119 ..._bgroup, ..._egroup, ..._skip,
120 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
121 ..._bbench, ..._ebench,
122 ..._error, ..._notice,
126 /*----- Human-readable output ---------------------------------------------*/
128 #define HAF_FGMASK 0x0f
129 #define HAF_FGSHIFT 0
130 #define HAF_BGMASK 0xf0
131 #define HAF_BGSHIFT 4
134 #define HAF_BOLD 1024u
135 #define HCOL_BLACK 0u
137 #define HCOL_GREEN 2u
138 #define HCOL_YELLOW 3u
140 #define HCOL_MAGENTA 5u
142 #define HCOL_WHITE 7u
143 #define HCF_BRIGHT 8u
144 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
145 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
147 #define HA_WIN (HFG(GREEN))
148 #define HA_LOSE (HFG(RED) | HAF_BOLD)
149 #define HA_SKIP (HFG(YELLOW))
150 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
152 struct human_output {
153 struct tvec_output _o;
154 struct tvec_state *tv;
161 #define HOF_DUPERR 2u
162 #define HOF_COLOUR 4u
163 #define HOF_PROGRESS 8u
166 static void set_colour(FILE *fp, int *sep_inout,
167 const char *norm, const char *bright,
170 if (*sep_inout) putc(*sep_inout, fp);
171 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
175 static void setattr(struct human_output *h, unsigned attr)
177 unsigned diff = h->attr ^ attr;
180 if (!diff || !(h->f&HOF_COLOUR)) return;
181 fputs("\x1b[", h->fp);
184 if (attr&HAF_BOLD) putc('1', h->fp);
185 else { putc('0', h->fp); diff = h->attr; }
188 if (diff&(HAF_FG | HAF_FGMASK)) {
190 set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT);
192 { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; }
194 if (diff&(HAF_BG | HAF_BGMASK)) {
196 set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT);
198 { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; }
201 putc('m', h->fp); h->attr = attr;
206 static void clear_progress(struct human_output *h)
210 if (h->f&HOF_PROGRESS) {
211 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
212 for (i = 0; i < n; i++) fputs("\b \b", h->fp);
213 h->f &= ~HOF_PROGRESS;
217 static void write_scoreboard_char(struct human_output *h, int ch)
220 case 'x': setattr(h, HA_LOSE); break;
221 case '_': setattr(h, HA_SKIP); break;
222 default: setattr(h, 0); break;
224 putc(ch, h->fp); setattr(h, 0);
227 static void show_progress(struct human_output *h)
229 struct tvec_state *tv = h->tv;
232 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
233 fprintf(h->fp, "%s: ", tv->test->name);
234 if (!(h->f&HOF_COLOUR))
235 dstr_write(&h->scoreboard, h->fp);
236 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
237 write_scoreboard_char(h, *p);
238 fflush(h->fp); h->f |= HOF_PROGRESS;
242 static void report_location(struct human_output *h, FILE *fp,
243 const char *file, unsigned lno)
248 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
250 if (fp != h->fp) f |= f_flush;
253 setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp);
254 setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
255 setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp);
256 setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
257 setattr(h, 0); FLUSH(h->fp); fputc(' ', fp);
264 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
265 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
267 static void report_skipped(struct human_output *h, unsigned n)
270 fprintf(h->fp, " (%u ", n);
271 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
276 static int human_esession(struct tvec_output *o)
278 struct human_output *h = (struct human_output *)o;
279 struct tvec_state *tv = h->tv;
281 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
282 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
283 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
284 all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
287 setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0);
288 fprintf(h->fp, " %s%u %s",
289 !(all_skip || grps_skip) ? "all " : "",
290 all_win, all_win == 1 ? "test" : "tests");
291 report_skipped(h, all_skip);
292 fprintf(h->fp, " in %u %s",
293 grps_win, grps_win == 1 ? "group" : "groups");
294 report_skipped(h, grps_skip);
296 setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
297 fprintf(h->fp, " %u out of %u %s",
298 all_lose, all_run, all_run == 1 ? "test" : "tests");
299 report_skipped(h, all_skip);
300 fprintf(h->fp, " in %u out of %u %s",
301 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
302 report_skipped(h, grps_skip);
306 if (tv->f&TVSF_ERROR) {
307 setattr(h, HA_ERR); fputs("ERRORS", h->fp); setattr(h, 0);
308 fputs(" found in input; tests may not have run correctly\n", h->fp);
311 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
314 static void human_bgroup(struct tvec_output *o)
316 struct human_output *h = (struct human_output *)o;
318 h->maxlen = register_maxnamelen(h->tv);
319 dstr_reset(&h->scoreboard); show_progress(h);
322 static void human_skipgroup(struct tvec_output *o,
323 const char *excuse, va_list *ap)
325 struct human_output *h = (struct human_output *)o;
327 if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
328 h->f &= ~HOF_PROGRESS;
329 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
331 fprintf(h->fp, "%s: ", h->tv->test->name);
332 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
334 if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
338 static void human_egroup(struct tvec_output *o)
340 struct human_output *h = (struct human_output *)o;
341 struct tvec_state *tv = h->tv;
342 unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
343 skip = tv->curr[TVOUT_SKIP], run = win + lose;
345 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
346 else fprintf(h->fp, "%s:", h->tv->test->name);
349 fprintf(h->fp, " %u/%u ", lose, run);
350 setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
351 report_skipped(h, skip);
353 fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0);
354 report_skipped(h, skip);
359 static void human_btest(struct tvec_output *o)
360 { struct human_output *h = (struct human_output *)o; show_progress(h); }
362 static void human_skip(struct tvec_output *o,
363 const char *excuse, va_list *ap)
365 struct human_output *h = (struct human_output *)o;
366 struct tvec_state *tv = h->tv;
369 report_location(h, h->fp, tv->infile, tv->test_lno);
370 fprintf(h->fp, "`%s' ", tv->test->name);
371 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
372 if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
376 static void human_fail(struct tvec_output *o,
377 const char *detail, va_list *ap)
379 struct human_output *h = (struct human_output *)o;
380 struct tvec_state *tv = h->tv;
383 report_location(h, h->fp, tv->infile, tv->test_lno);
384 fprintf(h->fp, "`%s' ", tv->test->name);
385 setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
386 if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); }
390 static void human_dumpreg(struct tvec_output *o,
391 unsigned disp, const union tvec_regval *rv,
392 const struct tvec_regdef *rd)
394 struct human_output *h = (struct human_output *)o;
395 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
397 fprintf(h->fp, "%*s%s %s = ", 10 + h->maxlen - n, "", ds, rd->name);
398 if (h->f&HOF_COLOUR) {
399 if (!rv) setattr(h, HFG(YELLOW));
400 else if (disp == TVRD_FOUND) setattr(h, HFG(RED));
401 else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN));
403 if (!rv) fprintf(h->fp, "#<unset>");
404 else rd->ty->dump(rv, rd, 0, &file_printops, h->fp);
405 setattr(h, 0); fputc('\n', h->fp);
408 static void human_etest(struct tvec_output *o, unsigned outcome)
410 struct human_output *h = (struct human_output *)o;
416 case TVOUT_WIN: ch = '.'; break;
417 case TVOUT_LOSE: ch = 'x'; break;
418 case TVOUT_SKIP: ch = '_'; break;
421 dstr_putc(&h->scoreboard, ch);
422 write_scoreboard_char(h, ch); fflush(h->fp);
426 static void human_bbench(struct tvec_output *o,
427 const char *ident, unsigned unit)
429 struct human_output *h = (struct human_output *)o;
430 struct tvec_state *tv = h->tv;
433 fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp);
436 static void human_ebench(struct tvec_output *o,
437 const char *ident, unsigned unit,
438 const struct bench_timing *tm)
440 struct human_output *h = (struct human_output *)o;
441 tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp);
444 static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
446 struct human_output *h = (struct human_output *)o;
447 struct tvec_state *tv = h->tv;
450 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
452 clear_progress(h); fflush(h->fp);
453 fprintf(stderr, "%s: ", QUIS);
454 report_location(h, stderr, tv->infile, tv->lno);
455 fwrite(d.buf, 1, d.len, stderr);
457 if (h->f&HOF_DUPERR) {
458 report_location(h, h->fp, tv->infile, tv->lno);
459 fwrite(d.buf, 1, d.len, h->fp);
464 static void human_destroy(struct tvec_output *o)
466 struct human_output *h = (struct human_output *)o;
468 if (h->f&HOF_DUPERR) fclose(h->fp);
469 dstr_destroy(&h->scoreboard);
473 static const struct tvec_outops human_ops = {
474 human_bsession, human_esession,
475 human_bgroup, human_skipgroup, human_egroup,
476 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
477 human_bbench, human_ebench,
478 human_report, human_report,
482 struct tvec_output *tvec_humanoutput(FILE *fp)
484 struct human_output *h;
487 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
488 h->f = 0; h->attr = 0;
492 switch (getenv_boolean("TVEC_TTY", -1)) {
493 case 1: h->f |= HOF_TTY; break;
496 if (isatty(fileno(fp))) h->f |= HOF_TTY;
499 switch (getenv_boolean("TVEC_COLOUR", -1)) {
500 case 1: h->f |= HOF_COLOUR; break;
505 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
510 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
511 dstr_create(&h->scoreboard);
515 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
518 struct tvec_output _o;
519 struct tvec_state *tv;
524 #define TOF_FRESHLINE 1u
527 static int tap_writech(void *go, int ch)
529 struct tap_output *t = go;
531 if (t->f&TOF_FRESHLINE) {
532 if (fputs("## ", t->fp) < 0) return (-1);
533 t->f &= ~TOF_FRESHLINE;
535 if (putc(ch, t->fp) < 0) return (-1);
536 if (ch == '\n') t->f |= TOF_FRESHLINE;
540 static int tap_writem(void *go, const char *p, size_t sz)
542 struct tap_output *t = go;
543 const char *q, *l = p + sz;
546 if (p == l) return (0);
547 if (t->f&TOF_FRESHLINE)
548 if (fputs("## ", t->fp) < 0) return (-1);
550 q = memchr(p, '\n', l - p); if (!q) break;
551 n = q + 1 - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
553 if (p == l) { t->f |= TOF_FRESHLINE; return (sz); }
554 if (fputs("## ", t->fp) < 0) return (-1);
556 n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
557 t->f &= ~TOF_FRESHLINE; return (0);
560 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
562 struct tap_output *t = go;
566 va_start(ap, p); DRESET(&t->d); DENSURE(&t->d, maxsz + 1);
568 n = vsnprintf(t->d.buf, maxsz + 1, p, ap);
570 n = vsprintf(t->d.buf, p, ap);
572 assert(0 <= n && n <= maxsz);
574 return (tap_writem(t, t->d.buf, n));
577 static const struct gprintf_ops tap_printops =
578 { tap_writech, tap_writem, tap_nwritef };
580 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
582 struct tap_output *t = (struct tap_output *)o;
585 fputs("TAP version 13\n", t->fp);
588 static unsigned tap_grpix(struct tap_output *t)
590 struct tvec_state *tv = t->tv;
592 return (tv->grps[TVOUT_WIN] +
593 tv->grps[TVOUT_LOSE] +
594 tv->grps[TVOUT_SKIP]);
597 static int tap_esession(struct tvec_output *o)
599 struct tap_output *t = (struct tap_output *)o;
600 struct tvec_state *tv = t->tv;
602 if (tv->f&TVSF_ERROR) {
604 "Errors found in input; tests may not have run correctly\n",
609 fprintf(t->fp, "1..%u\n", tap_grpix(t));
610 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
613 static void tap_bgroup(struct tvec_output *o)
615 struct tap_output *t = (struct tap_output *)o;
616 t->maxlen = register_maxnamelen(t->tv);
619 static void tap_skipgroup(struct tvec_output *o,
620 const char *excuse, va_list *ap)
622 struct tap_output *t = (struct tap_output *)o;
624 fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
625 if (excuse) { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); }
629 static void tap_egroup(struct tvec_output *o)
631 struct tap_output *t = (struct tap_output *)o;
632 struct tvec_state *tv = t->tv;
634 grpix = tap_grpix(t),
635 win = tv->curr[TVOUT_WIN],
636 lose = tv->curr[TVOUT_LOSE],
637 skip = tv->curr[TVOUT_SKIP];
640 fprintf(t->fp, "not ok %u - %s: FAILED %u/%u",
641 grpix, tv->test->name, lose, win + lose);
642 if (skip) fprintf(t->fp, " (skipped %u)", skip);
644 fprintf(t->fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
645 if (skip) fprintf(t->fp, " (skipped %u)", skip);
650 static void tap_btest(struct tvec_output *o) { ; }
652 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
654 struct tap_output *t = (struct tap_output *)o;
655 struct tvec_state *tv = t->tv;
657 fprintf(t->fp, "## %s:%u: `%s' skipped",
658 tv->infile, tv->test_lno, tv->test->name);
659 if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); }
663 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
665 struct tap_output *t = (struct tap_output *)o;
666 struct tvec_state *tv = t->tv;
668 fprintf(t->fp, "## %s:%u: `%s' FAILED",
669 tv->infile, tv->test_lno, tv->test->name);
670 if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); }
674 static void tap_dumpreg(struct tvec_output *o,
675 unsigned disp, const union tvec_regval *rv,
676 const struct tvec_regdef *rd)
678 struct tap_output *t = (struct tap_output *)o;
679 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
681 fprintf(t->fp, "## %*s%s %s = ", 10 + t->maxlen - n, "", ds, rd->name);
683 fprintf(t->fp, "#<unset>\n");
685 t->f &= ~TOF_FRESHLINE;
686 rd->ty->dump(rv, rd, 0, &tap_printops, t);
691 static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
693 static void tap_bbench(struct tvec_output *o,
694 const char *ident, unsigned unit)
697 static void tap_ebench(struct tvec_output *o,
698 const char *ident, unsigned unit,
699 const struct bench_timing *tm)
701 struct tap_output *t = (struct tap_output *)o;
702 struct tvec_state *tv = t->tv;
704 fprintf(t->fp, "## %s: %s: ", tv->test->name, ident);
705 t->f &= ~TOF_FRESHLINE; tvec_benchreport(&tap_printops, t, unit, tm);
709 static void tap_report(struct tap_output *t, const char *msg, va_list *ap)
711 struct tvec_state *tv = t->tv;
713 if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno);
714 vfprintf(t->fp, msg, *ap); fputc('\n', t->fp);
717 static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
719 struct tap_output *t = (struct tap_output *)o;
720 fputs("Bail out! ", t->fp); tap_report(t, msg, ap);
723 static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
725 struct tap_output *t = (struct tap_output *)o;
726 fputs("## ", t->fp); tap_report(t, msg, ap);
729 static void tap_destroy(struct tvec_output *o)
731 struct tap_output *t = (struct tap_output *)o;
733 if (t->fp != stdout && t->fp != stderr) fclose(t->fp);
738 static const struct tvec_outops tap_ops = {
739 tap_bsession, tap_esession,
740 tap_bgroup, tap_skipgroup, tap_egroup,
741 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
742 tap_bbench, tap_ebench,
743 tap_error, tap_notice,
747 struct tvec_output *tvec_tapoutput(FILE *fp)
749 struct tap_output *t;
751 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
753 t->f = 0; t->fp = fp;
757 /*----- Default output ----------------------------------------------------*/
759 struct tvec_output *tvec_dfltout(FILE *fp)
761 int ttyp = getenv_boolean("TVEC_TTY", -1);
763 if (ttyp == -1) ttyp = isatty(fileno(fp));
764 if (ttyp) return (tvec_humanoutput(fp));
765 else return (tvec_tapoutput(fp));
768 /*----- That's all, folks -------------------------------------------------*/