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 #include "ttycolour.h"
51 #include "tvec-bench.h"
52 #include "tvec-output.h"
54 /*----- Common machinery --------------------------------------------------*/
56 /* --- @regdisp@ --- *
58 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
60 * Returns: A human-readable adjective describing the register
64 static const char *regdisp(unsigned disp)
67 case TVRD_INPUT: return "input";
68 case TVRD_OUTPUT: return "output";
69 case TVRD_MATCH: return "matched";
70 case TVRD_FOUND: return "found";
71 case TVRD_EXPECT: return "expected";
76 /* --- @interpret_boolean@, @getenv_boolean@ --- *
78 * Arguments: @const char *val@ = a string
79 * @const char *var@ = environment variable name
80 * @int dflt@ = default value
81 * @const char *what, ...@ = format string describing where the
84 * Returns: @0@ if the string, or variable, is set to something
85 * falseish, @1@ if it's set to something truish, or @dflt@
89 static PRINTF_LIKE(3, 4)
90 int interpret_boolean(const char *val, int dflt, const char *what, ...)
98 else if (STRCMP(val, ==, "y") || STRCMP(val, ==, "yes") ||
99 STRCMP(val, ==, "t") || STRCMP(val, ==, "true") ||
100 STRCMP(val, ==, "on") || STRCMP(val, ==, "force") ||
101 STRCMP(val, ==, "1"))
103 else if (STRCMP(val, ==, "n") || STRCMP(val, ==, "no") ||
104 STRCMP(val, ==, "nil") || STRCMP(val, ==, "f") ||
105 STRCMP(val, ==, "false") ||
106 STRCMP(val, ==, "off") || STRCMP(val, ==, "inhibit") ||
107 STRCMP(val, ==, "0"))
110 va_start(ap, what); dstr_vputf(&d, what, &ap); va_end(ap);
111 moan("ignoring unexpected value `%s' for %s", val, d.buf);
114 dstr_destroy(&d); return (rc);
117 static int getenv_boolean(const char *var, int dflt)
119 return (interpret_boolean(getenv(var), dflt,
120 "environment variable `%s'", var));
123 /* --- @register_maxnamelen@ --- *
125 * Arguments: @const struct tvec_state *tv@ = test vector state
127 * Returns: The maximum length of a register name in the current test.
130 static int register_maxnamelen(const struct tvec_state *tv)
132 const struct tvec_regdef *rd;
135 for (rd = tv->test->regs; rd->name; rd++)
136 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
140 /* --- @print_ident@ --- *
142 * Arguments: @struct tvec_state *tv@ = test-vector state
143 * @unsigned style@ = style to use for register dumps
144 * @const struct gprintf_ops *gops@ = output operations
145 * @void *go@ = output state
149 * Use: Write a benchmark identification to the output.
152 static void print_ident(struct tvec_state *tv, unsigned style,
153 const struct gprintf_ops *gops, void *go)
155 const struct tvec_regdef *rd;
160 for (rd = tv->test->regs; rd->name; rd++)
162 if (!(f&f_any)) f |= f_any;
163 else if (style&TVSF_RAW) gops->putch(go, ' ');
164 else gprintf(gops, go, ", ");
165 gprintf(gops, go, "%s", rd->name);
166 if (style&TVSF_RAW) gops->putch(go, '=');
167 else gprintf(gops, go, " = ");
168 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, style, gops, go);
174 /*----- Output layout -----------------------------------------------------*/
176 /* We have two main jobs in output layout: trimming trailing blanks; and
177 * adding a prefix to each line.
179 * This is somehow much more complicated than it ought to be.
183 FILE *fp; /* output file */
184 const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
185 dstr w; /* trailing whitespace */
186 dstr ctrl; /* control sequence for next word */
187 unsigned f; /* flags */
188 #define LYTF_NEWL 1u /* start of output line */
191 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
195 #define SPLIT_RANGE(tail, base, limit) do { \
196 /* Set TAIL to point just after the last nonspace character between \
197 * BASE and LIMIT. If there are no nonspace characters, then set \
198 * TAIL to equal BASE. \
201 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
204 #define PUT_RANGE(base, limit) do { \
205 /* Write the range of characters between BASE and LIMIT to the output \
206 * file. Return immediately on error. \
209 size_t _n = limit - base; \
210 if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
213 #define PUT_CHAR(ch) do { \
214 /* Write CH to the output. Return immediately on error. */ \
216 if (putc(ch, lyt->fp) == EOF) return (-1); \
219 #define PUT_PREFIX do { \
220 /* Output the prefix, if there is one. Return immediately on error. */ \
222 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
225 #define PUT_SAVED do { \
226 /* Output the saved trailing blank material in the buffer. */ \
228 size_t _n = lyt->w.len; \
229 if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
232 #define PUT_CTRL do { \
233 /* Output the accumulated control string. */ \
235 size_t _n = lyt->ctrl.len; \
236 if (_n && fwrite(lyt->ctrl.buf, 1, _n, lyt->fp) < _n) return (-1); \
239 #define PUT_PFXINB do { \
240 /* Output the initial nonblank portion of the prefix, if there is \
241 * one. Return immediately on error. \
244 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
247 #define SAVE_PFXTAIL do { \
248 /* Save the trailing blank portion of the prefix. */ \
251 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
254 /* --- @set_layout_prefix@ --- *
256 * Arguments: @struct layout *lyt@ = layout state
257 * @const char *prefix@ = new prefix string or null
261 * Use: Change the configured prefix string. The change takes effect
262 * at the start of the next line (or the current line if it's
263 * empty or only whitespace so far).
266 static void set_layout_prefix(struct layout *lyt, const char *prefix)
270 if (!prefix || !*prefix)
271 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
273 lyt->prefix = prefix;
274 l = lyt->pfxlim = prefix + strlen(prefix);
275 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
279 /* --- @init_layout@ --- *
281 * Arguments: @struct layout *lyt@ = layout state to initialize
282 * @FILE *fp@ = output file
283 * @const char *prefix@ = prefix string (or null if empty)
287 * Use: Initialize a layout state.
290 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
294 dstr_create(&lyt->w); dstr_create(&lyt->ctrl);
295 set_layout_prefix(lyt, prefix);
298 /* --- @destroy_layout@ --- *
300 * Arguments: @struct layout *lyt@ = layout state
301 * @unsigned f@ = flags (@DLF_...@)
305 * Use: Releases a layout state and the resources it holds.
306 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
307 * it open (in case it's @stderr@ or something).
311 static void destroy_layout(struct layout *lyt, unsigned f)
313 if (f&DLF_CLOSE) fclose(lyt->fp);
314 dstr_destroy(&lyt->w); dstr_destroy(&lyt->ctrl);
317 /* --- @layout_char@ --- *
319 * Arguments: @struct layout *lyt@ = layout state
320 * @int ch@ = character to write
322 * Returns: Zero on success, @-1@ on failure.
324 * Use: Write a single character to the output.
327 static int layout_char(struct layout *lyt, int ch)
330 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
331 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
332 } else if (isspace(ch))
335 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
336 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
341 /* --- @layout_string@ --- *
343 * Arguments: @struct layout *lyt@ = layout state
344 * @const char *p@ = string to write
345 * @size_t sz@ = length of string
347 * Returns: Zero on success, @-1@ on failure.
349 * Use: Write a string to the output.
352 static int layout_string(struct layout *lyt, const char *p, size_t sz)
354 const char *q, *r, *l = p + sz;
356 /* This is rather vexing. There are a small number of jobs to do, but the
357 * logic for deciding which to do when gets rather hairy if, as I've tried
358 * here, one aims to minimize the number of decisions being checked, so
359 * it's worth canning them into macros.
361 * Here, a `blank' is a whitespace character other than newline. The input
362 * buffer consists of one or more `segments', each of which consists of:
364 * * an initial portion, which is either empty or ends with a nonblank
367 * * a suffix which consists only of blanks; and
369 * * an optional newline.
371 * All segments except the last end with a newline.
374 #define SPLIT_SEGMENT do { \
375 /* Determine the bounds of the current segment. If there is a final \
376 * newline, then q is non-null and points to this newline; otherwise, \
377 * q is null. The initial portion of the segment lies between p .. r \
378 * and the blank suffix lies between r .. q (or r .. l if q is null). \
379 * This sounds awkward, but the suffix is only relevant if there is \
383 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
386 #define PUT_NONBLANK do { \
387 /* Output the initial portion of the segment. */ \
392 #define PUT_NEWLINE do { \
393 /* Write a newline, and advance to the next segment. */ \
395 PUT_CHAR('\n'); p = q + 1; \
398 #define SAVE_TAIL do { \
399 /* Save the trailing blank portion of the segment in the buffer. \
400 * Assumes that there is no newline, since otherwise the suffix would \
404 DPUTM(&lyt->w, r, l - r); \
407 /* Determine the bounds of the first segment. Handling this is the most
408 * complicated part of this function.
413 /* This is the only segment. We'll handle the whole thing here.
415 * If there's an initial nonblank portion, then we need to write that
416 * out. Furthermore, if we're at the start of the line then we'll need
417 * to write the prefix, and if there's saved blank material then we'll
418 * need to write that. Otherwise, there's only blank stuff, which we
419 * accumulate in the buffer.
421 * If we're at the start of a line here, then put the prefix followed by
422 * any saved whitespace, and then our initial nonblank portion. Then
423 * save our new trailing space.
427 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
428 PUT_SAVED; PUT_CTRL; PUT_NONBLANK;
429 DRESET(&lyt->w); DRESET(&lyt->ctrl);
435 /* There is at least one more segment, so we know that there'll be a line
439 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
440 PUT_SAVED; PUT_CTRL; PUT_NONBLANK;
442 } else if (lyt->f&LYTF_NEWL)
444 PUT_NEWLINE; DRESET(&lyt->w);
447 /* Main loop over whole segments with trailing newlines. For each one, we
448 * know that we're starting at the beginning of a line and there's a final
449 * newline, so we write the initial prefix and drop the trailing blanks.
452 if (r > p) { PUT_PREFIX; PUT_CTRL; PUT_NONBLANK; DRESET(&lyt->ctrl); }
458 /* At the end, there's no final newline. If there's nonblank material,
459 * then we can write the prefix and the nonblank stuff. Otherwise, stash
460 * the blank stuff (including the trailing blanks of the prefix) and leave
461 * the newline flag set.
463 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
464 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
483 /*----- Human-readable output ---------------------------------------------*/
485 #define GENATTR(want, attrs, fgspc, fgcol, bgspc, bgcol) \
487 { ((fgspc) << TTAF_FGSPCSHIFT) | \
488 ((bgspc) << TTAF_BGSPCSHIFT) | (attrs), \
489 0, (fgcol), (bgcol) } }
491 #define FGBG(want, attrs, fgcol, bgcol) \
492 GENATTR(want | TTACF_FG | TTACF_BG, attrs, \
493 TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_1BPCBR, TTCOL_##bgcol)
494 #define FG(want, attrs, fgcol) \
495 GENATTR(want | TTACF_FG, attrs, \
496 TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_NONE, 0)
497 #define BG(want, attrs, bgcol) \
498 GENATTR(want | TTACF_BG, attrs, \
499 TTCSPC_NONE, 0, TTCSPC_1BPCBR, TTCOL_##bgcol)
500 #define ATTR(want, attrs) \
501 GENATTR(want, attrs, TTCSPC_NONE, 0, TTCSPC_NONE, 0)
503 #define BOLD (TTWT_BOLD << TTAF_WTSHIFT)
505 static const struct tty_attrlist
506 loc_attrs[] = { FG(0, 0, CYN), TTY_ATTRLIST_CLEAR },
507 locsep_attrs[] = { FG(0, 0, BRBLU), TTY_ATTRLIST_CLEAR },
508 note_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR },
509 err_attrs[] = { FG(0, BOLD, MGN), TTY_ATTRLIST_CLEAR },
510 unklv_attrs[] = { FGBG(0, BOLD, BRWHT, BRRED), ATTR(0, TTAF_INVV) },
511 vfound_attrs[] = { FG(0, 0, RED), TTY_ATTRLIST_CLEAR },
512 vexpect_attrs[] = { FG(0, 0, GRN), TTY_ATTRLIST_CLEAR },
513 vunset_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR },
514 lose_attrs[] = { FG(0, BOLD, RED), ATTR(0, BOLD | TTAF_INVV) },
515 skip_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR },
516 xfail_attrs[] = { FG(0, BOLD, BLU), ATTR(0, BOLD) },
517 win_attrs[] = { FG(0, 0, GRN), TTY_ATTRLIST_CLEAR },
518 sblose_attrs[] = { FG(0, BOLD, RED), ATTR(0, BOLD) };
527 /* Predefined attributes. */
528 #define HIGHLIGHTS(_st, _) \
529 _(_st, LOCFN, "lf", loc_attrs) /* location filename */ \
530 _(_st, LOCLN, "ln", loc_attrs) /* location line number */ \
531 _(_st, LOCSEP, "ls", locsep_attrs) /* location separator `:' */ \
532 _(_st, INFO, "mi", 0) /* information */ \
533 _(_st, NOTE, "mn", note_attrs) /* notices */ \
534 _(_st, ERR, "me", err_attrs) /* error messages */ \
535 _(_st, UNKLV, "mu", unklv_attrs) /* unknown-level messages */ \
536 _(_st, DSINPUT, "di", 0) /* disposition for input value */ \
537 _(_st, DSOUTPUT, "do", 0) /* ... unsolicited output */ \
538 _(_st, DSMATCH, "dm", 0) /* ... matching output */ \
539 _(_st, DSFOUND, "df", 0) /* ... incorrect output */ \
540 _(_st, DSEXPECT, "dx", 0) /* ... reference output */ \
541 _(_st, RNINPUT, "ri", 0) /* register name for input value */ \
542 _(_st, RNOUTPUT, "ro", 0) /* ... unsolicited output */ \
543 _(_st, RNMATCH, "rm", 0) /* ... matching output */ \
544 _(_st, RNFOUND, "rf", 0) /* ... incorrect output */ \
545 _(_st, RNEXPECT, "rx", 0) /* ... reference output */ \
546 _(_st, VINPUT, "vi", 0) /* input value */ \
547 _(_st, VOUTPUT, "vo", 0) /* unsolicited output value */ \
548 _(_st, VMATCH, "vm", 0) /* matching output value */ \
549 _(_st, VFOUND, "vf", vfound_attrs) /* incorrect output value */ \
550 _(_st, VEXPECT, "vx", vexpect_attrs) /* reference output value */ \
551 _(_st, VUNSET, "vu", vunset_attrs) /* register not set */ \
552 _(_st, LOSE, "ol", lose_attrs) /* report failure */ \
553 _(_st, SKIP, "os", skip_attrs) /* report a skipped test/group */ \
554 _(_st, XFAIL, "ox", xfail_attrs) /* report expected fail */ \
555 _(_st, WIN, "ow", win_attrs) /* report success */ \
556 _(_st, SBLOSE, "sl", sblose_attrs) /* scoreboard failure */ \
557 _(_st, SBSKIP, "ss", skip_attrs) /* scoreboard skipped test */ \
558 _(_st, SBXFAIL, "sx", xfail_attrs) /* scoreboard xfail */ \
559 _(_st, SBWIN, "sw", 0) /* scoreboard success */
561 TTYCOLOUR_DEFENUM(HIGHLIGHTS, HL_);
562 #define HL_PLAIN (-1)
564 /* Scoreboard indicators. */
565 static const char scoreboard[] = { 'x', '_', 'o', '.' };
567 struct human_output {
568 struct tvec_output _o; /* output base class */
569 struct tvec_state *tv; /* stashed testing state */
570 arena *a; /* arena for memory allocation */
571 struct tty *tty; /* output terminal, or null */
572 struct layout lyt; /* output layout */
573 char *outbuf; size_t outsz; /* buffer for formatted output */
574 dstr scoreboard; /* history of test group results */
575 struct tty_attr attr[HL__LIMIT]; /* highlight attribute map */
576 int maxlen; /* longest register name */
577 unsigned f; /* flags */
578 /* bits 0--7 from @TVHF_...@ */
579 #define HOF_DUPERR 0x0100u /* duplicate errors to stderr */
580 #define HOF_PROGRESS 0x0200u /* progress display is active */
583 /* --- @setattr@, @setattr_layout@ --- *
585 * Arguments: @struct human_output *h@ = output state
586 * @int hi@ = highlight code to set
590 * Use: Send a control sequence to the output stream so that
591 * subsequent text is printed with the given attributes.
594 static void setattr_common(struct human_output *h,
595 const struct gprintf_ops *gops, void *go, int hl)
597 if (h->f&TVHF_COLOUR)
598 tty_setattrg(h->tty, gops, go, hl >= 0 ? &h->attr[hl] : 0);
601 static void setattr(struct human_output *h, int hl)
602 { setattr_common(h, &file_printops, h->lyt.fp, hl); }
604 static void setattr_layout(struct human_output *h, int hl)
605 { setattr_common(h, &dstr_printops, &h->lyt.ctrl, hl); }
607 /* --- @clear_progress@ --- *
609 * Arguments: @struct human_output *h@ = output state
613 * Use: Remove the progress display from the terminal.
615 * If the progress display isn't active then do nothing.
618 static void clear_progress(struct human_output *h)
622 if (h->f&HOF_PROGRESS) {
623 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
624 for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
625 h->f &= ~HOF_PROGRESS;
629 /* --- @write_scoreboard_char@ --- *
631 * Arguments: @struct human_output *h@ = output state
632 * @int ch@ = scoreboard character to print
636 * Use: Write a scoreboard character, indicating the outcome of a
637 * test, to the output stream, with appropriate highlighting.
640 static void write_scoreboard_char(struct human_output *h, int ch)
642 assert(0 <= ch && ch < TVOUT_LIMIT);
643 setattr(h, HL_SBLOSE + ch); putc(scoreboard[ch], h->lyt.fp);
646 /* --- @show_progress@ --- *
648 * Arguments: @struct human_output *h@ = output state
652 * Use: Show the progress display, with the record of outcomes for
653 * the current test group.
655 * If the progress display is already active, or the output
656 * stream is not interactive, then nothing happens.
659 static void show_progress(struct human_output *h)
661 struct tvec_state *tv = h->tv;
664 if (tv->test && (h->f&TVHF_TTY) && !(h->f&HOF_PROGRESS)) {
665 fprintf(h->lyt.fp, "%s: ", tv->test->name);
666 for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
667 write_scoreboard_char(h, *p);
668 setattr(h, HL_PLAIN);
669 fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
673 /* --- @human_writech@, @human_write@, @human_writef@ --- *
675 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
676 * @int ch@ = character to write
677 * @const char *@p@, @size_t sz@ = string (with explicit length)
679 * @const char *p, ...@ = format control string and arguments to
684 * Use: Write characters, strings, or formatted strings to the
685 * output, applying appropriate layout.
687 * For the human output driver, the layout machinery just strips
691 static int human_writech(void *go, int ch)
692 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
694 static int human_writem(void *go, const char *p, size_t sz)
695 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
697 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
699 struct human_output *h = go;
704 n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
706 if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
710 static const struct gprintf_ops human_printops =
711 { human_writech, human_writem, human_nwritef };
713 /* --- @human_bsession@ --- *
715 * Arguments: @struct tvec_output *o@ = output sink, secretly a
716 * @struct human_output@
717 * @struct tvec_state *tv@ = the test state producing output
721 * Use: Begin a test session.
723 * The human driver just records the test state for later
727 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
728 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
730 /* --- @human_report_unusual@ --- *
732 * Arguments: @struct human_output *h@ = output sink
733 * @unsigned nxfail, nskip@ = number of expected failures and
738 * Use: Write (directly on the output stream) a note about expected
739 * failures and/or skipped tests, if there were any.
742 static void human_report_unusual(struct human_output *h,
743 unsigned nxfail, unsigned nskip)
749 fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nxfail);
750 setattr(h, HL_XFAIL);
751 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
752 setattr(h, HL_PLAIN);
757 fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nskip);
758 setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN);
762 if (f&f_any) fputc(')', h->lyt.fp);
767 /* --- @human_esession@ --- *
769 * Arguments: @struct tvec_output *o@ = output sink, secretly a
770 * @struct human_output@
772 * Returns: Suggested exit code.
774 * Use: End a test session.
776 * The human driver prints a final summary of the rest results
777 * and returns a suitable exit code.
780 static int human_esession(struct tvec_output *o)
782 struct human_output *h = (struct human_output *)o;
783 struct tvec_state *tv = h->tv;
785 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
786 all_xfail = tv->all[TVOUT_XFAIL],
787 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
788 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
789 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
790 grps_run = grps_win + grps_lose;
793 setattr(h, HL_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HL_PLAIN);
794 fprintf(h->lyt.fp, " %s%u %s",
795 !(all_skip || grps_skip) ? "all " : "",
796 all_pass, all_pass == 1 ? "test" : "tests");
797 human_report_unusual(h, all_xfail, all_skip);
798 fprintf(h->lyt.fp, " in %u %s",
799 grps_win, grps_win == 1 ? "group" : "groups");
800 human_report_unusual(h, 0, grps_skip);
802 setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN);
803 fprintf(h->lyt.fp, " %u out of %u %s",
804 all_lose, all_run, all_run == 1 ? "test" : "tests");
805 human_report_unusual(h, all_xfail, all_skip);
806 fprintf(h->lyt.fp, " in %u out of %u %s",
807 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
808 human_report_unusual(h, 0, grps_skip);
810 fputc('\n', h->lyt.fp);
812 if (tv->f&TVSF_ERROR) {
813 setattr(h, HL_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HL_PLAIN);
814 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
817 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
820 /* --- @human_bgroup@ --- *
822 * Arguments: @struct tvec_output *o@ = output sink, secretly a
823 * @struct human_output@
827 * Use: Begin a test group.
829 * The human driver determines the length of the longest
830 * register name, resets the group progress scoreboard, and
831 * activates the progress display.
834 static void human_bgroup(struct tvec_output *o)
836 struct human_output *h = (struct human_output *)o;
838 h->maxlen = register_maxnamelen(h->tv);
839 dstr_reset(&h->scoreboard); show_progress(h);
842 /* --- @human_skipgroup@ --- *
844 * Arguments: @struct tvec_output *o@ = output sink, secretly a
845 * @struct human_output@
846 * @const char *excuse@, @va_list *ap@ = reason for skipping the
851 * Use: Report that a test group is being skipped.
853 * The human driver just reports the situation to its output
857 static void human_skipgroup(struct tvec_output *o,
858 const char *excuse, va_list *ap)
860 struct human_output *h = (struct human_output *)o;
862 if (!(h->f&TVHF_TTY))
863 fprintf(h->lyt.fp, "%s ", h->tv->test->name);
865 show_progress(h); h->f &= ~HOF_PROGRESS;
866 if (h->scoreboard.len) putc(' ', h->lyt.fp);
868 setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN);
869 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
870 fputc('\n', h->lyt.fp);
873 /* --- @human_egroup@ --- *
875 * Arguments: @struct tvec_output *o@ = output sink, secretly a
876 * @struct human_output@
880 * Use: Report that a test group has finished.
882 * The human driver reports a summary of the group's tests.
885 static void human_egroup(struct tvec_output *o)
887 struct human_output *h = (struct human_output *)o;
888 struct tvec_state *tv = h->tv;
889 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
890 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
891 run = win + lose + xfail;
893 if (h->f&TVHF_TTY) h->f &= ~HOF_PROGRESS;
894 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
897 fprintf(h->lyt.fp, " %u/%u ", lose, run);
898 setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN);
899 human_report_unusual(h, xfail, skip);
901 fputc(' ', h->lyt.fp); setattr(h, HL_WIN);
902 fputs("ok", h->lyt.fp); setattr(h, HL_PLAIN);
903 human_report_unusual(h, xfail, skip);
905 fputc('\n', h->lyt.fp);
908 /* --- @human_btest@ --- *
910 * Arguments: @struct tvec_output *o@ = output sink, secretly a
911 * @struct human_output@
915 * Use: Report that a test is starting.
917 * The human driver makes sure the progress display is active.
920 static void human_btest(struct tvec_output *o)
921 { struct human_output *h = (struct human_output *)o; show_progress(h); }
923 /* --- @human_report_location@ --- *
925 * Arguments: @struct human_output *h@ = output state
926 * @FILE *fp@ = stream to write the location on
927 * @const char *file@ = filename
928 * @unsigned lno@ = line number
932 * Use: Print the filename and line number to the output stream @fp@.
933 * Also, if appropriate, print interleaved highlighting control
934 * codes to our usual output stream. If @file@ is null then do
938 static void human_report_location(struct human_output *h, FILE *fp,
939 const char *file, unsigned lno)
943 else if (fp != h->lyt.fp || !(h->f&TVHF_COLOUR))
944 fprintf(fp, "%s:%u: ", file, lno);
946 setattr(h, HL_LOCFN); fputs(file, fp);
947 setattr(h, HL_LOCSEP); fputc(':', fp);
948 setattr(h, HL_LOCLN); fprintf(fp, "%u", lno);
949 setattr(h, HL_LOCSEP); fputc(':', fp);
950 setattr(h, HL_PLAIN); fputc(' ', fp);
954 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
956 * Arguments: @struct tvec_output *o@ = output sink, secretly a
957 * @struct human_output@
958 * @unsigned attr@ = attribute to apply to the outcome
959 * @const char *outcome@ = outcome string to report
960 * @const char *detail@, @va_list *ap@ = a detail message
961 * @const char *excuse@, @va_list *ap@ = reason for skipping the
966 * Use: Report that a test has been skipped or failed.
968 * The human driver reports the situation on its output stream.
971 static void human_outcome(struct tvec_output *o,
972 unsigned attr, const char *outcome,
973 const char *detail, va_list *ap)
975 struct human_output *h = (struct human_output *)o;
976 struct tvec_state *tv = h->tv;
979 human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
980 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
981 setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HL_PLAIN);
982 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
983 fputc('\n', h->lyt.fp);
986 static void human_skip(struct tvec_output *o,
987 const char *excuse, va_list *ap)
988 { human_outcome(o, HL_SKIP, "skipped", excuse, ap); }
989 static void human_fail(struct tvec_output *o,
990 const char *detail, va_list *ap)
991 { human_outcome(o, HL_LOSE, "FAILED", detail, ap); }
993 /* --- @human_dumpreg@ --- *
995 * Arguments: @struct tvec_output *o@ = output sink, secretly a
996 * @struct human_output@
997 * @unsigned disp@ = register disposition
998 * @const union tvec_regval *rv@ = register value
999 * @const struct tvec_regdef *rd@ = register definition
1003 * Use: Dump a register.
1005 * The human driver applies highlighting to mismatching output
1006 * registers, but otherwise delegates to the register type
1007 * handler and the layout machinery.
1010 static void human_dumpreg(struct tvec_output *o,
1011 unsigned disp, const union tvec_regval *rv,
1012 const struct tvec_regdef *rd)
1014 struct human_output *h = (struct human_output *)o;
1015 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1018 gprintf(&human_printops, h, "%*s", 10 + h->maxlen - n, "");
1019 setattr_layout(h, HL_DSINPUT + disp);
1020 gprintf(&human_printops, h, "%s", ds);
1021 setattr(h, HL_PLAIN); layout_char(&h->lyt, ' ');
1022 setattr_layout(h, HL_RNINPUT + disp);
1023 gprintf(&human_printops, h, "%s", rd->name);
1024 setattr(h, HL_PLAIN); gprintf(&human_printops, h, " = ");
1026 setattr_layout(h, HL_VUNSET);
1027 gprintf(&human_printops, h, "#unset");
1029 setattr_layout(h, HL_VINPUT + disp);
1030 rd->ty->dump(rv, rd, 0, &human_printops, h);
1032 setattr(h, HL_PLAIN); layout_char(&h->lyt, '\n');
1035 /* --- @human_etest@ --- *
1037 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1038 * @struct human_output@
1039 * @unsigned outcome@ = the test outcome
1043 * Use: Report that a test has finished.
1045 * The human driver reactivates the progress display, if
1046 * necessary, and adds a new character for the completed test.
1049 static void human_etest(struct tvec_output *o, unsigned outcome)
1051 struct human_output *h = (struct human_output *)o;
1053 if (h->f&TVHF_TTY) {
1055 dstr_putc(&h->scoreboard, outcome); write_scoreboard_char(h, outcome);
1056 setattr(h, HL_PLAIN); fflush(h->lyt.fp);
1060 /* --- @human_report@ --- *
1062 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1063 * @struct human_output@
1064 * @unsigned level@ = message level (@TVLV_...@)
1065 * @const char *msg@, @va_list *ap@ = format string and
1070 * Use: Report a message to the user.
1072 * The human driver arranges to show the message on @stderr@ as
1073 * well as the usual output, with a certain amount of
1074 * intelligence in case they're both actually the same device.
1077 static void human_report(struct tvec_output *o, unsigned level,
1078 const char *msg, va_list *ap)
1080 struct human_output *h = (struct human_output *)o;
1081 struct tvec_state *tv = h->tv;
1082 const char *levstr; unsigned levhl;
1085 #define f_progress 1u
1087 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1090 #define CASE(tag, name, val) \
1091 case TVLV_##tag: levstr = name; levhl = HL_##tag; break;
1093 default: levstr = "??"; levhl = HL_UNKLV; break;
1096 if (h->f&HOF_PROGRESS) { clear_progress(h); f |= f_progress; }
1098 if (h->f&HOF_DUPERR) {
1099 fprintf(stderr, "%s: ", QUIS);
1100 human_report_location(h, stderr, tv->infile, tv->lno);
1101 fprintf(stderr, "%s: ", levstr);
1102 fwrite(d.buf, 1, d.len, stderr);
1105 human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
1106 setattr(h, levhl); fputs(levstr, h->lyt.fp);
1107 setattr(h, HL_PLAIN); fputs(": ", h->lyt.fp);
1108 fwrite(d.buf, 1, d.len, h->lyt.fp);
1110 if (f&f_progress) show_progress(h);
1116 /* --- @human_bbench@ --- *
1118 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1119 * @struct human_output@
1120 * @const char *desc@ = adhoc test description
1121 * @unsigned unit@ = measurement unit (@BTU_...@)
1125 * Use: Report that a benchmark has started.
1127 * The human driver just prints the start of the benchmark
1131 static void human_bbench(struct tvec_output *o,
1132 const char *desc, unsigned unit)
1134 struct human_output *h = (struct human_output *)o;
1135 struct tvec_state *tv = h->tv;
1138 gprintf(&human_printops, h, "%s ", tv->test->name);
1139 if (desc) gprintf(&human_printops, h, "%s", desc);
1140 else print_ident(tv, TVSF_COMPACT, &human_printops, h);
1141 gprintf(&human_printops, h, ": ");
1142 if (h->f&TVHF_TTY) fflush(h->lyt.fp);
1145 /* --- @human_ebench@ --- *
1147 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1148 * @struct human_output@
1149 * @const char *desc@ = adhoc test description
1150 * @unsigned unit@ = measurement unit (@BTU_...@)
1151 * @const struct bench_timing *t@ = measurement
1155 * Use: Report a benchmark's results.
1157 * The human driver just delegates to the default benchmark
1158 * reporting, via the layout machinery.
1161 static void human_ebench(struct tvec_output *o,
1162 const char *desc, unsigned unit,
1163 const struct bench_timing *t)
1165 struct human_output *h = (struct human_output *)o;
1167 tvec_benchreport(&human_printops, h, unit, 0, t);
1168 layout_char(&h->lyt, '\n');
1171 static const struct tvec_benchoutops human_benchops =
1172 { human_bbench, human_ebench };
1174 /* --- @human_extend@ --- *
1176 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1177 * @struct human_output@
1178 * @const char *name@ = extension name
1180 * Returns: A pointer to the extension implementation, or null.
1183 static const void *human_extend(struct tvec_output *o, const char *name)
1185 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
1189 /* --- @human_destroy@ --- *
1191 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1192 * @struct human_output@
1196 * Use: Release the resources held by the output driver.
1199 static void human_destroy(struct tvec_output *o)
1201 struct human_output *h = (struct human_output *)o;
1204 destroy_layout(&h->lyt,
1205 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1206 dstr_destroy(&h->scoreboard);
1207 x_free(h->a, h->outbuf); x_free(h->a, h);
1210 static const struct tvec_outops human_ops = {
1211 human_bsession, human_esession,
1212 human_bgroup, human_skipgroup, human_egroup,
1213 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1214 human_report, human_extend, human_destroy
1217 /* --- @tvec_humanoutput@ --- *
1219 * Arguments: @FILE *fp@ = output file to write on
1220 * @unsigned f, m@ = flags and mask
1222 * Returns: An output formatter.
1224 * Use: Return an output formatter which writes on @fp@ with the
1225 * expectation that a human will interpret the output.
1227 * The flags @f@ and mask @m@ operate together. Flag bits not
1228 * covered by the mask must be left clear, i.e., @f&~m$ must be
1229 * zero; the semantics are that a set mask bit indicates that
1230 * the corresponding bit of @f@ should control the indicated
1231 * behaviour; a clear mask bit indicates that a suitable default
1232 * should be chosen based on environmental conditions.
1234 * If @TVHF_TTY@ is set, then the output shows a `scoreboard'
1235 * indicating the outcome of each test case attempted, providing
1236 * a visual indication of progress. If @TVHF_COLOUR@ is set,
1237 * then the output uses control codes for colour and other
1238 * highlighting. It is unusual to set @TVHF_COLOUR@ without
1239 * @TVHF_TTY@, this is permitted anyway.
1241 * The environment variables %|TVEC_TTY|% and %|TVEC_COLOUR|%
1242 * provide default values for these settings. If they are not
1243 * set, then @TVHF_TTY@ is set if @fp@ refers to a terminal, and
1244 * @TVHF_COLOUR@ is set if @TVHF_TTY@ is set and, additionally,
1245 * the %|TERM|% environment variable is set to a value other
1249 struct tvec_output *tvec_humanoutput(FILE *fp, unsigned f, unsigned m)
1251 struct human_output *h;
1252 struct stat st_out, st_err;
1255 static const struct ttycolour_style hltab[] =
1256 TTYCOLOUR_INITTAB(HIGHLIGHTS);
1261 switch (getenv_boolean("MLIB_TVEC_TTY", -1)) {
1262 case 1: f |= TVHF_TTY; break;
1265 if (isatty(fileno(fp))) f |= TVHF_TTY;
1268 if (!(m&TVHF_COLOUR))
1269 switch (getenv_boolean("MLIB_TVEC_COLOUR", -1)) {
1270 case 1: f |= TVHF_COLOUR; break;
1273 if (ttycolour_enablep((f&TVHF_TTY ? TCEF_TTY : 0) | TCEF_DFLT))
1278 /* Decide whether to write copies of reports to stderr.
1280 * There's not much point if the output is already going to stderr.
1281 * Otherwise, check to see whether stdout is the same underlying file.
1284 rc_out = fstat(fileno(fp), &st_out);
1285 rc_err = fstat(STDERR_FILENO, &st_err);
1286 if (!rc_err && (rc_out ||
1287 st_out.st_dev != st_err.st_dev ||
1288 st_out.st_ino != st_err.st_ino))
1292 XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
1295 /* Initialize the colour tables. */
1296 if (!(h->f&TVHF_COLOUR))
1299 h->tty = tty_open(fp, TTF_BORROW, 0);
1301 h->f &= ~TVHF_COLOUR;
1303 ttycolour_config(h->attr, "MLIB_TVEC_COLOURS",
1304 TCIF_GETENV | TCIF_REPORT, h->tty, hltab);
1307 init_layout(&h->lyt, fp, 0);
1308 h->outbuf = 0; h->outsz = 0;
1310 dstr_create(&h->scoreboard);
1314 /*----- Machine-readable output -------------------------------------------*/
1316 struct machine_output {
1317 struct tvec_output _o; /* output base class */
1318 struct tvec_state *tv; /* stashed testing state */
1319 arena *a; /* arena for memory allocation */
1320 FILE *fp; /* output stream */
1321 char *outbuf; size_t outsz; /* buffer for formatted output */
1322 unsigned grpix, testix; /* group and test indices */
1323 unsigned f; /* flags */
1324 #define MF_BENCH 1u /* current test is a benchmark */
1327 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1329 * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
1330 * @int ch@ = character to write
1331 * @const char *@p@, @size_t sz@ = string (with explicit length)
1333 * @const char *p, ...@ = format control string and arguments to
1338 * Use: Write characters, strings, or formatted strings to the
1339 * output, applying appropriate quoting.
1342 static void machine_escape(struct machine_output *m, int ch)
1345 case 0: fputs("\\0", m->fp); break;
1346 case '\a': fputs("\\a", m->fp); break;
1347 case '\b': fputs("\\b", m->fp); break;
1348 case '\x1b': fputs("\\e", m->fp); break;
1349 case '\f': fputs("\\f", m->fp); break;
1350 case '\n': fputs("\\n", m->fp); break;
1351 case '\r': fputs("\\r", m->fp); break;
1352 case '\t': fputs("\\t", m->fp); break;
1353 case '\v': fputs("\\v", m->fp); break;
1354 case '"': fputs("\\\"", m->fp); break;
1355 case '\\': fputs("\\\\", m->fp); break;
1356 default: fprintf(m->fp, "\\x{%02x}", ch);
1360 static int machine_writech(void *go, int ch)
1362 struct machine_output *m = go;
1364 if (ISPRINT(ch)) putc(ch, m->fp);
1365 else machine_escape(m, ch);
1369 static int machine_writem(void *go, const char *p, size_t sz)
1371 struct machine_output *m = go;
1372 const char *q, *l = p + sz;
1377 if (q >= l) goto final;
1378 if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1381 if (p < q) fwrite(p, 1, q - p, m->fp);
1382 p = q; machine_escape(m, (unsigned char)*p++);
1385 if (p < l) fwrite(p, 1, l - p, m->fp);
1389 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1391 struct machine_output *m = go;
1396 n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
1398 return (machine_writem(m, m->outbuf, n));
1401 static const struct gprintf_ops machine_printops =
1402 { machine_writech, machine_writem, machine_nwritef };
1404 /* --- @machine_maybe_quote@ --- *
1406 * Arguments: @struct machine_output *m@ = output sink
1407 * @const char *p@ = pointer to string
1411 * Use: Print the string @p@, quoting it if necessary.
1414 static void machine_maybe_quote(struct machine_output *m, const char *p)
1418 for (q = p; *q; q++)
1419 if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
1420 fputs(p, m->fp); return;
1422 putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1425 /* --- @machine_bsession@ --- *
1427 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1428 * @struct machine_output@
1429 * @struct tvec_state *tv@ = the test state producing output
1433 * Use: Begin a test session.
1435 * The TAP driver records the test state for later reference,
1436 * initializes the group index counter, and prints the version
1440 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1442 struct machine_output *m = (struct machine_output *)o;
1444 m->tv = tv; m->grpix = 0;
1447 /* --- @machine_show_stats@ --- *
1449 * Arguments: @struct machine_output *m@ = output sink
1450 * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1454 * Use: Print a machine readable outcome statistics table
1457 static void machine_show_stats(struct machine_output *m,
1458 unsigned out[TVOUT_LIMIT])
1460 static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1463 for (i = 0; i < TVOUT_LIMIT; i++) {
1464 if (i) putc(' ', m->fp);
1465 fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1469 /* --- @machine_esession@ --- *
1471 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1472 * @struct machine_output@
1474 * Returns: Suggested exit code.
1476 * Use: End a test session.
1478 * The TAP driver prints a final summary of the rest results
1479 * and returns a suitable exit code. If errors occurred, it
1480 * instead prints a `Bail out!' line forcing the reader to
1484 static int machine_esession(struct tvec_output *o)
1486 struct machine_output *m = (struct machine_output *)o;
1487 struct tvec_state *tv = m->tv;
1489 fputs("END groups: ", m->fp);
1490 machine_show_stats(m, tv->grps);
1491 fputs("; tests: ", m->fp);
1492 machine_show_stats(m, tv->all);
1494 m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1497 /* --- @machine_bgroup@ --- *
1499 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1500 * @struct machine_output@
1504 * Use: Begin a test group.
1506 * The TAP driver determines the length of the longest
1507 * register name, resets the group progress scoreboard, and
1508 * activates the progress display.
1511 static void machine_bgroup(struct tvec_output *o)
1513 struct machine_output *m = (struct machine_output *)o;
1514 struct tvec_state *tv = m->tv;
1516 fputs("BGROUP ", m->fp);
1517 machine_maybe_quote(m, tv->test->name);
1519 m->grpix++; m->testix = 0;
1522 /* --- @machine_skipgroup@ --- *
1524 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1525 * @struct machine_output@
1526 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1531 * Use: Report that a test group is being skipped.
1533 * The TAP driver just reports the situation to its output
1537 static void machine_skipgroup(struct tvec_output *o,
1538 const char *excuse, va_list *ap)
1540 struct machine_output *m = (struct machine_output *)o;
1541 struct tvec_state *tv = m->tv;
1543 fputs("SKIPGRP ", m->fp);
1544 machine_maybe_quote(m, tv->test->name);
1546 fputs(" \"", m->fp);
1547 vgprintf(&machine_printops, m, excuse, ap);
1553 /* --- @machine_egroup@ --- *
1555 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1556 * @struct machine_output@
1560 * Use: Report that a test group has finished.
1562 * The TAP driver reports a summary of the group's tests.
1565 static void machine_egroup(struct tvec_output *o)
1567 struct machine_output *m = (struct machine_output *)o;
1568 struct tvec_state *tv = m->tv;
1570 if (!(tv->f&TVSF_SKIP)) {
1571 fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
1572 putc(' ', m->fp); machine_show_stats(m, tv->curr);
1577 /* --- @machine_btest@ --- *
1579 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1580 * @struct machine_output@
1584 * Use: Report that a test is starting.
1586 * The TAP driver advances its test counter. (We could do this
1587 * by adding up up the counters in @tv->curr@, and add on the
1588 * current test, but it's easier this way.)
1591 static void machine_btest(struct tvec_output *o) { ; }
1593 /* --- @machine_report_location@ --- *
1595 * Arguments: @struct human_output *h@ = output state
1596 * @const char *file@ = filename
1597 * @unsigned lno@ = line number
1601 * Use: Print the filename and line number to the output stream.
1604 static void machine_report_location(struct machine_output *m,
1605 const char *file, unsigned lno)
1609 machine_maybe_quote(m, file);
1610 fprintf(m->fp, ":%u", lno);
1614 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1616 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1617 * @struct machine_output@
1618 * @const char *outcome@ = outcome string to report
1619 * @const char *detail@, @va_list *ap@ = a detail message
1620 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1625 * Use: Report that a test has been skipped or failed.
1628 static void machine_outcome(struct tvec_output *o, const char *outcome,
1629 const char *detail, va_list *ap)
1631 struct machine_output *m = (struct machine_output *)o;
1632 struct tvec_state *tv = m->tv;
1634 fprintf(m->fp, "%s %u", outcome, m->testix);
1635 machine_report_location(m, tv->infile, tv->test_lno);
1637 { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1641 static void machine_skip(struct tvec_output *o,
1642 const char *excuse, va_list *ap)
1643 { machine_outcome(o, "SKIP", excuse, ap); }
1644 static void machine_fail(struct tvec_output *o,
1645 const char *detail, va_list *ap)
1646 { machine_outcome(o, "FAIL", detail, ap); }
1648 /* --- @machine_dumpreg@ --- *
1650 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1651 * @struct machine_output@
1652 * @unsigned disp@ = register disposition
1653 * @const union tvec_regval *rv@ = register value
1654 * @const struct tvec_regdef *rd@ = register definition
1658 * Use: Dump a register.
1660 * The machine driver applies highlighting to mismatching output
1661 * registers, but otherwise delegates to the register type
1665 static void machine_dumpreg(struct tvec_output *o,
1666 unsigned disp, const union tvec_regval *rv,
1667 const struct tvec_regdef *rd)
1669 struct machine_output *m = (struct machine_output *)o;
1675 case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
1676 case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
1677 case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
1678 case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
1679 case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
1683 if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
1684 if (!rv) fputs("#unset", m->fp);
1685 else rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
1686 if (f&f_nl) putc('\n', m->fp);
1692 /* --- @machine_etest@ --- *
1694 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1695 * @struct machine_output@
1696 * @unsigned outcome@ = the test outcome
1700 * Use: Report that a test has finished.
1702 * The machine driver reports the outcome of the test, if that's
1703 * not already decided.
1706 static void machine_etest(struct tvec_output *o, unsigned outcome)
1708 struct machine_output *m = (struct machine_output *)o;
1710 if (!(m->f&MF_BENCH)) switch (outcome) {
1711 case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
1712 case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
1714 m->testix++; m->f &= ~MF_BENCH;
1717 /* --- @machine_report@ --- *
1719 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1720 * @struct machine_output@
1721 * @unsigned level@ = message level (@TVLV_...@)
1722 * @const char *msg@, @va_list *ap@ = format string and
1727 * Use: Report a message to the user.
1729 * Each report level has its own output tag
1732 static void machine_report(struct tvec_output *o, unsigned level,
1733 const char *msg, va_list *ap)
1735 struct machine_output *m = (struct machine_output *)o;
1736 struct tvec_state *tv = m->tv;
1739 for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
1740 machine_report_location(m, tv->infile, tv->lno);
1741 fputs(" \"", m->fp); vgprintf(&machine_printops, m, msg, ap);
1746 /* --- @machine_bbench@ --- *
1748 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1749 * @struct machine_output@
1750 * @const char *desc@ = adhoc test description
1751 * @unsigned unit@ = measurement unit (@BTU_...@)
1755 * Use: Report that a benchmark has started.
1757 * The machine driver does nothing here. All of the reporting
1758 * happens in @machine_ebench@.
1761 static void machine_bbench(struct tvec_output *o,
1762 const char *desc, unsigned unit)
1765 /* --- @machine_ebench@ --- *
1767 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1768 * @struct machine_output@
1769 * @const char *desc@ = adhoc test description
1770 * @unsigned unit@ = measurement unit (@BTU_...@)
1771 * @const struct bench_timing *t@ = measurement
1775 * Use: Report a benchmark's results.
1777 * The machine driver prints the result as a `BENCH' output
1778 * line, in raw format.
1781 static void machine_ebench(struct tvec_output *o,
1782 const char *desc, unsigned unit,
1783 const struct bench_timing *t)
1785 struct machine_output *m = (struct machine_output *)o;
1786 struct tvec_state *tv = m->tv;
1788 fprintf(m->fp, "BENCH %u", m->testix);
1789 machine_report_location(m, tv->infile, tv->test_lno);
1791 if (desc) machine_maybe_quote(m, desc);
1792 else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
1794 tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
1795 putc('\n', m->fp); m->f |= MF_BENCH;
1798 static const struct tvec_benchoutops machine_benchops =
1799 { machine_bbench, machine_ebench };
1801 /* --- @machine_extend@ --- *
1803 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1804 * @struct machine_output@
1805 * @const char *name@ = extension name
1807 * Returns: A pointer to the extension implementation, or null.
1810 static const void *machine_extend(struct tvec_output *o, const char *name)
1812 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
1816 /* --- @machine_destroy@ --- *
1818 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1819 * @struct machine_output@
1823 * Use: Release the resources held by the output driver.
1826 static void machine_destroy(struct tvec_output *o)
1828 struct machine_output *m = (struct machine_output *)o;
1830 if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1831 x_free(m->a, m->outbuf); x_free(m->a, m);
1834 static const struct tvec_outops machine_ops = {
1835 machine_bsession, machine_esession,
1836 machine_bgroup, machine_skipgroup, machine_egroup,
1837 machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
1838 machine_report, machine_extend, machine_destroy
1841 /* --- @tvec_machineoutput@ --- *
1843 * Arguments: @FILE *fp@ = output file to write on
1845 * Returns: An output formatter.
1847 * Use: Return an output formatter which writes on @fp@ in a
1848 * moderately simple machine-readable format.
1851 struct tvec_output *tvec_machineoutput(FILE *fp)
1853 struct machine_output *m;
1855 XNEW(m); m->a = arena_global; m->_o.ops = &machine_ops;
1856 m->f = 0; m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
1860 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1863 struct tvec_output _o; /* output base class */
1864 struct tvec_state *tv; /* stashed testing state */
1865 arena *a; /* arena for memory allocation */
1866 struct layout lyt; /* output layout */
1867 char *outbuf; size_t outsz; /* buffer for formatted output */
1868 unsigned grpix, testix; /* group and test indices */
1869 unsigned previx; /* previously reported test index */
1870 int maxlen; /* longest register name */
1873 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1875 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1876 * @int ch@ = character to write
1877 * @const char *@p@, @size_t sz@ = string (with explicit length)
1879 * @const char *p, ...@ = format control string and arguments to
1884 * Use: Write characters, strings, or formatted strings to the
1885 * output, applying appropriate layout.
1887 * For the TAP output driver, the layout machinery prefixes each
1888 * line with ` ## ' and strips trailing spaces.
1891 static int tap_writech(void *go, int ch)
1893 struct tap_output *t = go;
1895 if (layout_char(&t->lyt, ch)) return (-1);
1899 static int tap_writem(void *go, const char *p, size_t sz)
1901 struct tap_output *t = go;
1903 if (layout_string(&t->lyt, p, sz)) return (-1);
1907 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1909 struct tap_output *t = go;
1914 n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
1916 if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1920 static const struct gprintf_ops tap_printops =
1921 { tap_writech, tap_writem, tap_nwritef };
1923 /* --- @tap_bsession@ --- *
1925 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1926 * @struct tap_output@
1927 * @struct tvec_state *tv@ = the test state producing output
1931 * Use: Begin a test session.
1933 * The TAP driver records the test state for later reference,
1934 * initializes the group index counter, and prints the version
1938 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1940 struct tap_output *t = (struct tap_output *)o;
1942 t->tv = tv; t->grpix = 0;
1943 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1946 /* --- @tap_esession@ --- *
1948 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1949 * @struct tap_output@
1951 * Returns: Suggested exit code.
1953 * Use: End a test session.
1955 * The TAP driver prints a final summary of the rest results
1956 * and returns a suitable exit code. If errors occurred, it
1957 * instead prints a `Bail out!' line forcing the reader to
1961 static int tap_esession(struct tvec_output *o)
1963 struct tap_output *t = (struct tap_output *)o;
1964 struct tvec_state *tv = t->tv;
1966 if (tv->f&TVSF_ERROR) {
1968 "Errors found in input; tests may not have run correctly\n",
1973 fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1974 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1977 /* --- @tap_bgroup@ --- *
1979 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1980 * @struct tap_output@
1984 * Use: Begin a test group.
1986 * The TAP driver determines the length of the longest
1987 * register name, resets the group progress scoreboard, and
1988 * activates the progress display.
1991 static void tap_bgroup(struct tvec_output *o)
1993 struct tap_output *t = (struct tap_output *)o;
1994 struct tvec_state *tv = t->tv;
1996 t->grpix++; t->testix = t->previx = 0;
1997 t->maxlen = register_maxnamelen(t->tv);
1998 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
2001 /* --- @tap_skipgroup@ --- *
2003 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2004 * @struct tap_output@
2005 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2010 * Use: Report that a test group is being skipped.
2012 * The TAP driver just reports the situation to its output
2016 static void tap_skipgroup(struct tvec_output *o,
2017 const char *excuse, va_list *ap)
2019 struct tap_output *t = (struct tap_output *)o;
2021 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
2022 fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
2023 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
2024 fputc('\n', t->lyt.fp);
2027 /* --- @tap_egroup@ --- *
2029 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2030 * @struct tap_output@
2034 * Use: Report that a test group has finished.
2036 * The TAP driver reports a summary of the group's tests.
2039 static void tap_egroup(struct tvec_output *o)
2041 struct tap_output *t = (struct tap_output *)o;
2042 struct tvec_state *tv = t->tv;
2044 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
2045 fprintf(t->lyt.fp, "%s %u - %s\n",
2046 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
2047 t->grpix, tv->test->name);
2050 /* --- @tap_btest@ --- *
2052 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2053 * @struct tap_output@
2057 * Use: Report that a test is starting.
2059 * The TAP driver advances its test counter. (We could do this
2060 * by adding up up the counters in @tv->curr@, and add on the
2061 * current test, but it's easier this way.)
2064 static void tap_btest(struct tvec_output *o)
2065 { struct tap_output *t = (struct tap_output *)o; t->testix++; }
2067 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2069 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2070 * @struct tap_output@
2071 * @const char *head, *tail@ = outcome strings to report
2072 * @const char *detail@, @va_list *ap@ = a detail message
2073 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2078 * Use: Report that a test has been skipped or failed.
2080 * The TAP driver reports the situation on its output stream.
2081 * TAP only allows us to report a single status for each
2082 * subtest, so we notice when we've already reported a status
2083 * for the current test and convert the second report as a
2084 * comment. This should only happen in the case of multiple
2088 static void tap_outcome(struct tvec_output *o,
2089 const char *head, const char *tail,
2090 const char *detail, va_list *ap)
2092 struct tap_output *t = (struct tap_output *)o;
2093 struct tvec_state *tv = t->tv;
2095 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
2096 t->testix == t->previx ? "##" : head,
2097 t->testix, tv->infile, tv->test_lno, tail);
2099 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2100 fputc('\n', t->lyt.fp);
2101 t->previx = t->testix;
2104 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2105 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
2106 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
2107 { tap_outcome(o, "not ok", "", detail, ap); }
2109 /* --- @tap_dumpreg@ --- *
2111 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2112 * @struct tap_output@
2113 * @unsigned disp@ = register disposition
2114 * @const union tvec_regval *rv@ = register value
2115 * @const struct tvec_regdef *rd@ = register definition
2119 * Use: Dump a register.
2121 * The TAP driver applies highlighting to mismatching output
2122 * registers, but otherwise delegates to the register type
2123 * handler and the layout machinery. The result is that the
2124 * register dump is marked as a comment and indented.
2127 static void tap_dumpreg(struct tvec_output *o,
2128 unsigned disp, const union tvec_regval *rv,
2129 const struct tvec_regdef *rd)
2131 struct tap_output *t = (struct tap_output *)o;
2132 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2134 set_layout_prefix(&t->lyt, " ## ");
2135 gprintf(&tap_printops, t, "%*s%s %s = ",
2136 10 + t->maxlen - n, "", ds, rd->name);
2137 if (!rv) gprintf(&tap_printops, t, "#<unset>");
2138 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
2139 layout_char(&t->lyt, '\n');
2142 /* --- @tap_etest@ --- *
2144 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2145 * @struct tap_output@
2146 * @unsigned outcome@ = the test outcome
2150 * Use: Report that a test has finished.
2152 * The TAP driver reports the outcome of the test, if that's not
2156 static void tap_etest(struct tvec_output *o, unsigned outcome)
2160 tap_outcome(o, "ok", "", 0, 0);
2163 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2168 /* --- @tap_report@ --- *
2170 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2171 * @struct tap_output@
2172 * @unsigned level@ = message level (@TVLV_...@)
2173 * @const char *msg@, @va_list *ap@ = format string and
2178 * Use: Report a message to the user.
2180 * Messages are reported as comments, so that they can be
2181 * accumulated by the reader. An error will cause a later
2182 * bailout or, if we crash before then, a missing plan line,
2183 * either of which will cause the reader to report a serious
2187 static void tap_report(struct tvec_output *o, unsigned level,
2188 const char *msg, va_list *ap)
2190 struct tap_output *t = (struct tap_output *)o;
2191 struct tvec_state *tv = t->tv;
2193 if (tv->test) set_layout_prefix(&t->lyt, " ## ");
2194 else set_layout_prefix(&t->lyt, "## ");
2196 if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
2197 gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
2198 vgprintf(&tap_printops, t, msg, ap);
2199 layout_char(&t->lyt, '\n');
2202 /* --- @tap_extend@ --- *
2204 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2205 * @struct tap_output@
2206 * @const char *name@ = extension name
2208 * Returns: A pointer to the extension implementation, or null.
2211 static const void *tap_extend(struct tvec_output *o, const char *name)
2214 /* --- @tap_destroy@ --- *
2216 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2217 * @struct tap_output@
2221 * Use: Release the resources held by the output driver.
2224 static void tap_destroy(struct tvec_output *o)
2226 struct tap_output *t = (struct tap_output *)o;
2228 destroy_layout(&t->lyt,
2229 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
2230 x_free(t->a, t->outbuf); x_free(t->a, t);
2233 static const struct tvec_outops tap_ops = {
2234 tap_bsession, tap_esession,
2235 tap_bgroup, tap_skipgroup, tap_egroup,
2236 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
2237 tap_report, tap_extend, tap_destroy
2240 /* --- @tvec_tapoutput@ --- *
2242 * Arguments: @FILE *fp@ = output file to write on
2244 * Returns: An output formatter.
2246 * Use: Return an output formatter which writes on @fp@ in `TAP'
2247 * (`Test Anything Protocol') format.
2249 * TAP comes from the Perl community, but has spread rather
2250 * further. This driver produces TAP version 14, but pretends
2251 * to be version 13. The driver produces a TAP `test point' --
2252 * i.e., a result reported as `ok' or `not ok' -- for each input
2253 * test group. Failure reports and register dumps are produced
2254 * as diagnostic messages before the final group result. (TAP
2255 * permits structuerd YAML data after the test-point result,
2256 * which could be used to report details, but (a) postponing the
2257 * details until after the report is inconvenient, and (b) there
2258 * is no standardization for the YAML anyway, so in practice
2259 * it's no more useful than the unstructured diagnostics.
2262 struct tvec_output *tvec_tapoutput(FILE *fp)
2264 struct tap_output *t;
2266 XNEW(t); t->a = arena_global; t->_o.ops = &tap_ops;
2267 init_layout(&t->lyt, fp, 0);
2268 t->outbuf = 0; t->outsz = 0;
2272 /*----- Automake support --------------------------------------------------*/
2274 struct automake_output {
2275 struct tvec_output _o;
2276 arena *a; /* arena */
2277 struct tvec_state *tv; /* test-vector state */
2278 struct tvec_output *progress; /* real-time progress output */
2279 struct tvec_output *log; /* log file output */
2280 FILE *trs; /* test result stream */
2283 /* --- @am_bsession@ --- *
2285 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2286 * @struct automake_output@
2287 * @struct tvec_state *tv@ = the test state producing output
2291 * Use: Begin a test session.
2293 * The Automake driver passes the event on to its subordinates.
2296 static void am_bsession(struct tvec_output *o, struct tvec_state *tv)
2298 struct automake_output *am = (struct automake_output *)o;
2301 human_bsession(am->progress, tv);
2302 machine_bsession(am->log, tv);
2305 /* --- @am_esession@ --- *
2307 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2308 * @struct automake_output@
2310 * Returns: Suggested exit code.
2312 * Use: End a test session.
2314 * The Automake driver completes the test-results file and
2315 * passes the event on to its subordinates.
2318 static void am_report_unusual(struct automake_output *am,
2319 unsigned xfail, unsigned skip)
2325 fprintf(am->trs, "%s%u expected %s", f&f_any ? ", " : " (",
2326 xfail, xfail == 1 ? "failure" : "failures");
2330 fprintf(am->trs, "%s%u skipped", f&f_any ? ", " : " (", skip);
2333 if (f&f_any) fputc(')', am->trs);
2338 static int am_esession(struct tvec_output *o)
2340 struct automake_output *am = (struct automake_output *)o;
2341 struct tvec_state *tv = am->tv;
2343 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
2344 all_xfail = tv->all[TVOUT_XFAIL],
2345 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
2346 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
2347 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
2348 grps_run = grps_win + grps_lose;
2350 human_esession(am->progress);
2351 machine_esession(am->log);
2353 fputs(":test-global-result: ", am->trs);
2354 if (tv->f&TVSF_ERROR) fputs("ERRORS; ", am->trs);
2356 fprintf(am->trs, "PASSED %s%u %s",
2357 !(all_skip || grps_skip) ? "all " : "",
2358 all_win, all_win == 1 ? "test" : "tests");
2359 am_report_unusual(am, all_xfail, all_skip);
2360 fprintf(am->trs, " in %u %s",
2361 grps_win, grps_win == 1 ? "group" : "groups");
2362 am_report_unusual(am, 0, grps_skip);
2364 fprintf(am->trs, "FAILED %u out of %u %s",
2365 all_lose, all_run, all_run == 1 ? "test" : "tests");
2366 am_report_unusual(am, all_xfail, all_skip);
2367 fprintf(am->trs, " in %u out of %u %s",
2368 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
2369 am_report_unusual(am, 0, grps_skip);
2371 fputc('\n', am->trs);
2373 fprintf(am->trs, ":copy-in-global-log: %s\n",
2374 !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes");
2375 fprintf(am->trs, ":recheck: %s\n",
2376 !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes");
2381 /* --- @am_bgroup@ --- *
2383 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2384 * @struct automake_output@
2388 * Use: Begin a test group.
2390 * The Automake driver passes the event on to its subordinates.
2393 static void am_bgroup(struct tvec_output *o)
2395 struct automake_output *am = (struct automake_output *)o;
2397 human_bgroup(am->progress);
2398 machine_bgroup(am->log);
2401 /* --- @am_skipgroup@ --- *
2403 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2404 * @struct automake_output@
2405 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2410 * Use: Report that a test group is being skipped.
2412 * The Automake driver makes a note in the test-results file and
2413 * passes the event on to its subordinates.
2416 static void am_skipgroup(struct tvec_output *o,
2417 const char *excuse, va_list *ap)
2419 struct automake_output *am = (struct automake_output *)o;
2420 struct tvec_state *tv = am->tv;
2422 fprintf(am->trs, ":test-result: SKIP %s\n", tv->test->name);
2423 human_skipgroup(am->progress, excuse, ap);
2424 machine_skipgroup(am->log, excuse, ap);
2427 /* --- @am_egroup@ --- *
2429 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2430 * @struct automake_output@
2434 * Use: Report that a test group has finished.
2436 * The Automake driver makes a note in the test-results file and
2437 * passes the event on to its subordinates.
2440 static void am_egroup(struct tvec_output *o)
2442 struct automake_output *am = (struct automake_output *)o;
2443 struct tvec_state *tv = am->tv;
2445 fprintf(am->trs, ":test-result: %s %s\n",
2446 tv->curr[TVOUT_LOSE] ? "FAIL" : "PASS", tv->test->name);
2447 human_egroup(am->progress);
2448 machine_egroup(am->log);
2451 /* --- @am_btest@ --- *
2453 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2454 * @struct automake_output@
2458 * Use: Report that a test is starting.
2460 * The Automake driver passes the event on to its subordinates.
2463 static void am_btest(struct tvec_output *o)
2465 struct automake_output *am = (struct automake_output *)o;
2467 human_btest(am->progress);
2468 machine_btest(am->log);
2471 /* --- @am_skip@, @am_fail@ --- *
2473 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2474 * @struct automake_output@
2475 * @const char *head, *tail@ = outcome strings to report
2476 * @const char *detail@, @va_list *ap@ = a detail message
2477 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2482 * Use: Report that a test has been skipped or failed.
2484 * The Automake driver passes the event on to its subordinates.
2487 static void am_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2489 struct automake_output *am = (struct automake_output *)o;
2491 human_skip(am->progress, excuse, ap);
2492 machine_skip(am->log, excuse, ap);
2495 static void am_fail(struct tvec_output *o, const char *detail, va_list *ap)
2497 struct automake_output *am = (struct automake_output *)o;
2499 human_fail(am->progress, detail, ap);
2500 machine_fail(am->log, detail, ap);
2503 /* --- @am_dumpreg@ --- *
2505 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2506 * @struct automake_output@
2507 * @unsigned disp@ = register disposition
2508 * @const union tvec_regval *rv@ = register value
2509 * @const struct tvec_regdef *rd@ = register definition
2513 * Use: Dump a register.
2515 * The Automake driver passes the event on to its subordinates.
2518 static void am_dumpreg(struct tvec_output *o,
2519 unsigned disp, const union tvec_regval *rv,
2520 const struct tvec_regdef *rd)
2522 struct automake_output *am = (struct automake_output *)o;
2524 human_dumpreg(am->progress, disp, rv, rd);
2525 machine_dumpreg(am->log, disp, rv, rd);
2528 /* --- @am_etest@ --- *
2530 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2531 * @struct automake_output@
2532 * @unsigned outcome@ = the test outcome
2536 * Use: Report that a test has finished.
2538 * The Automake driver passes the event on to its subordinates.
2541 static void am_etest(struct tvec_output *o, unsigned outcome)
2543 struct automake_output *am = (struct automake_output *)o;
2545 human_etest(am->progress, outcome);
2546 machine_etest(am->log, outcome);
2549 /* --- @am_report@ --- *
2551 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2552 * @struct automake_output@
2553 * @unsigned level@ = message level (@TVLV_...@)
2554 * @const char *msg@, @va_list *ap@ = format string and
2559 * Use: Report a message to the user.
2561 * The Automake driver passes the event on to its subordinates.
2564 static void am_report(struct tvec_output *o, unsigned level,
2565 const char *msg, va_list *ap)
2567 struct automake_output *am = (struct automake_output *)o;
2569 human_report(am->progress, level, msg, ap);
2570 machine_report(am->log, level, msg, ap);
2573 /* --- @am_bbench@ --- *
2575 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2576 * @struct automake_output@
2577 * @const char *desc@ = adhoc test description
2578 * @unsigned unit@ = measurement unit (@BTU_...@)
2582 * Use: Report that a benchmark has started.
2584 * The Automake driver passes the event on to its subordinates.
2587 static void am_bbench(struct tvec_output *o,
2588 const char *desc, unsigned unit)
2590 struct automake_output *am = (struct automake_output *)o;
2592 human_bbench(am->progress, desc, unit);
2593 machine_bbench(am->progress, desc, unit);
2596 /* --- @am_ebench@ --- *
2598 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2599 * @struct automake_output@
2600 * @const char *desc@ = adhoc test description
2601 * @unsigned unit@ = measurement unit (@BTU_...@)
2602 * @const struct bench_timing *t@ = measurement
2606 * Use: Report a benchmark's results.
2608 * The Automake driver passes the event on to its subordinates.
2611 static void am_ebench(struct tvec_output *o,
2612 const char *desc, unsigned unit,
2613 const struct bench_timing *t)
2615 struct automake_output *am = (struct automake_output *)o;
2617 human_ebench(am->progress, desc, unit, t);
2618 machine_ebench(am->progress, desc, unit, t);
2621 static const struct tvec_benchoutops am_benchops =
2622 { am_bbench, am_ebench };
2624 /* --- @am_extend@ --- *
2626 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2627 * @struct automake_output@
2628 * @const char *name@ = extension name
2630 * Returns: A pointer to the extension implementation, or null.
2633 static const void *am_extend(struct tvec_output *o, const char *name)
2635 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&am_benchops);
2639 /* --- @am_destroy@ --- *
2641 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2642 * @struct automake_output@
2646 * Use: Release the resources held by the output driver.
2649 static void am_destroy(struct tvec_output *o)
2651 struct automake_output *am = (struct automake_output *)o;
2653 human_destroy(am->progress);
2654 machine_destroy(am->log);
2655 fclose(am->trs); x_free(am->a, am);
2658 static const struct tvec_outops automake_ops = {
2659 am_bsession, am_esession,
2660 am_bgroup, am_skipgroup, am_egroup,
2661 am_btest, am_skip, am_fail, am_dumpreg, am_etest,
2662 am_report, am_extend, am_destroy
2665 /* --- @tvec_amoutput@ --- *
2667 * Arguments: @const struct tvec_amargs *a@ = arguments from Automake
2668 * command-line protocol
2670 * Returns: An output formatter.
2672 * Use: Returns an output formatter which writes on standard output
2673 * in human format, pretending that the output is to a terminal
2674 * (in order to cope with %%\manpage{make}{1}%%'s output-
2675 * buffering behaviour, writes to the log file @a->log@ in
2676 * machine-readable format, and writes an Automake rST-format
2677 * test result file to @a->trs@. The `test name' is currently
2678 * ignored, because the framework has its own means of
2679 * determining test names.
2682 struct tvec_output *tvec_amoutput(const struct tvec_amargs *a)
2684 struct automake_output *am;
2688 if (a->f&TVAF_COLOUR) f |= TVHF_COLOUR;
2690 XNEW(am); am->a = arena_global; am->_o.ops = &automake_ops;
2691 am->progress = tvec_humanoutput(stdout, f, TVHF_TTY | TVHF_COLOUR);
2692 am->log = tvec_machineoutput(a->log); am->trs = a->trs;
2696 /*----- Default output ----------------------------------------------------*/
2698 /* --- @tvec_dfltoutput@ --- *
2700 * Arguments: @FILE *fp@ = output file to write on
2702 * Returns: An output formatter.
2704 * Use: Selects and instantiates an output formatter suitable for
2705 * writing on @fp@. The policy is subject to change, but
2706 * currently the `human' output format is selected if @fp@ is
2707 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
2708 * otherwise the `machine' format is used.
2711 struct tvec_output *tvec_dfltoutput(FILE *fp)
2713 int ttyp = getenv_boolean("MLIB_TVEC_TTY", -1);
2715 if (ttyp == -1) ttyp = isatty(fileno(fp));
2716 if (ttyp) return (tvec_humanoutput(fp, TVHF_TTY, TVHF_TTY));
2717 else return (tvec_machineoutput(fp));
2720 /*----- That's all, folks -------------------------------------------------*/