chiark / gitweb /
@@@ doc wip
[mLib] / test / tvec-output.c
1 /* -*-c-*-
2  *
3  * Test vector output management
4  *
5  * (c) 2023 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
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.
16  *
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.
21  *
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,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <assert.h>
33 #include <ctype.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <unistd.h>
39
40 #include "alloc.h"
41 #include "bench.h"
42 #include "dstr.h"
43 #include "macros.h"
44 #include "quis.h"
45 #include "report.h"
46
47 #include "tvec.h"
48 #include "tvec-bench.h"
49 #include "tvec-output.h"
50
51 /*----- Common machinery --------------------------------------------------*/
52
53 /* --- @regdisp@ --- *
54  *
55  * Arguments:   @unsigned disp@ = a @TVRD_...@ disposition code
56  *
57  * Returns:     A human-readable adjective describing the register
58  *              disposition.
59  */
60
61 static const char *regdisp(unsigned disp)
62 {
63   switch (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";
69     default: abort();
70   }
71 }
72
73 /* --- @getenv_boolean@ --- *
74  *
75  * Arguments:   @const char *var@ = environment variable name
76  *              @int dflt@ = default value
77  *
78  * Returns:     @0@ if the variable is set to something falseish, @1@ if it's
79  *              set to something truish, or @dflt@ otherwise.
80  */
81
82 static int getenv_boolean(const char *var, int dflt)
83 {
84   const char *p;
85
86   p = getenv(var);
87   if (!p)
88     return (dflt);
89   else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
90            STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
91            STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
92            STRCMP(p, ==, "1"))
93     return (1);
94   else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
95            STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
96            STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
97            STRCMP(p, ==, "0"))
98     return (0);
99   else {
100     moan("ignoring unexpected value `%s' for environment variable `%s'",
101          var, p);
102     return (dflt);
103   }
104 }
105
106 /* --- @register_maxnamelen@ --- *
107  *
108  * Arguments:   @const struct tvec_state *tv@ = test vector state
109  *
110  * Returns:     The maximum length of a register name in the current test.
111  */
112
113 static int register_maxnamelen(const struct tvec_state *tv)
114 {
115   const struct tvec_regdef *rd;
116   int maxlen = 10, n;
117
118   for (rd = tv->test->regs; rd->name; rd++)
119     { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
120   return (maxlen);
121 }
122
123 /* --- @print_ident@ --- *
124  *
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
129  *
130  * Returns:     ---
131  *
132  * Use:         Write a benchmark identification to the output.
133  */
134
135 static void print_ident(struct tvec_state *tv, unsigned style,
136                         const struct gprintf_ops *gops, void *go)
137 {
138   const struct tvec_regdef *rd;
139   unsigned f = 0;
140
141 #define f_any 1u
142
143   for (rd = tv->test->regs; rd->name; rd++)
144     if (rd->f&TVRF_ID) {
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);
152     }
153
154 #undef f_any
155 }
156
157 /*----- Output layout -----------------------------------------------------*/
158
159 /* We have two main jobs in output layout: trimming trailing blanks; and
160  * adding a prefix to each line.
161  *
162  * This is somehow much more complicated than it ought to be.
163  */
164
165 struct layout {
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 */
171 };
172
173 /* Support macros.  These assume `lyt' is defined as a pointer to the `struct
174  * layout' state.
175  */
176
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.                                                \
181    */                                                                   \
182                                                                         \
183   for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--);         \
184 } while (0)
185
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.                                \
189    */                                                                   \
190                                                                         \
191   size_t _n = limit - base;                                             \
192   if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1);             \
193 } while (0)
194
195 #define PUT_CHAR(ch) do {                                               \
196   /* Write CH to the output. Return immediately on error. */            \
197                                                                         \
198   if (putc(ch, lyt->fp) == EOF) return (-1);                            \
199 } while (0)
200
201 #define PUT_PREFIX do {                                                 \
202   /* Output the prefix, if there is one.  Return immediately on error. */ \
203                                                                         \
204   if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim);                 \
205 } while (0)
206
207 #define PUT_SAVED do {                                                  \
208   /* Output the saved trailing blank material in the buffer. */         \
209                                                                         \
210   size_t _n = lyt->w.len;                                               \
211   if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1);       \
212 } while (0)
213
214 #define PUT_PFXINB do {                                                 \
215   /* Output the initial nonblank portion of the prefix, if there is     \
216    * one.  Return immediately on error.                                 \
217    */                                                                   \
218                                                                         \
219   if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail);                \
220 } while (0)
221
222 #define SAVE_PFXTAIL do {                                               \
223   /* Save the trailing blank portion of the prefix. */                  \
224                                                                         \
225   if (lyt->prefix)                                                      \
226     DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail);           \
227 } while (0)
228
229 /* --- @set_layout_prefix@ --- *
230  *
231  * Arguments:   @struct layout *lyt@ = layout state
232  *              @const char *prefix@ = new prefix string or null
233  *
234  * Returns:     ---
235  *
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).
239  */
240
241 static void set_layout_prefix(struct layout *lyt, const char *prefix)
242 {
243   const char *q, *l;
244
245  if (!prefix || !*prefix)
246     lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
247   else {
248     lyt->prefix = prefix;
249     l = lyt->pfxlim = prefix + strlen(prefix);
250     SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
251   }
252 }
253
254 /* --- @init_layout@ --- *
255  *
256  * Arguments:   @struct layout *lyt@ = layout state to initialize
257  *              @FILE *fp@ = output file
258  *              @const char *prefix@ = prefix string (or null if empty)
259  *
260  * Returns:     ---
261  *
262  * Use:         Initialize a layout state.
263  */
264
265 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
266 {
267   lyt->fp = fp;
268   lyt->f = LYTF_NEWL;
269   dstr_create(&lyt->w);
270   set_layout_prefix(lyt, prefix);
271 }
272
273 /* --- @destroy_layout@ --- *
274  *
275  * Arguments:   @struct layout *lyt@ = layout state
276  *              @unsigned f@ = flags (@DLF_...@)
277  *
278  * Returns:     ---
279  *
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).
283  */
284
285 #define DLF_CLOSE 1u
286 static void destroy_layout(struct layout *lyt, unsigned f)
287 {
288   if (f&DLF_CLOSE) fclose(lyt->fp);
289   dstr_destroy(&lyt->w);
290 }
291
292 /* --- @layout_char@ --- *
293  *
294  * Arguments:   @struct layout *lyt@ = layout state
295  *              @int ch@ = character to write
296  *
297  * Returns:     Zero on success, @-1@ on failure.
298  *
299  * Use:         Write a single character to the output.
300  */
301
302 static int layout_char(struct layout *lyt, int ch)
303 {
304   if (ch == '\n') {
305     if (lyt->f&LYTF_NEWL) PUT_PFXINB;
306     PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
307   } else if (isspace(ch))
308     DPUTC(&lyt->w, ch);
309   else {
310     if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
311     PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
312   }
313   return (0);
314 }
315
316 /* --- @layout_string@ --- *
317  *
318  * Arguments:   @struct layout *lyt@ = layout state
319  *              @const char *p@ = string to write
320  *              @size_t sz@ = length of string
321  *
322  * Returns:     Zero on success, @-1@ on failure.
323  *
324  * Use:         Write a string to the output.
325  */
326
327 static int layout_string(struct layout *lyt, const char *p, size_t sz)
328 {
329   const char *q, *r, *l = p + sz;
330
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.
335    *
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:
338    *
339    *   * an initial portion, which is either empty or ends with a nonblank
340    *     character;
341    *
342    *   * a suffix which consists only of blanks; and
343    *
344    *   * an optional newline.
345    *
346    * All segments except the last end with a newline.
347    */
348
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   \
355    * no newline.                                                        \
356    */                                                                   \
357                                                                         \
358   q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l);             \
359 } while (0)
360
361 #define PUT_NONBLANK do {                                               \
362   /* Output the initial portion of the segment. */                      \
363                                                                         \
364   PUT_RANGE(p, r);                                                      \
365 } while (0)
366
367 #define PUT_NEWLINE do {                                                \
368   /* Write a newline, and advance to the next segment. */               \
369                                                                         \
370   PUT_CHAR('\n'); p = q + 1;                                            \
371 } while (0)
372
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 \
376    * be omitted.                                                        \
377    */                                                                   \
378                                                                         \
379   DPUTM(&lyt->w, r, l - r);                                             \
380 } while (0)
381
382   /* Determine the bounds of the first segment.  Handling this is the most
383    * complicated part of this function.
384    */
385   SPLIT_SEGMENT;
386
387   if (!q) {
388     /* This is the only segment.  We'll handle the whole thing here.
389      *
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.
395      *
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.
399      */
400
401     if (r > p) {
402       if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
403       PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
404     }
405     SAVE_TAIL;
406     return (0);
407   }
408
409   /* There is at least one more segment, so we know that there'll be a line
410    * to output.
411    */
412   if (r > p) {
413     if (lyt->f&LYTF_NEWL) PUT_PREFIX;
414     PUT_SAVED; PUT_NONBLANK;
415   } else if (lyt->f&LYTF_NEWL)
416     PUT_PFXINB;
417   PUT_NEWLINE; DRESET(&lyt->w);
418   SPLIT_SEGMENT;
419
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.
423    */
424   while (q) {
425     if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
426     else PUT_PFXINB;
427     PUT_NEWLINE;
428     SPLIT_SEGMENT;
429   }
430
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.
435    */
436   if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
437   else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
438   SAVE_TAIL;
439
440 #undef SPLIT_SEGMENT
441 #undef PUT_NONBLANK
442 #undef PUT_NEWLINE
443 #undef SAVE_TAIL
444
445   return (0);
446 }
447
448 #undef SPLIT_RANGE
449 #undef PUT_RANGE
450 #undef PUT_PREFIX
451 #undef PUT_PFXINB
452 #undef PUT_SAVED
453 #undef PUT_CHAR
454 #undef SAVE_PFXTAIL
455
456 /*----- Human-readable output ---------------------------------------------*/
457
458 /* Attributes for colour output.  This should be done better, but @terminfo@
459  * is a disaster.
460  *
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.
465  */
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... */
474 #define HCOL_RED 1u
475 #define HCOL_GREEN 2u
476 #define HCOL_YELLOW 3u
477 #define HCOL_BLUE 4u
478 #define HCOL_MAGENTA 5u
479 #define HCOL_CYAN 6u
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 */
484
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 */
500
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 */
506
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 */
521 };
522
523 /* --- @set_colour@ --- *
524  *
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
530  *
531  * Returns:     ---
532  *
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 `%|;|%'.
538  *
539  *              This is an internal subroutine for @setattr@ below.
540  */
541
542 static void set_colour(FILE *fp, int *sep_inout,
543                        const char *norm, const char *bright,
544                        unsigned colour)
545 {
546   if (*sep_inout) putc(*sep_inout, fp);
547   fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
548   *sep_inout = ';';
549 }
550
551 /* --- @setattr@ --- *
552  *
553  * Arguments:   @struct human_output *h@ = output state
554  *              @unsigned attr@ = attribute code to set
555  *
556  * Returns:     ---
557  *
558  * Use:         Send a control sequence to the output stream so that
559  *              subsequent text is printed with the given attributes.
560  *
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.
564  */
565
566 static void setattr(struct human_output *h, unsigned attr)
567 {
568   unsigned diff = h->attr ^ attr;
569   int sep = 0;
570
571   /* If there's nothing to do, we might as well stop now. */
572   if (!diff || !(h->f&HOF_COLOUR)) return;
573
574   /* Start on the control command. */
575   fputs("\x1b[", h->lyt.fp);
576
577   /* Change the boldness if necessary. */
578   if (diff&HAF_BOLD) {
579     if (attr&HAF_BOLD) putc('1', h->lyt.fp);
580     else { putc('0', h->lyt.fp); diff = h->attr; }
581     sep = ';';
582   }
583
584   /* Change the foreground colour if necessary. */
585   if (diff&(HAF_FG | HAF_FGMASK)) {
586     if (attr&HAF_FG)
587       set_colour(h->lyt.fp, &sep, "3", "9",
588                  (attr&HAF_FGMASK) >> HAF_FGSHIFT);
589     else {
590       if (sep) putc(sep, h->lyt.fp);
591       fputs("39", h->lyt.fp); sep = ';';
592     }
593   }
594
595   /* Change the background colour if necessary. */
596   if (diff&(HAF_BG | HAF_BGMASK)) {
597     if (attr&HAF_BG)
598       set_colour(h->lyt.fp, &sep, "4", "10",
599                  (attr&HAF_BGMASK) >> HAF_BGSHIFT);
600     else {
601       if (sep) putc(sep, h->lyt.fp);
602       fputs("49", h->lyt.fp); sep = ';';
603     }
604   }
605
606   /* Terminate the control command and save the new attributes. */
607   putc('m', h->lyt.fp); h->attr = attr;
608 }
609
610 /* --- @clear_progress@ --- *
611  *
612  * Arguments:   @struct human_output *h@ = output state
613  *
614  * Returns:     ---
615  *
616  * Use:         Remove the progress display from the terminal.
617  *
618  *              If the progress display isn't active then do nothing.
619  */
620
621 static void clear_progress(struct human_output *h)
622 {
623   size_t i, n;
624
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;
629   }
630 }
631
632 /* --- @write_scoreboard_char@ --- *
633  *
634  * Arguments:   @struct human_output *h@ = output state
635  *              @int ch@ = scoreboard character to print
636  *
637  * Returns:     ---
638  *
639  * Use:         Write a scoreboard character, indicating the outcome of a
640  *              test, to the output stream, with appropriate highlighting.
641  */
642
643 static void write_scoreboard_char(struct human_output *h, int ch)
644 {
645   switch (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;
650   }
651   putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
652 }
653
654 /* --- @show_progress@ --- *
655  *
656  * Arguments:   @struct human_output *h@ = output state
657  *
658  * Returns:     ---
659  *
660  * Use:         Show the progress display, with the record of outcomes for
661  *              the current test group.
662  *
663  *              If the progress display is already active, or the output
664  *              stream is not interactive, then nothing happens.
665  */
666
667 static void show_progress(struct human_output *h)
668 {
669   struct tvec_state *tv = h->tv;
670   const char *p, *l;
671
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;
679   }
680 }
681
682 /* --- @human_writech@, @human_write@, @human_writef@ --- *
683  *
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)
687  *                      to write
688  *              @const char *p, ...@ = format control string and arguments to
689  *                      write
690  *
691  * Returns:     ---
692  *
693  * Use:         Write characters, strings, or formatted strings to the
694  *              output, applying appropriate layout.
695  *
696  *              For the human output driver, the layout machinery just strips
697  *              trailing spaces.
698  */
699
700 static int human_writech(void *go, int ch)
701   { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
702
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)); }
705
706 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
707 {
708   struct human_output *h = go;
709   size_t n;
710   va_list ap;
711
712   va_start(ap, p);
713   n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
714   va_end(ap);
715   if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
716   return (n);
717 }
718
719 static const struct gprintf_ops human_printops =
720   { human_writech, human_writem, human_nwritef };
721
722 /* --- @human_bsession@ --- *
723  *
724  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
725  *                       @struct human_output@
726  *              @struct tvec_state *tv@ = the test state producing output
727  *
728  * Returns:     ---
729  *
730  * Use:         Begin a test session.
731  *
732  *              The human driver just records the test state for later
733  *              reference.
734  */
735
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; }
738
739 /* --- @report_unusual@ --- *
740  *
741  * Arguments:   @struct human_output *h@ = output sink
742  *              @unsigned nxfail, nskip@ = number of expected failures and
743  *                      skipped tests
744  *
745  * Returns:     ---
746  *
747  * Use:         Write (directly on the output stream) a note about expected
748  *              failures and/or skipped tests, if there were any.
749  */
750
751 static void report_unusual(struct human_output *h,
752                            unsigned nxfail, unsigned nskip)
753 {
754   const char *sep = " (";
755   unsigned f = 0;
756 #define f_any 1u
757
758   if (nxfail) {
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;
764   }
765
766   if (nskip) {
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;
770   }
771
772   if (f&f_any) fputc(')', h->lyt.fp);
773
774 #undef f_any
775 }
776
777 /* --- @human_esession@ --- *
778  *
779  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
780  *                       @struct human_output@
781  *
782  * Returns:     Suggested exit code.
783  *
784  * Use:         End a test session.
785  *
786  *              The human driver prints a final summary of the rest results
787  *              and returns a suitable exit code.
788  */
789
790 static int human_esession(struct tvec_output *o)
791 {
792   struct human_output *h = (struct human_output *)o;
793   struct tvec_state *tv = h->tv;
794   unsigned
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;
801
802   if (!all_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);
811   } else {
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);
819   }
820   fputc('\n', h->lyt.fp);
821
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);
825   }
826
827   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
828 }
829
830 /* --- @human_bgroup@ --- *
831  *
832  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
833  *                       @struct human_output@
834  *
835  * Returns:     ---
836  *
837  * Use:         Begin a test group.
838  *
839  *              The human driver determines the length of the longest
840  *              register name, resets the group progress scoreboard, and
841  *              activates the progress display.
842  */
843
844 static void human_bgroup(struct tvec_output *o)
845 {
846   struct human_output *h = (struct human_output *)o;
847
848   h->maxlen = register_maxnamelen(h->tv);
849   dstr_reset(&h->scoreboard); show_progress(h);
850 }
851
852 /* --- @human_skipgroup@ --- *
853  *
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
857  *                      group, or null
858  *
859  * Returns:     ---
860  *
861  * Use:         Report that a test group is being skipped.
862  *
863  *              The human driver just reports the situation to its output
864  *              stream.
865  */
866
867 static void human_skipgroup(struct tvec_output *o,
868                             const char *excuse, va_list *ap)
869 {
870   struct human_output *h = (struct human_output *)o;
871
872   if (!(h->f&HOF_TTY))
873     fprintf(h->lyt.fp, "%s ", h->tv->test->name);
874   else {
875     show_progress(h); h->f &= ~HOF_PROGRESS;
876     if (h->scoreboard.len) putc(' ', h->lyt.fp);
877   }
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);
881 }
882
883 /* --- @human_egroup@ --- *
884  *
885  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
886  *                       @struct human_output@
887  *
888  * Returns:     ---
889  *
890  * Use:         Report that a test group has finished.
891  *
892  *              The human driver reports a summary of the group's tests.
893  */
894
895 static void human_egroup(struct tvec_output *o)
896 {
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;
902
903   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
904   else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
905
906   if (lose) {
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);
910   } else {
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);
914   }
915   fputc('\n', h->lyt.fp);
916 }
917
918 /* --- @human_btest@ --- *
919  *
920  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
921  *                       @struct human_output@
922  *
923  * Returns:     ---
924  *
925  * Use:         Report that a test is starting.
926  *
927  *              The human driver makes sure the progress display is active.
928  */
929
930 static void human_btest(struct tvec_output *o)
931   { struct human_output *h = (struct human_output *)o; show_progress(h); }
932
933 /* --- @human_report_location@ --- *
934  *
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
939  *
940  * Returns:     ---
941  *
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
945  *              nothing.
946  */
947
948 static void human_report_location(struct human_output *h, FILE *fp,
949                                   const char *file, unsigned lno)
950 {
951   unsigned f = 0;
952 #define f_flush 1u
953
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
958    * text synchronized.
959    */
960
961   if (!file)
962     /* nothing to do */;
963   else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
964     fprintf(fp, "%s:%u: ", file, lno);
965   else {
966     if (fp != h->lyt.fp) f |= f_flush;
967
968 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
969
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);
979     fputc(' ', fp);
980
981 #undef FLUSH
982   }
983
984 #undef f_flush
985 }
986
987 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
988  *
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
995  *                      test
996  *
997  * Returns:     ---
998  *
999  * Use:         Report that a test has been skipped or failed.
1000  *
1001  *              The human driver reports the situation on its output stream.
1002  */
1003
1004 static void human_outcome(struct tvec_output *o,
1005                           unsigned attr, const char *outcome,
1006                           const char *detail, va_list *ap)
1007 {
1008   struct human_output *h = (struct human_output *)o;
1009   struct tvec_state *tv = h->tv;
1010
1011   clear_progress(h);
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);
1017 }
1018
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); }
1025
1026 /* --- @human_dumpreg@ --- *
1027  *
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
1033  *
1034  * Returns:     ---
1035  *
1036  * Use:         Dump a register.
1037  *
1038  *              The human driver applies highlighting to mismatching output
1039  *              registers, but otherwise delegates to the register type
1040  *              handler and the layout machinery.
1041  */
1042
1043 static void human_dumpreg(struct tvec_output *o,
1044                           unsigned disp, const union tvec_regval *rv,
1045                           const struct tvec_regdef *rd)
1046 {
1047   struct human_output *h = (struct human_output *)o;
1048   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1049
1050   clear_progress(h);
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);
1057   }
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');
1061 }
1062
1063 /* --- @human_etest@ --- *
1064  *
1065  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1066  *                       @struct human_output@
1067  *              @unsigned outcome@ = the test outcome
1068  *
1069  * Returns:     ---
1070  *
1071  * Use:         Report that a test has finished.
1072  *
1073  *              The human driver reactivates the progress display, if
1074  *              necessary, and adds a new character for the completed test.
1075  */
1076
1077 static void human_etest(struct tvec_output *o, unsigned outcome)
1078 {
1079   struct human_output *h = (struct human_output *)o;
1080   int ch;
1081
1082   if (h->f&HOF_TTY) {
1083     show_progress(h);
1084     switch (outcome) {
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;
1089       default: abort();
1090     }
1091     dstr_putc(&h->scoreboard, ch);
1092     write_scoreboard_char(h, ch); fflush(h->lyt.fp);
1093   }
1094 }
1095
1096 /* --- @human_report@ --- *
1097  *
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
1102  *                      arguments
1103  *
1104  * Returns:     ---
1105  *
1106  * Use:         Report a message to the user.
1107  *
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.
1111  */
1112
1113 static void human_report(struct tvec_output *o, unsigned level,
1114                          const char *msg, va_list *ap)
1115 {
1116   struct human_output *h = (struct human_output *)o;
1117   struct tvec_state *tv = h->tv;
1118   const char *levstr; unsigned levattr;
1119   dstr d = DSTR_INIT;
1120   unsigned f = 0;
1121 #define f_flush 1u
1122 #define f_progress 2u
1123
1124   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1125
1126   switch (level) {
1127 #define CASE(tag, name, val)                                            \
1128     case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1129     TVEC_LEVELS(CASE)
1130     default: levstr = "??"; levattr = HA_UNKLEV; break;
1131   }
1132
1133   if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
1134
1135 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1136
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);
1143
1144 #undef FLUSH
1145
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);
1150   }
1151   if (f&f_progress) show_progress(h);
1152
1153   dstr_destroy(&d);
1154
1155 #undef f_flush
1156 #undef f_progress
1157 }
1158
1159 /* --- @human_bbench@ --- *
1160  *
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_...@)
1165  *
1166  * Returns:     ---
1167  *
1168  * Use:         Report that a benchmark has started.
1169  *
1170  *              The human driver just prints the start of the benchmark
1171  *              report.
1172  */
1173
1174 static void human_bbench(struct tvec_output *o,
1175                          const char *desc, unsigned unit)
1176 {
1177   struct human_output *h = (struct human_output *)o;
1178   struct tvec_state *tv = h->tv;
1179
1180   clear_progress(h);
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);
1186 }
1187
1188 /* --- @human_ebench@ --- *
1189  *
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
1195  *
1196  * Returns:     ---
1197  *
1198  * Use:         Report a benchmark's results.
1199  *
1200  *              The human driver just delegates to the default benchmark
1201  *              reporting, via the layout machinery.
1202  */
1203
1204 static void human_ebench(struct tvec_output *o,
1205                          const char *desc, unsigned unit,
1206                          const struct bench_timing *t)
1207 {
1208   struct human_output *h = (struct human_output *)o;
1209
1210   tvec_benchreport(&human_printops, h, unit, 0, t);
1211   layout_char(&h->lyt, '\n');
1212 }
1213
1214 static const struct tvec_benchoutops human_benchops =
1215   { human_bbench, human_ebench };
1216
1217 /* --- @human_extend@ --- *
1218  *
1219  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1220  *                       @struct human_output@
1221  *              @const char *name@ = extension name
1222  *
1223  * Returns:     A pointer to the extension implementation, or null.
1224  */
1225
1226 static const void *human_extend(struct tvec_output *o, const char *name)
1227 {
1228   if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
1229   else return (0);
1230 }
1231
1232 /* --- @human_destroy@ --- *
1233  *
1234  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1235  *                       @struct human_output@
1236  *
1237  * Returns:     ---
1238  *
1239  * Use:         Release the resources held by the output driver.
1240  */
1241
1242 static void human_destroy(struct tvec_output *o)
1243 {
1244   struct human_output *h = (struct human_output *)o;
1245
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);
1250 }
1251
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
1257 };
1258
1259 /* --- @tvec_humanoutput@ --- *
1260  *
1261  * Arguments:   @FILE *fp@ = output file to write on
1262  *
1263  * Returns:     An output formatter.
1264  *
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
1270  *              highlighting.
1271  */
1272
1273 struct tvec_output *tvec_humanoutput(FILE *fp)
1274 {
1275   struct human_output *h;
1276   const char *p;
1277
1278   XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
1279   h->f = 0; h->attr = 0;
1280
1281   init_layout(&h->lyt, fp, 0);
1282   h->outbuf = 0; h->outsz = 0;
1283
1284   switch (getenv_boolean("TVEC_TTY", -1)) {
1285     case 1: h->f |= HOF_TTY; break;
1286     case 0: break;
1287     default:
1288       if (isatty(fileno(fp))) h->f |= HOF_TTY;
1289       break;
1290   }
1291   switch (getenv_boolean("TVEC_COLOUR", -1)) {
1292     case 1: h->f |= HOF_COLOUR; break;
1293     case 0: break;
1294     default:
1295       if (h->f&HOF_TTY) {
1296         p = getenv("TERM");
1297         if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1298       }
1299       break;
1300   }
1301
1302   if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1303   dstr_create(&h->scoreboard);
1304   return (&h->_o);
1305 }
1306
1307 /*----- Machine-readable output -------------------------------------------*/
1308
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 */
1317 #define MF_BENCH 1u
1318 };
1319
1320 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1321  *
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)
1325  *                      to write
1326  *              @const char *p, ...@ = format control string and arguments to
1327  *                      write
1328  *
1329  * Returns:     ---
1330  *
1331  * Use:         Write characters, strings, or formatted strings to the
1332  *              output, applying appropriate quoting.
1333  */
1334
1335 static void machine_escape(struct machine_output *m, int ch)
1336 {
1337   switch (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);
1350   }
1351 }
1352
1353 static int machine_writech(void *go, int ch)
1354 {
1355   struct machine_output *m = go;
1356
1357   if (ISPRINT(ch)) putc(ch, m->fp);
1358   else machine_escape(m, ch);
1359   return (1);
1360 }
1361
1362 static int machine_writem(void *go, const char *p, size_t sz)
1363 {
1364   struct machine_output *m = go;
1365   const char *q, *l = p + sz;
1366
1367   for (;;) {
1368     q = p;
1369     for (;;) {
1370       if (q >= l) goto final;
1371       if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1372       q++;
1373     }
1374     if (p < q) fwrite(p, 1, q - p, m->fp);
1375     p = q; machine_escape(m, (unsigned char)*p++);
1376   }
1377 final:
1378   if (p < l) fwrite(p, 1, l - p, m->fp);
1379   return (sz);
1380 }
1381
1382 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1383 {
1384   struct machine_output *m = go;
1385   int n;
1386   va_list ap;
1387
1388   va_start(ap, p);
1389   n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
1390   va_end(ap);
1391   return (machine_writem(m, m->outbuf, n));
1392 }
1393
1394 static const struct gprintf_ops machine_printops =
1395   { machine_writech, machine_writem, machine_nwritef };
1396
1397 /* --- @machine_maybe_quote@ --- *
1398  *
1399  * Arguments:   @struct machine_output *m@ = output sink
1400  *              @const char *p@ = pointer to string
1401  *
1402  * Returns:     ---
1403  *
1404  * Use:         Print the string @p@, quoting it if necessary.
1405  */
1406
1407 static void machine_maybe_quote(struct machine_output *m, const char *p)
1408 {
1409   const char *q;
1410
1411   for (q = p; *q; q++)
1412     if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
1413   fputs(p, m->fp); return;
1414 quote:
1415   putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1416 }
1417
1418 /* --- @machine_bsession@ --- *
1419  *
1420  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1421  *                       @struct machine_output@
1422  *              @struct tvec_state *tv@ = the test state producing output
1423  *
1424  * Returns:     ---
1425  *
1426  * Use:         Begin a test session.
1427  *
1428  *              The TAP driver records the test state for later reference,
1429  *              initializes the group index counter, and prints the version
1430  *              number.
1431  */
1432
1433 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1434 {
1435   struct machine_output *m = (struct machine_output *)o;
1436
1437   m->tv = tv; m->grpix = 0;
1438 }
1439
1440 /* --- @machine_show_stats@ --- *
1441  *
1442  * Arguments:   @struct machine_output *m@ = output sink
1443  *              @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1444  *
1445  * Returns:     ---
1446  *
1447  * Use:         Print a machine readable outcome statistics table
1448  */
1449
1450 static void machine_show_stats(struct machine_output *m,
1451                                unsigned out[TVOUT_LIMIT])
1452 {
1453   static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1454   unsigned i;
1455
1456   for (i = 0; i < TVOUT_LIMIT; i++) {
1457     if (i) putc(' ', m->fp);
1458     fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1459   }
1460 }
1461
1462 /* --- @machine_esession@ --- *
1463  *
1464  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1465  *                       @struct machine_output@
1466  *
1467  * Returns:     Suggested exit code.
1468  *
1469  * Use:         End a test session.
1470  *
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
1474  *              report a failure.
1475  */
1476
1477 static int machine_esession(struct tvec_output *o)
1478 {
1479   struct machine_output *m = (struct machine_output *)o;
1480   struct tvec_state *tv = m->tv;
1481
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);
1486   putc('\n', m->fp);
1487   m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1488 }
1489
1490 /* --- @machine_bgroup@ --- *
1491  *
1492  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1493  *                       @struct machine_output@
1494  *
1495  * Returns:     ---
1496  *
1497  * Use:         Begin a test group.
1498  *
1499  *              The TAP driver determines the length of the longest
1500  *              register name, resets the group progress scoreboard, and
1501  *              activates the progress display.
1502  */
1503
1504 static void machine_bgroup(struct tvec_output *o)
1505 {
1506   struct machine_output *m = (struct machine_output *)o;
1507   struct tvec_state *tv = m->tv;
1508
1509   fputs("BGROUP ", m->fp);
1510   machine_maybe_quote(m, tv->test->name);
1511   putc('\n', m->fp);
1512   m->grpix++; m->testix = 0;
1513 }
1514
1515 /* --- @machine_skipgroup@ --- *
1516  *
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
1520  *                      group, or null
1521  *
1522  * Returns:     ---
1523  *
1524  * Use:         Report that a test group is being skipped.
1525  *
1526  *              The TAP driver just reports the situation to its output
1527  *              stream.
1528  */
1529
1530 static void machine_skipgroup(struct tvec_output *o,
1531                           const char *excuse, va_list *ap)
1532 {
1533   struct machine_output *m = (struct machine_output *)o;
1534   struct tvec_state *tv = m->tv;
1535
1536   fputs("SKIPGRP ", m->fp);
1537   machine_maybe_quote(m, tv->test->name);
1538   if (excuse) {
1539     fputs(" \"", m->fp);
1540     vgprintf(&machine_printops, m, excuse, ap);
1541     putc('\"', m->fp);
1542   }
1543   putc('\n', m->fp);
1544 }
1545
1546 /* --- @machine_egroup@ --- *
1547  *
1548  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1549  *                       @struct machine_output@
1550  *
1551  * Returns:     ---
1552  *
1553  * Use:         Report that a test group has finished.
1554  *
1555  *              The TAP driver reports a summary of the group's tests.
1556  */
1557
1558 static void machine_egroup(struct tvec_output *o)
1559 {
1560   struct machine_output *m = (struct machine_output *)o;
1561   struct tvec_state *tv = m->tv;
1562
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);
1566     putc('\n', m->fp);
1567   }
1568 }
1569
1570 /* --- @machine_btest@ --- *
1571  *
1572  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1573  *                       @struct machine_output@
1574  *
1575  * Returns:     ---
1576  *
1577  * Use:         Report that a test is starting.
1578  *
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.)
1582  */
1583
1584 static void machine_btest(struct tvec_output *o) { ; }
1585
1586 /* --- @machine_report_location@ --- *
1587  *
1588  * Arguments:   @struct human_output *h@ = output state
1589  *              @const char *file@ = filename
1590  *              @unsigned lno@ = line number
1591  *
1592  * Returns:     ---
1593  *
1594  * Use:         Print the filename and line number to the output stream.
1595  */
1596
1597 static void machine_report_location(struct machine_output *m,
1598                                     const char *file, unsigned lno)
1599 {
1600   if (file) {
1601     putc(' ', m->fp);
1602     machine_maybe_quote(m, file);
1603     fprintf(m->fp, ":%u", lno);
1604   }
1605 }
1606
1607 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1608  *
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
1614  *                      test
1615  *
1616  * Returns:     ---
1617  *
1618  * Use:         Report that a test has been skipped or failed.
1619  */
1620
1621 static void machine_outcome(struct tvec_output *o, const char *outcome,
1622                             const char *detail, va_list *ap)
1623 {
1624   struct machine_output *m = (struct machine_output *)o;
1625   struct tvec_state *tv = m->tv;
1626
1627   fprintf(m->fp, "%s %u", outcome, m->testix);
1628   machine_report_location(m, tv->infile, tv->test_lno);
1629   if (detail)
1630     { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1631   putc('\n', m->fp);
1632 }
1633
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); }
1640
1641 /* --- @machine_dumpreg@ --- *
1642  *
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
1648  *
1649  * Returns:     ---
1650  *
1651  * Use:         Dump a register.
1652  *
1653  *              The machine driver applies highlighting to mismatching output
1654  *              registers, but otherwise delegates to the register type
1655  *              handler.
1656  */
1657
1658 static void machine_dumpreg(struct tvec_output *o,
1659                         unsigned disp, const union tvec_regval *rv,
1660                         const struct tvec_regdef *rd)
1661 {
1662   struct machine_output *m = (struct machine_output *)o;
1663   unsigned f = 0;
1664 #define f_reg 1u
1665 #define f_nl 2u
1666
1667   switch (disp) {
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;
1673     default: abort();
1674   }
1675
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);
1680
1681 #undef f_reg
1682 #undef f_newline
1683 }
1684
1685 /* --- @machine_etest@ --- *
1686  *
1687  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1688  *                       @struct machine_output@
1689  *              @unsigned outcome@ = the test outcome
1690  *
1691  * Returns:     ---
1692  *
1693  * Use:         Report that a test has finished.
1694  *
1695  *              The machine driver reports the outcome of the test, if that's
1696  *              not already decided.
1697  */
1698
1699 static void machine_etest(struct tvec_output *o, unsigned outcome)
1700 {
1701   struct machine_output *m = (struct machine_output *)o;
1702
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;
1706   }
1707   m->testix++; m->f &= ~MF_BENCH;
1708 }
1709
1710 /* --- @machine_report@ --- *
1711  *
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
1716  *                      arguments
1717  *
1718  * Returns:     ---
1719  *
1720  * Use:         Report a message to the user.
1721  *
1722  *              Each report level has its own output tag
1723  */
1724
1725 static void machine_report(struct tvec_output *o, unsigned level,
1726                            const char *msg, va_list *ap)
1727 {
1728   struct machine_output *m = (struct machine_output *)o;
1729   struct tvec_state *tv = m->tv;
1730   const char *p;
1731
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);
1735   putc('"', m->fp);
1736   putc('\n', m->fp);
1737 }
1738
1739 /* --- @machine_bbench@ --- *
1740  *
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_...@)
1745  *
1746  * Returns:     ---
1747  *
1748  * Use:         Report that a benchmark has started.
1749  *
1750  *              The machine driver does nothing here.  All of the reporting
1751  *              happens in @machine_ebench@.
1752  */
1753
1754 static void machine_bbench(struct tvec_output *o,
1755                            const char *desc, unsigned unit)
1756   { ; }
1757
1758 /* --- @machine_ebench@ --- *
1759  *
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
1765  *
1766  * Returns:     ---
1767  *
1768  * Use:         Report a benchmark's results.
1769  *
1770  *              The machine driver prints the result as a `BENCH' output
1771  *              line, in raw format.
1772  */
1773
1774 static void machine_ebench(struct tvec_output *o,
1775                            const char *desc, unsigned unit,
1776                            const struct bench_timing *t)
1777 {
1778   struct machine_output *m = (struct machine_output *)o;
1779   struct tvec_state *tv = m->tv;
1780
1781   fprintf(m->fp, "BENCH %u", m->testix);
1782   machine_report_location(m, tv->infile, tv->test_lno);
1783   putc(' ', m->fp);
1784   if (desc) machine_maybe_quote(m, desc);
1785   else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
1786   fputs(": ", m->fp);
1787   tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
1788   putc('\n', m->fp); m->f |= MF_BENCH;
1789 }
1790
1791 static const struct tvec_benchoutops machine_benchops =
1792   { machine_bbench, machine_ebench };
1793
1794 /* --- @machine_extend@ --- *
1795  *
1796  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1797  *                       @struct machine_output@
1798  *              @const char *name@ = extension name
1799  *
1800  * Returns:     A pointer to the extension implementation, or null.
1801  */
1802
1803 static const void *machine_extend(struct tvec_output *o, const char *name)
1804 {
1805   if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
1806   else return (0);
1807 }
1808
1809 /* --- @machine_destroy@ --- *
1810  *
1811  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1812  *                       @struct machine_output@
1813  *
1814  * Returns:     ---
1815  *
1816  * Use:         Release the resources held by the output driver.
1817  */
1818
1819 static void machine_destroy(struct tvec_output *o)
1820 {
1821   struct machine_output *m = (struct machine_output *)o;
1822
1823   if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1824   x_free(m->a, m->outbuf); x_free(m->a, m);
1825 }
1826
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
1832 };
1833
1834 /* --- @tvec_machineoutput@ --- *
1835  *
1836  * Arguments:   @FILE *fp@ = output file to write on
1837  *
1838  * Returns:     An output formatter.
1839  *
1840  * Use:         Return an output formatter which writes on @fp@ in a
1841  *              moderately simple machine-readable format.
1842  */
1843
1844 struct tvec_output *tvec_machineoutput(FILE *fp)
1845 {
1846   struct machine_output *m;
1847
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;
1850   return (&m->_o);
1851 }
1852
1853 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1854
1855 struct tap_output {
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 */
1864 };
1865
1866 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1867  *
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)
1871  *                      to write
1872  *              @const char *p, ...@ = format control string and arguments to
1873  *                      write
1874  *
1875  * Returns:     ---
1876  *
1877  * Use:         Write characters, strings, or formatted strings to the
1878  *              output, applying appropriate layout.
1879  *
1880  *              For the TAP output driver, the layout machinery prefixes each
1881  *              line with `    ## ' and strips trailing spaces.
1882  */
1883
1884 static int tap_writech(void *go, int ch)
1885 {
1886   struct tap_output *t = go;
1887
1888   if (layout_char(&t->lyt, ch)) return (-1);
1889   else return (1);
1890 }
1891
1892 static int tap_writem(void *go, const char *p, size_t sz)
1893 {
1894   struct tap_output *t = go;
1895
1896   if (layout_string(&t->lyt, p, sz)) return (-1);
1897   else return (sz);
1898 }
1899
1900 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1901 {
1902   struct tap_output *t = go;
1903   size_t n;
1904   va_list ap;
1905
1906   va_start(ap, p);
1907   n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
1908   va_end(ap);
1909   if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1910   return (n);
1911 }
1912
1913 static const struct gprintf_ops tap_printops =
1914   { tap_writech, tap_writem, tap_nwritef };
1915
1916 /* --- @tap_bsession@ --- *
1917  *
1918  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1919  *                       @struct tap_output@
1920  *              @struct tvec_state *tv@ = the test state producing output
1921  *
1922  * Returns:     ---
1923  *
1924  * Use:         Begin a test session.
1925  *
1926  *              The TAP driver records the test state for later reference,
1927  *              initializes the group index counter, and prints the version
1928  *              number.
1929  */
1930
1931 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1932 {
1933   struct tap_output *t = (struct tap_output *)o;
1934
1935   t->tv = tv; t->grpix = 0;
1936   fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1937 }
1938
1939 /* --- @tap_esession@ --- *
1940  *
1941  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1942  *                       @struct tap_output@
1943  *
1944  * Returns:     Suggested exit code.
1945  *
1946  * Use:         End a test session.
1947  *
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
1951  *              report a failure.
1952  */
1953
1954 static int tap_esession(struct tvec_output *o)
1955 {
1956   struct tap_output *t = (struct tap_output *)o;
1957   struct tvec_state *tv = t->tv;
1958
1959   if (tv->f&TVSF_ERROR) {
1960     fputs("Bail out!  "
1961           "Errors found in input; tests may not have run correctly\n",
1962           t->lyt.fp);
1963     return (2);
1964   }
1965
1966   fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1967   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1968 }
1969
1970 /* --- @tap_bgroup@ --- *
1971  *
1972  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1973  *                       @struct tap_output@
1974  *
1975  * Returns:     ---
1976  *
1977  * Use:         Begin a test group.
1978  *
1979  *              The TAP driver determines the length of the longest
1980  *              register name, resets the group progress scoreboard, and
1981  *              activates the progress display.
1982  */
1983
1984 static void tap_bgroup(struct tvec_output *o)
1985 {
1986   struct tap_output *t = (struct tap_output *)o;
1987   struct tvec_state *tv = t->tv;
1988
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);
1992 }
1993
1994 /* --- @tap_skipgroup@ --- *
1995  *
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
1999  *                      group, or null
2000  *
2001  * Returns:     ---
2002  *
2003  * Use:         Report that a test group is being skipped.
2004  *
2005  *              The TAP driver just reports the situation to its output
2006  *              stream.
2007  */
2008
2009 static void tap_skipgroup(struct tvec_output *o,
2010                           const char *excuse, va_list *ap)
2011 {
2012   struct tap_output *t = (struct tap_output *)o;
2013
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);
2018 }
2019
2020 /* --- @tap_egroup@ --- *
2021  *
2022  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2023  *                       @struct tap_output@
2024  *
2025  * Returns:     ---
2026  *
2027  * Use:         Report that a test group has finished.
2028  *
2029  *              The TAP driver reports a summary of the group's tests.
2030  */
2031
2032 static void tap_egroup(struct tvec_output *o)
2033 {
2034   struct tap_output *t = (struct tap_output *)o;
2035   struct tvec_state *tv = t->tv;
2036
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);
2041 }
2042
2043 /* --- @tap_btest@ --- *
2044  *
2045  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2046  *                       @struct tap_output@
2047  *
2048  * Returns:     ---
2049  *
2050  * Use:         Report that a test is starting.
2051  *
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.)
2055  */
2056
2057 static void tap_btest(struct tvec_output *o)
2058   { struct tap_output *t = (struct tap_output *)o; t->testix++; }
2059
2060 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2061  *
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
2067  *                      test
2068  *
2069  * Returns:     ---
2070  *
2071  * Use:         Report that a test has been skipped or failed.
2072  *
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
2078  *              failures.
2079  */
2080
2081 static void tap_outcome(struct tvec_output *o,
2082                         const char *head, const char *tail,
2083                         const char *detail, va_list *ap)
2084 {
2085   struct tap_output *t = (struct tap_output *)o;
2086   struct tvec_state *tv = t->tv;
2087
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);
2091   if (detail)
2092     { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2093   fputc('\n', t->lyt.fp);
2094   t->previx = t->testix;
2095 }
2096
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); }
2101
2102 /* --- @tap_dumpreg@ --- *
2103  *
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
2109  *
2110  * Returns:     ---
2111  *
2112  * Use:         Dump a register.
2113  *
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.
2118  */
2119
2120 static void tap_dumpreg(struct tvec_output *o,
2121                         unsigned disp, const union tvec_regval *rv,
2122                         const struct tvec_regdef *rd)
2123 {
2124   struct tap_output *t = (struct tap_output *)o;
2125   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2126
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');
2133 }
2134
2135 /* --- @tap_etest@ --- *
2136  *
2137  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2138  *                       @struct tap_output@
2139  *              @unsigned outcome@ = the test outcome
2140  *
2141  * Returns:     ---
2142  *
2143  * Use:         Report that a test has finished.
2144  *
2145  *              The TAP driver reports the outcome of the test, if that's not
2146  *              already decided.
2147  */
2148
2149 static void tap_etest(struct tvec_output *o, unsigned outcome)
2150 {
2151   switch (outcome) {
2152     case TVOUT_WIN:
2153       tap_outcome(o, "ok", "", 0, 0);
2154       break;
2155     case TVOUT_XFAIL:
2156       tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2157       break;
2158   }
2159 }
2160
2161 /* --- @tap_report@ --- *
2162  *
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
2167  *                      arguments
2168  *
2169  * Returns:     ---
2170  *
2171  * Use:         Report a message to the user.
2172  *
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
2177  *              problem.
2178  */
2179
2180 static void tap_report(struct tvec_output *o, unsigned level,
2181                        const char *msg, va_list *ap)
2182 {
2183   struct tap_output *t = (struct tap_output *)o;
2184   struct tvec_state *tv = t->tv;
2185
2186   if (tv->test) set_layout_prefix(&t->lyt, "    ## ");
2187   else set_layout_prefix(&t->lyt, "## ");
2188
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');
2193 }
2194
2195 /* --- @tap_extend@ --- *
2196  *
2197  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2198  *                       @struct tap_output@
2199  *              @const char *name@ = extension name
2200  *
2201  * Returns:     A pointer to the extension implementation, or null.
2202  */
2203
2204 static const void *tap_extend(struct tvec_output *o, const char *name)
2205   { return (0); }
2206
2207 /* --- @tap_destroy@ --- *
2208  *
2209  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2210  *                       @struct tap_output@
2211  *
2212  * Returns:     ---
2213  *
2214  * Use:         Release the resources held by the output driver.
2215  */
2216
2217 static void tap_destroy(struct tvec_output *o)
2218 {
2219   struct tap_output *t = (struct tap_output *)o;
2220
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);
2224 }
2225
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
2231 };
2232
2233 /* --- @tvec_tapoutput@ --- *
2234  *
2235  * Arguments:   @FILE *fp@ = output file to write on
2236  *
2237  * Returns:     An output formatter.
2238  *
2239  * Use:         Return an output formatter which writes on @fp@ in `TAP'
2240  *              (`Test Anything Protocol') format.
2241  *
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.
2253  */
2254
2255 struct tvec_output *tvec_tapoutput(FILE *fp)
2256 {
2257   struct tap_output *t;
2258
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;
2262   return (&t->_o);
2263 }
2264
2265 /*----- Default output ----------------------------------------------------*/
2266
2267 /* --- @tvec_dfltoutput@ --- *
2268  *
2269  * Arguments:   @FILE *fp@ = output file to write on
2270  *
2271  * Returns:     An output formatter.
2272  *
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.
2278  */
2279
2280 struct tvec_output *tvec_dfltout(FILE *fp)
2281 {
2282   int ttyp = getenv_boolean("TVEC_TTY", -1);
2283
2284   if (ttyp == -1) ttyp = isatty(fileno(fp));
2285   if (ttyp) return (tvec_humanoutput(fp));
2286   else return (tvec_machineoutput(fp));
2287 }
2288
2289 /*----- That's all, folks -------------------------------------------------*/