3 * Test vector output management
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
48 #include "tvec-bench.h"
49 #include "tvec-output.h"
51 /*----- Common machinery --------------------------------------------------*/
53 /* --- @regdisp@ --- *
55 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
57 * Returns: A human-readable adjective describing the register
61 static const char *regdisp(unsigned disp)
64 case TVRD_INPUT: return "input";
65 case TVRD_OUTPUT: return "output";
66 case TVRD_MATCH: return "matched";
67 case TVRD_FOUND: return "found";
68 case TVRD_EXPECT: return "expected";
73 /* --- @getenv_boolean@ --- *
75 * Arguments: @const char *var@ = environment variable name
76 * @int dflt@ = default value
78 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
79 * set to something truish, or @dflt@ otherwise.
82 static int getenv_boolean(const char *var, int dflt)
89 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
90 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
91 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
94 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
95 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
96 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
100 moan("ignoring unexpected value `%s' for environment variable `%s'",
106 /* --- @register_maxnamelen@ --- *
108 * Arguments: @const struct tvec_state *tv@ = test vector state
110 * Returns: The maximum length of a register name in the current test.
113 static int register_maxnamelen(const struct tvec_state *tv)
115 const struct tvec_regdef *rd;
118 for (rd = tv->test->regs; rd->name; rd++)
119 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
123 /* --- @print_ident@ --- *
125 * Arguments: @struct tvec_state *tv@ = test-vector state
126 * @unsigned style@ = style to use for register dumps
127 * @const struct gprintf_ops *gops@ = output operations
128 * @void *go@ = output state
132 * Use: Write a benchmark identification to the output.
135 static void print_ident(struct tvec_state *tv, unsigned style,
136 const struct gprintf_ops *gops, void *go)
138 const struct tvec_regdef *rd;
143 for (rd = tv->test->regs; rd->name; rd++)
145 if (!(f&f_any)) f |= f_any;
146 else if (style&TVSF_RAW) gops->putch(go, ' ');
147 else gprintf(gops, go, ", ");
148 gprintf(gops, go, "%s", rd->name);
149 if (style&TVSF_RAW) gops->putch(go, '=');
150 else gprintf(gops, go, " = ");
151 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, style, gops, go);
157 /*----- Output layout -----------------------------------------------------*/
159 /* We have two main jobs in output layout: trimming trailing blanks; and
160 * adding a prefix to each line.
162 * This is somehow much more complicated than it ought to be.
166 FILE *fp; /* output file */
167 const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
168 dstr w; /* trailing whitespace */
169 unsigned f; /* flags */
170 #define LYTF_NEWL 1u /* start of output line */
173 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
177 #define SPLIT_RANGE(tail, base, limit) do { \
178 /* Set TAIL to point just after the last nonspace character between \
179 * BASE and LIMIT. If there are no nonspace characters, then set \
180 * TAIL to equal BASE. \
183 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
186 #define PUT_RANGE(base, limit) do { \
187 /* Write the range of characters between BASE and LIMIT to the output \
188 * file. Return immediately on error. \
191 size_t _n = limit - base; \
192 if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
195 #define PUT_CHAR(ch) do { \
196 /* Write CH to the output. Return immediately on error. */ \
198 if (putc(ch, lyt->fp) == EOF) return (-1); \
201 #define PUT_PREFIX do { \
202 /* Output the prefix, if there is one. Return immediately on error. */ \
204 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
207 #define PUT_SAVED do { \
208 /* Output the saved trailing blank material in the buffer. */ \
210 size_t _n = lyt->w.len; \
211 if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
214 #define PUT_PFXINB do { \
215 /* Output the initial nonblank portion of the prefix, if there is \
216 * one. Return immediately on error. \
219 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
222 #define SAVE_PFXTAIL do { \
223 /* Save the trailing blank portion of the prefix. */ \
226 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
229 /* --- @set_layout_prefix@ --- *
231 * Arguments: @struct layout *lyt@ = layout state
232 * @const char *prefix@ = new prefix string or null
236 * Use: Change the configured prefix string. The change takes effect
237 * at the start of the next line (or the current line if it's
238 * empty or only whitespace so far).
241 static void set_layout_prefix(struct layout *lyt, const char *prefix)
245 if (!prefix || !*prefix)
246 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
248 lyt->prefix = prefix;
249 l = lyt->pfxlim = prefix + strlen(prefix);
250 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
254 /* --- @init_layout@ --- *
256 * Arguments: @struct layout *lyt@ = layout state to initialize
257 * @FILE *fp@ = output file
258 * @const char *prefix@ = prefix string (or null if empty)
262 * Use: Initialize a layout state.
265 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
269 dstr_create(&lyt->w);
270 set_layout_prefix(lyt, prefix);
273 /* --- @destroy_layout@ --- *
275 * Arguments: @struct layout *lyt@ = layout state
276 * @unsigned f@ = flags (@DLF_...@)
280 * Use: Releases a layout state and the resources it holds.
281 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
282 * it open (in case it's @stderr@ or something).
286 static void destroy_layout(struct layout *lyt, unsigned f)
288 if (f&DLF_CLOSE) fclose(lyt->fp);
289 dstr_destroy(&lyt->w);
292 /* --- @layout_char@ --- *
294 * Arguments: @struct layout *lyt@ = layout state
295 * @int ch@ = character to write
297 * Returns: Zero on success, @-1@ on failure.
299 * Use: Write a single character to the output.
302 static int layout_char(struct layout *lyt, int ch)
305 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
306 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
307 } else if (isspace(ch))
310 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
311 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
316 /* --- @layout_string@ --- *
318 * Arguments: @struct layout *lyt@ = layout state
319 * @const char *p@ = string to write
320 * @size_t sz@ = length of string
322 * Returns: Zero on success, @-1@ on failure.
324 * Use: Write a string to the output.
327 static int layout_string(struct layout *lyt, const char *p, size_t sz)
329 const char *q, *r, *l = p + sz;
331 /* This is rather vexing. There are a small number of jobs to do, but the
332 * logic for deciding which to do when gets rather hairy if, as I've tried
333 * here, one aims to minimize the number of decisions being checked, so
334 * it's worth canning them into macros.
336 * Here, a `blank' is a whitespace character other than newline. The input
337 * buffer consists of one or more `segments', each of which consists of:
339 * * an initial portion, which is either empty or ends with a nonblank
342 * * a suffix which consists only of blanks; and
344 * * an optional newline.
346 * All segments except the last end with a newline.
349 #define SPLIT_SEGMENT do { \
350 /* Determine the bounds of the current segment. If there is a final \
351 * newline, then q is non-null and points to this newline; otherwise, \
352 * q is null. The initial portion of the segment lies between p .. r \
353 * and the blank suffix lies between r .. q (or r .. l if q is null). \
354 * This sounds awkward, but the suffix is only relevant if there is \
358 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
361 #define PUT_NONBLANK do { \
362 /* Output the initial portion of the segment. */ \
367 #define PUT_NEWLINE do { \
368 /* Write a newline, and advance to the next segment. */ \
370 PUT_CHAR('\n'); p = q + 1; \
373 #define SAVE_TAIL do { \
374 /* Save the trailing blank portion of the segment in the buffer. \
375 * Assumes that there is no newline, since otherwise the suffix would \
379 DPUTM(&lyt->w, r, l - r); \
382 /* Determine the bounds of the first segment. Handling this is the most
383 * complicated part of this function.
388 /* This is the only segment. We'll handle the whole thing here.
390 * If there's an initial nonblank portion, then we need to write that
391 * out. Furthermore, if we're at the start of the line then we'll need
392 * to write the prefix, and if there's saved blank material then we'll
393 * need to write that. Otherwise, there's only blank stuff, which we
394 * accumulate in the buffer.
396 * If we're at the start of a line here, then put the prefix followed by
397 * any saved whitespace, and then our initial nonblank portion. Then
398 * save our new trailing space.
402 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
403 PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
409 /* There is at least one more segment, so we know that there'll be a line
413 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
414 PUT_SAVED; PUT_NONBLANK;
415 } else if (lyt->f&LYTF_NEWL)
417 PUT_NEWLINE; DRESET(&lyt->w);
420 /* Main loop over whole segments with trailing newlines. For each one, we
421 * know that we're starting at the beginning of a line and there's a final
422 * newline, so we write the initial prefix and drop the trailing blanks.
425 if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
431 /* At the end, there's no final newline. If there's nonblank material,
432 * then we can write the prefix and the nonblank stuff. Otherwise, stash
433 * the blank stuff (including the trailing blanks of the prefix) and leave
434 * the newline flag set.
436 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
437 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
456 /*----- Human-readable output ---------------------------------------------*/
458 /* Attributes for colour output. This should be done better, but @terminfo@
461 * An attribute byte holds a foreground colour in the low nibble, a
462 * background colour in the next nibble, and some flags in the next few
463 * bits. A colour is expressed in classic 1-bit-per-channel style, with red,
464 * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
466 #define HAF_FGMASK 0x0f /* foreground colour mask */
467 #define HAF_FGSHIFT 0 /* foreground colour shift */
468 #define HAF_BGMASK 0xf0 /* background colour mask */
469 #define HAF_BGSHIFT 4 /* background colour shift */
470 #define HAF_FG 256u /* set foreground? */
471 #define HAF_BG 512u /* set background? */
472 #define HAF_BOLD 1024u /* set bold? */
473 #define HCOL_BLACK 0u /* colour codes... */
475 #define HCOL_GREEN 2u
476 #define HCOL_YELLOW 3u
478 #define HCOL_MAGENTA 5u
480 #define HCOL_WHITE 7u
481 #define HCF_BRIGHT 8u /* bright colour flag */
482 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
483 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
485 /* Predefined attributes. */
486 #define HA_PLAIN 0 /* nothing special: terminal defaults */
487 #define HA_LOC (HFG(CYAN)) /* filename or line number */
488 #define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */
489 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
490 #define HA_NOTE (HFG(YELLOW)) /* notices */
491 #define HA_INFO 0 /* information */
492 #define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
493 #define HA_UNSET (HFG(YELLOW)) /* register not set */
494 #define HA_FOUND (HFG(RED)) /* incorrect output value */
495 #define HA_EXPECT (HFG(GREEN)) /* what the value should have been */
496 #define HA_WIN (HFG(GREEN)) /* reporting success */
497 #define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */
498 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
499 #define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */
501 /* Scoreboard indicators. */
502 #define HSB_WIN '.' /* test passed */
503 #define HSB_LOSE 'x' /* test failed */
504 #define HSB_XFAIL 'o' /* test failed expectedly */
505 #define HSB_SKIP '_' /* test wasn't run */
507 struct human_output {
508 struct tvec_output _o; /* output base class */
509 struct tvec_state *tv; /* stashed testing state */
510 arena *a; /* arena for memory allocation */
511 struct layout lyt; /* output layout */
512 char *outbuf; size_t outsz; /* buffer for formatted output */
513 dstr scoreboard; /* history of test group results */
514 unsigned attr; /* current terminal attributes */
515 int maxlen; /* longest register name */
516 unsigned f; /* flags */
517 #define HOF_TTY 1u /* writing to terminal */
518 #define HOF_DUPERR 2u /* duplicate errors to stderr */
519 #define HOF_COLOUR 4u /* print in angry fruit salad */
520 #define HOF_PROGRESS 8u /* progress display is active */
523 /* --- @set_colour@ --- *
525 * Arguments: @FILE *fp@ = output stream to write on
526 * @int *sep_inout@ = where to maintain separator
527 * @const char *norm@ = prefix for normal colour
528 * @const char *bright@ = prefix for bright colour
529 * @unsigned colour@ = four bit colour code
533 * Use: Write to the output stream @fp@, the current character at
534 * @*sep_inout@, if that's not zero, followed by either @norm@
535 * or @bright@, according to whether the @HCF_BRIGHT@ flag is
536 * set in @colour@, followed by the plain colour code from
537 * @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
539 * This is an internal subroutine for @setattr@ below.
542 static void set_colour(FILE *fp, int *sep_inout,
543 const char *norm, const char *bright,
546 if (*sep_inout) putc(*sep_inout, fp);
547 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
551 /* --- @setattr@ --- *
553 * Arguments: @struct human_output *h@ = output state
554 * @unsigned attr@ = attribute code to set
558 * Use: Send a control sequence to the output stream so that
559 * subsequent text is printed with the given attributes.
561 * Some effort is taken to avoid unnecessary control sequences.
562 * In particular, if @attr@ matches the current terminal
563 * settings already, then nothing is written.
566 static void setattr(struct human_output *h, unsigned attr)
568 unsigned diff = h->attr ^ attr;
571 /* If there's nothing to do, we might as well stop now. */
572 if (!diff || !(h->f&HOF_COLOUR)) return;
574 /* Start on the control command. */
575 fputs("\x1b[", h->lyt.fp);
577 /* Change the boldness if necessary. */
579 if (attr&HAF_BOLD) putc('1', h->lyt.fp);
580 else { putc('0', h->lyt.fp); diff = h->attr; }
584 /* Change the foreground colour if necessary. */
585 if (diff&(HAF_FG | HAF_FGMASK)) {
587 set_colour(h->lyt.fp, &sep, "3", "9",
588 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
590 if (sep) putc(sep, h->lyt.fp);
591 fputs("39", h->lyt.fp); sep = ';';
595 /* Change the background colour if necessary. */
596 if (diff&(HAF_BG | HAF_BGMASK)) {
598 set_colour(h->lyt.fp, &sep, "4", "10",
599 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
601 if (sep) putc(sep, h->lyt.fp);
602 fputs("49", h->lyt.fp); sep = ';';
606 /* Terminate the control command and save the new attributes. */
607 putc('m', h->lyt.fp); h->attr = attr;
610 /* --- @clear_progress@ --- *
612 * Arguments: @struct human_output *h@ = output state
616 * Use: Remove the progress display from the terminal.
618 * If the progress display isn't active then do nothing.
621 static void clear_progress(struct human_output *h)
625 if (h->f&HOF_PROGRESS) {
626 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
627 for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
628 h->f &= ~HOF_PROGRESS;
632 /* --- @write_scoreboard_char@ --- *
634 * Arguments: @struct human_output *h@ = output state
635 * @int ch@ = scoreboard character to print
639 * Use: Write a scoreboard character, indicating the outcome of a
640 * test, to the output stream, with appropriate highlighting.
643 static void write_scoreboard_char(struct human_output *h, int ch)
646 case HSB_LOSE: setattr(h, HA_LOSE); break;
647 case HSB_SKIP: setattr(h, HA_SKIP); break;
648 case HSB_XFAIL: setattr(h, HA_XFAIL); break;
649 default: setattr(h, HA_PLAIN); break;
651 putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
654 /* --- @show_progress@ --- *
656 * Arguments: @struct human_output *h@ = output state
660 * Use: Show the progress display, with the record of outcomes for
661 * the current test group.
663 * If the progress display is already active, or the output
664 * stream is not interactive, then nothing happens.
667 static void show_progress(struct human_output *h)
669 struct tvec_state *tv = h->tv;
672 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
673 fprintf(h->lyt.fp, "%s: ", tv->test->name);
674 if (!(h->f&HOF_COLOUR))
675 dstr_write(&h->scoreboard, h->lyt.fp);
676 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
677 write_scoreboard_char(h, *p);
678 fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
682 /* --- @human_writech@, @human_write@, @human_writef@ --- *
684 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
685 * @int ch@ = character to write
686 * @const char *@p@, @size_t sz@ = string (with explicit length)
688 * @const char *p, ...@ = format control string and arguments to
693 * Use: Write characters, strings, or formatted strings to the
694 * output, applying appropriate layout.
696 * For the human output driver, the layout machinery just strips
700 static int human_writech(void *go, int ch)
701 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
703 static int human_writem(void *go, const char *p, size_t sz)
704 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
706 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
708 struct human_output *h = go;
713 n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
715 if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
719 static const struct gprintf_ops human_printops =
720 { human_writech, human_writem, human_nwritef };
722 /* --- @human_bsession@ --- *
724 * Arguments: @struct tvec_output *o@ = output sink, secretly a
725 * @struct human_output@
726 * @struct tvec_state *tv@ = the test state producing output
730 * Use: Begin a test session.
732 * The human driver just records the test state for later
736 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
737 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
739 /* --- @report_unusual@ --- *
741 * Arguments: @struct human_output *h@ = output sink
742 * @unsigned nxfail, nskip@ = number of expected failures and
747 * Use: Write (directly on the output stream) a note about expected
748 * failures and/or skipped tests, if there were any.
751 static void report_unusual(struct human_output *h,
752 unsigned nxfail, unsigned nskip)
754 const char *sep = " (";
759 fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
760 setattr(h, HA_XFAIL);
761 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
762 setattr(h, HA_PLAIN);
763 sep = ", "; f |= f_any;
767 fprintf(h->lyt.fp, "%s%u ", sep, nskip);
768 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
769 sep = ", "; f |= f_any;
772 if (f&f_any) fputc(')', h->lyt.fp);
777 /* --- @human_esession@ --- *
779 * Arguments: @struct tvec_output *o@ = output sink, secretly a
780 * @struct human_output@
782 * Returns: Suggested exit code.
784 * Use: End a test session.
786 * The human driver prints a final summary of the rest results
787 * and returns a suitable exit code.
790 static int human_esession(struct tvec_output *o)
792 struct human_output *h = (struct human_output *)o;
793 struct tvec_state *tv = h->tv;
795 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
796 all_xfail = tv->all[TVOUT_XFAIL],
797 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
798 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
799 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
800 grps_run = grps_win + grps_lose;
803 setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
804 fprintf(h->lyt.fp, " %s%u %s",
805 !(all_skip || grps_skip) ? "all " : "",
806 all_pass, all_pass == 1 ? "test" : "tests");
807 report_unusual(h, all_xfail, all_skip);
808 fprintf(h->lyt.fp, " in %u %s",
809 grps_win, grps_win == 1 ? "group" : "groups");
810 report_unusual(h, 0, grps_skip);
812 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
813 fprintf(h->lyt.fp, " %u out of %u %s",
814 all_lose, all_run, all_run == 1 ? "test" : "tests");
815 report_unusual(h, all_xfail, all_skip);
816 fprintf(h->lyt.fp, " in %u out of %u %s",
817 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
818 report_unusual(h, 0, grps_skip);
820 fputc('\n', h->lyt.fp);
822 if (tv->f&TVSF_ERROR) {
823 setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
824 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
827 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
830 /* --- @human_bgroup@ --- *
832 * Arguments: @struct tvec_output *o@ = output sink, secretly a
833 * @struct human_output@
837 * Use: Begin a test group.
839 * The human driver determines the length of the longest
840 * register name, resets the group progress scoreboard, and
841 * activates the progress display.
844 static void human_bgroup(struct tvec_output *o)
846 struct human_output *h = (struct human_output *)o;
848 h->maxlen = register_maxnamelen(h->tv);
849 dstr_reset(&h->scoreboard); show_progress(h);
852 /* --- @human_skipgroup@ --- *
854 * Arguments: @struct tvec_output *o@ = output sink, secretly a
855 * @struct human_output@
856 * @const char *excuse@, @va_list *ap@ = reason for skipping the
861 * Use: Report that a test group is being skipped.
863 * The human driver just reports the situation to its output
867 static void human_skipgroup(struct tvec_output *o,
868 const char *excuse, va_list *ap)
870 struct human_output *h = (struct human_output *)o;
873 fprintf(h->lyt.fp, "%s ", h->tv->test->name);
875 show_progress(h); h->f &= ~HOF_PROGRESS;
876 if (h->scoreboard.len) putc(' ', h->lyt.fp);
878 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
879 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
880 fputc('\n', h->lyt.fp);
883 /* --- @human_egroup@ --- *
885 * Arguments: @struct tvec_output *o@ = output sink, secretly a
886 * @struct human_output@
890 * Use: Report that a test group has finished.
892 * The human driver reports a summary of the group's tests.
895 static void human_egroup(struct tvec_output *o)
897 struct human_output *h = (struct human_output *)o;
898 struct tvec_state *tv = h->tv;
899 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
900 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
901 run = win + lose + xfail;
903 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
904 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
907 fprintf(h->lyt.fp, " %u/%u ", lose, run);
908 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
909 report_unusual(h, xfail, skip);
911 fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
912 fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
913 report_unusual(h, xfail, skip);
915 fputc('\n', h->lyt.fp);
918 /* --- @human_btest@ --- *
920 * Arguments: @struct tvec_output *o@ = output sink, secretly a
921 * @struct human_output@
925 * Use: Report that a test is starting.
927 * The human driver makes sure the progress display is active.
930 static void human_btest(struct tvec_output *o)
931 { struct human_output *h = (struct human_output *)o; show_progress(h); }
933 /* --- @human_report_location@ --- *
935 * Arguments: @struct human_output *h@ = output state
936 * @FILE *fp@ = stream to write the location on
937 * @const char *file@ = filename
938 * @unsigned lno@ = line number
942 * Use: Print the filename and line number to the output stream @fp@.
943 * Also, if appropriate, print interleaved highlighting control
944 * codes to our usual output stream. If @file@ is null then do
948 static void human_report_location(struct human_output *h, FILE *fp,
949 const char *file, unsigned lno)
954 /* We emit highlighting if @fp@ is our usual output stream, or the
955 * duplicate-errors flag is clear indicating that (we assume) they're
956 * secretly going to the same place anyway. If they're different streams,
957 * though, we have to be careful to keep the highlighting and the actual
963 else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
964 fprintf(fp, "%s:%u: ", file, lno);
966 if (fp != h->lyt.fp) f |= f_flush;
968 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
970 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
971 fputs(file, fp); FLUSH(fp);
972 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
973 fputc(':', fp); FLUSH(fp);
974 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
975 fprintf(fp, "%u", lno); FLUSH(fp);
976 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
977 fputc(':', fp); FLUSH(fp);
978 setattr(h, HA_PLAIN); FLUSH(h->lyt.fp);
987 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
989 * Arguments: @struct tvec_output *o@ = output sink, secretly a
990 * @struct human_output@
991 * @unsigned attr@ = attribute to apply to the outcome
992 * @const char *outcome@ = outcome string to report
993 * @const char *detail@, @va_list *ap@ = a detail message
994 * @const char *excuse@, @va_list *ap@ = reason for skipping the
999 * Use: Report that a test has been skipped or failed.
1001 * The human driver reports the situation on its output stream.
1004 static void human_outcome(struct tvec_output *o,
1005 unsigned attr, const char *outcome,
1006 const char *detail, va_list *ap)
1008 struct human_output *h = (struct human_output *)o;
1009 struct tvec_state *tv = h->tv;
1012 human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
1013 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
1014 setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
1015 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
1016 fputc('\n', h->lyt.fp);
1019 static void human_skip(struct tvec_output *o,
1020 const char *excuse, va_list *ap)
1021 { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
1022 static void human_fail(struct tvec_output *o,
1023 const char *detail, va_list *ap)
1024 { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
1026 /* --- @human_dumpreg@ --- *
1028 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1029 * @struct human_output@
1030 * @unsigned disp@ = register disposition
1031 * @const union tvec_regval *rv@ = register value
1032 * @const struct tvec_regdef *rd@ = register definition
1036 * Use: Dump a register.
1038 * The human driver applies highlighting to mismatching output
1039 * registers, but otherwise delegates to the register type
1040 * handler and the layout machinery.
1043 static void human_dumpreg(struct tvec_output *o,
1044 unsigned disp, const union tvec_regval *rv,
1045 const struct tvec_regdef *rd)
1047 struct human_output *h = (struct human_output *)o;
1048 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1051 gprintf(&human_printops, h, "%*s%s %s = ",
1052 10 + h->maxlen - n, "", ds, rd->name);
1053 if (h->f&HOF_COLOUR) {
1054 if (!rv) setattr(h, HA_UNSET);
1055 else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
1056 else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
1058 if (!rv) gprintf(&human_printops, h, "#unset");
1059 else rd->ty->dump(rv, rd, 0, &human_printops, h);
1060 setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
1063 /* --- @human_etest@ --- *
1065 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1066 * @struct human_output@
1067 * @unsigned outcome@ = the test outcome
1071 * Use: Report that a test has finished.
1073 * The human driver reactivates the progress display, if
1074 * necessary, and adds a new character for the completed test.
1077 static void human_etest(struct tvec_output *o, unsigned outcome)
1079 struct human_output *h = (struct human_output *)o;
1085 case TVOUT_WIN: ch = HSB_WIN; break;
1086 case TVOUT_LOSE: ch = HSB_LOSE; break;
1087 case TVOUT_XFAIL: ch = HSB_XFAIL; break;
1088 case TVOUT_SKIP: ch = HSB_SKIP; break;
1091 dstr_putc(&h->scoreboard, ch);
1092 write_scoreboard_char(h, ch); fflush(h->lyt.fp);
1096 /* --- @human_report@ --- *
1098 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1099 * @struct human_output@
1100 * @unsigned level@ = message level (@TVLEV_...@)
1101 * @const char *msg@, @va_list *ap@ = format string and
1106 * Use: Report a message to the user.
1108 * The human driver arranges to show the message on @stderr@ as
1109 * well as the usual output, with a certain amount of
1110 * intelligence in case they're both actually the same device.
1113 static void human_report(struct tvec_output *o, unsigned level,
1114 const char *msg, va_list *ap)
1116 struct human_output *h = (struct human_output *)o;
1117 struct tvec_state *tv = h->tv;
1118 const char *levstr; unsigned levattr;
1122 #define f_progress 2u
1124 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1127 #define CASE(tag, name, val) \
1128 case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1130 default: levstr = "??"; levattr = HA_UNKLEV; break;
1133 if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
1135 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1137 if (h->f^HOF_PROGRESS)
1138 { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
1139 fprintf(stderr, "%s: ", QUIS);
1140 human_report_location(h, stderr, tv->infile, tv->lno);
1141 setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
1142 fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
1146 if (h->f&HOF_DUPERR) {
1147 human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
1148 fprintf(h->lyt.fp, "%s: ", levstr);
1149 fwrite(d.buf, 1, d.len, h->lyt.fp);
1151 if (f&f_progress) show_progress(h);
1159 /* --- @human_bbench@ --- *
1161 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1162 * @struct human_output@
1163 * @const char *desc@ = adhoc test description
1164 * @unsigned unit@ = measurement unit (@BTU_...@)
1168 * Use: Report that a benchmark has started.
1170 * The human driver just prints the start of the benchmark
1174 static void human_bbench(struct tvec_output *o,
1175 const char *desc, unsigned unit)
1177 struct human_output *h = (struct human_output *)o;
1178 struct tvec_state *tv = h->tv;
1181 gprintf(&human_printops, h, "%s ", tv->test->name);
1182 if (desc) gprintf(&human_printops, h, "%s", desc);
1183 else print_ident(tv, TVSF_COMPACT, &human_printops, h);
1184 gprintf(&human_printops, h, ": ");
1185 if (h->f&HOF_TTY) fflush(h->lyt.fp);
1188 /* --- @human_ebench@ --- *
1190 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1191 * @struct human_output@
1192 * @const char *desc@ = adhoc test description
1193 * @unsigned unit@ = measurement unit (@BTU_...@)
1194 * @const struct bench_timing *t@ = measurement
1198 * Use: Report a benchmark's results.
1200 * The human driver just delegates to the default benchmark
1201 * reporting, via the layout machinery.
1204 static void human_ebench(struct tvec_output *o,
1205 const char *desc, unsigned unit,
1206 const struct bench_timing *t)
1208 struct human_output *h = (struct human_output *)o;
1210 tvec_benchreport(&human_printops, h, unit, 0, t);
1211 layout_char(&h->lyt, '\n');
1214 static const struct tvec_benchoutops human_benchops =
1215 { human_bbench, human_ebench };
1217 /* --- @human_extend@ --- *
1219 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1220 * @struct human_output@
1221 * @const char *name@ = extension name
1223 * Returns: A pointer to the extension implementation, or null.
1226 static const void *human_extend(struct tvec_output *o, const char *name)
1228 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
1232 /* --- @human_destroy@ --- *
1234 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1235 * @struct human_output@
1239 * Use: Release the resources held by the output driver.
1242 static void human_destroy(struct tvec_output *o)
1244 struct human_output *h = (struct human_output *)o;
1246 destroy_layout(&h->lyt,
1247 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1248 dstr_destroy(&h->scoreboard);
1249 x_free(h->a, h->outbuf); x_free(h->a, h);
1252 static const struct tvec_outops human_ops = {
1253 human_bsession, human_esession,
1254 human_bgroup, human_skipgroup, human_egroup,
1255 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1256 human_report, human_extend, human_destroy
1259 /* --- @tvec_humanoutput@ --- *
1261 * Arguments: @FILE *fp@ = output file to write on
1263 * Returns: An output formatter.
1265 * Use: Return an output formatter which writes on @fp@ with the
1266 * expectation that a human will be watching and interpreting
1267 * the output. If @fp@ denotes a terminal, the display shows a
1268 * `scoreboard' indicating the outcome of each test case
1269 * attempted, and may in addition use colour and other
1273 struct tvec_output *tvec_humanoutput(FILE *fp)
1275 struct human_output *h;
1278 XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
1279 h->f = 0; h->attr = 0;
1281 init_layout(&h->lyt, fp, 0);
1282 h->outbuf = 0; h->outsz = 0;
1284 switch (getenv_boolean("TVEC_TTY", -1)) {
1285 case 1: h->f |= HOF_TTY; break;
1288 if (isatty(fileno(fp))) h->f |= HOF_TTY;
1291 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1292 case 1: h->f |= HOF_COLOUR; break;
1297 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1302 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1303 dstr_create(&h->scoreboard);
1307 /*----- Machine-readable output -------------------------------------------*/
1309 struct machine_output {
1310 struct tvec_output _o; /* output base class */
1311 struct tvec_state *tv; /* stashed testing state */
1312 arena *a; /* arena for memory allocation */
1313 FILE *fp; /* output stream */
1314 char *outbuf; size_t outsz; /* buffer for formatted output */
1315 unsigned grpix, testix; /* group and test indices */
1316 unsigned f; /* flags */
1320 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1322 * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
1323 * @int ch@ = character to write
1324 * @const char *@p@, @size_t sz@ = string (with explicit length)
1326 * @const char *p, ...@ = format control string and arguments to
1331 * Use: Write characters, strings, or formatted strings to the
1332 * output, applying appropriate quoting.
1335 static void machine_escape(struct machine_output *m, int ch)
1338 case 0: fputs("\\0", m->fp); break;
1339 case '\a': fputs("\\a", m->fp); break;
1340 case '\b': fputs("\\b", m->fp); break;
1341 case '\x1b': fputs("\\e", m->fp); break;
1342 case '\f': fputs("\\f", m->fp); break;
1343 case '\n': fputs("\\n", m->fp); break;
1344 case '\r': fputs("\\r", m->fp); break;
1345 case '\t': fputs("\\t", m->fp); break;
1346 case '\v': fputs("\\v", m->fp); break;
1347 case '"': fputs("\\\"", m->fp); break;
1348 case '\\': fputs("\\\\", m->fp); break;
1349 default: fprintf(m->fp, "\\x{%02x}", ch);
1353 static int machine_writech(void *go, int ch)
1355 struct machine_output *m = go;
1357 if (ISPRINT(ch)) putc(ch, m->fp);
1358 else machine_escape(m, ch);
1362 static int machine_writem(void *go, const char *p, size_t sz)
1364 struct machine_output *m = go;
1365 const char *q, *l = p + sz;
1370 if (q >= l) goto final;
1371 if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1374 if (p < q) fwrite(p, 1, q - p, m->fp);
1375 p = q; machine_escape(m, (unsigned char)*p++);
1378 if (p < l) fwrite(p, 1, l - p, m->fp);
1382 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1384 struct machine_output *m = go;
1389 n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
1391 return (machine_writem(m, m->outbuf, n));
1394 static const struct gprintf_ops machine_printops =
1395 { machine_writech, machine_writem, machine_nwritef };
1397 /* --- @machine_maybe_quote@ --- *
1399 * Arguments: @struct machine_output *m@ = output sink
1400 * @const char *p@ = pointer to string
1404 * Use: Print the string @p@, quoting it if necessary.
1407 static void machine_maybe_quote(struct machine_output *m, const char *p)
1411 for (q = p; *q; q++)
1412 if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
1413 fputs(p, m->fp); return;
1415 putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1418 /* --- @machine_bsession@ --- *
1420 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1421 * @struct machine_output@
1422 * @struct tvec_state *tv@ = the test state producing output
1426 * Use: Begin a test session.
1428 * The TAP driver records the test state for later reference,
1429 * initializes the group index counter, and prints the version
1433 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1435 struct machine_output *m = (struct machine_output *)o;
1437 m->tv = tv; m->grpix = 0;
1440 /* --- @machine_show_stats@ --- *
1442 * Arguments: @struct machine_output *m@ = output sink
1443 * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1447 * Use: Print a machine readable outcome statistics table
1450 static void machine_show_stats(struct machine_output *m,
1451 unsigned out[TVOUT_LIMIT])
1453 static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1456 for (i = 0; i < TVOUT_LIMIT; i++) {
1457 if (i) putc(' ', m->fp);
1458 fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1462 /* --- @machine_esession@ --- *
1464 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1465 * @struct machine_output@
1467 * Returns: Suggested exit code.
1469 * Use: End a test session.
1471 * The TAP driver prints a final summary of the rest results
1472 * and returns a suitable exit code. If errors occurred, it
1473 * instead prints a `Bail out!' line forcing the reader to
1477 static int machine_esession(struct tvec_output *o)
1479 struct machine_output *m = (struct machine_output *)o;
1480 struct tvec_state *tv = m->tv;
1482 fputs("END groups: ", m->fp);
1483 machine_show_stats(m, tv->grps);
1484 fputs("; tests: ", m->fp);
1485 machine_show_stats(m, tv->all);
1487 m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1490 /* --- @machine_bgroup@ --- *
1492 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1493 * @struct machine_output@
1497 * Use: Begin a test group.
1499 * The TAP driver determines the length of the longest
1500 * register name, resets the group progress scoreboard, and
1501 * activates the progress display.
1504 static void machine_bgroup(struct tvec_output *o)
1506 struct machine_output *m = (struct machine_output *)o;
1507 struct tvec_state *tv = m->tv;
1509 fputs("BGROUP ", m->fp);
1510 machine_maybe_quote(m, tv->test->name);
1512 m->grpix++; m->testix = 0;
1515 /* --- @machine_skipgroup@ --- *
1517 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1518 * @struct machine_output@
1519 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1524 * Use: Report that a test group is being skipped.
1526 * The TAP driver just reports the situation to its output
1530 static void machine_skipgroup(struct tvec_output *o,
1531 const char *excuse, va_list *ap)
1533 struct machine_output *m = (struct machine_output *)o;
1534 struct tvec_state *tv = m->tv;
1536 fputs("SKIPGRP ", m->fp);
1537 machine_maybe_quote(m, tv->test->name);
1539 fputs(" \"", m->fp);
1540 vgprintf(&machine_printops, m, excuse, ap);
1546 /* --- @machine_egroup@ --- *
1548 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1549 * @struct machine_output@
1553 * Use: Report that a test group has finished.
1555 * The TAP driver reports a summary of the group's tests.
1558 static void machine_egroup(struct tvec_output *o)
1560 struct machine_output *m = (struct machine_output *)o;
1561 struct tvec_state *tv = m->tv;
1563 if (!(tv->f&TVSF_SKIP)) {
1564 fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
1565 putc(' ', m->fp); machine_show_stats(m, tv->curr);
1570 /* --- @machine_btest@ --- *
1572 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1573 * @struct machine_output@
1577 * Use: Report that a test is starting.
1579 * The TAP driver advances its test counter. (We could do this
1580 * by adding up up the counters in @tv->curr@, and add on the
1581 * current test, but it's easier this way.)
1584 static void machine_btest(struct tvec_output *o) { ; }
1586 /* --- @machine_report_location@ --- *
1588 * Arguments: @struct human_output *h@ = output state
1589 * @const char *file@ = filename
1590 * @unsigned lno@ = line number
1594 * Use: Print the filename and line number to the output stream.
1597 static void machine_report_location(struct machine_output *m,
1598 const char *file, unsigned lno)
1602 machine_maybe_quote(m, file);
1603 fprintf(m->fp, ":%u", lno);
1607 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1609 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1610 * @struct machine_output@
1611 * @const char *outcome@ = outcome string to report
1612 * @const char *detail@, @va_list *ap@ = a detail message
1613 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1618 * Use: Report that a test has been skipped or failed.
1621 static void machine_outcome(struct tvec_output *o, const char *outcome,
1622 const char *detail, va_list *ap)
1624 struct machine_output *m = (struct machine_output *)o;
1625 struct tvec_state *tv = m->tv;
1627 fprintf(m->fp, "%s %u", outcome, m->testix);
1628 machine_report_location(m, tv->infile, tv->test_lno);
1630 { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1634 static void machine_skip(struct tvec_output *o,
1635 const char *excuse, va_list *ap)
1636 { machine_outcome(o, "SKIP", excuse, ap); }
1637 static void machine_fail(struct tvec_output *o,
1638 const char *detail, va_list *ap)
1639 { machine_outcome(o, "FAIL", detail, ap); }
1641 /* --- @machine_dumpreg@ --- *
1643 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1644 * @struct machine_output@
1645 * @unsigned disp@ = register disposition
1646 * @const union tvec_regval *rv@ = register value
1647 * @const struct tvec_regdef *rd@ = register definition
1651 * Use: Dump a register.
1653 * The machine driver applies highlighting to mismatching output
1654 * registers, but otherwise delegates to the register type
1658 static void machine_dumpreg(struct tvec_output *o,
1659 unsigned disp, const union tvec_regval *rv,
1660 const struct tvec_regdef *rd)
1662 struct machine_output *m = (struct machine_output *)o;
1668 case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
1669 case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
1670 case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
1671 case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
1672 case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
1676 if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
1677 if (!rv) fputs("#unset", m->fp);
1678 else rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
1679 if (f&f_nl) putc('\n', m->fp);
1685 /* --- @machine_etest@ --- *
1687 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1688 * @struct machine_output@
1689 * @unsigned outcome@ = the test outcome
1693 * Use: Report that a test has finished.
1695 * The machine driver reports the outcome of the test, if that's
1696 * not already decided.
1699 static void machine_etest(struct tvec_output *o, unsigned outcome)
1701 struct machine_output *m = (struct machine_output *)o;
1703 if (!(m->f&MF_BENCH)) switch (outcome) {
1704 case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
1705 case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
1707 m->testix++; m->f &= ~MF_BENCH;
1710 /* --- @machine_report@ --- *
1712 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1713 * @struct machine_output@
1714 * @unsigned level@ = message level (@TVLEV_...@)
1715 * @const char *msg@, @va_list *ap@ = format string and
1720 * Use: Report a message to the user.
1722 * Each report level has its own output tag
1725 static void machine_report(struct tvec_output *o, unsigned level,
1726 const char *msg, va_list *ap)
1728 struct machine_output *m = (struct machine_output *)o;
1729 struct tvec_state *tv = m->tv;
1732 for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
1733 machine_report_location(m, tv->infile, tv->lno);
1734 fputs(" \"", m->fp); vgprintf(&machine_printops, m, msg, ap);
1739 /* --- @machine_bbench@ --- *
1741 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1742 * @struct machine_output@
1743 * @const char *desc@ = adhoc test description
1744 * @unsigned unit@ = measurement unit (@BTU_...@)
1748 * Use: Report that a benchmark has started.
1750 * The machine driver does nothing here. All of the reporting
1751 * happens in @machine_ebench@.
1754 static void machine_bbench(struct tvec_output *o,
1755 const char *desc, unsigned unit)
1758 /* --- @machine_ebench@ --- *
1760 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1761 * @struct machine_output@
1762 * @const char *desc@ = adhoc test description
1763 * @unsigned unit@ = measurement unit (@BTU_...@)
1764 * @const struct bench_timing *t@ = measurement
1768 * Use: Report a benchmark's results.
1770 * The machine driver prints the result as a `BENCH' output
1771 * line, in raw format.
1774 static void machine_ebench(struct tvec_output *o,
1775 const char *desc, unsigned unit,
1776 const struct bench_timing *t)
1778 struct machine_output *m = (struct machine_output *)o;
1779 struct tvec_state *tv = m->tv;
1781 fprintf(m->fp, "BENCH %u", m->testix);
1782 machine_report_location(m, tv->infile, tv->test_lno);
1784 if (desc) machine_maybe_quote(m, desc);
1785 else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
1787 tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
1788 putc('\n', m->fp); m->f |= MF_BENCH;
1791 static const struct tvec_benchoutops machine_benchops =
1792 { machine_bbench, machine_ebench };
1794 /* --- @machine_extend@ --- *
1796 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1797 * @struct machine_output@
1798 * @const char *name@ = extension name
1800 * Returns: A pointer to the extension implementation, or null.
1803 static const void *machine_extend(struct tvec_output *o, const char *name)
1805 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
1809 /* --- @machine_destroy@ --- *
1811 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1812 * @struct machine_output@
1816 * Use: Release the resources held by the output driver.
1819 static void machine_destroy(struct tvec_output *o)
1821 struct machine_output *m = (struct machine_output *)o;
1823 if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1824 x_free(m->a, m->outbuf); x_free(m->a, m);
1827 static const struct tvec_outops machine_ops = {
1828 machine_bsession, machine_esession,
1829 machine_bgroup, machine_skipgroup, machine_egroup,
1830 machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
1831 machine_report, machine_extend, machine_destroy
1834 /* --- @tvec_machineoutput@ --- *
1836 * Arguments: @FILE *fp@ = output file to write on
1838 * Returns: An output formatter.
1840 * Use: Return an output formatter which writes on @fp@ in a
1841 * moderately simple machine-readable format.
1844 struct tvec_output *tvec_machineoutput(FILE *fp)
1846 struct machine_output *m;
1848 XNEW(m); m->a = arena_global; m->_o.ops = &machine_ops;
1849 m->f = 0; m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
1853 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1856 struct tvec_output _o; /* output base class */
1857 struct tvec_state *tv; /* stashed testing state */
1858 arena *a; /* arena for memory allocation */
1859 struct layout lyt; /* output layout */
1860 char *outbuf; size_t outsz; /* buffer for formatted output */
1861 unsigned grpix, testix; /* group and test indices */
1862 unsigned previx; /* previously reported test index */
1863 int maxlen; /* longest register name */
1866 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1868 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1869 * @int ch@ = character to write
1870 * @const char *@p@, @size_t sz@ = string (with explicit length)
1872 * @const char *p, ...@ = format control string and arguments to
1877 * Use: Write characters, strings, or formatted strings to the
1878 * output, applying appropriate layout.
1880 * For the TAP output driver, the layout machinery prefixes each
1881 * line with ` ## ' and strips trailing spaces.
1884 static int tap_writech(void *go, int ch)
1886 struct tap_output *t = go;
1888 if (layout_char(&t->lyt, ch)) return (-1);
1892 static int tap_writem(void *go, const char *p, size_t sz)
1894 struct tap_output *t = go;
1896 if (layout_string(&t->lyt, p, sz)) return (-1);
1900 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1902 struct tap_output *t = go;
1907 n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
1909 if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1913 static const struct gprintf_ops tap_printops =
1914 { tap_writech, tap_writem, tap_nwritef };
1916 /* --- @tap_bsession@ --- *
1918 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1919 * @struct tap_output@
1920 * @struct tvec_state *tv@ = the test state producing output
1924 * Use: Begin a test session.
1926 * The TAP driver records the test state for later reference,
1927 * initializes the group index counter, and prints the version
1931 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1933 struct tap_output *t = (struct tap_output *)o;
1935 t->tv = tv; t->grpix = 0;
1936 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1939 /* --- @tap_esession@ --- *
1941 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1942 * @struct tap_output@
1944 * Returns: Suggested exit code.
1946 * Use: End a test session.
1948 * The TAP driver prints a final summary of the rest results
1949 * and returns a suitable exit code. If errors occurred, it
1950 * instead prints a `Bail out!' line forcing the reader to
1954 static int tap_esession(struct tvec_output *o)
1956 struct tap_output *t = (struct tap_output *)o;
1957 struct tvec_state *tv = t->tv;
1959 if (tv->f&TVSF_ERROR) {
1961 "Errors found in input; tests may not have run correctly\n",
1966 fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1967 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1970 /* --- @tap_bgroup@ --- *
1972 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1973 * @struct tap_output@
1977 * Use: Begin a test group.
1979 * The TAP driver determines the length of the longest
1980 * register name, resets the group progress scoreboard, and
1981 * activates the progress display.
1984 static void tap_bgroup(struct tvec_output *o)
1986 struct tap_output *t = (struct tap_output *)o;
1987 struct tvec_state *tv = t->tv;
1989 t->grpix++; t->testix = t->previx = 0;
1990 t->maxlen = register_maxnamelen(t->tv);
1991 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1994 /* --- @tap_skipgroup@ --- *
1996 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1997 * @struct tap_output@
1998 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2003 * Use: Report that a test group is being skipped.
2005 * The TAP driver just reports the situation to its output
2009 static void tap_skipgroup(struct tvec_output *o,
2010 const char *excuse, va_list *ap)
2012 struct tap_output *t = (struct tap_output *)o;
2014 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
2015 fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
2016 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
2017 fputc('\n', t->lyt.fp);
2020 /* --- @tap_egroup@ --- *
2022 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2023 * @struct tap_output@
2027 * Use: Report that a test group has finished.
2029 * The TAP driver reports a summary of the group's tests.
2032 static void tap_egroup(struct tvec_output *o)
2034 struct tap_output *t = (struct tap_output *)o;
2035 struct tvec_state *tv = t->tv;
2037 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
2038 fprintf(t->lyt.fp, "%s %u - %s\n",
2039 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
2040 t->grpix, tv->test->name);
2043 /* --- @tap_btest@ --- *
2045 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2046 * @struct tap_output@
2050 * Use: Report that a test is starting.
2052 * The TAP driver advances its test counter. (We could do this
2053 * by adding up up the counters in @tv->curr@, and add on the
2054 * current test, but it's easier this way.)
2057 static void tap_btest(struct tvec_output *o)
2058 { struct tap_output *t = (struct tap_output *)o; t->testix++; }
2060 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2062 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2063 * @struct tap_output@
2064 * @const char *head, *tail@ = outcome strings to report
2065 * @const char *detail@, @va_list *ap@ = a detail message
2066 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2071 * Use: Report that a test has been skipped or failed.
2073 * The TAP driver reports the situation on its output stream.
2074 * TAP only allows us to report a single status for each
2075 * subtest, so we notice when we've already reported a status
2076 * for the current test and convert the second report as a
2077 * comment. This should only happen in the case of multiple
2081 static void tap_outcome(struct tvec_output *o,
2082 const char *head, const char *tail,
2083 const char *detail, va_list *ap)
2085 struct tap_output *t = (struct tap_output *)o;
2086 struct tvec_state *tv = t->tv;
2088 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
2089 t->testix == t->previx ? "##" : head,
2090 t->testix, tv->infile, tv->test_lno, tail);
2092 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2093 fputc('\n', t->lyt.fp);
2094 t->previx = t->testix;
2097 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2098 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
2099 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
2100 { tap_outcome(o, "not ok", "", detail, ap); }
2102 /* --- @tap_dumpreg@ --- *
2104 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2105 * @struct tap_output@
2106 * @unsigned disp@ = register disposition
2107 * @const union tvec_regval *rv@ = register value
2108 * @const struct tvec_regdef *rd@ = register definition
2112 * Use: Dump a register.
2114 * The TAP driver applies highlighting to mismatching output
2115 * registers, but otherwise delegates to the register type
2116 * handler and the layout machinery. The result is that the
2117 * register dump is marked as a comment and indented.
2120 static void tap_dumpreg(struct tvec_output *o,
2121 unsigned disp, const union tvec_regval *rv,
2122 const struct tvec_regdef *rd)
2124 struct tap_output *t = (struct tap_output *)o;
2125 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2127 set_layout_prefix(&t->lyt, " ## ");
2128 gprintf(&tap_printops, t, "%*s%s %s = ",
2129 10 + t->maxlen - n, "", ds, rd->name);
2130 if (!rv) gprintf(&tap_printops, t, "#<unset>");
2131 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
2132 layout_char(&t->lyt, '\n');
2135 /* --- @tap_etest@ --- *
2137 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2138 * @struct tap_output@
2139 * @unsigned outcome@ = the test outcome
2143 * Use: Report that a test has finished.
2145 * The TAP driver reports the outcome of the test, if that's not
2149 static void tap_etest(struct tvec_output *o, unsigned outcome)
2153 tap_outcome(o, "ok", "", 0, 0);
2156 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2161 /* --- @tap_report@ --- *
2163 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2164 * @struct tap_output@
2165 * @unsigned level@ = message level (@TVLEV_...@)
2166 * @const char *msg@, @va_list *ap@ = format string and
2171 * Use: Report a message to the user.
2173 * Messages are reported as comments, so that they can be
2174 * accumulated by the reader. An error will cause a later
2175 * bailout or, if we crash before then, a missing plan line,
2176 * either of which will cause the reader to report a serious
2180 static void tap_report(struct tvec_output *o, unsigned level,
2181 const char *msg, va_list *ap)
2183 struct tap_output *t = (struct tap_output *)o;
2184 struct tvec_state *tv = t->tv;
2186 if (tv->test) set_layout_prefix(&t->lyt, " ## ");
2187 else set_layout_prefix(&t->lyt, "## ");
2189 if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
2190 gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
2191 vgprintf(&tap_printops, t, msg, ap);
2192 layout_char(&t->lyt, '\n');
2195 /* --- @tap_extend@ --- *
2197 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2198 * @struct tap_output@
2199 * @const char *name@ = extension name
2201 * Returns: A pointer to the extension implementation, or null.
2204 static const void *tap_extend(struct tvec_output *o, const char *name)
2207 /* --- @tap_destroy@ --- *
2209 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2210 * @struct tap_output@
2214 * Use: Release the resources held by the output driver.
2217 static void tap_destroy(struct tvec_output *o)
2219 struct tap_output *t = (struct tap_output *)o;
2221 destroy_layout(&t->lyt,
2222 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
2223 x_free(t->a, t->outbuf); x_free(t->a, t);
2226 static const struct tvec_outops tap_ops = {
2227 tap_bsession, tap_esession,
2228 tap_bgroup, tap_skipgroup, tap_egroup,
2229 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
2230 tap_report, tap_extend, tap_destroy
2233 /* --- @tvec_tapoutput@ --- *
2235 * Arguments: @FILE *fp@ = output file to write on
2237 * Returns: An output formatter.
2239 * Use: Return an output formatter which writes on @fp@ in `TAP'
2240 * (`Test Anything Protocol') format.
2242 * TAP comes from the Perl community, but has spread rather
2243 * further. This driver produces TAP version 14, but pretends
2244 * to be version 13. The driver produces a TAP `test point' --
2245 * i.e., a result reported as `ok' or `not ok' -- for each input
2246 * test group. Failure reports and register dumps are produced
2247 * as diagnostic messages before the final group result. (TAP
2248 * permits structuerd YAML data after the test-point result,
2249 * which could be used to report details, but (a) postponing the
2250 * details until after the report is inconvenient, and (b) there
2251 * is no standardization for the YAML anyway, so in practice
2252 * it's no more useful than the unstructured diagnostics.
2255 struct tvec_output *tvec_tapoutput(FILE *fp)
2257 struct tap_output *t;
2259 XNEW(t); t->a = arena_global; t->_o.ops = &tap_ops;
2260 init_layout(&t->lyt, fp, 0);
2261 t->outbuf = 0; t->outsz = 0;
2265 /*----- Default output ----------------------------------------------------*/
2267 /* --- @tvec_dfltoutput@ --- *
2269 * Arguments: @FILE *fp@ = output file to write on
2271 * Returns: An output formatter.
2273 * Use: Selects and instantiates an output formatter suitable for
2274 * writing on @fp@. The policy is subject to change, but
2275 * currently the `human' output format is selected if @fp@ is
2276 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
2277 * otherwise the `machine' format is used.
2280 struct tvec_output *tvec_dfltout(FILE *fp)
2282 int ttyp = getenv_boolean("TVEC_TTY", -1);
2284 if (ttyp == -1) ttyp = isatty(fileno(fp));
2285 if (ttyp) return (tvec_humanoutput(fp));
2286 else return (tvec_machineoutput(fp));
2289 /*----- That's all, folks -------------------------------------------------*/