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 ------------------------------------------------------*/
47 #include "ttycolour.h"
50 #include "tvec-bench.h"
51 #include "tvec-output.h"
53 /*----- Common machinery --------------------------------------------------*/
55 /* --- @regdisp@ --- *
57 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
59 * Returns: A human-readable adjective describing the register
63 static const char *regdisp(unsigned disp)
66 case TVRD_INPUT: return "input";
67 case TVRD_OUTPUT: return "output";
68 case TVRD_MATCH: return "matched";
69 case TVRD_FOUND: return "found";
70 case TVRD_EXPECT: return "expected";
75 /* --- @interpret_boolean@, @getenv_boolean@ --- *
77 * Arguments: @const char *val@ = a string
78 * @const char *var@ = environment variable name
79 * @int dflt@ = default value
80 * @const char *what, ...@ = format string describing where the
83 * Returns: @0@ if the string, or variable, is set to something
84 * falseish, @1@ if it's set to something truish, or @dflt@
88 static PRINTF_LIKE(3, 4)
89 int interpret_boolean(const char *val, int dflt, const char *what, ...)
97 else if (STRCMP(val, ==, "y") || STRCMP(val, ==, "yes") ||
98 STRCMP(val, ==, "t") || STRCMP(val, ==, "true") ||
99 STRCMP(val, ==, "on") || STRCMP(val, ==, "force") ||
100 STRCMP(val, ==, "1"))
102 else if (STRCMP(val, ==, "n") || STRCMP(val, ==, "no") ||
103 STRCMP(val, ==, "nil") || STRCMP(val, ==, "f") ||
104 STRCMP(val, ==, "false") ||
105 STRCMP(val, ==, "off") || STRCMP(val, ==, "inhibit") ||
106 STRCMP(val, ==, "0"))
109 va_start(ap, what); dstr_vputf(&d, what, &ap); va_end(ap);
110 moan("ignoring unexpected value `%s' for %s", val, d.buf);
113 dstr_destroy(&d); return (rc);
116 static int getenv_boolean(const char *var, int dflt)
118 return (interpret_boolean(getenv(var), dflt,
119 "environment variable `%s'", var));
122 /* --- @register_maxnamelen@ --- *
124 * Arguments: @const struct tvec_state *tv@ = test vector state
126 * Returns: The maximum length of a register name in the current test.
129 static int register_maxnamelen(const struct tvec_state *tv)
131 const struct tvec_regdef *rd;
134 for (rd = tv->test->regs; rd->name; rd++)
135 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
139 /* --- @print_ident@ --- *
141 * Arguments: @struct tvec_state *tv@ = test-vector state
142 * @unsigned style@ = style to use for register dumps
143 * @const struct gprintf_ops *gops@ = output operations
144 * @void *go@ = output state
148 * Use: Write a benchmark identification to the output.
151 static void print_ident(struct tvec_state *tv, unsigned style,
152 const struct gprintf_ops *gops, void *go)
154 const struct tvec_regdef *rd;
159 for (rd = tv->test->regs; rd->name; rd++)
161 if (!(f&f_any)) f |= f_any;
162 else if (style&TVSF_RAW) gops->putch(go, ' ');
163 else gprintf(gops, go, ", ");
164 gprintf(gops, go, "%s", rd->name);
165 if (style&TVSF_RAW) gops->putch(go, '=');
166 else gprintf(gops, go, " = ");
167 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, style, gops, go);
173 /*----- Output layout -----------------------------------------------------*/
175 /* We have two main jobs in output layout: trimming trailing blanks; and
176 * adding a prefix to each line.
178 * This is somehow much more complicated than it ought to be.
182 FILE *fp; /* output file */
183 const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
184 dstr w; /* trailing whitespace */
185 dstr ctrl; /* control sequence for next word */
186 unsigned f; /* flags */
187 #define LYTF_NEWL 1u /* start of output line */
190 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
194 #define SPLIT_RANGE(tail, base, limit) do { \
195 /* Set TAIL to point just after the last nonspace character between \
196 * BASE and LIMIT. If there are no nonspace characters, then set \
197 * TAIL to equal BASE. \
200 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
203 #define PUT_RANGE(base, limit) do { \
204 /* Write the range of characters between BASE and LIMIT to the output \
205 * file. Return immediately on error. \
208 size_t _n = limit - base; \
209 if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
212 #define PUT_CHAR(ch) do { \
213 /* Write CH to the output. Return immediately on error. */ \
215 if (putc(ch, lyt->fp) == EOF) return (-1); \
218 #define PUT_PREFIX do { \
219 /* Output the prefix, if there is one. Return immediately on error. */ \
221 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
224 #define PUT_SAVED do { \
225 /* Output the saved trailing blank material in the buffer. */ \
227 size_t _n = lyt->w.len; \
228 if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
231 #define PUT_CTRL do { \
232 /* Output the accumulated control string. */ \
234 size_t _n = lyt->ctrl.len; \
235 if (_n && fwrite(lyt->ctrl.buf, 1, _n, lyt->fp) < _n) return (-1); \
238 #define PUT_PFXINB do { \
239 /* Output the initial nonblank portion of the prefix, if there is \
240 * one. Return immediately on error. \
243 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
246 #define SAVE_PFXTAIL do { \
247 /* Save the trailing blank portion of the prefix. */ \
250 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
253 /* --- @set_layout_prefix@ --- *
255 * Arguments: @struct layout *lyt@ = layout state
256 * @const char *prefix@ = new prefix string or null
260 * Use: Change the configured prefix string. The change takes effect
261 * at the start of the next line (or the current line if it's
262 * empty or only whitespace so far).
265 static void set_layout_prefix(struct layout *lyt, const char *prefix)
269 if (!prefix || !*prefix)
270 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
272 lyt->prefix = prefix;
273 l = lyt->pfxlim = prefix + strlen(prefix);
274 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
278 /* --- @init_layout@ --- *
280 * Arguments: @struct layout *lyt@ = layout state to initialize
281 * @FILE *fp@ = output file
282 * @const char *prefix@ = prefix string (or null if empty)
286 * Use: Initialize a layout state.
289 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
293 dstr_create(&lyt->w); dstr_create(&lyt->ctrl);
294 set_layout_prefix(lyt, prefix);
297 /* --- @destroy_layout@ --- *
299 * Arguments: @struct layout *lyt@ = layout state
300 * @unsigned f@ = flags (@DLF_...@)
304 * Use: Releases a layout state and the resources it holds.
305 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
306 * it open (in case it's @stderr@ or something).
310 static void destroy_layout(struct layout *lyt, unsigned f)
312 if (f&DLF_CLOSE) fclose(lyt->fp);
313 dstr_destroy(&lyt->w); dstr_destroy(&lyt->ctrl);
316 /* --- @layout_char@ --- *
318 * Arguments: @struct layout *lyt@ = layout state
319 * @int ch@ = character to write
321 * Returns: Zero on success, @-1@ on failure.
323 * Use: Write a single character to the output.
326 static int layout_char(struct layout *lyt, int ch)
329 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
330 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
331 } else if (isspace(ch))
334 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
335 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
340 /* --- @layout_string@ --- *
342 * Arguments: @struct layout *lyt@ = layout state
343 * @const char *p@ = string to write
344 * @size_t sz@ = length of string
346 * Returns: Zero on success, @-1@ on failure.
348 * Use: Write a string to the output.
351 static int layout_string(struct layout *lyt, const char *p, size_t sz)
353 const char *q, *r, *l = p + sz;
355 /* This is rather vexing. There are a small number of jobs to do, but the
356 * logic for deciding which to do when gets rather hairy if, as I've tried
357 * here, one aims to minimize the number of decisions being checked, so
358 * it's worth canning them into macros.
360 * Here, a `blank' is a whitespace character other than newline. The input
361 * buffer consists of one or more `segments', each of which consists of:
363 * * an initial portion, which is either empty or ends with a nonblank
366 * * a suffix which consists only of blanks; and
368 * * an optional newline.
370 * All segments except the last end with a newline.
373 #define SPLIT_SEGMENT do { \
374 /* Determine the bounds of the current segment. If there is a final \
375 * newline, then q is non-null and points to this newline; otherwise, \
376 * q is null. The initial portion of the segment lies between p .. r \
377 * and the blank suffix lies between r .. q (or r .. l if q is null). \
378 * This sounds awkward, but the suffix is only relevant if there is \
382 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
385 #define PUT_NONBLANK do { \
386 /* Output the initial portion of the segment. */ \
391 #define PUT_NEWLINE do { \
392 /* Write a newline, and advance to the next segment. */ \
394 PUT_CHAR('\n'); p = q + 1; \
397 #define SAVE_TAIL do { \
398 /* Save the trailing blank portion of the segment in the buffer. \
399 * Assumes that there is no newline, since otherwise the suffix would \
403 DPUTM(&lyt->w, r, l - r); \
406 /* Determine the bounds of the first segment. Handling this is the most
407 * complicated part of this function.
412 /* This is the only segment. We'll handle the whole thing here.
414 * If there's an initial nonblank portion, then we need to write that
415 * out. Furthermore, if we're at the start of the line then we'll need
416 * to write the prefix, and if there's saved blank material then we'll
417 * need to write that. Otherwise, there's only blank stuff, which we
418 * accumulate in the buffer.
420 * If we're at the start of a line here, then put the prefix followed by
421 * any saved whitespace, and then our initial nonblank portion. Then
422 * save our new trailing space.
426 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
427 PUT_SAVED; PUT_CTRL; PUT_NONBLANK;
428 DRESET(&lyt->w); DRESET(&lyt->ctrl);
434 /* There is at least one more segment, so we know that there'll be a line
438 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
439 PUT_SAVED; PUT_CTRL; PUT_NONBLANK;
441 } else if (lyt->f&LYTF_NEWL)
443 PUT_NEWLINE; DRESET(&lyt->w);
446 /* Main loop over whole segments with trailing newlines. For each one, we
447 * know that we're starting at the beginning of a line and there's a final
448 * newline, so we write the initial prefix and drop the trailing blanks.
451 if (r > p) { PUT_PREFIX; PUT_CTRL; PUT_NONBLANK; DRESET(&lyt->ctrl); }
457 /* At the end, there's no final newline. If there's nonblank material,
458 * then we can write the prefix and the nonblank stuff. Otherwise, stash
459 * the blank stuff (including the trailing blanks of the prefix) and leave
460 * the newline flag set.
462 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
463 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
482 /*----- Human-readable output ---------------------------------------------*/
484 /* Predefined attributes. */
485 #define HIGHLIGHTS(_st, _) \
486 _(_st, LOCFN, "lf", TC_FG(CYAN)) /* location filename */ \
487 _(_st, LOCLN, "ln", TC_FG(CYAN)) /* location line number */ \
488 _(_st, LOCSEP, "ls", TC_FG(BRBLUE)) /* location separator `:' */ \
489 _(_st, INFO, "mi", 0) /* information */ \
490 _(_st, NOTE, "mn", TC_FG(YELLOW)) /* notices */ \
491 _(_st, ERR, "me", TC_FG(MAGENTA) | TCAF_BOLD) /* error messages */ \
492 _(_st, UNKLEV, "mu", TC_FG(WHITE) | TC_BG(RED) | TCAF_BOLD) \
493 _(_st, DSINPUT, "di", 0) /* disposition for input value */ \
494 _(_st, DSOUTPUT, "do", 0) /* ... unsolicited output */ \
495 _(_st, DSMATCH, "dm", 0) /* ... matching output */ \
496 _(_st, DSFOUND, "df", 0) /* ... incorrect output */ \
497 _(_st, DSEXPECT, "dx", 0) /* ... reference output */ \
498 _(_st, RNINPUT, "ri", 0) /* register name for input value */ \
499 _(_st, RNOUTPUT, "ro", 0) /* ... unsolicited output */ \
500 _(_st, RNMATCH, "rm", 0) /* ... matching output */ \
501 _(_st, RNFOUND, "rf", 0) /* ... incorrect output */ \
502 _(_st, RNEXPECT, "rx", 0) /* ... reference output */ \
503 _(_st, VINPUT, "vi", 0) /* input value */ \
504 _(_st, VOUTPUT, "vo", 0) /* unsolicited output value */ \
505 _(_st, VMATCH, "vm", 0) /* matching output value */ \
506 _(_st, VFOUND, "vf", TC_FG(BRRED)) /* incorrect output value */ \
507 _(_st, VEXPECT, "vx", TC_FG(GREEN)) /* reference output value */ \
508 _(_st, VUNSET, "vu", TC_FG(YELLOW)) /* register not set */ \
509 _(_st, LOSE, "ol", TC_FG(RED) | TCAF_BOLD) /* report failure */ \
510 _(_st, SKIP, "os", TC_FG(YELLOW)) /* report a skipped test/group */ \
511 _(_st, XFAIL, "ox", TC_FG(BLUE) | TCAF_BOLD) /* report expected fail */ \
512 _(_st, WIN, "ow", TC_FG(GREEN)) /* report success */ \
513 _(_st, SBLOSE, "sl", TC_FG(RED) | TCAF_BOLD) /* scoreboard failure */ \
514 _(_st, SBSKIP, "ss", TC_FG(YELLOW)) /* scoreboard skipped test */ \
515 _(_st, SBXFAIL, "sx", TC_FG(BLUE) | TCAF_BOLD) /* scoreboard xfail */ \
516 _(_st, SBWIN, "sw", 0) /* scoreboard success */
518 TTYCOLOUR_DEFENUM(HIGHLIGHTS, HL_);
519 #define HL_PLAIN (-1)
521 /* Scoreboard indicators. */
522 static const char scoreboard[] = { 'x', '_', 'o', '.' };
524 struct human_output {
525 struct tvec_output _o; /* output base class */
526 struct tvec_state *tv; /* stashed testing state */
527 arena *a; /* arena for memory allocation */
528 struct layout lyt; /* output layout */
529 char *outbuf; size_t outsz; /* buffer for formatted output */
530 dstr scoreboard; /* history of test group results */
531 unsigned short attr[HL__LIMIT]; /* highlight attribute map */
532 struct ttycolour_state tc; /* terminal colour state */
533 int maxlen; /* longest register name */
534 unsigned f; /* flags */
535 /* bits 0--7 from @TVHF_...@ */
536 #define HOF_DUPERR 0x0100u /* duplicate errors to stderr */
537 #define HOF_PROGRESS 0x0200u /* progress display is active */
540 /* --- @setattr@, @setattr_layout@ --- *
542 * Arguments: @struct human_output *h@ = output state
543 * @int hi@ = highlight code to set
547 * Use: Send a control sequence to the output stream so that
548 * subsequent text is printed with the given attributes.
551 static void setattr_common(struct human_output *h,
552 const struct gprintf_ops *gops, void *go, int hl)
554 if (h->f&TVHF_COLOUR)
555 ttycolour_setattr(gops, go, &h->tc, hl < 0 ? 0 : h->attr[hl]);
558 static void setattr(struct human_output *h, int hl)
559 { setattr_common(h, &file_printops, h->lyt.fp, hl); }
561 static void setattr_layout(struct human_output *h, int hl)
562 { setattr_common(h, &dstr_printops, &h->lyt.ctrl, hl); }
564 /* --- @clear_progress@ --- *
566 * Arguments: @struct human_output *h@ = output state
570 * Use: Remove the progress display from the terminal.
572 * If the progress display isn't active then do nothing.
575 static void clear_progress(struct human_output *h)
579 if (h->f&HOF_PROGRESS) {
580 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
581 for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
582 h->f &= ~HOF_PROGRESS;
586 /* --- @write_scoreboard_char@ --- *
588 * Arguments: @struct human_output *h@ = output state
589 * @int ch@ = scoreboard character to print
593 * Use: Write a scoreboard character, indicating the outcome of a
594 * test, to the output stream, with appropriate highlighting.
597 static void write_scoreboard_char(struct human_output *h, int ch)
599 assert(0 <= ch && ch < TVOUT_LIMIT);
600 setattr(h, HL_SBLOSE + ch); putc(scoreboard[ch], h->lyt.fp);
603 /* --- @show_progress@ --- *
605 * Arguments: @struct human_output *h@ = output state
609 * Use: Show the progress display, with the record of outcomes for
610 * the current test group.
612 * If the progress display is already active, or the output
613 * stream is not interactive, then nothing happens.
616 static void show_progress(struct human_output *h)
618 struct tvec_state *tv = h->tv;
621 if (tv->test && (h->f&TVHF_TTY) && !(h->f&HOF_PROGRESS)) {
622 fprintf(h->lyt.fp, "%s: ", tv->test->name);
623 for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
624 write_scoreboard_char(h, *p);
625 setattr(h, HL_PLAIN);
626 fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
630 /* --- @human_writech@, @human_write@, @human_writef@ --- *
632 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
633 * @int ch@ = character to write
634 * @const char *@p@, @size_t sz@ = string (with explicit length)
636 * @const char *p, ...@ = format control string and arguments to
641 * Use: Write characters, strings, or formatted strings to the
642 * output, applying appropriate layout.
644 * For the human output driver, the layout machinery just strips
648 static int human_writech(void *go, int ch)
649 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
651 static int human_writem(void *go, const char *p, size_t sz)
652 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
654 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
656 struct human_output *h = go;
661 n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
663 if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
667 static const struct gprintf_ops human_printops =
668 { human_writech, human_writem, human_nwritef };
670 /* --- @human_bsession@ --- *
672 * Arguments: @struct tvec_output *o@ = output sink, secretly a
673 * @struct human_output@
674 * @struct tvec_state *tv@ = the test state producing output
678 * Use: Begin a test session.
680 * The human driver just records the test state for later
684 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
685 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
687 /* --- @human_report_unusual@ --- *
689 * Arguments: @struct human_output *h@ = output sink
690 * @unsigned nxfail, nskip@ = number of expected failures and
695 * Use: Write (directly on the output stream) a note about expected
696 * failures and/or skipped tests, if there were any.
699 static void human_report_unusual(struct human_output *h,
700 unsigned nxfail, unsigned nskip)
706 fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nxfail);
707 setattr(h, HL_XFAIL);
708 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
709 setattr(h, HL_PLAIN);
714 fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nskip);
715 setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN);
719 if (f&f_any) fputc(')', h->lyt.fp);
724 /* --- @human_esession@ --- *
726 * Arguments: @struct tvec_output *o@ = output sink, secretly a
727 * @struct human_output@
729 * Returns: Suggested exit code.
731 * Use: End a test session.
733 * The human driver prints a final summary of the rest results
734 * and returns a suitable exit code.
737 static int human_esession(struct tvec_output *o)
739 struct human_output *h = (struct human_output *)o;
740 struct tvec_state *tv = h->tv;
742 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
743 all_xfail = tv->all[TVOUT_XFAIL],
744 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
745 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
746 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
747 grps_run = grps_win + grps_lose;
750 setattr(h, HL_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HL_PLAIN);
751 fprintf(h->lyt.fp, " %s%u %s",
752 !(all_skip || grps_skip) ? "all " : "",
753 all_pass, all_pass == 1 ? "test" : "tests");
754 human_report_unusual(h, all_xfail, all_skip);
755 fprintf(h->lyt.fp, " in %u %s",
756 grps_win, grps_win == 1 ? "group" : "groups");
757 human_report_unusual(h, 0, grps_skip);
759 setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN);
760 fprintf(h->lyt.fp, " %u out of %u %s",
761 all_lose, all_run, all_run == 1 ? "test" : "tests");
762 human_report_unusual(h, all_xfail, all_skip);
763 fprintf(h->lyt.fp, " in %u out of %u %s",
764 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
765 human_report_unusual(h, 0, grps_skip);
767 fputc('\n', h->lyt.fp);
769 if (tv->f&TVSF_ERROR) {
770 setattr(h, HL_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HL_PLAIN);
771 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
774 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
777 /* --- @human_bgroup@ --- *
779 * Arguments: @struct tvec_output *o@ = output sink, secretly a
780 * @struct human_output@
784 * Use: Begin a test group.
786 * The human driver determines the length of the longest
787 * register name, resets the group progress scoreboard, and
788 * activates the progress display.
791 static void human_bgroup(struct tvec_output *o)
793 struct human_output *h = (struct human_output *)o;
795 h->maxlen = register_maxnamelen(h->tv);
796 dstr_reset(&h->scoreboard); show_progress(h);
799 /* --- @human_skipgroup@ --- *
801 * Arguments: @struct tvec_output *o@ = output sink, secretly a
802 * @struct human_output@
803 * @const char *excuse@, @va_list *ap@ = reason for skipping the
808 * Use: Report that a test group is being skipped.
810 * The human driver just reports the situation to its output
814 static void human_skipgroup(struct tvec_output *o,
815 const char *excuse, va_list *ap)
817 struct human_output *h = (struct human_output *)o;
819 if (!(h->f&TVHF_TTY))
820 fprintf(h->lyt.fp, "%s ", h->tv->test->name);
822 show_progress(h); h->f &= ~HOF_PROGRESS;
823 if (h->scoreboard.len) putc(' ', h->lyt.fp);
825 setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN);
826 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
827 fputc('\n', h->lyt.fp);
830 /* --- @human_egroup@ --- *
832 * Arguments: @struct tvec_output *o@ = output sink, secretly a
833 * @struct human_output@
837 * Use: Report that a test group has finished.
839 * The human driver reports a summary of the group's tests.
842 static void human_egroup(struct tvec_output *o)
844 struct human_output *h = (struct human_output *)o;
845 struct tvec_state *tv = h->tv;
846 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
847 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
848 run = win + lose + xfail;
850 if (h->f&TVHF_TTY) h->f &= ~HOF_PROGRESS;
851 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
854 fprintf(h->lyt.fp, " %u/%u ", lose, run);
855 setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN);
856 human_report_unusual(h, xfail, skip);
858 fputc(' ', h->lyt.fp); setattr(h, HL_WIN);
859 fputs("ok", h->lyt.fp); setattr(h, HL_PLAIN);
860 human_report_unusual(h, xfail, skip);
862 fputc('\n', h->lyt.fp);
865 /* --- @human_btest@ --- *
867 * Arguments: @struct tvec_output *o@ = output sink, secretly a
868 * @struct human_output@
872 * Use: Report that a test is starting.
874 * The human driver makes sure the progress display is active.
877 static void human_btest(struct tvec_output *o)
878 { struct human_output *h = (struct human_output *)o; show_progress(h); }
880 /* --- @human_report_location@ --- *
882 * Arguments: @struct human_output *h@ = output state
883 * @FILE *fp@ = stream to write the location on
884 * @const char *file@ = filename
885 * @unsigned lno@ = line number
889 * Use: Print the filename and line number to the output stream @fp@.
890 * Also, if appropriate, print interleaved highlighting control
891 * codes to our usual output stream. If @file@ is null then do
895 static void human_report_location(struct human_output *h, FILE *fp,
896 const char *file, unsigned lno)
900 else if (fp != h->lyt.fp || !(h->f&TVHF_COLOUR))
901 fprintf(fp, "%s:%u: ", file, lno);
903 setattr(h, HL_LOCFN); fputs(file, fp);
904 setattr(h, HL_LOCSEP); fputc(':', fp);
905 setattr(h, HL_LOCLN); fprintf(fp, "%u", lno);
906 setattr(h, HL_LOCSEP); fputc(':', fp);
907 setattr(h, HL_PLAIN); fputc(' ', fp);
911 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
913 * Arguments: @struct tvec_output *o@ = output sink, secretly a
914 * @struct human_output@
915 * @unsigned attr@ = attribute to apply to the outcome
916 * @const char *outcome@ = outcome string to report
917 * @const char *detail@, @va_list *ap@ = a detail message
918 * @const char *excuse@, @va_list *ap@ = reason for skipping the
923 * Use: Report that a test has been skipped or failed.
925 * The human driver reports the situation on its output stream.
928 static void human_outcome(struct tvec_output *o,
929 unsigned attr, const char *outcome,
930 const char *detail, va_list *ap)
932 struct human_output *h = (struct human_output *)o;
933 struct tvec_state *tv = h->tv;
936 human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
937 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
938 setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HL_PLAIN);
939 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
940 fputc('\n', h->lyt.fp);
943 static void human_skip(struct tvec_output *o,
944 const char *excuse, va_list *ap)
945 { human_outcome(o, HL_SKIP, "skipped", excuse, ap); }
946 static void human_fail(struct tvec_output *o,
947 const char *detail, va_list *ap)
948 { human_outcome(o, HL_LOSE, "FAILED", detail, ap); }
950 /* --- @human_dumpreg@ --- *
952 * Arguments: @struct tvec_output *o@ = output sink, secretly a
953 * @struct human_output@
954 * @unsigned disp@ = register disposition
955 * @const union tvec_regval *rv@ = register value
956 * @const struct tvec_regdef *rd@ = register definition
960 * Use: Dump a register.
962 * The human driver applies highlighting to mismatching output
963 * registers, but otherwise delegates to the register type
964 * handler and the layout machinery.
967 static void human_dumpreg(struct tvec_output *o,
968 unsigned disp, const union tvec_regval *rv,
969 const struct tvec_regdef *rd)
971 struct human_output *h = (struct human_output *)o;
972 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
975 gprintf(&human_printops, h, "%*s", 10 + h->maxlen - n, "");
976 setattr_layout(h, HL_DSINPUT + disp);
977 gprintf(&human_printops, h, "%s", ds);
978 setattr(h, HL_PLAIN); layout_char(&h->lyt, ' ');
979 setattr_layout(h, HL_RNINPUT + disp);
980 gprintf(&human_printops, h, "%s", rd->name);
981 setattr(h, HL_PLAIN); gprintf(&human_printops, h, " = ");
983 setattr_layout(h, HL_VUNSET);
984 gprintf(&human_printops, h, "#unset");
986 setattr_layout(h, HL_VINPUT + disp);
987 rd->ty->dump(rv, rd, 0, &human_printops, h);
989 setattr(h, HL_PLAIN); layout_char(&h->lyt, '\n');
992 /* --- @human_etest@ --- *
994 * Arguments: @struct tvec_output *o@ = output sink, secretly a
995 * @struct human_output@
996 * @unsigned outcome@ = the test outcome
1000 * Use: Report that a test has finished.
1002 * The human driver reactivates the progress display, if
1003 * necessary, and adds a new character for the completed test.
1006 static void human_etest(struct tvec_output *o, unsigned outcome)
1008 struct human_output *h = (struct human_output *)o;
1010 if (h->f&TVHF_TTY) {
1012 dstr_putc(&h->scoreboard, outcome); write_scoreboard_char(h, outcome);
1013 setattr(h, HL_PLAIN); fflush(h->lyt.fp);
1017 /* --- @human_report@ --- *
1019 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1020 * @struct human_output@
1021 * @unsigned level@ = message level (@TVLEV_...@)
1022 * @const char *msg@, @va_list *ap@ = format string and
1027 * Use: Report a message to the user.
1029 * The human driver arranges to show the message on @stderr@ as
1030 * well as the usual output, with a certain amount of
1031 * intelligence in case they're both actually the same device.
1034 static void human_report(struct tvec_output *o, unsigned level,
1035 const char *msg, va_list *ap)
1037 struct human_output *h = (struct human_output *)o;
1038 struct tvec_state *tv = h->tv;
1039 const char *levstr; unsigned levhl;
1042 #define f_progress 1u
1044 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1047 #define CASE(tag, name, val) \
1048 case TVLEV_##tag: levstr = name; levhl = HL_##tag; break;
1050 default: levstr = "??"; levhl = HL_UNKLEV; break;
1053 if (h->f&HOF_PROGRESS) { clear_progress(h); f |= f_progress; }
1055 if (h->f&HOF_DUPERR) {
1056 fprintf(stderr, "%s: ", QUIS);
1057 human_report_location(h, stderr, tv->infile, tv->lno);
1058 fprintf(stderr, "%s: ", levstr);
1059 fwrite(d.buf, 1, d.len, stderr);
1062 human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
1063 setattr(h, levhl); fputs(levstr, h->lyt.fp);
1064 setattr(h, HL_PLAIN); fputs(": ", h->lyt.fp);
1065 fwrite(d.buf, 1, d.len, h->lyt.fp);
1067 if (f&f_progress) show_progress(h);
1073 /* --- @human_bbench@ --- *
1075 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1076 * @struct human_output@
1077 * @const char *desc@ = adhoc test description
1078 * @unsigned unit@ = measurement unit (@BTU_...@)
1082 * Use: Report that a benchmark has started.
1084 * The human driver just prints the start of the benchmark
1088 static void human_bbench(struct tvec_output *o,
1089 const char *desc, unsigned unit)
1091 struct human_output *h = (struct human_output *)o;
1092 struct tvec_state *tv = h->tv;
1095 gprintf(&human_printops, h, "%s ", tv->test->name);
1096 if (desc) gprintf(&human_printops, h, "%s", desc);
1097 else print_ident(tv, TVSF_COMPACT, &human_printops, h);
1098 gprintf(&human_printops, h, ": ");
1099 if (h->f&TVHF_TTY) fflush(h->lyt.fp);
1102 /* --- @human_ebench@ --- *
1104 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1105 * @struct human_output@
1106 * @const char *desc@ = adhoc test description
1107 * @unsigned unit@ = measurement unit (@BTU_...@)
1108 * @const struct bench_timing *t@ = measurement
1112 * Use: Report a benchmark's results.
1114 * The human driver just delegates to the default benchmark
1115 * reporting, via the layout machinery.
1118 static void human_ebench(struct tvec_output *o,
1119 const char *desc, unsigned unit,
1120 const struct bench_timing *t)
1122 struct human_output *h = (struct human_output *)o;
1124 tvec_benchreport(&human_printops, h, unit, 0, t);
1125 layout_char(&h->lyt, '\n');
1128 static const struct tvec_benchoutops human_benchops =
1129 { human_bbench, human_ebench };
1131 /* --- @human_extend@ --- *
1133 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1134 * @struct human_output@
1135 * @const char *name@ = extension name
1137 * Returns: A pointer to the extension implementation, or null.
1140 static const void *human_extend(struct tvec_output *o, const char *name)
1142 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
1146 /* --- @human_destroy@ --- *
1148 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1149 * @struct human_output@
1153 * Use: Release the resources held by the output driver.
1156 static void human_destroy(struct tvec_output *o)
1158 struct human_output *h = (struct human_output *)o;
1160 destroy_layout(&h->lyt,
1161 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1162 dstr_destroy(&h->scoreboard);
1163 x_free(h->a, h->outbuf); x_free(h->a, h);
1166 static const struct tvec_outops human_ops = {
1167 human_bsession, human_esession,
1168 human_bgroup, human_skipgroup, human_egroup,
1169 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1170 human_report, human_extend, human_destroy
1173 /* --- @tvec_humanoutput@ --- *
1175 * Arguments: @FILE *fp@ = output file to write on
1176 * @unsigned f, m@ = flags and mask
1178 * Returns: An output formatter.
1180 * Use: Return an output formatter which writes on @fp@ with the
1181 * expectation that a human will interpret the output.
1183 * The flags @f@ and mask @m@ operate together. Flag bits not
1184 * covered by the mask must be left clear, i.e., @f&~m$ must be
1185 * zero; the semantics are that a set mask bit indicates that
1186 * the corresponding bit of @f@ should control the indicated
1187 * behaviour; a clear mask bit indicates that a suitable default
1188 * should be chosen based on environmental conditions.
1190 * If @TVHF_TTY@ is set, then the output shows a `scoreboard'
1191 * indicating the outcome of each test case attempted, providing
1192 * a visual indication of progress. If @TVHF_COLOUR@ is set,
1193 * then the output uses control codes for colour and other
1194 * highlighting. It is unusual to set @TVHF_COLOUR@ without
1195 * @TVHF_TTY@, this is permitted anyway.
1197 * The environment variables %|TVEC_TTY|% and %|TVEC_COLOUR|%
1198 * provide default values for these settings. If they are not
1199 * set, then @TVHF_TTY@ is set if @fp@ refers to a terminal, and
1200 * @TVHF_COLOUR@ is set if @TVHF_TTY@ is set and, additionally,
1201 * the %|TERM|% environment variable is set to a value other
1205 struct tvec_output *tvec_humanoutput(FILE *fp, unsigned f, unsigned m)
1207 struct human_output *h;
1209 struct stat st_out, st_err;
1212 static const struct ttycolour_style hltab[] =
1213 TTYCOLOUR_INITTAB(HIGHLIGHTS);
1218 switch (getenv_boolean("TVEC_TTY", -1)) {
1219 case 1: f |= TVHF_TTY; break;
1222 if (isatty(fileno(fp))) f |= TVHF_TTY;
1225 if (!(m&TVHF_COLOUR))
1226 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1227 case 1: f |= TVHF_COLOUR; break;
1232 if (p && STRCMP(p, !=, "dumb")) f |= TVHF_COLOUR;
1237 /* Decide whether to write copies of reports to stderr.
1239 * There's not much point if the output is already going to stderr.
1240 * Otherwise, check to see whether stdout is the same underlying file.
1243 rc_out = fstat(fileno(fp), &st_out);
1244 rc_err = fstat(STDERR_FILENO, &st_err);
1245 if (!rc_err && (rc_out ||
1246 st_out.st_dev != st_err.st_dev ||
1247 st_out.st_ino != st_err.st_ino))
1251 XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
1254 /* Initialize the colour tables. */
1255 if (h->f&TVHF_COLOUR) {
1256 ttycolour_config(h->attr, "TVEC_COLOURS", TCIF_GETENV | TCIF_REPORT,
1258 ttycolour_init(&h->tc);
1261 init_layout(&h->lyt, fp, 0);
1262 h->outbuf = 0; h->outsz = 0;
1264 dstr_create(&h->scoreboard);
1268 /*----- Machine-readable output -------------------------------------------*/
1270 struct machine_output {
1271 struct tvec_output _o; /* output base class */
1272 struct tvec_state *tv; /* stashed testing state */
1273 arena *a; /* arena for memory allocation */
1274 FILE *fp; /* output stream */
1275 char *outbuf; size_t outsz; /* buffer for formatted output */
1276 unsigned grpix, testix; /* group and test indices */
1277 unsigned f; /* flags */
1278 #define MF_BENCH 1u /* current test is a benchmark */
1281 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1283 * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
1284 * @int ch@ = character to write
1285 * @const char *@p@, @size_t sz@ = string (with explicit length)
1287 * @const char *p, ...@ = format control string and arguments to
1292 * Use: Write characters, strings, or formatted strings to the
1293 * output, applying appropriate quoting.
1296 static void machine_escape(struct machine_output *m, int ch)
1299 case 0: fputs("\\0", m->fp); break;
1300 case '\a': fputs("\\a", m->fp); break;
1301 case '\b': fputs("\\b", m->fp); break;
1302 case '\x1b': fputs("\\e", m->fp); break;
1303 case '\f': fputs("\\f", m->fp); break;
1304 case '\n': fputs("\\n", m->fp); break;
1305 case '\r': fputs("\\r", m->fp); break;
1306 case '\t': fputs("\\t", m->fp); break;
1307 case '\v': fputs("\\v", m->fp); break;
1308 case '"': fputs("\\\"", m->fp); break;
1309 case '\\': fputs("\\\\", m->fp); break;
1310 default: fprintf(m->fp, "\\x{%02x}", ch);
1314 static int machine_writech(void *go, int ch)
1316 struct machine_output *m = go;
1318 if (ISPRINT(ch)) putc(ch, m->fp);
1319 else machine_escape(m, ch);
1323 static int machine_writem(void *go, const char *p, size_t sz)
1325 struct machine_output *m = go;
1326 const char *q, *l = p + sz;
1331 if (q >= l) goto final;
1332 if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1335 if (p < q) fwrite(p, 1, q - p, m->fp);
1336 p = q; machine_escape(m, (unsigned char)*p++);
1339 if (p < l) fwrite(p, 1, l - p, m->fp);
1343 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1345 struct machine_output *m = go;
1350 n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
1352 return (machine_writem(m, m->outbuf, n));
1355 static const struct gprintf_ops machine_printops =
1356 { machine_writech, machine_writem, machine_nwritef };
1358 /* --- @machine_maybe_quote@ --- *
1360 * Arguments: @struct machine_output *m@ = output sink
1361 * @const char *p@ = pointer to string
1365 * Use: Print the string @p@, quoting it if necessary.
1368 static void machine_maybe_quote(struct machine_output *m, const char *p)
1372 for (q = p; *q; q++)
1373 if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
1374 fputs(p, m->fp); return;
1376 putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1379 /* --- @machine_bsession@ --- *
1381 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1382 * @struct machine_output@
1383 * @struct tvec_state *tv@ = the test state producing output
1387 * Use: Begin a test session.
1389 * The TAP driver records the test state for later reference,
1390 * initializes the group index counter, and prints the version
1394 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1396 struct machine_output *m = (struct machine_output *)o;
1398 m->tv = tv; m->grpix = 0;
1401 /* --- @machine_show_stats@ --- *
1403 * Arguments: @struct machine_output *m@ = output sink
1404 * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1408 * Use: Print a machine readable outcome statistics table
1411 static void machine_show_stats(struct machine_output *m,
1412 unsigned out[TVOUT_LIMIT])
1414 static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1417 for (i = 0; i < TVOUT_LIMIT; i++) {
1418 if (i) putc(' ', m->fp);
1419 fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1423 /* --- @machine_esession@ --- *
1425 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1426 * @struct machine_output@
1428 * Returns: Suggested exit code.
1430 * Use: End a test session.
1432 * The TAP driver prints a final summary of the rest results
1433 * and returns a suitable exit code. If errors occurred, it
1434 * instead prints a `Bail out!' line forcing the reader to
1438 static int machine_esession(struct tvec_output *o)
1440 struct machine_output *m = (struct machine_output *)o;
1441 struct tvec_state *tv = m->tv;
1443 fputs("END groups: ", m->fp);
1444 machine_show_stats(m, tv->grps);
1445 fputs("; tests: ", m->fp);
1446 machine_show_stats(m, tv->all);
1448 m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1451 /* --- @machine_bgroup@ --- *
1453 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1454 * @struct machine_output@
1458 * Use: Begin a test group.
1460 * The TAP driver determines the length of the longest
1461 * register name, resets the group progress scoreboard, and
1462 * activates the progress display.
1465 static void machine_bgroup(struct tvec_output *o)
1467 struct machine_output *m = (struct machine_output *)o;
1468 struct tvec_state *tv = m->tv;
1470 fputs("BGROUP ", m->fp);
1471 machine_maybe_quote(m, tv->test->name);
1473 m->grpix++; m->testix = 0;
1476 /* --- @machine_skipgroup@ --- *
1478 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1479 * @struct machine_output@
1480 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1485 * Use: Report that a test group is being skipped.
1487 * The TAP driver just reports the situation to its output
1491 static void machine_skipgroup(struct tvec_output *o,
1492 const char *excuse, va_list *ap)
1494 struct machine_output *m = (struct machine_output *)o;
1495 struct tvec_state *tv = m->tv;
1497 fputs("SKIPGRP ", m->fp);
1498 machine_maybe_quote(m, tv->test->name);
1500 fputs(" \"", m->fp);
1501 vgprintf(&machine_printops, m, excuse, ap);
1507 /* --- @machine_egroup@ --- *
1509 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1510 * @struct machine_output@
1514 * Use: Report that a test group has finished.
1516 * The TAP driver reports a summary of the group's tests.
1519 static void machine_egroup(struct tvec_output *o)
1521 struct machine_output *m = (struct machine_output *)o;
1522 struct tvec_state *tv = m->tv;
1524 if (!(tv->f&TVSF_SKIP)) {
1525 fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
1526 putc(' ', m->fp); machine_show_stats(m, tv->curr);
1531 /* --- @machine_btest@ --- *
1533 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1534 * @struct machine_output@
1538 * Use: Report that a test is starting.
1540 * The TAP driver advances its test counter. (We could do this
1541 * by adding up up the counters in @tv->curr@, and add on the
1542 * current test, but it's easier this way.)
1545 static void machine_btest(struct tvec_output *o) { ; }
1547 /* --- @machine_report_location@ --- *
1549 * Arguments: @struct human_output *h@ = output state
1550 * @const char *file@ = filename
1551 * @unsigned lno@ = line number
1555 * Use: Print the filename and line number to the output stream.
1558 static void machine_report_location(struct machine_output *m,
1559 const char *file, unsigned lno)
1563 machine_maybe_quote(m, file);
1564 fprintf(m->fp, ":%u", lno);
1568 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1570 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1571 * @struct machine_output@
1572 * @const char *outcome@ = outcome string to report
1573 * @const char *detail@, @va_list *ap@ = a detail message
1574 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1579 * Use: Report that a test has been skipped or failed.
1582 static void machine_outcome(struct tvec_output *o, const char *outcome,
1583 const char *detail, va_list *ap)
1585 struct machine_output *m = (struct machine_output *)o;
1586 struct tvec_state *tv = m->tv;
1588 fprintf(m->fp, "%s %u", outcome, m->testix);
1589 machine_report_location(m, tv->infile, tv->test_lno);
1591 { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1595 static void machine_skip(struct tvec_output *o,
1596 const char *excuse, va_list *ap)
1597 { machine_outcome(o, "SKIP", excuse, ap); }
1598 static void machine_fail(struct tvec_output *o,
1599 const char *detail, va_list *ap)
1600 { machine_outcome(o, "FAIL", detail, ap); }
1602 /* --- @machine_dumpreg@ --- *
1604 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1605 * @struct machine_output@
1606 * @unsigned disp@ = register disposition
1607 * @const union tvec_regval *rv@ = register value
1608 * @const struct tvec_regdef *rd@ = register definition
1612 * Use: Dump a register.
1614 * The machine driver applies highlighting to mismatching output
1615 * registers, but otherwise delegates to the register type
1619 static void machine_dumpreg(struct tvec_output *o,
1620 unsigned disp, const union tvec_regval *rv,
1621 const struct tvec_regdef *rd)
1623 struct machine_output *m = (struct machine_output *)o;
1629 case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
1630 case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
1631 case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
1632 case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
1633 case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
1637 if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
1638 if (!rv) fputs("#unset", m->fp);
1639 else rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
1640 if (f&f_nl) putc('\n', m->fp);
1646 /* --- @machine_etest@ --- *
1648 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1649 * @struct machine_output@
1650 * @unsigned outcome@ = the test outcome
1654 * Use: Report that a test has finished.
1656 * The machine driver reports the outcome of the test, if that's
1657 * not already decided.
1660 static void machine_etest(struct tvec_output *o, unsigned outcome)
1662 struct machine_output *m = (struct machine_output *)o;
1664 if (!(m->f&MF_BENCH)) switch (outcome) {
1665 case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
1666 case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
1668 m->testix++; m->f &= ~MF_BENCH;
1671 /* --- @machine_report@ --- *
1673 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1674 * @struct machine_output@
1675 * @unsigned level@ = message level (@TVLEV_...@)
1676 * @const char *msg@, @va_list *ap@ = format string and
1681 * Use: Report a message to the user.
1683 * Each report level has its own output tag
1686 static void machine_report(struct tvec_output *o, unsigned level,
1687 const char *msg, va_list *ap)
1689 struct machine_output *m = (struct machine_output *)o;
1690 struct tvec_state *tv = m->tv;
1693 for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
1694 machine_report_location(m, tv->infile, tv->lno);
1695 fputs(" \"", m->fp); vgprintf(&machine_printops, m, msg, ap);
1700 /* --- @machine_bbench@ --- *
1702 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1703 * @struct machine_output@
1704 * @const char *desc@ = adhoc test description
1705 * @unsigned unit@ = measurement unit (@BTU_...@)
1709 * Use: Report that a benchmark has started.
1711 * The machine driver does nothing here. All of the reporting
1712 * happens in @machine_ebench@.
1715 static void machine_bbench(struct tvec_output *o,
1716 const char *desc, unsigned unit)
1719 /* --- @machine_ebench@ --- *
1721 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1722 * @struct machine_output@
1723 * @const char *desc@ = adhoc test description
1724 * @unsigned unit@ = measurement unit (@BTU_...@)
1725 * @const struct bench_timing *t@ = measurement
1729 * Use: Report a benchmark's results.
1731 * The machine driver prints the result as a `BENCH' output
1732 * line, in raw format.
1735 static void machine_ebench(struct tvec_output *o,
1736 const char *desc, unsigned unit,
1737 const struct bench_timing *t)
1739 struct machine_output *m = (struct machine_output *)o;
1740 struct tvec_state *tv = m->tv;
1742 fprintf(m->fp, "BENCH %u", m->testix);
1743 machine_report_location(m, tv->infile, tv->test_lno);
1745 if (desc) machine_maybe_quote(m, desc);
1746 else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
1748 tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
1749 putc('\n', m->fp); m->f |= MF_BENCH;
1752 static const struct tvec_benchoutops machine_benchops =
1753 { machine_bbench, machine_ebench };
1755 /* --- @machine_extend@ --- *
1757 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1758 * @struct machine_output@
1759 * @const char *name@ = extension name
1761 * Returns: A pointer to the extension implementation, or null.
1764 static const void *machine_extend(struct tvec_output *o, const char *name)
1766 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
1770 /* --- @machine_destroy@ --- *
1772 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1773 * @struct machine_output@
1777 * Use: Release the resources held by the output driver.
1780 static void machine_destroy(struct tvec_output *o)
1782 struct machine_output *m = (struct machine_output *)o;
1784 if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1785 x_free(m->a, m->outbuf); x_free(m->a, m);
1788 static const struct tvec_outops machine_ops = {
1789 machine_bsession, machine_esession,
1790 machine_bgroup, machine_skipgroup, machine_egroup,
1791 machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
1792 machine_report, machine_extend, machine_destroy
1795 /* --- @tvec_machineoutput@ --- *
1797 * Arguments: @FILE *fp@ = output file to write on
1799 * Returns: An output formatter.
1801 * Use: Return an output formatter which writes on @fp@ in a
1802 * moderately simple machine-readable format.
1805 struct tvec_output *tvec_machineoutput(FILE *fp)
1807 struct machine_output *m;
1809 XNEW(m); m->a = arena_global; m->_o.ops = &machine_ops;
1810 m->f = 0; m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
1814 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1817 struct tvec_output _o; /* output base class */
1818 struct tvec_state *tv; /* stashed testing state */
1819 arena *a; /* arena for memory allocation */
1820 struct layout lyt; /* output layout */
1821 char *outbuf; size_t outsz; /* buffer for formatted output */
1822 unsigned grpix, testix; /* group and test indices */
1823 unsigned previx; /* previously reported test index */
1824 int maxlen; /* longest register name */
1827 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1829 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1830 * @int ch@ = character to write
1831 * @const char *@p@, @size_t sz@ = string (with explicit length)
1833 * @const char *p, ...@ = format control string and arguments to
1838 * Use: Write characters, strings, or formatted strings to the
1839 * output, applying appropriate layout.
1841 * For the TAP output driver, the layout machinery prefixes each
1842 * line with ` ## ' and strips trailing spaces.
1845 static int tap_writech(void *go, int ch)
1847 struct tap_output *t = go;
1849 if (layout_char(&t->lyt, ch)) return (-1);
1853 static int tap_writem(void *go, const char *p, size_t sz)
1855 struct tap_output *t = go;
1857 if (layout_string(&t->lyt, p, sz)) return (-1);
1861 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1863 struct tap_output *t = go;
1868 n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
1870 if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1874 static const struct gprintf_ops tap_printops =
1875 { tap_writech, tap_writem, tap_nwritef };
1877 /* --- @tap_bsession@ --- *
1879 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1880 * @struct tap_output@
1881 * @struct tvec_state *tv@ = the test state producing output
1885 * Use: Begin a test session.
1887 * The TAP driver records the test state for later reference,
1888 * initializes the group index counter, and prints the version
1892 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1894 struct tap_output *t = (struct tap_output *)o;
1896 t->tv = tv; t->grpix = 0;
1897 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1900 /* --- @tap_esession@ --- *
1902 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1903 * @struct tap_output@
1905 * Returns: Suggested exit code.
1907 * Use: End a test session.
1909 * The TAP driver prints a final summary of the rest results
1910 * and returns a suitable exit code. If errors occurred, it
1911 * instead prints a `Bail out!' line forcing the reader to
1915 static int tap_esession(struct tvec_output *o)
1917 struct tap_output *t = (struct tap_output *)o;
1918 struct tvec_state *tv = t->tv;
1920 if (tv->f&TVSF_ERROR) {
1922 "Errors found in input; tests may not have run correctly\n",
1927 fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1928 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1931 /* --- @tap_bgroup@ --- *
1933 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1934 * @struct tap_output@
1938 * Use: Begin a test group.
1940 * The TAP driver determines the length of the longest
1941 * register name, resets the group progress scoreboard, and
1942 * activates the progress display.
1945 static void tap_bgroup(struct tvec_output *o)
1947 struct tap_output *t = (struct tap_output *)o;
1948 struct tvec_state *tv = t->tv;
1950 t->grpix++; t->testix = t->previx = 0;
1951 t->maxlen = register_maxnamelen(t->tv);
1952 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1955 /* --- @tap_skipgroup@ --- *
1957 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1958 * @struct tap_output@
1959 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1964 * Use: Report that a test group is being skipped.
1966 * The TAP driver just reports the situation to its output
1970 static void tap_skipgroup(struct tvec_output *o,
1971 const char *excuse, va_list *ap)
1973 struct tap_output *t = (struct tap_output *)o;
1975 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
1976 fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
1977 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1978 fputc('\n', t->lyt.fp);
1981 /* --- @tap_egroup@ --- *
1983 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1984 * @struct tap_output@
1988 * Use: Report that a test group has finished.
1990 * The TAP driver reports a summary of the group's tests.
1993 static void tap_egroup(struct tvec_output *o)
1995 struct tap_output *t = (struct tap_output *)o;
1996 struct tvec_state *tv = t->tv;
1998 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
1999 fprintf(t->lyt.fp, "%s %u - %s\n",
2000 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
2001 t->grpix, tv->test->name);
2004 /* --- @tap_btest@ --- *
2006 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2007 * @struct tap_output@
2011 * Use: Report that a test is starting.
2013 * The TAP driver advances its test counter. (We could do this
2014 * by adding up up the counters in @tv->curr@, and add on the
2015 * current test, but it's easier this way.)
2018 static void tap_btest(struct tvec_output *o)
2019 { struct tap_output *t = (struct tap_output *)o; t->testix++; }
2021 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2023 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2024 * @struct tap_output@
2025 * @const char *head, *tail@ = outcome strings to report
2026 * @const char *detail@, @va_list *ap@ = a detail message
2027 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2032 * Use: Report that a test has been skipped or failed.
2034 * The TAP driver reports the situation on its output stream.
2035 * TAP only allows us to report a single status for each
2036 * subtest, so we notice when we've already reported a status
2037 * for the current test and convert the second report as a
2038 * comment. This should only happen in the case of multiple
2042 static void tap_outcome(struct tvec_output *o,
2043 const char *head, const char *tail,
2044 const char *detail, va_list *ap)
2046 struct tap_output *t = (struct tap_output *)o;
2047 struct tvec_state *tv = t->tv;
2049 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
2050 t->testix == t->previx ? "##" : head,
2051 t->testix, tv->infile, tv->test_lno, tail);
2053 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2054 fputc('\n', t->lyt.fp);
2055 t->previx = t->testix;
2058 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2059 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
2060 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
2061 { tap_outcome(o, "not ok", "", detail, ap); }
2063 /* --- @tap_dumpreg@ --- *
2065 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2066 * @struct tap_output@
2067 * @unsigned disp@ = register disposition
2068 * @const union tvec_regval *rv@ = register value
2069 * @const struct tvec_regdef *rd@ = register definition
2073 * Use: Dump a register.
2075 * The TAP driver applies highlighting to mismatching output
2076 * registers, but otherwise delegates to the register type
2077 * handler and the layout machinery. The result is that the
2078 * register dump is marked as a comment and indented.
2081 static void tap_dumpreg(struct tvec_output *o,
2082 unsigned disp, const union tvec_regval *rv,
2083 const struct tvec_regdef *rd)
2085 struct tap_output *t = (struct tap_output *)o;
2086 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2088 set_layout_prefix(&t->lyt, " ## ");
2089 gprintf(&tap_printops, t, "%*s%s %s = ",
2090 10 + t->maxlen - n, "", ds, rd->name);
2091 if (!rv) gprintf(&tap_printops, t, "#<unset>");
2092 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
2093 layout_char(&t->lyt, '\n');
2096 /* --- @tap_etest@ --- *
2098 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2099 * @struct tap_output@
2100 * @unsigned outcome@ = the test outcome
2104 * Use: Report that a test has finished.
2106 * The TAP driver reports the outcome of the test, if that's not
2110 static void tap_etest(struct tvec_output *o, unsigned outcome)
2114 tap_outcome(o, "ok", "", 0, 0);
2117 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2122 /* --- @tap_report@ --- *
2124 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2125 * @struct tap_output@
2126 * @unsigned level@ = message level (@TVLEV_...@)
2127 * @const char *msg@, @va_list *ap@ = format string and
2132 * Use: Report a message to the user.
2134 * Messages are reported as comments, so that they can be
2135 * accumulated by the reader. An error will cause a later
2136 * bailout or, if we crash before then, a missing plan line,
2137 * either of which will cause the reader to report a serious
2141 static void tap_report(struct tvec_output *o, unsigned level,
2142 const char *msg, va_list *ap)
2144 struct tap_output *t = (struct tap_output *)o;
2145 struct tvec_state *tv = t->tv;
2147 if (tv->test) set_layout_prefix(&t->lyt, " ## ");
2148 else set_layout_prefix(&t->lyt, "## ");
2150 if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
2151 gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
2152 vgprintf(&tap_printops, t, msg, ap);
2153 layout_char(&t->lyt, '\n');
2156 /* --- @tap_extend@ --- *
2158 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2159 * @struct tap_output@
2160 * @const char *name@ = extension name
2162 * Returns: A pointer to the extension implementation, or null.
2165 static const void *tap_extend(struct tvec_output *o, const char *name)
2168 /* --- @tap_destroy@ --- *
2170 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2171 * @struct tap_output@
2175 * Use: Release the resources held by the output driver.
2178 static void tap_destroy(struct tvec_output *o)
2180 struct tap_output *t = (struct tap_output *)o;
2182 destroy_layout(&t->lyt,
2183 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
2184 x_free(t->a, t->outbuf); x_free(t->a, t);
2187 static const struct tvec_outops tap_ops = {
2188 tap_bsession, tap_esession,
2189 tap_bgroup, tap_skipgroup, tap_egroup,
2190 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
2191 tap_report, tap_extend, tap_destroy
2194 /* --- @tvec_tapoutput@ --- *
2196 * Arguments: @FILE *fp@ = output file to write on
2198 * Returns: An output formatter.
2200 * Use: Return an output formatter which writes on @fp@ in `TAP'
2201 * (`Test Anything Protocol') format.
2203 * TAP comes from the Perl community, but has spread rather
2204 * further. This driver produces TAP version 14, but pretends
2205 * to be version 13. The driver produces a TAP `test point' --
2206 * i.e., a result reported as `ok' or `not ok' -- for each input
2207 * test group. Failure reports and register dumps are produced
2208 * as diagnostic messages before the final group result. (TAP
2209 * permits structuerd YAML data after the test-point result,
2210 * which could be used to report details, but (a) postponing the
2211 * details until after the report is inconvenient, and (b) there
2212 * is no standardization for the YAML anyway, so in practice
2213 * it's no more useful than the unstructured diagnostics.
2216 struct tvec_output *tvec_tapoutput(FILE *fp)
2218 struct tap_output *t;
2220 XNEW(t); t->a = arena_global; t->_o.ops = &tap_ops;
2221 init_layout(&t->lyt, fp, 0);
2222 t->outbuf = 0; t->outsz = 0;
2226 /*----- Automake support --------------------------------------------------*/
2228 struct automake_output {
2229 struct tvec_output _o;
2230 arena *a; /* arena */
2231 struct tvec_state *tv; /* test-vector state */
2232 struct tvec_output *progress; /* real-time progress output */
2233 struct tvec_output *log; /* log file output */
2234 FILE *trs; /* test result stream */
2237 /* --- @am_bsession@ --- *
2239 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2240 * @struct automake_output@
2241 * @struct tvec_state *tv@ = the test state producing output
2245 * Use: Begin a test session.
2247 * The Automake driver passes the event on to its subordinates.
2250 static void am_bsession(struct tvec_output *o, struct tvec_state *tv)
2252 struct automake_output *am = (struct automake_output *)o;
2255 human_bsession(am->progress, tv);
2256 machine_bsession(am->log, tv);
2259 /* --- @am_esession@ --- *
2261 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2262 * @struct automake_output@
2264 * Returns: Suggested exit code.
2266 * Use: End a test session.
2268 * The Automake driver completes the test-results file and
2269 * passes the event on to its subordinates.
2272 static void am_report_unusual(struct automake_output *am,
2273 unsigned xfail, unsigned skip)
2279 fprintf(am->trs, "%s%u expected %s", f&f_any ? ", " : " (",
2280 xfail, xfail == 1 ? "failure" : "failures");
2284 fprintf(am->trs, "%s%u skipped", f&f_any ? ", " : " (", skip);
2287 if (f&f_any) fputc(')', am->trs);
2292 static int am_esession(struct tvec_output *o)
2294 struct automake_output *am = (struct automake_output *)o;
2295 struct tvec_state *tv = am->tv;
2297 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
2298 all_xfail = tv->all[TVOUT_XFAIL],
2299 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
2300 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
2301 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
2302 grps_run = grps_win + grps_lose;
2304 human_esession(am->progress);
2305 machine_esession(am->log);
2307 fputs(":test-global-result: ", am->trs);
2308 if (tv->f&TVSF_ERROR) fputs("ERRORS; ", am->trs);
2310 fprintf(am->trs, "PASSED %s%u %s",
2311 !(all_skip || grps_skip) ? "all " : "",
2312 all_win, all_win == 1 ? "test" : "tests");
2313 am_report_unusual(am, all_xfail, all_skip);
2314 fprintf(am->trs, " in %u %s",
2315 grps_win, grps_win == 1 ? "group" : "groups");
2316 am_report_unusual(am, 0, grps_skip);
2318 fprintf(am->trs, "FAILED %u out of %u %s",
2319 all_lose, all_run, all_run == 1 ? "test" : "tests");
2320 am_report_unusual(am, all_xfail, all_skip);
2321 fprintf(am->trs, " in %u out of %u %s",
2322 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
2323 am_report_unusual(am, 0, grps_skip);
2325 fputc('\n', am->trs);
2327 fprintf(am->trs, ":copy-in-global-log: %s\n",
2328 !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes");
2329 fprintf(am->trs, ":recheck: %s\n",
2330 !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes");
2335 /* --- @am_bgroup@ --- *
2337 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2338 * @struct automake_output@
2342 * Use: Begin a test group.
2344 * The Automake driver passes the event on to its subordinates.
2347 static void am_bgroup(struct tvec_output *o)
2349 struct automake_output *am = (struct automake_output *)o;
2351 human_bgroup(am->progress);
2352 machine_bgroup(am->log);
2355 /* --- @am_skipgroup@ --- *
2357 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2358 * @struct automake_output@
2359 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2364 * Use: Report that a test group is being skipped.
2366 * The Automake driver makes a note in the test-results file and
2367 * passes the event on to its subordinates.
2370 static void am_skipgroup(struct tvec_output *o,
2371 const char *excuse, va_list *ap)
2373 struct automake_output *am = (struct automake_output *)o;
2374 struct tvec_state *tv = am->tv;
2376 fprintf(am->trs, ":test-result: SKIP %s\n", tv->test->name);
2377 human_skipgroup(am->progress, excuse, ap);
2378 machine_skipgroup(am->log, excuse, ap);
2381 /* --- @am_egroup@ --- *
2383 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2384 * @struct automake_output@
2388 * Use: Report that a test group has finished.
2390 * The Automake driver makes a note in the test-results file and
2391 * passes the event on to its subordinates.
2394 static void am_egroup(struct tvec_output *o)
2396 struct automake_output *am = (struct automake_output *)o;
2397 struct tvec_state *tv = am->tv;
2399 fprintf(am->trs, ":test-result: %s %s\n",
2400 tv->curr[TVOUT_LOSE] ? "FAIL" : "PASS", tv->test->name);
2401 human_egroup(am->progress);
2402 machine_egroup(am->log);
2405 /* --- @am_btest@ --- *
2407 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2408 * @struct automake_output@
2412 * Use: Report that a test is starting.
2414 * The Automake driver passes the event on to its subordinates.
2417 static void am_btest(struct tvec_output *o)
2419 struct automake_output *am = (struct automake_output *)o;
2421 human_btest(am->progress);
2422 machine_btest(am->log);
2425 /* --- @am_skip@, @am_fail@ --- *
2427 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2428 * @struct automake_output@
2429 * @const char *head, *tail@ = outcome strings to report
2430 * @const char *detail@, @va_list *ap@ = a detail message
2431 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2436 * Use: Report that a test has been skipped or failed.
2438 * The Automake driver passes the event on to its subordinates.
2441 static void am_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2443 struct automake_output *am = (struct automake_output *)o;
2445 human_skip(am->progress, excuse, ap);
2446 machine_skip(am->log, excuse, ap);
2449 static void am_fail(struct tvec_output *o, const char *detail, va_list *ap)
2451 struct automake_output *am = (struct automake_output *)o;
2453 human_fail(am->progress, detail, ap);
2454 machine_fail(am->log, detail, ap);
2457 /* --- @am_dumpreg@ --- *
2459 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2460 * @struct automake_output@
2461 * @unsigned disp@ = register disposition
2462 * @const union tvec_regval *rv@ = register value
2463 * @const struct tvec_regdef *rd@ = register definition
2467 * Use: Dump a register.
2469 * The Automake driver passes the event on to its subordinates.
2472 static void am_dumpreg(struct tvec_output *o,
2473 unsigned disp, const union tvec_regval *rv,
2474 const struct tvec_regdef *rd)
2476 struct automake_output *am = (struct automake_output *)o;
2478 human_dumpreg(am->progress, disp, rv, rd);
2479 machine_dumpreg(am->log, disp, rv, rd);
2482 /* --- @am_etest@ --- *
2484 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2485 * @struct automake_output@
2486 * @unsigned outcome@ = the test outcome
2490 * Use: Report that a test has finished.
2492 * The Automake driver passes the event on to its subordinates.
2495 static void am_etest(struct tvec_output *o, unsigned outcome)
2497 struct automake_output *am = (struct automake_output *)o;
2499 human_etest(am->progress, outcome);
2500 machine_etest(am->log, outcome);
2503 /* --- @am_report@ --- *
2505 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2506 * @struct automake_output@
2507 * @unsigned level@ = message level (@TVLEV_...@)
2508 * @const char *msg@, @va_list *ap@ = format string and
2513 * Use: Report a message to the user.
2515 * The Automake driver passes the event on to its subordinates.
2518 static void am_report(struct tvec_output *o, unsigned level,
2519 const char *msg, va_list *ap)
2521 struct automake_output *am = (struct automake_output *)o;
2523 human_report(am->progress, level, msg, ap);
2524 machine_report(am->log, level, msg, ap);
2527 /* --- @am_bbench@ --- *
2529 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2530 * @struct automake_output@
2531 * @const char *desc@ = adhoc test description
2532 * @unsigned unit@ = measurement unit (@BTU_...@)
2536 * Use: Report that a benchmark has started.
2538 * The Automake driver passes the event on to its subordinates.
2541 static void am_bbench(struct tvec_output *o,
2542 const char *desc, unsigned unit)
2544 struct automake_output *am = (struct automake_output *)o;
2546 human_bbench(am->progress, desc, unit);
2547 machine_bbench(am->progress, desc, unit);
2550 /* --- @am_ebench@ --- *
2552 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2553 * @struct automake_output@
2554 * @const char *desc@ = adhoc test description
2555 * @unsigned unit@ = measurement unit (@BTU_...@)
2556 * @const struct bench_timing *t@ = measurement
2560 * Use: Report a benchmark's results.
2562 * The Automake driver passes the event on to its subordinates.
2565 static void am_ebench(struct tvec_output *o,
2566 const char *desc, unsigned unit,
2567 const struct bench_timing *t)
2569 struct automake_output *am = (struct automake_output *)o;
2571 human_ebench(am->progress, desc, unit, t);
2572 machine_ebench(am->progress, desc, unit, t);
2575 static const struct tvec_benchoutops am_benchops =
2576 { am_bbench, am_ebench };
2578 /* --- @am_extend@ --- *
2580 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2581 * @struct automake_output@
2582 * @const char *name@ = extension name
2584 * Returns: A pointer to the extension implementation, or null.
2587 static const void *am_extend(struct tvec_output *o, const char *name)
2589 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&am_benchops);
2593 /* --- @am_destroy@ --- *
2595 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2596 * @struct automake_output@
2600 * Use: Release the resources held by the output driver.
2603 static void am_destroy(struct tvec_output *o)
2605 struct automake_output *am = (struct automake_output *)o;
2607 human_destroy(am->progress);
2608 machine_destroy(am->log);
2609 fclose(am->trs); x_free(am->a, am);
2612 static const struct tvec_outops automake_ops = {
2613 am_bsession, am_esession,
2614 am_bgroup, am_skipgroup, am_egroup,
2615 am_btest, am_skip, am_fail, am_dumpreg, am_etest,
2616 am_report, am_extend, am_destroy
2619 /* --- @tvec_amoutput@ --- *
2621 * Arguments: @const struct tvec_amargs *a@ = arguments from Automake
2622 * command-line protocol
2624 * Returns: An output formatter.
2626 * Use: Returns an output formatter which writes on standard output
2627 * in human format, pretending that the output is to a terminal
2628 * (in order to cope with %%\manpage{make}{1}%%'s output-
2629 * buffering behaviour, writes to the log file @a->log@ in
2630 * machine-readable format, and writes an Automake rST-format
2631 * test result file to @a->trs@. The `test name' is currently
2632 * ignored, because the framework has its own means of
2633 * determining test names.
2636 struct tvec_output *tvec_amoutput(const struct tvec_amargs *a)
2638 struct automake_output *am;
2642 if (a->f&TVAF_COLOUR) f |= TVHF_COLOUR;
2644 XNEW(am); am->a = arena_global; am->_o.ops = &automake_ops;
2645 am->progress = tvec_humanoutput(stdout, f, TVHF_TTY | TVHF_COLOUR);
2646 am->log = tvec_machineoutput(a->log); am->trs = a->trs;
2650 /*----- Default output ----------------------------------------------------*/
2652 /* --- @tvec_dfltoutput@ --- *
2654 * Arguments: @FILE *fp@ = output file to write on
2656 * Returns: An output formatter.
2658 * Use: Selects and instantiates an output formatter suitable for
2659 * writing on @fp@. The policy is subject to change, but
2660 * currently the `human' output format is selected if @fp@ is
2661 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
2662 * otherwise the `machine' format is used.
2665 struct tvec_output *tvec_dfltoutput(FILE *fp)
2667 int ttyp = getenv_boolean("TVEC_TTY", -1);
2669 if (ttyp == -1) ttyp = isatty(fileno(fp));
2670 if (ttyp) return (tvec_humanoutput(fp, 0, 0));
2671 else return (tvec_machineoutput(fp));
2674 /*----- That's all, folks -------------------------------------------------*/