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 ------------------------------------------------------*/
48 /*----- Common machinery --------------------------------------------------*/
50 /* --- @regdisp@ --- *
52 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
54 * Returns: A human-readable adjective describing the register
58 static const char *regdisp(unsigned disp)
61 case TVRD_INPUT: return "input";
62 case TVRD_OUTPUT: return "output";
63 case TVRD_MATCH: return "matched";
64 case TVRD_EXPECT: return "expected";
65 case TVRD_FOUND: return "found";
70 /* --- @getenv_boolean@ --- *
72 * Arguments: @const char *var@ = environment variable name
73 * @int dflt@ = default value
75 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
76 * set to something truish, or @dflt@ otherwise.
79 static int getenv_boolean(const char *var, int dflt)
86 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
87 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
88 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
91 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
92 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
93 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
97 moan("ignoring unexpected value `%s' for environment variable `%s'",
103 /* --- @register_maxnamelen@ --- *
105 * Arguments: @const struct tvec_state *tv@ = test vector state
107 * Returns: The maximum length of a register name in the current test.
110 static int register_maxnamelen(const struct tvec_state *tv)
112 const struct tvec_regdef *rd;
115 for (rd = tv->test->regs; rd->name; rd++)
116 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
120 /*----- Output formatting -------------------------------------------------*/
122 /* We have two main jobs in output formatting: trimming trailing blanks; and
123 * adding a prefix to each line.
125 * This is somehow much more complicated than it ought to be.
129 FILE *fp; /* output file */
130 const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
131 dstr w; /* trailing whitespace */
132 unsigned f; /* flags */
133 #define FMTF_NEWL 1u /* start of output line */
136 /* Support macros. These assume `fmt' is defined as a pointer to the `struct
140 #define SPLIT_RANGE(tail, base, limit) do { \
141 /* Set TAIL to point just after the last nonspace character between \
142 * BASE and LIMIT. If there are no nonspace characters, then set \
143 * TAIL to equal BASE. \
146 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
149 #define PUT_RANGE(base, limit) do { \
150 /* Write the range of characters between BASE and LIMIT to the output \
151 * file. Return immediately on error. \
154 size_t n = limit - base; \
155 if (fwrite(base, 1, n, fmt->fp) < n) return (-1); \
158 #define PUT_CHAR(ch) do { \
159 /* Write CH to the output. Return immediately on error. */ \
161 if (putc(ch, fmt->fp) == EOF) return (-1); \
164 #define PUT_PREFIX do { \
165 /* Output the prefix, if there is one. Return immediately on error. */ \
167 if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim); \
170 #define PUT_SAVED do { \
171 /* Output the saved trailing blank material in the buffer. */ \
173 size_t n = fmt->w.len; \
174 if (n && fwrite(fmt->w.buf, 1, n, fmt->fp) < n) return (-1); \
177 #define PUT_PFXINB do { \
178 /* Output the initial nonblank portion of the prefix, if there is \
179 * one. Return immediately on error. \
182 if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail); \
185 #define SAVE_PFXTAIL do { \
186 /* Save the trailing blank portion of the prefix. */ \
189 DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail); \
192 /* --- @init_fmt@ --- *
194 * Arguments: @struct format *fmt@ = formatting state to initialize
195 * @FILE *fp@ = output file
196 * @const char *prefix@ = prefix string (or null if empty)
200 * Use: Initialize a formatting state.
203 static void init_fmt(struct format *fmt, FILE *fp, const char *prefix)
210 dstr_create(&fmt->w);
212 /* Prefix portions. */
213 if (!prefix || !*prefix)
214 fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0;
216 fmt->prefix = prefix;
217 l = fmt->pfxlim = prefix + strlen(prefix);
218 SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q;
222 /* --- @destroy_fmt@ --- *
224 * Arguments: @struct format *fmt@ = formatting state
225 * @unsigned f@ = flags (@DFF_...@)
229 * Use: Releases a formatting state and the resources it holds.
230 * Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave
231 * it open (in case it's @stderr@ or something).
235 static void destroy_fmt(struct format *fmt, unsigned f)
237 if (f&DFF_CLOSE) fclose(fmt->fp);
238 dstr_destroy(&fmt->w);
241 /* --- @format_char@ --- *
243 * Arguments: @struct format *fmt@ = formatting state
244 * @int ch@ = character to write
246 * Returns: Zero on success, @-1@ on failure.
248 * Use: Write a single character to the output.
251 static int format_char(struct format *fmt, int ch)
254 if (fmt->f&FMTF_NEWL) PUT_PFXINB;
255 PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w);
256 } else if (isspace(ch))
259 if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
260 PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w);
265 /* --- @format_string@ --- *
267 * Arguments: @struct format *fmt@ = formatting state
268 * @const char *p@ = string to write
269 * @size_t sz@ = length of string
271 * Returns: Zero on success, @-1@ on failure.
273 * Use: Write a string to the output.
276 static int format_string(struct format *fmt, const char *p, size_t sz)
278 const char *q, *r, *l = p + sz;
280 /* This is rather vexing. There are a small number of jobs to do, but the
281 * logic for deciding which to do when gets rather hairy if, as I've tried
282 * here, one aims to minimize the number of decisions being checked, so
283 * it's worth canning them into macros.
285 * Here, a `blank' is a whitespace character other than newline. The input
286 * buffer consists of one or more `segments', each of which consists of:
288 * * an initial portion, which is either empty or ends with a nonblank
291 * * a suffix which consists only of blanks; and
293 * * an optional newline.
295 * All segments except the last end with a newline.
298 #define SPLIT_SEGMENT do { \
299 /* Determine the bounds of the current segment. If there is a final \
300 * newline, then q is non-null and points to this newline; otherwise, \
301 * q is null. The initial portion of the segment lies between p .. r \
302 * and the blank suffix lies between r .. q (or r .. l if q is null). \
303 * This sounds awkward, but the suffix is only relevant if there is \
307 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
310 #define PUT_NONBLANK do { \
311 /* Output the initial portion of the segment. */ \
316 #define PUT_NEWLINE do { \
317 /* Write a newline, and advance to the next segment. */ \
319 PUT_CHAR('\n'); p = q + 1; \
322 #define SAVE_TAIL do { \
323 /* Save the trailing blank portion of the segment in the buffer. \
324 * Assumes that there is no newline, since otherwise the suffix would \
328 DPUTM(&fmt->w, r, l - r); \
331 /* Determine the bounds of the first segment. Handling this is the most
332 * complicated part of this function.
337 /* This is the only segment. We'll handle the whole thing here.
339 * If there's an initial nonblank portion, then we need to write that
340 * out. Furthermore, if we're at the start of the line then we'll need
341 * to write the prefix, and if there's saved blank material then we'll
342 * need to write that. Otherwise, there's only blank stuff, which we
343 * accumulate in the buffer.
345 * If we're at the start of a line here, then
349 if (fmt->f&FMTF_NEWL) { PUT_PREFIX; fmt->f &= ~FMTF_NEWL; }
350 PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w);
356 /* There is at least one more segment, so we know that there'll be a line
360 if (fmt->f&FMTF_NEWL) PUT_PREFIX;
361 PUT_SAVED; PUT_NONBLANK;
362 } else if (fmt->f&FMTF_NEWL)
364 PUT_NEWLINE; DRESET(&fmt->w);
367 /* Main loop over whole segments with trailing newlines. For each one, we
368 * know that we're starting at the beginning of a line and there's a final
369 * newline, so we write the initial prefix and drop the trailing blanks.
372 if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
378 /* At the end, there's no final newline. If there's nonblank material,
379 * then we can write the prefix and the nonblank stuff. Otherwise, stash
380 * the blank stuff (including the trailing blanks of the prefix) and leave
381 * the newline flag set.
383 if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; }
384 else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; }
403 /*----- Skeleton ----------------------------------------------------------*/
405 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
406 static int ..._esession(struct tvec_output *o)
407 static void ..._bgroup(struct tvec_output *o)
408 static void ..._skipgroup(struct tvec_output *o,
409 const char *excuse, va_list *ap)
410 static void ..._egroup(struct tvec_output *o)
411 static void ..._btest(struct tvec_output *o)
412 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
413 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
414 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
415 union tvec_regval *rv, const struct tvec_regdef *rd)
416 static void ..._etest(struct tvec_output *o, unsigned outcome)
417 static void ..._bbench(struct tvec_output *o,
418 const char *ident, unsigned unit)
419 static void ..._ebench(struct tvec_output *o,
420 const char *ident, unsigned unit,
421 const struct tvec_timing *t)
422 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
423 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
424 static void ..._destroy(struct tvec_output *o)
426 static const struct tvec_outops ..._ops = {
427 ..._bsession, ..._esession,
428 ..._bgroup, ..._egroup, ..._skip,
429 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
430 ..._bbench, ..._ebench,
431 ..._error, ..._notice,
435 /*----- Human-readable output ---------------------------------------------*/
437 #define HAF_FGMASK 0x0f
438 #define HAF_FGSHIFT 0
439 #define HAF_BGMASK 0xf0
440 #define HAF_BGSHIFT 4
443 #define HAF_BOLD 1024u
444 #define HCOL_BLACK 0u
446 #define HCOL_GREEN 2u
447 #define HCOL_YELLOW 3u
449 #define HCOL_MAGENTA 5u
451 #define HCOL_WHITE 7u
452 #define HCF_BRIGHT 8u
453 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
454 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
456 #define HA_WIN (HFG(GREEN))
457 #define HA_LOSE (HFG(RED) | HAF_BOLD)
458 #define HA_SKIP (HFG(YELLOW))
459 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
461 struct human_output {
462 struct tvec_output _o;
463 struct tvec_state *tv;
465 char *outbuf; size_t outsz;
471 #define HOF_DUPERR 2u
472 #define HOF_COLOUR 4u
473 #define HOF_PROGRESS 8u
476 static void set_colour(FILE *fp, int *sep_inout,
477 const char *norm, const char *bright,
480 if (*sep_inout) putc(*sep_inout, fp);
481 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
485 static void setattr(struct human_output *h, unsigned attr)
487 unsigned diff = h->attr ^ attr;
490 if (!diff || !(h->f&HOF_COLOUR)) return;
491 fputs("\x1b[", h->fmt.fp);
494 if (attr&HAF_BOLD) putc('1', h->fmt.fp);
495 else { putc('0', h->fmt.fp); diff = h->attr; }
498 if (diff&(HAF_FG | HAF_FGMASK)) {
500 set_colour(h->fmt.fp, &sep, "3", "9",
501 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
503 { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; }
505 if (diff&(HAF_BG | HAF_BGMASK)) {
507 set_colour(h->fmt.fp, &sep, "4", "10",
508 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
510 { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; }
513 putc('m', h->fmt.fp); h->attr = attr;
518 static void clear_progress(struct human_output *h)
522 if (h->f&HOF_PROGRESS) {
523 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
524 for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp);
525 h->f &= ~HOF_PROGRESS;
529 static void write_scoreboard_char(struct human_output *h, int ch)
532 case 'x': setattr(h, HA_LOSE); break;
533 case '_': setattr(h, HA_SKIP); break;
534 default: setattr(h, 0); break;
536 putc(ch, h->fmt.fp); setattr(h, 0);
539 static void show_progress(struct human_output *h)
541 struct tvec_state *tv = h->tv;
544 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
545 fprintf(h->fmt.fp, "%s: ", tv->test->name);
546 if (!(h->f&HOF_COLOUR))
547 dstr_write(&h->scoreboard, h->fmt.fp);
548 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
549 write_scoreboard_char(h, *p);
550 fflush(h->fmt.fp); h->f |= HOF_PROGRESS;
554 static void report_location(struct human_output *h, FILE *fp,
555 const char *file, unsigned lno)
560 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
562 if (fp != h->fmt.fp) f |= f_flush;
565 setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp);
566 fputs(file, fp); FLUSH(fp);
567 setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp);
568 fputc(':', fp); FLUSH(fp);
569 setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp);
570 fprintf(fp, "%u", lno); FLUSH(fp);
571 setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp);
572 fputc(':', fp); FLUSH(fp);
573 setattr(h, 0); FLUSH(h->fmt.fp);
581 static int human_writech(void *go, int ch)
582 { struct human_output *h = go; return (format_char(&h->fmt, ch)); }
584 static int human_writem(void *go, const char *p, size_t sz)
585 { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); }
587 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
589 struct human_output *h = go;
594 n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
596 return (format_string(&h->fmt, h->outbuf, n));
599 static const struct gprintf_ops human_printops =
600 { human_writech, human_writem, human_nwritef };
602 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
603 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
605 static void report_skipped(struct human_output *h, unsigned n)
608 fprintf(h->fmt.fp, " (%u ", n);
609 setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
610 fputc(')', h->fmt.fp);
614 static int human_esession(struct tvec_output *o)
616 struct human_output *h = (struct human_output *)o;
617 struct tvec_state *tv = h->tv;
619 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
620 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
621 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
622 all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
625 setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0);
626 fprintf(h->fmt.fp, " %s%u %s",
627 !(all_skip || grps_skip) ? "all " : "",
628 all_win, all_win == 1 ? "test" : "tests");
629 report_skipped(h, all_skip);
630 fprintf(h->fmt.fp, " in %u %s",
631 grps_win, grps_win == 1 ? "group" : "groups");
632 report_skipped(h, grps_skip);
634 setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
635 fprintf(h->fmt.fp, " %u out of %u %s",
636 all_lose, all_run, all_run == 1 ? "test" : "tests");
637 report_skipped(h, all_skip);
638 fprintf(h->fmt.fp, " in %u out of %u %s",
639 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
640 report_skipped(h, grps_skip);
642 fputc('\n', h->fmt.fp);
644 if (tv->f&TVSF_ERROR) {
645 setattr(h, HA_ERR); fputs("ERRORS", h->fmt.fp); setattr(h, 0);
646 fputs(" found in input; tests may not have run correctly\n", h->fmt.fp);
649 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
652 static void human_bgroup(struct tvec_output *o)
654 struct human_output *h = (struct human_output *)o;
656 h->maxlen = register_maxnamelen(h->tv);
657 dstr_reset(&h->scoreboard); show_progress(h);
660 static void human_skipgroup(struct tvec_output *o,
661 const char *excuse, va_list *ap)
663 struct human_output *h = (struct human_output *)o;
665 if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
666 h->f &= ~HOF_PROGRESS;
667 putc(' ', h->fmt.fp);
668 setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
670 fprintf(h->fmt.fp, "%s: ", h->tv->test->name);
671 setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
673 if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
674 fputc('\n', h->fmt.fp);
677 static void human_egroup(struct tvec_output *o)
679 struct human_output *h = (struct human_output *)o;
680 struct tvec_state *tv = h->tv;
681 unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
682 skip = tv->curr[TVOUT_SKIP], run = win + lose;
684 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
685 else fprintf(h->fmt.fp, "%s:", h->tv->test->name);
688 fprintf(h->fmt.fp, " %u/%u ", lose, run);
689 setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
690 report_skipped(h, skip);
692 fputc(' ', h->fmt.fp); setattr(h, HA_WIN);
693 fputs("ok", h->fmt.fp); setattr(h, 0);
694 report_skipped(h, skip);
696 fputc('\n', h->fmt.fp);
699 static void human_btest(struct tvec_output *o)
700 { struct human_output *h = (struct human_output *)o; show_progress(h); }
702 static void human_skip(struct tvec_output *o,
703 const char *excuse, va_list *ap)
705 struct human_output *h = (struct human_output *)o;
706 struct tvec_state *tv = h->tv;
709 report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
710 fprintf(h->fmt.fp, "`%s' ", tv->test->name);
711 setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
712 if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
713 fputc('\n', h->fmt.fp);
716 static void human_fail(struct tvec_output *o,
717 const char *detail, va_list *ap)
719 struct human_output *h = (struct human_output *)o;
720 struct tvec_state *tv = h->tv;
723 report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
724 fprintf(h->fmt.fp, "`%s' ", tv->test->name);
725 setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
726 if (detail) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, detail, *ap); }
727 fputc('\n', h->fmt.fp);
730 static void human_dumpreg(struct tvec_output *o,
731 unsigned disp, const union tvec_regval *rv,
732 const struct tvec_regdef *rd)
734 struct human_output *h = (struct human_output *)o;
735 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
738 gprintf(&human_printops, h, "%*s%s %s = ",
739 10 + h->maxlen - n, "", ds, rd->name);
740 if (h->f&HOF_COLOUR) {
741 if (!rv) setattr(h, HFG(YELLOW));
742 else if (disp == TVRD_FOUND) setattr(h, HFG(RED));
743 else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN));
745 if (!rv) gprintf(&human_printops, h, "#unset");
746 else rd->ty->dump(rv, rd, 0, &human_printops, h);
747 setattr(h, 0); format_char(&h->fmt, '\n');
750 static void human_etest(struct tvec_output *o, unsigned outcome)
752 struct human_output *h = (struct human_output *)o;
758 case TVOUT_WIN: ch = '.'; break;
759 case TVOUT_LOSE: ch = 'x'; break;
760 case TVOUT_SKIP: ch = '_'; break;
763 dstr_putc(&h->scoreboard, ch);
764 write_scoreboard_char(h, ch); fflush(h->fmt.fp);
768 static void human_bbench(struct tvec_output *o,
769 const char *ident, unsigned unit)
771 struct human_output *h = (struct human_output *)o;
772 struct tvec_state *tv = h->tv;
775 fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp);
778 static void human_ebench(struct tvec_output *o,
779 const char *ident, unsigned unit,
780 const struct bench_timing *tm)
782 struct human_output *h = (struct human_output *)o;
784 tvec_benchreport(&human_printops, h->fmt.fp, unit, tm);
785 fputc('\n', h->fmt.fp);
788 static void human_report(struct tvec_output *o, unsigned level,
789 const char *msg, va_list *ap)
791 struct human_output *h = (struct human_output *)o;
792 struct tvec_state *tv = h->tv;
795 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
797 clear_progress(h); fflush(h->fmt.fp);
798 fprintf(stderr, "%s: ", QUIS);
799 report_location(h, stderr, tv->infile, tv->lno);
800 fwrite(d.buf, 1, d.len, stderr);
802 if (h->f&HOF_DUPERR) {
803 report_location(h, h->fmt.fp, tv->infile, tv->lno);
804 fwrite(d.buf, 1, d.len, h->fmt.fp);
809 static void human_destroy(struct tvec_output *o)
811 struct human_output *h = (struct human_output *)o;
813 destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0);
814 dstr_destroy(&h->scoreboard);
815 xfree(h->outbuf); xfree(h);
818 static const struct tvec_outops human_ops = {
819 human_bsession, human_esession,
820 human_bgroup, human_skipgroup, human_egroup,
821 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
822 human_bbench, human_ebench,
827 struct tvec_output *tvec_humanoutput(FILE *fp)
829 struct human_output *h;
832 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
833 h->f = 0; h->attr = 0;
835 init_fmt(&h->fmt, fp, 0);
836 h->outbuf = 0; h->outsz = 0;
838 switch (getenv_boolean("TVEC_TTY", -1)) {
839 case 1: h->f |= HOF_TTY; break;
842 if (isatty(fileno(fp))) h->f |= HOF_TTY;
845 switch (getenv_boolean("TVEC_COLOUR", -1)) {
846 case 1: h->f |= HOF_COLOUR; break;
851 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
856 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
857 dstr_create(&h->scoreboard);
861 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
864 struct tvec_output _o;
865 struct tvec_state *tv;
867 char *outbuf; size_t outsz;
871 static int tap_writech(void *go, int ch)
872 { struct tap_output *t = go; return (format_char(&t->fmt, ch)); }
874 static int tap_writem(void *go, const char *p, size_t sz)
875 { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); }
877 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
879 struct human_output *t = go;
884 n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
886 return (format_string(&t->fmt, t->outbuf, n));
889 static const struct gprintf_ops tap_printops =
890 { tap_writech, tap_writem, tap_nwritef };
892 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
894 struct tap_output *t = (struct tap_output *)o;
897 fputs("TAP version 13\n", t->fmt.fp);
900 static unsigned tap_grpix(struct tap_output *t)
902 struct tvec_state *tv = t->tv;
904 return (tv->grps[TVOUT_WIN] +
905 tv->grps[TVOUT_LOSE] +
906 tv->grps[TVOUT_SKIP]);
909 static int tap_esession(struct tvec_output *o)
911 struct tap_output *t = (struct tap_output *)o;
912 struct tvec_state *tv = t->tv;
914 if (tv->f&TVSF_ERROR) {
916 "Errors found in input; tests may not have run correctly\n",
921 fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t));
922 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
925 static void tap_bgroup(struct tvec_output *o)
927 struct tap_output *t = (struct tap_output *)o;
928 t->maxlen = register_maxnamelen(t->tv);
931 static void tap_skipgroup(struct tvec_output *o,
932 const char *excuse, va_list *ap)
934 struct tap_output *t = (struct tap_output *)o;
936 fprintf(t->fmt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
937 if (excuse) { fputc(' ', t->fmt.fp); vfprintf(t->fmt.fp, excuse, *ap); }
938 fputc('\n', t->fmt.fp);
941 static void tap_egroup(struct tvec_output *o)
943 struct tap_output *t = (struct tap_output *)o;
944 struct tvec_state *tv = t->tv;
946 grpix = tap_grpix(t),
947 win = tv->curr[TVOUT_WIN],
948 lose = tv->curr[TVOUT_LOSE],
949 skip = tv->curr[TVOUT_SKIP];
952 fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u",
953 grpix, tv->test->name, lose, win + lose);
954 if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
956 fprintf(t->fmt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
957 if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
959 fputc('\n', t->fmt.fp);
962 static void tap_btest(struct tvec_output *o) { ; }
964 static void tap_outcome(struct tvec_output *o, const char *outcome,
965 const char *detail, va_list *ap)
967 struct tap_output *t = (struct tap_output *)o;
968 struct tvec_state *tv = t->tv;
970 gprintf(&tap_printops, t, "%s:%u: `%s' %s",
971 tv->infile, tv->test_lno, tv->test->name, outcome);
973 format_string(&t->fmt, ": ", 2);
974 vgprintf(&tap_printops, t, detail, ap);
976 format_char(&t->fmt, '\n');
979 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
980 { tap_outcome(o, "skipped", excuse, ap); }
981 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
982 { tap_outcome(o, "FAILED", detail, ap); }
984 static void tap_dumpreg(struct tvec_output *o,
985 unsigned disp, const union tvec_regval *rv,
986 const struct tvec_regdef *rd)
988 struct tap_output *t = (struct tap_output *)o;
989 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
991 gprintf(&tap_printops, t, "%*s%s %s = ",
992 10 + t->maxlen - n, "", ds, rd->name);
993 if (!rv) gprintf(&tap_printops, t, "#<unset>");
994 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
995 format_char(&t->fmt, '\n');
998 static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
1000 static void tap_bbench(struct tvec_output *o,
1001 const char *ident, unsigned unit)
1004 static void tap_ebench(struct tvec_output *o,
1005 const char *ident, unsigned unit,
1006 const struct bench_timing *tm)
1008 struct tap_output *t = (struct tap_output *)o;
1009 struct tvec_state *tv = t->tv;
1011 gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1012 tvec_benchreport(&tap_printops, t, unit, tm);
1013 format_char(&t->fmt, '\n');
1016 static void tap_report(struct tvec_output *o, unsigned level,
1017 const char *msg, va_list *ap)
1019 struct tap_output *t = (struct tap_output *)o;
1020 struct tvec_state *tv = t->tv;
1021 const struct gprintf_ops *gops; void *go;
1023 if (level >= TVLEV_ERR) {
1024 fputs("Bail out! ", t->fmt.fp);
1025 gops = &file_printops; go = t->fmt.fp;
1027 gops = &tap_printops; go = t;
1029 if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
1030 gprintf(gops, go, msg, ap); gops->putch(go, '\n');
1033 static void tap_destroy(struct tvec_output *o)
1035 struct tap_output *t = (struct tap_output *)o;
1037 destroy_fmt(&t->fmt,
1038 t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE);
1039 xfree(t->outbuf); xfree(t);
1042 static const struct tvec_outops tap_ops = {
1043 tap_bsession, tap_esession,
1044 tap_bgroup, tap_skipgroup, tap_egroup,
1045 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
1046 tap_bbench, tap_ebench,
1051 struct tvec_output *tvec_tapoutput(FILE *fp)
1053 struct tap_output *t;
1055 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
1056 init_fmt(&t->fmt, fp, "## ");
1057 t->outbuf = 0; t->outsz = 0;
1061 /*----- Default output ----------------------------------------------------*/
1063 struct tvec_output *tvec_dfltout(FILE *fp)
1065 int ttyp = getenv_boolean("TVEC_TTY", -1);
1067 if (ttyp == -1) ttyp = isatty(fileno(fp));
1068 if (ttyp) return (tvec_humanoutput(fp));
1069 else return (tvec_tapoutput(fp));
1072 /*----- That's all, folks -------------------------------------------------*/