3 * Test vector output management
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
48 /*----- Common machinery --------------------------------------------------*/
50 /* --- @regdisp@ --- *
52 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
54 * Returns: A human-readable adjective describing the register
58 static const char *regdisp(unsigned disp)
61 case TVRD_INPUT: return "input";
62 case TVRD_OUTPUT: return "output";
63 case TVRD_MATCH: return "matched";
64 case TVRD_EXPECT: return "expected";
65 case TVRD_FOUND: return "found";
70 /* --- @getenv_boolean@ --- *
72 * Arguments: @const char *var@ = environment variable name
73 * @int dflt@ = default value
75 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
76 * set to something truish, or @dflt@ otherwise.
79 static int getenv_boolean(const char *var, int dflt)
86 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
87 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
88 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
91 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
92 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
93 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
97 moan("ignoring unexpected value `%s' for environment variable `%s'",
103 /* --- @register_maxnamelen@ --- *
105 * Arguments: @const struct tvec_state *tv@ = test vector state
107 * Returns: The maximum length of a register name in the current test.
110 static int register_maxnamelen(const struct tvec_state *tv)
112 const struct tvec_regdef *rd;
115 for (rd = tv->test->regs; rd->name; rd++)
116 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
120 /*----- Output layout -----------------------------------------------------*/
122 /* We have two main jobs in output layout: trimming trailing blanks; and
123 * adding a prefix to each line.
125 * This is somehow much more complicated than it ought to be.
129 FILE *fp; /* output file */
130 const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
131 dstr w; /* trailing whitespace */
132 unsigned f; /* flags */
133 #define LYTF_NEWL 1u /* start of output line */
136 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
140 #define SPLIT_RANGE(tail, base, limit) do { \
141 /* Set TAIL to point just after the last nonspace character between \
142 * BASE and LIMIT. If there are no nonspace characters, then set \
143 * TAIL to equal BASE. \
146 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
149 #define PUT_RANGE(base, limit) do { \
150 /* Write the range of characters between BASE and LIMIT to the output \
151 * file. Return immediately on error. \
154 size_t _n = limit - base; \
155 if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
158 #define PUT_CHAR(ch) do { \
159 /* Write CH to the output. Return immediately on error. */ \
161 if (putc(ch, lyt->fp) == EOF) return (-1); \
164 #define PUT_PREFIX do { \
165 /* Output the prefix, if there is one. Return immediately on error. */ \
167 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
170 #define PUT_SAVED do { \
171 /* Output the saved trailing blank material in the buffer. */ \
173 size_t _n = lyt->w.len; \
174 if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
177 #define PUT_PFXINB do { \
178 /* Output the initial nonblank portion of the prefix, if there is \
179 * one. Return immediately on error. \
182 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
185 #define SAVE_PFXTAIL do { \
186 /* Save the trailing blank portion of the prefix. */ \
189 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
192 /* --- @set_layout_prefix@ --- *
194 * Arguments: @struct layout *lyt@ = layout state
195 * @const char *prefix@ = new prefix string or null
199 * Use: Change the configured prefix string. The change takes effect
200 * at the start of the next line (or the current line if it's
201 * empty or only whitespace so far).
204 static void set_layout_prefix(struct layout *lyt, const char *prefix)
208 if (!prefix || !*prefix)
209 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
211 lyt->prefix = prefix;
212 l = lyt->pfxlim = prefix + strlen(prefix);
213 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
217 /* --- @init_layout@ --- *
219 * Arguments: @struct layout *lyt@ = layout state to initialize
220 * @FILE *fp@ = output file
221 * @const char *prefix@ = prefix string (or null if empty)
225 * Use: Initialize a layout state.
228 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
232 dstr_create(&lyt->w);
233 set_layout_prefix(lyt, prefix);
236 /* --- @destroy_layout@ --- *
238 * Arguments: @struct layout *lyt@ = layout state
239 * @unsigned f@ = flags (@DLF_...@)
243 * Use: Releases a layout state and the resources it holds.
244 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
245 * it open (in case it's @stderr@ or something).
249 static void destroy_layout(struct layout *lyt, unsigned f)
251 if (f&DLF_CLOSE) fclose(lyt->fp);
252 dstr_destroy(&lyt->w);
255 /* --- @layout_char@ --- *
257 * Arguments: @struct layout *lyt@ = layout state
258 * @int ch@ = character to write
260 * Returns: Zero on success, @-1@ on failure.
262 * Use: Write a single character to the output.
265 static int layout_char(struct layout *lyt, int ch)
268 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
269 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
270 } else if (isspace(ch))
273 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
274 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
279 /* --- @layout_string@ --- *
281 * Arguments: @struct layout *lyt@ = layout state
282 * @const char *p@ = string to write
283 * @size_t sz@ = length of string
285 * Returns: Zero on success, @-1@ on failure.
287 * Use: Write a string to the output.
290 static int layout_string(struct layout *lyt, const char *p, size_t sz)
292 const char *q, *r, *l = p + sz;
294 /* This is rather vexing. There are a small number of jobs to do, but the
295 * logic for deciding which to do when gets rather hairy if, as I've tried
296 * here, one aims to minimize the number of decisions being checked, so
297 * it's worth canning them into macros.
299 * Here, a `blank' is a whitespace character other than newline. The input
300 * buffer consists of one or more `segments', each of which consists of:
302 * * an initial portion, which is either empty or ends with a nonblank
305 * * a suffix which consists only of blanks; and
307 * * an optional newline.
309 * All segments except the last end with a newline.
312 #define SPLIT_SEGMENT do { \
313 /* Determine the bounds of the current segment. If there is a final \
314 * newline, then q is non-null and points to this newline; otherwise, \
315 * q is null. The initial portion of the segment lies between p .. r \
316 * and the blank suffix lies between r .. q (or r .. l if q is null). \
317 * This sounds awkward, but the suffix is only relevant if there is \
321 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
324 #define PUT_NONBLANK do { \
325 /* Output the initial portion of the segment. */ \
330 #define PUT_NEWLINE do { \
331 /* Write a newline, and advance to the next segment. */ \
333 PUT_CHAR('\n'); p = q + 1; \
336 #define SAVE_TAIL do { \
337 /* Save the trailing blank portion of the segment in the buffer. \
338 * Assumes that there is no newline, since otherwise the suffix would \
342 DPUTM(&lyt->w, r, l - r); \
345 /* Determine the bounds of the first segment. Handling this is the most
346 * complicated part of this function.
351 /* This is the only segment. We'll handle the whole thing here.
353 * If there's an initial nonblank portion, then we need to write that
354 * out. Furthermore, if we're at the start of the line then we'll need
355 * to write the prefix, and if there's saved blank material then we'll
356 * need to write that. Otherwise, there's only blank stuff, which we
357 * accumulate in the buffer.
359 * If we're at the start of a line here, then put the prefix followed by
360 * any saved whitespace, and then our initial nonblank portion. Then
361 * save our new trailing space.
365 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
366 PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
372 /* There is at least one more segment, so we know that there'll be a line
376 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
377 PUT_SAVED; PUT_NONBLANK;
378 } else if (lyt->f&LYTF_NEWL)
380 PUT_NEWLINE; DRESET(&lyt->w);
383 /* Main loop over whole segments with trailing newlines. For each one, we
384 * know that we're starting at the beginning of a line and there's a final
385 * newline, so we write the initial prefix and drop the trailing blanks.
388 if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
394 /* At the end, there's no final newline. If there's nonblank material,
395 * then we can write the prefix and the nonblank stuff. Otherwise, stash
396 * the blank stuff (including the trailing blanks of the prefix) and leave
397 * the newline flag set.
399 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
400 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
419 /*----- Skeleton ----------------------------------------------------------*/
421 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
422 static int ..._esession(struct tvec_output *o)
423 static void ..._bgroup(struct tvec_output *o)
424 static void ..._skipgroup(struct tvec_output *o,
425 const char *excuse, va_list *ap)
426 static void ..._egroup(struct tvec_output *o)
427 static void ..._btest(struct tvec_output *o)
428 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
429 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
430 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
431 union tvec_regval *rv, const struct tvec_regdef *rd)
432 static void ..._etest(struct tvec_output *o, unsigned outcome)
433 static void ..._bbench(struct tvec_output *o,
434 const char *ident, unsigned unit)
435 static void ..._ebench(struct tvec_output *o,
436 const char *ident, unsigned unit,
437 const struct tvec_timing *t)
438 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
439 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
440 static void ..._destroy(struct tvec_output *o)
442 static const struct tvec_outops ..._ops = {
443 ..._bsession, ..._esession,
444 ..._bgroup, ..._egroup, ..._skip,
445 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
446 ..._bbench, ..._ebench,
447 ..._error, ..._notice,
451 /*----- Human-readable output ---------------------------------------------*/
453 /* Attributes for colour output. This should be done better, but @terminfo@
456 * An attribute byte holds a foreground colour in the low nibble, a
457 * background colour in the next nibble, and some flags in the next few
458 * bits. A colour is expressed in classic 1-bit-per-channel style, with red,
459 * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
461 #define HAF_FGMASK 0x0f /* foreground colour mask */
462 #define HAF_FGSHIFT 0 /* foreground colour shift */
463 #define HAF_BGMASK 0xf0 /* background colour mask */
464 #define HAF_BGSHIFT 4 /* background colour shift */
465 #define HAF_FG 256u /* set foreground? */
466 #define HAF_BG 512u /* set background? */
467 #define HAF_BOLD 1024u /* set bold? */
468 #define HCOL_BLACK 0u /* colour codes... */
470 #define HCOL_GREEN 2u
471 #define HCOL_YELLOW 3u
473 #define HCOL_MAGENTA 5u
475 #define HCOL_WHITE 7u
476 #define HCF_BRIGHT 8u /* bright colour flag */
477 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
478 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
480 /* Predefined attributes. */
481 #define HA_PLAIN 0 /* nothing special: terminal defaults */
482 #define HA_LOC (HFG(CYAN)) /* filename or line number */
483 #define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */
484 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
485 #define HA_NOTE (HFG(YELLOW)) /* notices */
486 #define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
487 #define HA_UNSET (HFG(YELLOW)) /* register not set */
488 #define HA_FOUND (HFG(RED)) /* incorrect output value */
489 #define HA_EXPECT (HFG(GREEN)) /* what the value should have been */
490 #define HA_WIN (HFG(GREEN)) /* reporting success */
491 #define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */
492 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
493 #define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */
495 /* Scoreboard indicators. */
496 #define HSB_WIN '.' /* test passed */
497 #define HSB_LOSE 'x' /* test failed */
498 #define HSB_XFAIL 'o' /* test failed expectedly */
499 #define HSB_SKIP '_' /* test wasn't run */
501 struct human_output {
502 struct tvec_output _o; /* output base class */
503 struct tvec_state *tv; /* stashed testing state */
504 struct layout lyt; /* output layout */
505 char *outbuf; size_t outsz; /* buffer for formatted output */
506 dstr scoreboard; /* history of test group results */
507 unsigned attr; /* current terminal attributes */
508 int maxlen; /* longest register name */
509 unsigned f; /* flags */
510 #define HOF_TTY 1u /* writing to terminal */
511 #define HOF_DUPERR 2u /* duplicate errors to stderr */
512 #define HOF_COLOUR 4u /* print in angry fruit salad */
513 #define HOF_PROGRESS 8u /* progress display is active */
516 /* --- @set_colour@ --- *
518 * Arguments: @FILE *fp@ = output stream to write on
519 * @int *sep_inout@ = where to maintain separator
520 * @const char *norm@ = prefix for normal colour
521 * @const char *bright@ = prefix for bright colour
522 * @unsigned colour@ = four bit colour code
526 * Use: Write to the output stream @fp@, the current character at
527 * @*sep_inout@, if that's not zero, followed by either @norm@
528 * or @bright@, according to whether the @HCF_BRIGHT@ flag is
529 * set in @colour@, followed by the plain colour code from
530 * @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
532 * This is an internal subroutine for @setattr@ below.
535 static void set_colour(FILE *fp, int *sep_inout,
536 const char *norm, const char *bright,
539 if (*sep_inout) putc(*sep_inout, fp);
540 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
544 /* --- @setattr@ --- *
546 * Arguments: @struct human_output *h@ = output state
547 * @unsigned attr@ = attribute code to set
551 * Use: Send a control sequence to the output stream so that
552 * subsequent text is printed with the given attributes.
554 * Some effort is taken to avoid unnecessary control sequences.
555 * In particular, if @attr@ matches the current terminal
556 * settings already, then nothing is written.
559 static void setattr(struct human_output *h, unsigned attr)
561 unsigned diff = h->attr ^ attr;
564 /* If there's nothing to do, we might as well stop now. */
565 if (!diff || !(h->f&HOF_COLOUR)) return;
567 /* Start on the control command. */
568 fputs("\x1b[", h->lyt.fp);
570 /* Change the boldness if necessary. */
572 if (attr&HAF_BOLD) putc('1', h->lyt.fp);
573 else { putc('0', h->lyt.fp); diff = h->attr; }
577 /* Change the foreground colour if necessary. */
578 if (diff&(HAF_FG | HAF_FGMASK)) {
580 set_colour(h->lyt.fp, &sep, "3", "9",
581 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
583 if (sep) putc(sep, h->lyt.fp);
584 fputs("39", h->lyt.fp); sep = ';';
588 /* Change the background colour if necessary. */
589 if (diff&(HAF_BG | HAF_BGMASK)) {
591 set_colour(h->lyt.fp, &sep, "4", "10",
592 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
594 if (sep) putc(sep, h->lyt.fp);
595 fputs("49", h->lyt.fp); sep = ';';
599 /* Terminate the control command and save the new attributes. */
600 putc('m', h->lyt.fp); h->attr = attr;
603 /* --- @clear_progress@ --- *
605 * Arguments: @struct human_output *h@ = output state
609 * Use: Remove the progress display from the terminal.
611 * If the progress display isn't active then do nothing.
614 static void clear_progress(struct human_output *h)
618 if (h->f&HOF_PROGRESS) {
619 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
620 for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
621 h->f &= ~HOF_PROGRESS;
625 /* --- @write_scoreboard_char@ --- *
627 * Arguments: @struct human_output *h@ = output state
628 * @int ch@ = scoreboard character to print
632 * Use: Write a scoreboard character, indicating the outcome of a
633 * test, to the output stream, with appropriate highlighting.
636 static void write_scoreboard_char(struct human_output *h, int ch)
639 case HSB_LOSE: setattr(h, HA_LOSE); break;
640 case HSB_SKIP: setattr(h, HA_SKIP); break;
641 case HSB_XFAIL: setattr(h, HA_XFAIL); break;
642 default: setattr(h, HA_PLAIN); break;
644 putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
647 /* --- @show_progress@ --- *
649 * Arguments: @struct human_output *h@ = output state
653 * Use: Show the progress display, with the record of outcomes for
654 * the current test group.
656 * If the progress display is already active, or the output
657 * stream is not interactive, then nothing happens.
660 static void show_progress(struct human_output *h)
662 struct tvec_state *tv = h->tv;
665 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
666 fprintf(h->lyt.fp, "%s: ", tv->test->name);
667 if (!(h->f&HOF_COLOUR))
668 dstr_write(&h->scoreboard, h->lyt.fp);
669 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
670 write_scoreboard_char(h, *p);
671 fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
675 /* --- @human_writech@, @human_write@, @human_writef@ --- *
677 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
678 * @int ch@ = character to write
679 * @const char *@p@, @size_t sz@ = string (with explicit length)
681 * @const char *p, ...@ = format control string and arguments to
686 * Use: Write characters, strings, or formatted strings to the
687 * output, applying appropriate layout.
689 * For the human output driver, the layout machinery just strips
693 static int human_writech(void *go, int ch)
694 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
696 static int human_writem(void *go, const char *p, size_t sz)
697 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
699 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
701 struct human_output *h = go;
706 n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
708 return (layout_string(&h->lyt, h->outbuf, n));
711 static const struct gprintf_ops human_printops =
712 { human_writech, human_writem, human_nwritef };
714 /* --- @human_bsession@ --- *
716 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
718 * @struct tvec_state *tv@ = the test state producing output
722 * Use: Begin a test session.
724 * The human driver just records the test state for later
728 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
729 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
731 /* --- @report_unusual@ --- *
733 * Arguments: @struct human_output *h@ = output sink
734 * @unsigned nxfail, nskip@ = number of expected failures and
739 * Use: Write (directly on the output stream) a note about expected
740 * failures and/or skipped tests, if there were any.
743 static void report_unusual(struct human_output *h,
744 unsigned nxfail, unsigned nskip)
746 const char *sep = " (";
751 fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
752 setattr(h, HA_XFAIL);
753 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
754 setattr(h, HA_PLAIN);
755 sep = ", "; f |= f_any;
759 fprintf(h->lyt.fp, "%s%u ", sep, nskip);
760 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
761 sep = ", "; f |= f_any;
764 if (f&f_any) fputc(')', h->lyt.fp);
769 /* --- @human_esession@ --- *
771 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
774 * Returns: Suggested exit code.
776 * Use: End a test session.
778 * The human driver prints a final summary of the rest results
779 * and returns a suitable exit code.
782 static int human_esession(struct tvec_output *o)
784 struct human_output *h = (struct human_output *)o;
785 struct tvec_state *tv = h->tv;
787 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
788 all_xfail = tv->all[TVOUT_XFAIL],
789 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
790 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
791 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
792 grps_run = grps_win + grps_lose;
795 setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
796 fprintf(h->lyt.fp, " %s%u %s",
797 !(all_skip || grps_skip) ? "all " : "",
798 all_pass, all_pass == 1 ? "test" : "tests");
799 report_unusual(h, all_xfail, all_skip);
800 fprintf(h->lyt.fp, " in %u %s",
801 grps_win, grps_win == 1 ? "group" : "groups");
802 report_unusual(h, 0, grps_skip);
804 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
805 fprintf(h->lyt.fp, " %u out of %u %s",
806 all_lose, all_run, all_run == 1 ? "test" : "tests");
807 report_unusual(h, all_xfail, all_skip);
808 fprintf(h->lyt.fp, " in %u out of %u %s",
809 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
810 report_unusual(h, 0, grps_skip);
812 fputc('\n', h->lyt.fp);
814 if (tv->f&TVSF_ERROR) {
815 setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
816 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
819 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
822 /* --- @human_bgroup@ --- *
824 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
829 * Use: Begin a test group.
831 * The human driver determines the length of the longest
832 * register name, resets the group progress scoreboard, and
833 * activates the progress display.
836 static void human_bgroup(struct tvec_output *o)
838 struct human_output *h = (struct human_output *)o;
840 h->maxlen = register_maxnamelen(h->tv);
841 dstr_reset(&h->scoreboard); show_progress(h);
844 /* --- @human_skipgroup@ --- *
846 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
848 * @const char *excuse@, @va_list *ap@ = reason for skipping the
853 * Use: Report that a test group is being skipped.
855 * The human driver just reports the situation to its output
859 static void human_skipgroup(struct tvec_output *o,
860 const char *excuse, va_list *ap)
862 struct human_output *h = (struct human_output *)o;
865 fprintf(h->lyt.fp, "%s ", h->tv->test->name);
867 show_progress(h); h->f &= ~HOF_PROGRESS;
868 if (h->scoreboard.len) putc(' ', h->lyt.fp);
870 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
871 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
872 fputc('\n', h->lyt.fp);
875 /* --- @human_egroup@ --- *
877 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
882 * Use: Report that a test group has finished.
884 * The human driver reports a summary of the group's tests.
887 static void human_egroup(struct tvec_output *o)
889 struct human_output *h = (struct human_output *)o;
890 struct tvec_state *tv = h->tv;
891 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
892 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
893 run = win + lose + xfail;
895 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
896 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
899 fprintf(h->lyt.fp, " %u/%u ", lose, run);
900 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
901 report_unusual(h, xfail, skip);
903 fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
904 fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
905 report_unusual(h, xfail, skip);
907 fputc('\n', h->lyt.fp);
910 /* --- @human_btest@ --- *
912 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
917 * Use: Report that a test is starting.
919 * The human driver makes sure the progress display is active.
922 static void human_btest(struct tvec_output *o)
923 { struct human_output *h = (struct human_output *)o; show_progress(h); }
925 /* --- @report_location@ --- *
927 * Arguments: @struct human_output *h@ = output state
928 * @FILE *fp@ = stream to write the location on
929 * @const char *file@ = filename
930 * @unsigned lno@ = line number
934 * Use: Print the filename and line number to the output stream @fp@.
935 * Also, if appropriate, print interleaved highlighting control
936 * codes to our usual output stream. If @file@ is null then do
940 static void report_location(struct human_output *h, FILE *fp,
941 const char *file, unsigned lno)
946 /* We emit highlighting if @fp@ is our usual output stream, or the
947 * duplicate-errors flag is clear indicating that (we assume) they're
948 * secretly going to the same place anyway. If they're different streams,
949 * though, we have to be careful to keep the highlighting and the actual
955 else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
956 fprintf(fp, "%s:%u: ", file, lno);
958 if (fp != h->lyt.fp) f |= f_flush;
960 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
962 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
963 fputs(file, fp); FLUSH(fp);
964 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
965 fputc(':', fp); FLUSH(fp);
966 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
967 fprintf(fp, "%u", lno); FLUSH(fp);
968 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
969 fputc(':', fp); FLUSH(fp);
970 setattr(h, HA_PLAIN); FLUSH(h->lyt.fp);
979 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
981 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
983 * @unsigned attr@ = attribute to apply to the outcome
984 * @const char *outcome@ = outcome string to report
985 * @const char *detail@, @va_list *ap@ = a detail message
986 * @const char *excuse@, @va_list *ap@ = reason for skipping the
991 * Use: Report that a test has been skipped or failed.
993 * The human driver reports the situation on its output stream.
996 static void human_outcome(struct tvec_output *o,
997 unsigned attr, const char *outcome,
998 const char *detail, va_list *ap)
1000 struct human_output *h = (struct human_output *)o;
1001 struct tvec_state *tv = h->tv;
1004 report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
1005 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
1006 setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
1007 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
1008 fputc('\n', h->lyt.fp);
1011 static void human_skip(struct tvec_output *o,
1012 const char *excuse, va_list *ap)
1013 { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
1014 static void human_fail(struct tvec_output *o,
1015 const char *detail, va_list *ap)
1016 { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
1018 /* --- @human_dumpreg@ --- *
1020 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1022 * @unsigned disp@ = register disposition
1023 * @const union tvec_regval *rv@ = register value
1024 * @const struct tvec_regdef *rd@ = register definition
1028 * Use: Dump a register.
1030 * The human driver applies highlighting to mismatching output
1031 * registers, but otherwise delegates to the register type
1032 * handler and the layout machinery.
1035 static void human_dumpreg(struct tvec_output *o,
1036 unsigned disp, const union tvec_regval *rv,
1037 const struct tvec_regdef *rd)
1039 struct human_output *h = (struct human_output *)o;
1040 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1043 gprintf(&human_printops, h, "%*s%s %s = ",
1044 10 + h->maxlen - n, "", ds, rd->name);
1045 if (h->f&HOF_COLOUR) {
1046 if (!rv) setattr(h, HA_UNSET);
1047 else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
1048 else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
1050 if (!rv) gprintf(&human_printops, h, "#unset");
1051 else rd->ty->dump(rv, rd, 0, &human_printops, h);
1052 setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
1055 /* --- @human_etest@ --- *
1057 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1059 * @unsigned outcome@ = the test outcome
1063 * Use: Report that a test has finished.
1065 * The human driver reactivates the progress display, if
1066 * necessary, and adds a new character for the completed test.
1069 static void human_etest(struct tvec_output *o, unsigned outcome)
1071 struct human_output *h = (struct human_output *)o;
1077 case TVOUT_WIN: ch = HSB_WIN; break;
1078 case TVOUT_LOSE: ch = HSB_LOSE; break;
1079 case TVOUT_XFAIL: ch = HSB_XFAIL; break;
1080 case TVOUT_SKIP: ch = HSB_SKIP; break;
1083 dstr_putc(&h->scoreboard, ch);
1084 write_scoreboard_char(h, ch); fflush(h->lyt.fp);
1088 /* --- @human_bbench@ --- *
1090 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1092 * @const char *ident@ = identifying register values
1093 * @unsigned unit@ = measurement unit (@TVBU_...@)
1097 * Use: Report that a benchmark has started.
1099 * The human driver just prints the start of the benchmark
1103 static void human_bbench(struct tvec_output *o,
1104 const char *ident, unsigned unit)
1106 struct human_output *h = (struct human_output *)o;
1107 struct tvec_state *tv = h->tv;
1110 fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
1113 /* --- @human_ebench@ --- *
1115 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1117 * @const char *ident@ = identifying register values
1118 * @unsigned unit@ = measurement unit (@TVBU_...@)
1119 * @const struct bench_timing *tm@ = measurement
1123 * Use: Report a benchmark's results
1125 * The human driver just delegates to the default benchmark
1126 * reporting, via the layout machinery.
1129 static void human_ebench(struct tvec_output *o,
1130 const char *ident, unsigned unit,
1131 const struct bench_timing *tm)
1133 struct human_output *h = (struct human_output *)o;
1135 tvec_benchreport(&human_printops, h, unit, tm);
1136 fputc('\n', h->lyt.fp);
1139 /* --- @human_report@ --- *
1141 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1143 * @unsigned level@ = message level (@TVLEV_...@)
1144 * @const char *msg@, @va_list *ap@ = format string and
1149 * Use: Report a message to the user.
1151 * The human driver arranges to show the message on @stderr@ as
1152 * well as the usual output, with a certain amount of
1153 * intelligence in case they're both actually the same device.
1156 static void human_report(struct tvec_output *o, unsigned level,
1157 const char *msg, va_list *ap)
1159 struct human_output *h = (struct human_output *)o;
1160 struct tvec_state *tv = h->tv;
1161 const char *levstr; unsigned levattr;
1165 #define f_progress 2u
1167 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1170 #define CASE(tag, name, val) \
1171 case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1173 default: levstr = "??"; levattr = HA_UNKLEV; break;
1176 if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
1178 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1180 if (h->f^HOF_PROGRESS)
1181 { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
1182 fprintf(stderr, "%s: ", QUIS);
1183 report_location(h, stderr, tv->infile, tv->lno);
1184 setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
1185 fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
1189 if (h->f&HOF_DUPERR) {
1190 report_location(h, h->lyt.fp, tv->infile, tv->lno);
1191 fprintf(h->lyt.fp, "%s: ", levstr);
1192 fwrite(d.buf, 1, d.len, h->lyt.fp);
1194 if (f&f_progress) show_progress(h);
1200 /* --- @human_destroy@ --- *
1202 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1207 * Use: Release the resources held by the output driver.
1210 static void human_destroy(struct tvec_output *o)
1212 struct human_output *h = (struct human_output *)o;
1214 destroy_layout(&h->lyt,
1215 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1216 dstr_destroy(&h->scoreboard);
1217 xfree(h->outbuf); xfree(h);
1220 static const struct tvec_outops human_ops = {
1221 human_bsession, human_esession,
1222 human_bgroup, human_skipgroup, human_egroup,
1223 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1224 human_bbench, human_ebench,
1229 /* --- @tvec_humanoutput@ --- *
1231 * Arguments: @FILE *fp@ = output file to write on
1233 * Returns: An output formatter.
1235 * Use: Return an output formatter which writes on @fp@ with the
1236 * expectation that a human will be watching and interpreting
1237 * the output. If @fp@ denotes a terminal, the display shows a
1238 * `scoreboard' indicating the outcome of each test case
1239 * attempted, and may in addition use colour and other
1243 struct tvec_output *tvec_humanoutput(FILE *fp)
1245 struct human_output *h;
1248 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
1249 h->f = 0; h->attr = 0;
1251 init_layout(&h->lyt, fp, 0);
1252 h->outbuf = 0; h->outsz = 0;
1254 switch (getenv_boolean("TVEC_TTY", -1)) {
1255 case 1: h->f |= HOF_TTY; break;
1258 if (isatty(fileno(fp))) h->f |= HOF_TTY;
1261 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1262 case 1: h->f |= HOF_COLOUR; break;
1267 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1272 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1273 dstr_create(&h->scoreboard);
1277 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1280 struct tvec_output _o; /* output base class */
1281 struct tvec_state *tv; /* stashed testing state */
1282 struct layout lyt; /* output layout */
1283 char *outbuf; size_t outsz; /* buffer for formatted output */
1284 unsigned grpix, testix; /* group and test indices */
1285 unsigned previx; /* previously reported test index */
1286 int maxlen; /* longest register name */
1289 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1291 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1292 * @int ch@ = character to write
1293 * @const char *@p@, @size_t sz@ = string (with explicit length)
1295 * @const char *p, ...@ = format control string and arguments to
1300 * Use: Write characters, strings, or formatted strings to the
1301 * output, applying appropriate layout.
1303 * For the TAP output driver, the layout machinery prefixes each
1304 * line with ` ## ' and strips trailing spaces.
1307 static int tap_writech(void *go, int ch)
1308 { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
1310 static int tap_writem(void *go, const char *p, size_t sz)
1311 { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
1313 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1315 struct tap_output *t = go;
1320 n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
1322 return (layout_string(&t->lyt, t->outbuf, n));
1325 static const struct gprintf_ops tap_printops =
1326 { tap_writech, tap_writem, tap_nwritef };
1328 /* --- @tap_bsession@ --- *
1330 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1332 * @struct tvec_state *tv@ = the test state producing output
1336 * Use: Begin a test session.
1338 * The TAP driver records the test state for later reference,
1339 * initializes the group index counter, and prints the version
1343 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1345 struct tap_output *t = (struct tap_output *)o;
1347 t->tv = tv; t->grpix = 0;
1348 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1351 /* --- @tap_esession@ --- *
1353 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1356 * Returns: Suggested exit code.
1358 * Use: End a test session.
1360 * The TAP driver prints a final summary of the rest results
1361 * and returns a suitable exit code. If errors occurred, it
1362 * instead prints a `Bail out!' line forcing the reader to
1366 static int tap_esession(struct tvec_output *o)
1368 struct tap_output *t = (struct tap_output *)o;
1369 struct tvec_state *tv = t->tv;
1371 if (tv->f&TVSF_ERROR) {
1373 "Errors found in input; tests may not have run correctly\n",
1378 fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1379 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1382 /* --- @tap_bgroup@ --- *
1384 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1389 * Use: Begin a test group.
1391 * The TAP driver determines the length of the longest
1392 * register name, resets the group progress scoreboard, and
1393 * activates the progress display.
1396 static void tap_bgroup(struct tvec_output *o)
1398 struct tap_output *t = (struct tap_output *)o;
1399 struct tvec_state *tv = t->tv;
1401 t->grpix++; t->testix = t->previx = 0;
1402 t->maxlen = register_maxnamelen(t->tv);
1403 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1406 /* --- @tap_skipgroup@ --- *
1408 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1410 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1415 * Use: Report that a test group is being skipped.
1417 * The TAP driver just reports the situation to its output
1421 static void tap_skipgroup(struct tvec_output *o,
1422 const char *excuse, va_list *ap)
1424 struct tap_output *t = (struct tap_output *)o;
1426 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
1427 fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
1428 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1429 fputc('\n', t->lyt.fp);
1432 /* --- @tap_egroup@ --- *
1434 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1439 * Use: Report that a test group has finished.
1441 * The TAP driver reports a summary of the group's tests.
1444 static void tap_egroup(struct tvec_output *o)
1446 struct tap_output *t = (struct tap_output *)o;
1447 struct tvec_state *tv = t->tv;
1449 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
1450 fprintf(t->lyt.fp, "%s %u - %s\n",
1451 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
1452 t->grpix, tv->test->name);
1455 /* --- @tap_btest@ --- *
1457 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1462 * Use: Report that a test is starting.
1464 * The TAP driver advances its test counter. (We could do this
1465 * by adding up up the counters in @tv->curr@, and add on the
1466 * current test, but it's easier this way.)
1469 static void tap_btest(struct tvec_output *o)
1470 { struct tap_output *t = (struct tap_output *)o; t->testix++; }
1472 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
1474 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1476 * @unsigned attr@ = attribute to apply to the outcome
1477 * @const char *outcome@ = outcome string to report
1478 * @const char *detail@, @va_list *ap@ = a detail message
1479 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1484 * Use: Report that a test has been skipped or failed.
1486 * The TAP driver reports the situation on its output stream.
1487 * TAP only allows us to report a single status for each
1488 * subtest, so we notice when we've already reported a status
1489 * for the current test and convert the second report as a
1490 * comment. This should only happen in the case of multiple
1494 static void tap_outcome(struct tvec_output *o,
1495 const char *head, const char *tail,
1496 const char *detail, va_list *ap)
1498 struct tap_output *t = (struct tap_output *)o;
1499 struct tvec_state *tv = t->tv;
1501 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
1502 t->testix == t->previx ? "##" : head,
1503 t->testix, tv->infile, tv->test_lno, tail);
1505 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
1506 fputc('\n', t->lyt.fp);
1507 t->previx = t->testix;
1510 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
1511 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
1512 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
1513 { tap_outcome(o, "not ok", "", detail, ap); }
1515 /* --- @tap_dumpreg@ --- *
1517 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1519 * @unsigned disp@ = register disposition
1520 * @const union tvec_regval *rv@ = register value
1521 * @const struct tvec_regdef *rd@ = register definition
1525 * Use: Dump a register.
1527 * The TAP driver applies highlighting to mismatching output
1528 * registers, but otherwise delegates to the register type
1529 * handler and the layout machinery. The result is that the
1530 * register dump is marked as a comment and indented.
1533 static void tap_dumpreg(struct tvec_output *o,
1534 unsigned disp, const union tvec_regval *rv,
1535 const struct tvec_regdef *rd)
1537 struct tap_output *t = (struct tap_output *)o;
1538 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1540 set_layout_prefix(&t->lyt, " ## ");
1541 gprintf(&tap_printops, t, "%*s%s %s = ",
1542 10 + t->maxlen - n, "", ds, rd->name);
1543 if (!rv) gprintf(&tap_printops, t, "#<unset>");
1544 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
1545 layout_char(&t->lyt, '\n');
1548 /* --- @tap_etest@ --- *
1550 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1552 * @unsigned outcome@ = the test outcome
1556 * Use: Report that a test has finished.
1558 * The TAP driver reports the outcome of the test, if that's not
1562 static void tap_etest(struct tvec_output *o, unsigned outcome)
1566 tap_outcome(o, "ok", "", 0, 0);
1569 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
1574 /* --- @tap_bbench@ --- *
1576 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1578 * @const char *ident@ = identifying register values
1579 * @unsigned unit@ = measurement unit (@TVBU_...@)
1583 * Use: Report that a benchmark has started.
1585 * The TAP driver does nothing here. All of the reporting
1586 * happens in @tap_ebench@.
1589 static void tap_bbench(struct tvec_output *o,
1590 const char *ident, unsigned unit)
1593 /* --- @tap_ebench@ --- *
1595 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1597 * @const char *ident@ = identifying register values
1598 * @unsigned unit@ = measurement unit (@TVBU_...@)
1599 * @const struct bench_timing *tm@ = measurement
1603 * Use: Report a benchmark's results
1605 * The TAP driver just delegates to the default benchmark
1606 * reporting, via the layout machinery so that the result is
1607 * printed as a comment.
1610 static void tap_ebench(struct tvec_output *o,
1611 const char *ident, unsigned unit,
1612 const struct bench_timing *tm)
1614 struct tap_output *t = (struct tap_output *)o;
1615 struct tvec_state *tv = t->tv;
1617 set_layout_prefix(&t->lyt, " ## ");
1618 gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1619 tvec_benchreport(&tap_printops, t, unit, tm);
1620 layout_char(&t->lyt, '\n');
1623 /* --- @tap_report@ --- *
1625 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1627 * @unsigned level@ = message level (@TVLEV_...@)
1628 * @const char *msg@, @va_list *ap@ = format string and
1633 * Use: Report a message to the user.
1635 * Messages are reported as comments, so that they can be
1636 * accumulated by the reader. An error will cause a later
1637 * bailout or, if we crash before then, a missing plan line,
1638 * either of which will cause the reader to report a serious
1642 static void tap_report(struct tvec_output *o, unsigned level,
1643 const char *msg, va_list *ap)
1645 struct tap_output *t = (struct tap_output *)o;
1646 struct tvec_state *tv = t->tv;
1648 if (tv->test) set_layout_prefix(&t->lyt, " ## ");
1649 else set_layout_prefix(&t->lyt, "## ");
1651 if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
1652 gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
1653 vgprintf(&tap_printops, t, msg, ap);
1654 layout_char(&t->lyt, '\n');
1657 /* --- @tap_destroy@ --- *
1659 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1664 * Use: Release the resources held by the output driver.
1667 static void tap_destroy(struct tvec_output *o)
1669 struct tap_output *t = (struct tap_output *)o;
1671 destroy_layout(&t->lyt,
1672 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
1673 xfree(t->outbuf); xfree(t);
1676 static const struct tvec_outops tap_ops = {
1677 tap_bsession, tap_esession,
1678 tap_bgroup, tap_skipgroup, tap_egroup,
1679 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
1680 tap_bbench, tap_ebench,
1685 /* --- @tvec_tapoutput@ --- *
1687 * Arguments: @FILE *fp@ = output file to write on
1689 * Returns: An output formatter.
1691 * Use: Return an output formatter which writes on @fp@ in `TAP'
1692 * (`Test Anything Protocol') format.
1694 * TAP comes from the Perl community, but has spread rather
1695 * further. This driver produces TAP version 14, but pretends
1696 * to be version 13. The driver produces a TAP `test point' --
1697 * i.e., a result reported as `ok' or `not ok' -- for each input
1698 * test group. Failure reports and register dumps are produced
1699 * as diagnostic messages before the final group result. (TAP
1700 * permits structuerd YAML data after the test-point result,
1701 * which could be used to report details, but (a) postponing the
1702 * details until after the report is inconvenient, and (b) there
1703 * is no standardization for the YAML anyway, so in practice
1704 * it's no more useful than the unstructured diagnostics.
1707 struct tvec_output *tvec_tapoutput(FILE *fp)
1709 struct tap_output *t;
1711 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
1712 init_layout(&t->lyt, fp, 0);
1713 t->outbuf = 0; t->outsz = 0;
1717 /*----- Default output ----------------------------------------------------*/
1719 /* --- @tvec_dfltoutput@ --- *
1721 * Arguments: @FILE *fp@ = output file to write on
1723 * Returns: An output formatter.
1725 * Use: Selects and instantiates an output formatter suitable for
1726 * writing on @fp@. The policy is subject to change, but
1727 * currently the `human' output format is selected if @fp@ is
1728 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
1729 * otherwise the `tap' format is used.
1732 struct tvec_output *tvec_dfltout(FILE *fp)
1734 int ttyp = getenv_boolean("TVEC_TTY", -1);
1736 if (ttyp == -1) ttyp = isatty(fileno(fp));
1737 if (ttyp) return (tvec_humanoutput(fp));
1738 else return (tvec_tapoutput(fp));
1741 /*----- That's all, folks -------------------------------------------------*/