chiark / gitweb /
@@@ tty cleanup
[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 #include <sys/stat.h>
40
41 #include "alloc.h"
42 #include "bench.h"
43 #include "dstr.h"
44 #include "macros.h"
45 #include "quis.h"
46 #include "report.h"
47 #include "tty.h"
48 #include "ttycolour.h"
49
50 #include "tvec.h"
51 #include "tvec-bench.h"
52 #include "tvec-output.h"
53
54 /*----- Common machinery --------------------------------------------------*/
55
56 /* --- @regdisp@ --- *
57  *
58  * Arguments:   @unsigned disp@ = a @TVRD_...@ disposition code
59  *
60  * Returns:     A human-readable adjective describing the register
61  *              disposition.
62  */
63
64 static const char *regdisp(unsigned disp)
65 {
66   switch (disp) {
67     case TVRD_INPUT: return "input";
68     case TVRD_OUTPUT: return "output";
69     case TVRD_MATCH: return "matched";
70     case TVRD_FOUND: return "found";
71     case TVRD_EXPECT: return "expected";
72     default: abort();
73   }
74 }
75
76 /* --- @interpret_boolean@, @getenv_boolean@ --- *
77  *
78  * Arguments:   @const char *val@ = a string
79  *              @const char *var@ = environment variable name
80  *              @int dflt@ = default value
81  *              @const char *what, ...@ = format string describing where the
82  *                      setting came from
83  *
84  * Returns:     @0@ if the string, or variable, is set to something
85  *              falseish, @1@ if it's set to something truish, or @dflt@
86  *              otherwise.
87  */
88
89 static PRINTF_LIKE(3, 4)
90   int interpret_boolean(const char *val, int dflt, const char *what, ...)
91 {
92   dstr d = DSTR_INIT;
93   va_list ap;
94   int rc;
95
96   if (!val)
97     rc = dflt;
98   else if (STRCMP(val, ==, "y") || STRCMP(val, ==, "yes") ||
99            STRCMP(val, ==, "t") || STRCMP(val, ==, "true") ||
100            STRCMP(val, ==, "on") || STRCMP(val, ==, "force") ||
101            STRCMP(val, ==, "1"))
102     rc = 1;
103   else if (STRCMP(val, ==, "n") || STRCMP(val, ==, "no") ||
104            STRCMP(val, ==, "nil") || STRCMP(val, ==, "f") ||
105              STRCMP(val, ==, "false") ||
106            STRCMP(val, ==, "off") || STRCMP(val, ==, "inhibit") ||
107            STRCMP(val, ==, "0"))
108     rc = 0;
109   else {
110     va_start(ap, what); dstr_vputf(&d, what, &ap); va_end(ap);
111     moan("ignoring unexpected value `%s' for %s", val, d.buf);
112     rc = dflt;
113   }
114   dstr_destroy(&d); return (rc);
115 }
116
117 static int getenv_boolean(const char *var, int dflt)
118 {
119   return (interpret_boolean(getenv(var), dflt,
120                             "environment variable `%s'", var));
121 }
122
123 /* --- @register_maxnamelen@ --- *
124  *
125  * Arguments:   @const struct tvec_state *tv@ = test vector state
126  *
127  * Returns:     The maximum length of a register name in the current test.
128  */
129
130 static int register_maxnamelen(const struct tvec_state *tv)
131 {
132   const struct tvec_regdef *rd;
133   int maxlen = 10, n;
134
135   for (rd = tv->test->regs; rd->name; rd++)
136     { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
137   return (maxlen);
138 }
139
140 /* --- @print_ident@ --- *
141  *
142  * Arguments:   @struct tvec_state *tv@ = test-vector state
143  *              @unsigned style@ = style to use for register dumps
144  *              @const struct gprintf_ops *gops@ = output operations
145  *              @void *go@ = output state
146  *
147  * Returns:     ---
148  *
149  * Use:         Write a benchmark identification to the output.
150  */
151
152 static void print_ident(struct tvec_state *tv, unsigned style,
153                         const struct gprintf_ops *gops, void *go)
154 {
155   const struct tvec_regdef *rd;
156   unsigned f = 0;
157
158 #define f_any 1u
159
160   for (rd = tv->test->regs; rd->name; rd++)
161     if (rd->f&TVRF_ID) {
162       if (!(f&f_any)) f |= f_any;
163       else if (style&TVSF_RAW) gops->putch(go, ' ');
164       else gprintf(gops, go, ", ");
165       gprintf(gops, go, "%s", rd->name);
166       if (style&TVSF_RAW) gops->putch(go, '=');
167       else gprintf(gops, go, " = ");
168       rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, style, gops, go);
169     }
170
171 #undef f_any
172 }
173
174 /*----- Output layout -----------------------------------------------------*/
175
176 /* We have two main jobs in output layout: trimming trailing blanks; and
177  * adding a prefix to each line.
178  *
179  * This is somehow much more complicated than it ought to be.
180  */
181
182 struct layout {
183   FILE *fp;                              /* output file */
184   const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
185   dstr w;                               /* trailing whitespace */
186   dstr ctrl;                            /* control sequence for next word */
187   unsigned f;                           /* flags */
188 #define LYTF_NEWL 1u                    /*   start of output line */
189 };
190
191 /* Support macros.  These assume `lyt' is defined as a pointer to the `struct
192  * layout' state.
193  */
194
195 #define SPLIT_RANGE(tail, base, limit) do {                             \
196   /* Set TAIL to point just after the last nonspace character between   \
197    * BASE and LIMIT.  If there are no nonspace characters, then set     \
198    * TAIL to equal BASE.                                                \
199    */                                                                   \
200                                                                         \
201   for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--);         \
202 } while (0)
203
204 #define PUT_RANGE(base, limit) do {                                     \
205   /* Write the range of characters between BASE and LIMIT to the output \
206    * file.  Return immediately on error.                                \
207    */                                                                   \
208                                                                         \
209   size_t _n = limit - base;                                             \
210   if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1);             \
211 } while (0)
212
213 #define PUT_CHAR(ch) do {                                               \
214   /* Write CH to the output. Return immediately on error. */            \
215                                                                         \
216   if (putc(ch, lyt->fp) == EOF) return (-1);                            \
217 } while (0)
218
219 #define PUT_PREFIX do {                                                 \
220   /* Output the prefix, if there is one.  Return immediately on error. */ \
221                                                                         \
222   if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim);                 \
223 } while (0)
224
225 #define PUT_SAVED do {                                                  \
226   /* Output the saved trailing blank material in the buffer. */         \
227                                                                         \
228   size_t _n = lyt->w.len;                                               \
229   if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1);       \
230 } while (0)
231
232 #define PUT_CTRL do {                                                   \
233   /* Output the accumulated control string. */                          \
234                                                                         \
235   size_t _n = lyt->ctrl.len;                                            \
236   if (_n && fwrite(lyt->ctrl.buf, 1, _n, lyt->fp) < _n) return (-1);    \
237 } while (0)
238
239 #define PUT_PFXINB do {                                                 \
240   /* Output the initial nonblank portion of the prefix, if there is     \
241    * one.  Return immediately on error.                                 \
242    */                                                                   \
243                                                                         \
244   if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail);                \
245 } while (0)
246
247 #define SAVE_PFXTAIL do {                                               \
248   /* Save the trailing blank portion of the prefix. */                  \
249                                                                         \
250   if (lyt->prefix)                                                      \
251     DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail);           \
252 } while (0)
253
254 /* --- @set_layout_prefix@ --- *
255  *
256  * Arguments:   @struct layout *lyt@ = layout state
257  *              @const char *prefix@ = new prefix string or null
258  *
259  * Returns:     ---
260  *
261  * Use:         Change the configured prefix string.  The change takes effect
262  *              at the start of the next line (or the current line if it's
263  *              empty or only whitespace so far).
264  */
265
266 static void set_layout_prefix(struct layout *lyt, const char *prefix)
267 {
268   const char *q, *l;
269
270  if (!prefix || !*prefix)
271     lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
272   else {
273     lyt->prefix = prefix;
274     l = lyt->pfxlim = prefix + strlen(prefix);
275     SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
276   }
277 }
278
279 /* --- @init_layout@ --- *
280  *
281  * Arguments:   @struct layout *lyt@ = layout state to initialize
282  *              @FILE *fp@ = output file
283  *              @const char *prefix@ = prefix string (or null if empty)
284  *
285  * Returns:     ---
286  *
287  * Use:         Initialize a layout state.
288  */
289
290 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
291 {
292   lyt->fp = fp;
293   lyt->f = LYTF_NEWL;
294   dstr_create(&lyt->w); dstr_create(&lyt->ctrl);
295   set_layout_prefix(lyt, prefix);
296 }
297
298 /* --- @destroy_layout@ --- *
299  *
300  * Arguments:   @struct layout *lyt@ = layout state
301  *              @unsigned f@ = flags (@DLF_...@)
302  *
303  * Returns:     ---
304  *
305  * Use:         Releases a layout state and the resources it holds.
306  *              Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
307  *              it open (in case it's @stderr@ or something).
308  */
309
310 #define DLF_CLOSE 1u
311 static void destroy_layout(struct layout *lyt, unsigned f)
312 {
313   if (f&DLF_CLOSE) fclose(lyt->fp);
314   dstr_destroy(&lyt->w); dstr_destroy(&lyt->ctrl);
315 }
316
317 /* --- @layout_char@ --- *
318  *
319  * Arguments:   @struct layout *lyt@ = layout state
320  *              @int ch@ = character to write
321  *
322  * Returns:     Zero on success, @-1@ on failure.
323  *
324  * Use:         Write a single character to the output.
325  */
326
327 static int layout_char(struct layout *lyt, int ch)
328 {
329   if (ch == '\n') {
330     if (lyt->f&LYTF_NEWL) PUT_PFXINB;
331     PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
332   } else if (isspace(ch))
333     DPUTC(&lyt->w, ch);
334   else {
335     if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
336     PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
337   }
338   return (0);
339 }
340
341 /* --- @layout_string@ --- *
342  *
343  * Arguments:   @struct layout *lyt@ = layout state
344  *              @const char *p@ = string to write
345  *              @size_t sz@ = length of string
346  *
347  * Returns:     Zero on success, @-1@ on failure.
348  *
349  * Use:         Write a string to the output.
350  */
351
352 static int layout_string(struct layout *lyt, const char *p, size_t sz)
353 {
354   const char *q, *r, *l = p + sz;
355
356   /* This is rather vexing.  There are a small number of jobs to do, but the
357    * logic for deciding which to do when gets rather hairy if, as I've tried
358    * here, one aims to minimize the number of decisions being checked, so
359    * it's worth canning them into macros.
360    *
361    * Here, a `blank' is a whitespace character other than newline.  The input
362    * buffer consists of one or more `segments', each of which consists of:
363    *
364    *   * an initial portion, which is either empty or ends with a nonblank
365    *     character;
366    *
367    *   * a suffix which consists only of blanks; and
368    *
369    *   * an optional newline.
370    *
371    * All segments except the last end with a newline.
372    */
373
374 #define SPLIT_SEGMENT do {                                              \
375   /* Determine the bounds of the current segment.  If there is a final  \
376    * newline, then q is non-null and points to this newline; otherwise, \
377    * q is null.  The initial portion of the segment lies between p .. r \
378    * and the blank suffix lies between r .. q (or r .. l if q is null). \
379    * This sounds awkward, but the suffix is only relevant if there is   \
380    * no newline.                                                        \
381    */                                                                   \
382                                                                         \
383   q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l);             \
384 } while (0)
385
386 #define PUT_NONBLANK do {                                               \
387   /* Output the initial portion of the segment. */                      \
388                                                                         \
389   PUT_RANGE(p, r);                                                      \
390 } while (0)
391
392 #define PUT_NEWLINE do {                                                \
393   /* Write a newline, and advance to the next segment. */               \
394                                                                         \
395   PUT_CHAR('\n'); p = q + 1;                                            \
396 } while (0)
397
398 #define SAVE_TAIL do {                                                  \
399   /* Save the trailing blank portion of the segment in the buffer.      \
400    * Assumes that there is no newline, since otherwise the suffix would \
401    * be omitted.                                                        \
402    */                                                                   \
403                                                                         \
404   DPUTM(&lyt->w, r, l - r);                                             \
405 } while (0)
406
407   /* Determine the bounds of the first segment.  Handling this is the most
408    * complicated part of this function.
409    */
410   SPLIT_SEGMENT;
411
412   if (!q) {
413     /* This is the only segment.  We'll handle the whole thing here.
414      *
415      * If there's an initial nonblank portion, then we need to write that
416      * out.  Furthermore, if we're at the start of the line then we'll need
417      * to write the prefix, and if there's saved blank material then we'll
418      * need to write that.  Otherwise, there's only blank stuff, which we
419      * accumulate in the buffer.
420      *
421      * If we're at the start of a line here, then put the prefix followed by
422      * any saved whitespace, and then our initial nonblank portion.  Then
423      * save our new trailing space.
424      */
425
426     if (r > p) {
427       if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
428       PUT_SAVED; PUT_CTRL; PUT_NONBLANK;
429       DRESET(&lyt->w); DRESET(&lyt->ctrl);
430     }
431     SAVE_TAIL;
432     return (0);
433   }
434
435   /* There is at least one more segment, so we know that there'll be a line
436    * to output.
437    */
438   if (r > p) {
439     if (lyt->f&LYTF_NEWL) PUT_PREFIX;
440     PUT_SAVED; PUT_CTRL; PUT_NONBLANK;
441     DRESET(&lyt->ctrl);
442   } else if (lyt->f&LYTF_NEWL)
443     PUT_PFXINB;
444   PUT_NEWLINE; DRESET(&lyt->w);
445   SPLIT_SEGMENT;
446
447   /* Main loop over whole segments with trailing newlines.  For each one, we
448    * know that we're starting at the beginning of a line and there's a final
449    * newline, so we write the initial prefix and drop the trailing blanks.
450    */
451   while (q) {
452     if (r > p) { PUT_PREFIX; PUT_CTRL; PUT_NONBLANK; DRESET(&lyt->ctrl); }
453     else PUT_PFXINB;
454     PUT_NEWLINE;
455     SPLIT_SEGMENT;
456   }
457
458   /* At the end, there's no final newline.  If there's nonblank material,
459    * then we can write the prefix and the nonblank stuff.  Otherwise, stash
460    * the blank stuff (including the trailing blanks of the prefix) and leave
461    * the newline flag set.
462    */
463   if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
464   else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
465   SAVE_TAIL;
466
467 #undef SPLIT_SEGMENT
468 #undef PUT_NONBLANK
469 #undef PUT_NEWLINE
470 #undef SAVE_TAIL
471
472   return (0);
473 }
474
475 #undef SPLIT_RANGE
476 #undef PUT_RANGE
477 #undef PUT_PREFIX
478 #undef PUT_PFXINB
479 #undef PUT_SAVED
480 #undef PUT_CHAR
481 #undef SAVE_PFXTAIL
482
483 /*----- Human-readable output ---------------------------------------------*/
484
485 #define GENATTR(want, attrs, fgspc, fgcol, bgspc, bgcol)                \
486   { (want), (want),                                                     \
487     { ((fgspc) << TTAF_FGSPCSHIFT) |                                    \
488       ((bgspc) << TTAF_BGSPCSHIFT) | (attrs),                           \
489       0, (fgcol), (bgcol) } }
490
491 #define FGBG(want, attrs, fgcol, bgcol)                                 \
492   GENATTR(want | TTACF_FG | TTACF_BG, attrs,                            \
493           TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_1BPCBR, TTCOL_##bgcol)
494 #define FG(want, attrs, fgcol)                                          \
495   GENATTR(want | TTACF_FG, attrs,                                       \
496           TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_NONE, 0)
497 #define BG(want, attrs, bgcol)                                          \
498   GENATTR(want | TTACF_BG, attrs,                                       \
499           TTCSPC_NONE, 0, TTCSPC_1BPCBR, TTCOL_##bgcol)
500 #define ATTR(want, attrs)                                               \
501   GENATTR(want, attrs, TTCSPC_NONE, 0, TTCSPC_NONE, 0)
502
503 #define BOLD (TTWT_BOLD << TTAF_WTSHIFT)
504
505 static const struct tty_attrlist
506   loc_attrs[] = { FG(0, 0, CYN), TTY_ATTRLIST_CLEAR },
507   locsep_attrs[] = { FG(0, 0, BRBLU), TTY_ATTRLIST_CLEAR },
508   note_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR },
509   err_attrs[] = { FG(0, BOLD, MGN), TTY_ATTRLIST_CLEAR },
510   unklv_attrs[] = { FGBG(0, BOLD, BRWHT, BRRED), ATTR(0, TTAF_INVV) },
511   vfound_attrs[] = { FG(0, 0, RED), TTY_ATTRLIST_CLEAR },
512   vexpect_attrs[] = { FG(0, 0, GRN), TTY_ATTRLIST_CLEAR },
513   vunset_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR },
514   lose_attrs[] = { FG(0, BOLD, RED), ATTR(0, BOLD | TTAF_INVV) },
515   skip_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR },
516   xfail_attrs[] = { FG(0, BOLD, BLU), ATTR(0, BOLD) },
517   win_attrs[] = { FG(0, 0, GRN), TTY_ATTRLIST_CLEAR },
518   sblose_attrs[] = { FG(0, BOLD, RED), ATTR(0, BOLD) };
519
520 #undef GENATTR
521 #undef FGBG
522 #undef FG
523 #undef BG
524 #undef ATTR
525 #undef BOLD
526
527 /* Predefined attributes. */
528 #define HIGHLIGHTS(_st, _)                                              \
529   _(_st, LOCFN,    "lf", loc_attrs)     /* location filename */         \
530   _(_st, LOCLN,    "ln", loc_attrs)     /* location line number */      \
531   _(_st, LOCSEP,   "ls", locsep_attrs)  /* location separator `:' */    \
532   _(_st, INFO,     "mi", 0)             /* information */               \
533   _(_st, NOTE,     "mn", note_attrs)    /* notices */                   \
534   _(_st, ERR,      "me", err_attrs)     /* error messages */            \
535   _(_st, UNKLV,    "mu", unklv_attrs)   /* unknown-level messages */    \
536   _(_st, DSINPUT,  "di", 0)             /* disposition for input value */ \
537   _(_st, DSOUTPUT, "do", 0)             /* ... unsolicited output */    \
538   _(_st, DSMATCH,  "dm", 0)             /* ... matching output */       \
539   _(_st, DSFOUND,  "df", 0)             /* ... incorrect output */      \
540   _(_st, DSEXPECT, "dx", 0)             /* ... reference output */      \
541   _(_st, RNINPUT,  "ri", 0)             /* register name for input value */ \
542   _(_st, RNOUTPUT, "ro", 0)             /* ... unsolicited output */    \
543   _(_st, RNMATCH,  "rm", 0)             /* ... matching output */       \
544   _(_st, RNFOUND,  "rf", 0)             /* ... incorrect output */      \
545   _(_st, RNEXPECT, "rx", 0)             /* ... reference output */      \
546   _(_st, VINPUT,   "vi", 0)             /* input value */               \
547   _(_st, VOUTPUT,  "vo", 0)             /* unsolicited output value */  \
548   _(_st, VMATCH,   "vm", 0)             /* matching output value */     \
549   _(_st, VFOUND,   "vf", vfound_attrs)  /* incorrect output value */    \
550   _(_st, VEXPECT,  "vx", vexpect_attrs) /* reference output value */    \
551   _(_st, VUNSET,   "vu", vunset_attrs)  /* register not set */          \
552   _(_st, LOSE,     "ol", lose_attrs)    /* report failure */            \
553   _(_st, SKIP,     "os", skip_attrs)    /* report a skipped test/group */ \
554   _(_st, XFAIL,    "ox", xfail_attrs)   /* report expected fail */      \
555   _(_st, WIN,      "ow", win_attrs)     /* report success */            \
556   _(_st, SBLOSE,   "sl", sblose_attrs)  /* scoreboard failure */        \
557   _(_st, SBSKIP,   "ss", skip_attrs)    /* scoreboard skipped test */   \
558   _(_st, SBXFAIL,  "sx", xfail_attrs)   /* scoreboard xfail */          \
559   _(_st, SBWIN,    "sw", 0)             /* scoreboard success */
560
561 TTYCOLOUR_DEFENUM(HIGHLIGHTS, HL_);
562 #define HL_PLAIN (-1)
563
564 /* Scoreboard indicators. */
565 static const char scoreboard[] = { 'x', '_', 'o', '.' };
566
567 struct human_output {
568   struct tvec_output _o;                /* output base class */
569   struct tvec_state *tv;                /* stashed testing state */
570   arena *a;                             /* arena for memory allocation */
571   struct tty *tty;                      /* output terminal, or null */
572   struct layout lyt;                    /* output layout */
573   char *outbuf; size_t outsz;           /* buffer for formatted output */
574   dstr scoreboard;                      /* history of test group results */
575   struct tty_attr attr[HL__LIMIT];      /* highlight attribute map */
576   int maxlen;                           /* longest register name */
577   unsigned f;                           /* flags */
578                                         /*   bits 0--7 from @TVHF_...@ */
579 #define HOF_DUPERR 0x0100u              /*   duplicate errors to stderr */
580 #define HOF_PROGRESS 0x0200u            /*   progress display is active */
581 };
582
583 /* --- @setattr@, @setattr_layout@ --- *
584  *
585  * Arguments:   @struct human_output *h@ = output state
586  *              @int hi@ = highlight code to set
587  *
588  * Returns:     ---
589  *
590  * Use:         Send a control sequence to the output stream so that
591  *              subsequent text is printed with the given attributes.
592  */
593
594 static void setattr_common(struct human_output *h,
595                            const struct gprintf_ops *gops, void *go, int hl)
596 {
597   if (h->f&TVHF_COLOUR)
598     tty_setattrg(h->tty, gops, go, hl >= 0 ? &h->attr[hl] : 0);
599 }
600
601 static void setattr(struct human_output *h, int hl)
602   { setattr_common(h, &file_printops, h->lyt.fp, hl); }
603
604 static void setattr_layout(struct human_output *h, int hl)
605   { setattr_common(h, &dstr_printops, &h->lyt.ctrl, hl); }
606
607 /* --- @clear_progress@ --- *
608  *
609  * Arguments:   @struct human_output *h@ = output state
610  *
611  * Returns:     ---
612  *
613  * Use:         Remove the progress display from the terminal.
614  *
615  *              If the progress display isn't active then do nothing.
616  */
617
618 static void clear_progress(struct human_output *h)
619 {
620   size_t i, n;
621
622   if (h->f&HOF_PROGRESS) {
623     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
624     for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
625     h->f &= ~HOF_PROGRESS;
626   }
627 }
628
629 /* --- @write_scoreboard_char@ --- *
630  *
631  * Arguments:   @struct human_output *h@ = output state
632  *              @int ch@ = scoreboard character to print
633  *
634  * Returns:     ---
635  *
636  * Use:         Write a scoreboard character, indicating the outcome of a
637  *              test, to the output stream, with appropriate highlighting.
638  */
639
640 static void write_scoreboard_char(struct human_output *h, int ch)
641 {
642   assert(0 <= ch && ch < TVOUT_LIMIT);
643   setattr(h, HL_SBLOSE + ch); putc(scoreboard[ch], h->lyt.fp);
644 }
645
646 /* --- @show_progress@ --- *
647  *
648  * Arguments:   @struct human_output *h@ = output state
649  *
650  * Returns:     ---
651  *
652  * Use:         Show the progress display, with the record of outcomes for
653  *              the current test group.
654  *
655  *              If the progress display is already active, or the output
656  *              stream is not interactive, then nothing happens.
657  */
658
659 static void show_progress(struct human_output *h)
660 {
661   struct tvec_state *tv = h->tv;
662   const char *p, *l;
663
664   if (tv->test && (h->f&TVHF_TTY) && !(h->f&HOF_PROGRESS)) {
665     fprintf(h->lyt.fp, "%s: ", tv->test->name);
666     for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
667       write_scoreboard_char(h, *p);
668     setattr(h, HL_PLAIN);
669     fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
670   }
671 }
672
673 /* --- @human_writech@, @human_write@, @human_writef@ --- *
674  *
675  * Arguments:   @void *go@ = output sink, secretly a @struct human_output@
676  *              @int ch@ = character to write
677  *              @const char *@p@, @size_t sz@ = string (with explicit length)
678  *                      to write
679  *              @const char *p, ...@ = format control string and arguments to
680  *                      write
681  *
682  * Returns:     ---
683  *
684  * Use:         Write characters, strings, or formatted strings to the
685  *              output, applying appropriate layout.
686  *
687  *              For the human output driver, the layout machinery just strips
688  *              trailing spaces.
689  */
690
691 static int human_writech(void *go, int ch)
692   { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
693
694 static int human_writem(void *go, const char *p, size_t sz)
695   { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
696
697 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
698 {
699   struct human_output *h = go;
700   size_t n;
701   va_list ap;
702
703   va_start(ap, p);
704   n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
705   va_end(ap);
706   if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
707   return (n);
708 }
709
710 static const struct gprintf_ops human_printops =
711   { human_writech, human_writem, human_nwritef };
712
713 /* --- @human_bsession@ --- *
714  *
715  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
716  *                       @struct human_output@
717  *              @struct tvec_state *tv@ = the test state producing output
718  *
719  * Returns:     ---
720  *
721  * Use:         Begin a test session.
722  *
723  *              The human driver just records the test state for later
724  *              reference.
725  */
726
727 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
728   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
729
730 /* --- @human_report_unusual@ --- *
731  *
732  * Arguments:   @struct human_output *h@ = output sink
733  *              @unsigned nxfail, nskip@ = number of expected failures and
734  *                      skipped tests
735  *
736  * Returns:     ---
737  *
738  * Use:         Write (directly on the output stream) a note about expected
739  *              failures and/or skipped tests, if there were any.
740  */
741
742 static void human_report_unusual(struct human_output *h,
743                                  unsigned nxfail, unsigned nskip)
744 {
745   unsigned f = 0;
746 #define f_any 1u
747
748   if (nxfail) {
749     fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nxfail);
750     setattr(h, HL_XFAIL);
751     fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
752     setattr(h, HL_PLAIN);
753     f |= f_any;
754   }
755
756   if (nskip) {
757     fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nskip);
758     setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN);
759     f |= f_any;
760   }
761
762   if (f&f_any) fputc(')', h->lyt.fp);
763
764 #undef f_any
765 }
766
767 /* --- @human_esession@ --- *
768  *
769  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
770  *                       @struct human_output@
771  *
772  * Returns:     Suggested exit code.
773  *
774  * Use:         End a test session.
775  *
776  *              The human driver prints a final summary of the rest results
777  *              and returns a suitable exit code.
778  */
779
780 static int human_esession(struct tvec_output *o)
781 {
782   struct human_output *h = (struct human_output *)o;
783   struct tvec_state *tv = h->tv;
784   unsigned
785     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
786     all_xfail = tv->all[TVOUT_XFAIL],
787     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
788     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
789     all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
790     grps_run = grps_win + grps_lose;
791
792   if (!all_lose) {
793     setattr(h, HL_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HL_PLAIN);
794     fprintf(h->lyt.fp, " %s%u %s",
795             !(all_skip || grps_skip) ? "all " : "",
796             all_pass, all_pass == 1 ? "test" : "tests");
797     human_report_unusual(h, all_xfail, all_skip);
798     fprintf(h->lyt.fp, " in %u %s",
799             grps_win, grps_win == 1 ? "group" : "groups");
800     human_report_unusual(h, 0, grps_skip);
801   } else {
802     setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN);
803     fprintf(h->lyt.fp, " %u out of %u %s",
804             all_lose, all_run, all_run == 1 ? "test" : "tests");
805     human_report_unusual(h, all_xfail, all_skip);
806     fprintf(h->lyt.fp, " in %u out of %u %s",
807             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
808     human_report_unusual(h, 0, grps_skip);
809   }
810   fputc('\n', h->lyt.fp);
811
812   if (tv->f&TVSF_ERROR) {
813     setattr(h, HL_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HL_PLAIN);
814     fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
815   }
816
817   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
818 }
819
820 /* --- @human_bgroup@ --- *
821  *
822  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
823  *                       @struct human_output@
824  *
825  * Returns:     ---
826  *
827  * Use:         Begin a test group.
828  *
829  *              The human driver determines the length of the longest
830  *              register name, resets the group progress scoreboard, and
831  *              activates the progress display.
832  */
833
834 static void human_bgroup(struct tvec_output *o)
835 {
836   struct human_output *h = (struct human_output *)o;
837
838   h->maxlen = register_maxnamelen(h->tv);
839   dstr_reset(&h->scoreboard); show_progress(h);
840 }
841
842 /* --- @human_skipgroup@ --- *
843  *
844  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
845  *                       @struct human_output@
846  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
847  *                      group, or null
848  *
849  * Returns:     ---
850  *
851  * Use:         Report that a test group is being skipped.
852  *
853  *              The human driver just reports the situation to its output
854  *              stream.
855  */
856
857 static void human_skipgroup(struct tvec_output *o,
858                             const char *excuse, va_list *ap)
859 {
860   struct human_output *h = (struct human_output *)o;
861
862   if (!(h->f&TVHF_TTY))
863     fprintf(h->lyt.fp, "%s ", h->tv->test->name);
864   else {
865     show_progress(h); h->f &= ~HOF_PROGRESS;
866     if (h->scoreboard.len) putc(' ', h->lyt.fp);
867   }
868   setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN);
869   if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
870   fputc('\n', h->lyt.fp);
871 }
872
873 /* --- @human_egroup@ --- *
874  *
875  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
876  *                       @struct human_output@
877  *
878  * Returns:     ---
879  *
880  * Use:         Report that a test group has finished.
881  *
882  *              The human driver reports a summary of the group's tests.
883  */
884
885 static void human_egroup(struct tvec_output *o)
886 {
887   struct human_output *h = (struct human_output *)o;
888   struct tvec_state *tv = h->tv;
889   unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
890     lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
891     run = win + lose + xfail;
892
893   if (h->f&TVHF_TTY) h->f &= ~HOF_PROGRESS;
894   else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
895
896   if (lose) {
897     fprintf(h->lyt.fp, " %u/%u ", lose, run);
898     setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN);
899     human_report_unusual(h, xfail, skip);
900   } else {
901     fputc(' ', h->lyt.fp); setattr(h, HL_WIN);
902     fputs("ok", h->lyt.fp); setattr(h, HL_PLAIN);
903     human_report_unusual(h, xfail, skip);
904   }
905   fputc('\n', h->lyt.fp);
906 }
907
908 /* --- @human_btest@ --- *
909  *
910  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
911  *                       @struct human_output@
912  *
913  * Returns:     ---
914  *
915  * Use:         Report that a test is starting.
916  *
917  *              The human driver makes sure the progress display is active.
918  */
919
920 static void human_btest(struct tvec_output *o)
921   { struct human_output *h = (struct human_output *)o; show_progress(h); }
922
923 /* --- @human_report_location@ --- *
924  *
925  * Arguments:   @struct human_output *h@ = output state
926  *              @FILE *fp@ = stream to write the location on
927  *              @const char *file@ = filename
928  *              @unsigned lno@ = line number
929  *
930  * Returns:     ---
931  *
932  * Use:         Print the filename and line number to the output stream @fp@.
933  *              Also, if appropriate, print interleaved highlighting control
934  *              codes to our usual output stream.  If @file@ is null then do
935  *              nothing.
936  */
937
938 static void human_report_location(struct human_output *h, FILE *fp,
939                                   const char *file, unsigned lno)
940 {
941   if (!file)
942     /* nothing to do */;
943   else if (fp != h->lyt.fp || !(h->f&TVHF_COLOUR))
944     fprintf(fp, "%s:%u: ", file, lno);
945   else {
946     setattr(h, HL_LOCFN);       fputs(file, fp);
947     setattr(h, HL_LOCSEP);      fputc(':', fp);
948     setattr(h, HL_LOCLN);       fprintf(fp, "%u", lno);
949     setattr(h, HL_LOCSEP);      fputc(':', fp);
950     setattr(h, HL_PLAIN);       fputc(' ', fp);
951   }
952 }
953
954 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
955  *
956  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
957  *                       @struct human_output@
958  *              @unsigned attr@ = attribute to apply to the outcome
959  *              @const char *outcome@ = outcome string to report
960  *              @const char *detail@, @va_list *ap@ = a detail message
961  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
962  *                      test
963  *
964  * Returns:     ---
965  *
966  * Use:         Report that a test has been skipped or failed.
967  *
968  *              The human driver reports the situation on its output stream.
969  */
970
971 static void human_outcome(struct tvec_output *o,
972                           unsigned attr, const char *outcome,
973                           const char *detail, va_list *ap)
974 {
975   struct human_output *h = (struct human_output *)o;
976   struct tvec_state *tv = h->tv;
977
978   clear_progress(h);
979   human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
980   fprintf(h->lyt.fp, "`%s' ", tv->test->name);
981   setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HL_PLAIN);
982   if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
983   fputc('\n', h->lyt.fp);
984 }
985
986 static void human_skip(struct tvec_output *o,
987                        const char *excuse, va_list *ap)
988   { human_outcome(o, HL_SKIP, "skipped", excuse, ap); }
989 static void human_fail(struct tvec_output *o,
990                        const char *detail, va_list *ap)
991   { human_outcome(o, HL_LOSE, "FAILED", detail, ap); }
992
993 /* --- @human_dumpreg@ --- *
994  *
995  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
996  *                       @struct human_output@
997  *              @unsigned disp@ = register disposition
998  *              @const union tvec_regval *rv@ = register value
999  *              @const struct tvec_regdef *rd@ = register definition
1000  *
1001  * Returns:     ---
1002  *
1003  * Use:         Dump a register.
1004  *
1005  *              The human driver applies highlighting to mismatching output
1006  *              registers, but otherwise delegates to the register type
1007  *              handler and the layout machinery.
1008  */
1009
1010 static void human_dumpreg(struct tvec_output *o,
1011                           unsigned disp, const union tvec_regval *rv,
1012                           const struct tvec_regdef *rd)
1013 {
1014   struct human_output *h = (struct human_output *)o;
1015   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1016
1017   clear_progress(h);
1018   gprintf(&human_printops, h, "%*s", 10 + h->maxlen - n, "");
1019   setattr_layout(h, HL_DSINPUT + disp);
1020   gprintf(&human_printops, h, "%s", ds);
1021   setattr(h, HL_PLAIN); layout_char(&h->lyt, ' ');
1022   setattr_layout(h, HL_RNINPUT + disp);
1023   gprintf(&human_printops, h, "%s", rd->name);
1024   setattr(h, HL_PLAIN); gprintf(&human_printops, h, " = ");
1025   if (!rv) {
1026     setattr_layout(h, HL_VUNSET);
1027     gprintf(&human_printops, h, "#unset");
1028   } else {
1029     setattr_layout(h, HL_VINPUT + disp);
1030     rd->ty->dump(rv, rd, 0, &human_printops, h);
1031   }
1032   setattr(h, HL_PLAIN); layout_char(&h->lyt, '\n');
1033 }
1034
1035 /* --- @human_etest@ --- *
1036  *
1037  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1038  *                       @struct human_output@
1039  *              @unsigned outcome@ = the test outcome
1040  *
1041  * Returns:     ---
1042  *
1043  * Use:         Report that a test has finished.
1044  *
1045  *              The human driver reactivates the progress display, if
1046  *              necessary, and adds a new character for the completed test.
1047  */
1048
1049 static void human_etest(struct tvec_output *o, unsigned outcome)
1050 {
1051   struct human_output *h = (struct human_output *)o;
1052
1053   if (h->f&TVHF_TTY) {
1054     show_progress(h);
1055     dstr_putc(&h->scoreboard, outcome); write_scoreboard_char(h, outcome);
1056     setattr(h, HL_PLAIN); fflush(h->lyt.fp);
1057   }
1058 }
1059
1060 /* --- @human_report@ --- *
1061  *
1062  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1063  *                       @struct human_output@
1064  *              @unsigned level@ = message level (@TVLV_...@)
1065  *              @const char *msg@, @va_list *ap@ = format string and
1066  *                      arguments
1067  *
1068  * Returns:     ---
1069  *
1070  * Use:         Report a message to the user.
1071  *
1072  *              The human driver arranges to show the message on @stderr@ as
1073  *              well as the usual output, with a certain amount of
1074  *              intelligence in case they're both actually the same device.
1075  */
1076
1077 static void human_report(struct tvec_output *o, unsigned level,
1078                          const char *msg, va_list *ap)
1079 {
1080   struct human_output *h = (struct human_output *)o;
1081   struct tvec_state *tv = h->tv;
1082   const char *levstr; unsigned levhl;
1083   dstr d = DSTR_INIT;
1084   unsigned f = 0;
1085 #define f_progress 1u
1086
1087   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1088
1089   switch (level) {
1090 #define CASE(tag, name, val)                                            \
1091     case TVLV_##tag: levstr = name; levhl = HL_##tag; break;
1092     TVEC_LEVELS(CASE)
1093     default: levstr = "??"; levhl = HL_UNKLV; break;
1094   }
1095
1096   if (h->f&HOF_PROGRESS) { clear_progress(h); f |= f_progress; }
1097
1098   if (h->f&HOF_DUPERR) {
1099     fprintf(stderr, "%s: ", QUIS);
1100     human_report_location(h, stderr, tv->infile, tv->lno);
1101     fprintf(stderr, "%s: ", levstr);
1102     fwrite(d.buf, 1, d.len, stderr);
1103   }
1104
1105   human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
1106   setattr(h, levhl); fputs(levstr, h->lyt.fp);
1107   setattr(h, HL_PLAIN); fputs(": ", h->lyt.fp);
1108   fwrite(d.buf, 1, d.len, h->lyt.fp);
1109
1110   if (f&f_progress) show_progress(h);
1111   dstr_destroy(&d);
1112
1113 #undef f_progress
1114 }
1115
1116 /* --- @human_bbench@ --- *
1117  *
1118  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1119  *                       @struct human_output@
1120  *              @const char *desc@ = adhoc test description
1121  *              @unsigned unit@ = measurement unit (@BTU_...@)
1122  *
1123  * Returns:     ---
1124  *
1125  * Use:         Report that a benchmark has started.
1126  *
1127  *              The human driver just prints the start of the benchmark
1128  *              report.
1129  */
1130
1131 static void human_bbench(struct tvec_output *o,
1132                          const char *desc, unsigned unit)
1133 {
1134   struct human_output *h = (struct human_output *)o;
1135   struct tvec_state *tv = h->tv;
1136
1137   clear_progress(h);
1138   gprintf(&human_printops, h, "%s ", tv->test->name);
1139   if (desc) gprintf(&human_printops, h, "%s", desc);
1140   else print_ident(tv, TVSF_COMPACT, &human_printops, h);
1141   gprintf(&human_printops, h, ": ");
1142   if (h->f&TVHF_TTY) fflush(h->lyt.fp);
1143 }
1144
1145 /* --- @human_ebench@ --- *
1146  *
1147  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1148  *                       @struct human_output@
1149  *              @const char *desc@ = adhoc test description
1150  *              @unsigned unit@ = measurement unit (@BTU_...@)
1151  *              @const struct bench_timing *t@ = measurement
1152  *
1153  * Returns:     ---
1154  *
1155  * Use:         Report a benchmark's results.
1156  *
1157  *              The human driver just delegates to the default benchmark
1158  *              reporting, via the layout machinery.
1159  */
1160
1161 static void human_ebench(struct tvec_output *o,
1162                          const char *desc, unsigned unit,
1163                          const struct bench_timing *t)
1164 {
1165   struct human_output *h = (struct human_output *)o;
1166
1167   tvec_benchreport(&human_printops, h, unit, 0, t);
1168   layout_char(&h->lyt, '\n');
1169 }
1170
1171 static const struct tvec_benchoutops human_benchops =
1172   { human_bbench, human_ebench };
1173
1174 /* --- @human_extend@ --- *
1175  *
1176  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1177  *                       @struct human_output@
1178  *              @const char *name@ = extension name
1179  *
1180  * Returns:     A pointer to the extension implementation, or null.
1181  */
1182
1183 static const void *human_extend(struct tvec_output *o, const char *name)
1184 {
1185   if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
1186   else return (0);
1187 }
1188
1189 /* --- @human_destroy@ --- *
1190  *
1191  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1192  *                       @struct human_output@
1193  *
1194  * Returns:     ---
1195  *
1196  * Use:         Release the resources held by the output driver.
1197  */
1198
1199 static void human_destroy(struct tvec_output *o)
1200 {
1201   struct human_output *h = (struct human_output *)o;
1202
1203   tty_close(h->tty);
1204   destroy_layout(&h->lyt,
1205                  h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1206   dstr_destroy(&h->scoreboard);
1207   x_free(h->a, h->outbuf); x_free(h->a, h);
1208 }
1209
1210 static const struct tvec_outops human_ops = {
1211   human_bsession, human_esession,
1212   human_bgroup, human_skipgroup, human_egroup,
1213   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1214   human_report, human_extend, human_destroy
1215 };
1216
1217 /* --- @tvec_humanoutput@ --- *
1218  *
1219  * Arguments:   @FILE *fp@ = output file to write on
1220  *              @unsigned f, m@ = flags and mask
1221  *
1222  * Returns:     An output formatter.
1223  *
1224  * Use:         Return an output formatter which writes on @fp@ with the
1225  *              expectation that a human will interpret the output.
1226  *
1227  *              The flags @f@ and mask @m@ operate together.  Flag bits not
1228  *              covered by the mask must be left clear, i.e., @f&~m$ must be
1229  *              zero; the semantics are that a set mask bit indicates that
1230  *              the corresponding bit of @f@ should control the indicated
1231  *              behaviour; a clear mask bit indicates that a suitable default
1232  *              should be chosen based on environmental conditions.
1233  *
1234  *              If @TVHF_TTY@ is set, then the output shows a `scoreboard'
1235  *              indicating the outcome of each test case attempted, providing
1236  *              a visual indication of progress.  If @TVHF_COLOUR@ is set,
1237  *              then the output uses control codes for colour and other
1238  *              highlighting.  It is unusual to set @TVHF_COLOUR@ without
1239  *              @TVHF_TTY@, this is permitted anyway.
1240  *
1241  *              The environment variables %|TVEC_TTY|% and %|TVEC_COLOUR|%
1242  *              provide default values for these settings.  If they are not
1243  *              set, then @TVHF_TTY@ is set if @fp@ refers to a terminal, and
1244  *              @TVHF_COLOUR@ is set if @TVHF_TTY@ is set and, additionally,
1245  *              the %|TERM|% environment variable is set to a value other
1246  *              than %|dumb|%.
1247  */
1248
1249 struct tvec_output *tvec_humanoutput(FILE *fp, unsigned f, unsigned m)
1250 {
1251   struct human_output *h;
1252   struct stat st_out, st_err;
1253   int rc_out, rc_err;
1254
1255   static const struct ttycolour_style hltab[] =
1256     TTYCOLOUR_INITTAB(HIGHLIGHTS);
1257
1258   assert(!(f&~m));
1259
1260   if (!(m&TVHF_TTY))
1261     switch (getenv_boolean("MLIB_TVEC_TTY", -1)) {
1262       case 1: f |= TVHF_TTY; break;
1263       case 0: break;
1264       default:
1265         if (isatty(fileno(fp))) f |= TVHF_TTY;
1266         break;
1267     }
1268   if (!(m&TVHF_COLOUR))
1269     switch (getenv_boolean("MLIB_TVEC_COLOUR", -1)) {
1270       case 1: f |= TVHF_COLOUR; break;
1271       case 0: break;
1272       default:
1273         if (ttycolour_enablep((f&TVHF_TTY ? TCEF_TTY : 0) | TCEF_DFLT))
1274           f |= TVHF_COLOUR;
1275         break;
1276     }
1277
1278   /* Decide whether to write copies of reports to stderr.
1279    *
1280    * There's not much point if the output is already going to stderr.
1281    * Otherwise, check to see whether stdout is the same underlying file.
1282    */
1283   if (fp != stderr) {
1284     rc_out = fstat(fileno(fp), &st_out);
1285     rc_err = fstat(STDERR_FILENO, &st_err);
1286     if (!rc_err && (rc_out ||
1287                     st_out.st_dev != st_err.st_dev ||
1288                     st_out.st_ino != st_err.st_ino))
1289       f |= HOF_DUPERR;
1290   }
1291
1292   XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
1293   h->f = f;
1294
1295   /* Initialize the colour tables. */
1296   if (!(h->f&TVHF_COLOUR))
1297     h->tty = 0;
1298   else {
1299     h->tty = tty_open(fp, TTF_BORROW, 0);
1300     if (!h->tty)
1301       h->f &= ~TVHF_COLOUR;
1302     else
1303       ttycolour_config(h->attr, "MLIB_TVEC_COLOURS",
1304                        TCIF_GETENV | TCIF_REPORT, h->tty, hltab);
1305   }
1306
1307   init_layout(&h->lyt, fp, 0);
1308   h->outbuf = 0; h->outsz = 0;
1309
1310   dstr_create(&h->scoreboard);
1311   return (&h->_o);
1312 }
1313
1314 /*----- Machine-readable output -------------------------------------------*/
1315
1316 struct machine_output {
1317   struct tvec_output _o;                /* output base class */
1318   struct tvec_state *tv;                /* stashed testing state */
1319   arena *a;                             /* arena for memory allocation */
1320   FILE *fp;                             /* output stream */
1321   char *outbuf; size_t outsz;           /* buffer for formatted output */
1322   unsigned grpix, testix;               /* group and test indices */
1323   unsigned f;                           /* flags */
1324 #define MF_BENCH 1u                     /*   current test is a benchmark */
1325 };
1326
1327 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1328  *
1329  * Arguments:   @void *go@ = output sink, secretly a @struct machine_output@
1330  *              @int ch@ = character to write
1331  *              @const char *@p@, @size_t sz@ = string (with explicit length)
1332  *                      to write
1333  *              @const char *p, ...@ = format control string and arguments to
1334  *                      write
1335  *
1336  * Returns:     ---
1337  *
1338  * Use:         Write characters, strings, or formatted strings to the
1339  *              output, applying appropriate quoting.
1340  */
1341
1342 static void machine_escape(struct machine_output *m, int ch)
1343 {
1344   switch (ch) {
1345     case 0: fputs("\\0", m->fp); break;
1346     case '\a': fputs("\\a", m->fp); break;
1347     case '\b': fputs("\\b", m->fp); break;
1348     case '\x1b': fputs("\\e", m->fp); break;
1349     case '\f': fputs("\\f", m->fp); break;
1350     case '\n': fputs("\\n", m->fp); break;
1351     case '\r': fputs("\\r", m->fp); break;
1352     case '\t': fputs("\\t", m->fp); break;
1353     case '\v': fputs("\\v", m->fp); break;
1354     case '"': fputs("\\\"", m->fp); break;
1355     case '\\': fputs("\\\\", m->fp); break;
1356     default: fprintf(m->fp, "\\x{%02x}", ch);
1357   }
1358 }
1359
1360 static int machine_writech(void *go, int ch)
1361 {
1362   struct machine_output *m = go;
1363
1364   if (ISPRINT(ch)) putc(ch, m->fp);
1365   else machine_escape(m, ch);
1366   return (1);
1367 }
1368
1369 static int machine_writem(void *go, const char *p, size_t sz)
1370 {
1371   struct machine_output *m = go;
1372   const char *q, *l = p + sz;
1373
1374   for (;;) {
1375     q = p;
1376     for (;;) {
1377       if (q >= l) goto final;
1378       if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1379       q++;
1380     }
1381     if (p < q) fwrite(p, 1, q - p, m->fp);
1382     p = q; machine_escape(m, (unsigned char)*p++);
1383   }
1384 final:
1385   if (p < l) fwrite(p, 1, l - p, m->fp);
1386   return (sz);
1387 }
1388
1389 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1390 {
1391   struct machine_output *m = go;
1392   int n;
1393   va_list ap;
1394
1395   va_start(ap, p);
1396   n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
1397   va_end(ap);
1398   return (machine_writem(m, m->outbuf, n));
1399 }
1400
1401 static const struct gprintf_ops machine_printops =
1402   { machine_writech, machine_writem, machine_nwritef };
1403
1404 /* --- @machine_maybe_quote@ --- *
1405  *
1406  * Arguments:   @struct machine_output *m@ = output sink
1407  *              @const char *p@ = pointer to string
1408  *
1409  * Returns:     ---
1410  *
1411  * Use:         Print the string @p@, quoting it if necessary.
1412  */
1413
1414 static void machine_maybe_quote(struct machine_output *m, const char *p)
1415 {
1416   const char *q;
1417
1418   for (q = p; *q; q++)
1419     if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
1420   fputs(p, m->fp); return;
1421 quote:
1422   putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1423 }
1424
1425 /* --- @machine_bsession@ --- *
1426  *
1427  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1428  *                       @struct machine_output@
1429  *              @struct tvec_state *tv@ = the test state producing output
1430  *
1431  * Returns:     ---
1432  *
1433  * Use:         Begin a test session.
1434  *
1435  *              The TAP driver records the test state for later reference,
1436  *              initializes the group index counter, and prints the version
1437  *              number.
1438  */
1439
1440 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1441 {
1442   struct machine_output *m = (struct machine_output *)o;
1443
1444   m->tv = tv; m->grpix = 0;
1445 }
1446
1447 /* --- @machine_show_stats@ --- *
1448  *
1449  * Arguments:   @struct machine_output *m@ = output sink
1450  *              @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1451  *
1452  * Returns:     ---
1453  *
1454  * Use:         Print a machine readable outcome statistics table
1455  */
1456
1457 static void machine_show_stats(struct machine_output *m,
1458                                unsigned out[TVOUT_LIMIT])
1459 {
1460   static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1461   unsigned i;
1462
1463   for (i = 0; i < TVOUT_LIMIT; i++) {
1464     if (i) putc(' ', m->fp);
1465     fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1466   }
1467 }
1468
1469 /* --- @machine_esession@ --- *
1470  *
1471  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1472  *                       @struct machine_output@
1473  *
1474  * Returns:     Suggested exit code.
1475  *
1476  * Use:         End a test session.
1477  *
1478  *              The TAP driver prints a final summary of the rest results
1479  *              and returns a suitable exit code.  If errors occurred, it
1480  *              instead prints a `Bail out!' line forcing the reader to
1481  *              report a failure.
1482  */
1483
1484 static int machine_esession(struct tvec_output *o)
1485 {
1486   struct machine_output *m = (struct machine_output *)o;
1487   struct tvec_state *tv = m->tv;
1488
1489   fputs("END groups: ", m->fp);
1490   machine_show_stats(m, tv->grps);
1491   fputs("; tests: ", m->fp);
1492   machine_show_stats(m, tv->all);
1493   putc('\n', m->fp);
1494   m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1495 }
1496
1497 /* --- @machine_bgroup@ --- *
1498  *
1499  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1500  *                       @struct machine_output@
1501  *
1502  * Returns:     ---
1503  *
1504  * Use:         Begin a test group.
1505  *
1506  *              The TAP driver determines the length of the longest
1507  *              register name, resets the group progress scoreboard, and
1508  *              activates the progress display.
1509  */
1510
1511 static void machine_bgroup(struct tvec_output *o)
1512 {
1513   struct machine_output *m = (struct machine_output *)o;
1514   struct tvec_state *tv = m->tv;
1515
1516   fputs("BGROUP ", m->fp);
1517   machine_maybe_quote(m, tv->test->name);
1518   putc('\n', m->fp);
1519   m->grpix++; m->testix = 0;
1520 }
1521
1522 /* --- @machine_skipgroup@ --- *
1523  *
1524  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1525  *                       @struct machine_output@
1526  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1527  *                      group, or null
1528  *
1529  * Returns:     ---
1530  *
1531  * Use:         Report that a test group is being skipped.
1532  *
1533  *              The TAP driver just reports the situation to its output
1534  *              stream.
1535  */
1536
1537 static void machine_skipgroup(struct tvec_output *o,
1538                           const char *excuse, va_list *ap)
1539 {
1540   struct machine_output *m = (struct machine_output *)o;
1541   struct tvec_state *tv = m->tv;
1542
1543   fputs("SKIPGRP ", m->fp);
1544   machine_maybe_quote(m, tv->test->name);
1545   if (excuse) {
1546     fputs(" \"", m->fp);
1547     vgprintf(&machine_printops, m, excuse, ap);
1548     putc('\"', m->fp);
1549   }
1550   putc('\n', m->fp);
1551 }
1552
1553 /* --- @machine_egroup@ --- *
1554  *
1555  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1556  *                       @struct machine_output@
1557  *
1558  * Returns:     ---
1559  *
1560  * Use:         Report that a test group has finished.
1561  *
1562  *              The TAP driver reports a summary of the group's tests.
1563  */
1564
1565 static void machine_egroup(struct tvec_output *o)
1566 {
1567   struct machine_output *m = (struct machine_output *)o;
1568   struct tvec_state *tv = m->tv;
1569
1570   if (!(tv->f&TVSF_SKIP)) {
1571     fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
1572     putc(' ', m->fp); machine_show_stats(m, tv->curr);
1573     putc('\n', m->fp);
1574   }
1575 }
1576
1577 /* --- @machine_btest@ --- *
1578  *
1579  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1580  *                       @struct machine_output@
1581  *
1582  * Returns:     ---
1583  *
1584  * Use:         Report that a test is starting.
1585  *
1586  *              The TAP driver advances its test counter.  (We could do this
1587  *              by adding up up the counters in @tv->curr@, and add on the
1588  *              current test, but it's easier this way.)
1589  */
1590
1591 static void machine_btest(struct tvec_output *o) { ; }
1592
1593 /* --- @machine_report_location@ --- *
1594  *
1595  * Arguments:   @struct human_output *h@ = output state
1596  *              @const char *file@ = filename
1597  *              @unsigned lno@ = line number
1598  *
1599  * Returns:     ---
1600  *
1601  * Use:         Print the filename and line number to the output stream.
1602  */
1603
1604 static void machine_report_location(struct machine_output *m,
1605                                     const char *file, unsigned lno)
1606 {
1607   if (file) {
1608     putc(' ', m->fp);
1609     machine_maybe_quote(m, file);
1610     fprintf(m->fp, ":%u", lno);
1611   }
1612 }
1613
1614 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1615  *
1616  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1617  *                       @struct machine_output@
1618  *              @const char *outcome@ = outcome string to report
1619  *              @const char *detail@, @va_list *ap@ = a detail message
1620  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1621  *                      test
1622  *
1623  * Returns:     ---
1624  *
1625  * Use:         Report that a test has been skipped or failed.
1626  */
1627
1628 static void machine_outcome(struct tvec_output *o, const char *outcome,
1629                             const char *detail, va_list *ap)
1630 {
1631   struct machine_output *m = (struct machine_output *)o;
1632   struct tvec_state *tv = m->tv;
1633
1634   fprintf(m->fp, "%s %u", outcome, m->testix);
1635   machine_report_location(m, tv->infile, tv->test_lno);
1636   if (detail)
1637     { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1638   putc('\n', m->fp);
1639 }
1640
1641 static void machine_skip(struct tvec_output *o,
1642                          const char *excuse, va_list *ap)
1643   { machine_outcome(o, "SKIP", excuse, ap); }
1644 static void machine_fail(struct tvec_output *o,
1645                          const char *detail, va_list *ap)
1646   { machine_outcome(o, "FAIL", detail, ap); }
1647
1648 /* --- @machine_dumpreg@ --- *
1649  *
1650  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1651  *                       @struct machine_output@
1652  *              @unsigned disp@ = register disposition
1653  *              @const union tvec_regval *rv@ = register value
1654  *              @const struct tvec_regdef *rd@ = register definition
1655  *
1656  * Returns:     ---
1657  *
1658  * Use:         Dump a register.
1659  *
1660  *              The machine driver applies highlighting to mismatching output
1661  *              registers, but otherwise delegates to the register type
1662  *              handler.
1663  */
1664
1665 static void machine_dumpreg(struct tvec_output *o,
1666                         unsigned disp, const union tvec_regval *rv,
1667                         const struct tvec_regdef *rd)
1668 {
1669   struct machine_output *m = (struct machine_output *)o;
1670   unsigned f = 0;
1671 #define f_reg 1u
1672 #define f_nl 2u
1673
1674   switch (disp) {
1675     case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
1676     case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
1677     case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
1678     case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
1679     case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
1680     default: abort();
1681   }
1682
1683   if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
1684   if (!rv) fputs("#unset", m->fp);
1685   else rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
1686   if (f&f_nl) putc('\n', m->fp);
1687
1688 #undef f_reg
1689 #undef f_newline
1690 }
1691
1692 /* --- @machine_etest@ --- *
1693  *
1694  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1695  *                       @struct machine_output@
1696  *              @unsigned outcome@ = the test outcome
1697  *
1698  * Returns:     ---
1699  *
1700  * Use:         Report that a test has finished.
1701  *
1702  *              The machine driver reports the outcome of the test, if that's
1703  *              not already decided.
1704  */
1705
1706 static void machine_etest(struct tvec_output *o, unsigned outcome)
1707 {
1708   struct machine_output *m = (struct machine_output *)o;
1709
1710   if (!(m->f&MF_BENCH)) switch (outcome) {
1711     case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
1712     case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
1713   }
1714   m->testix++; m->f &= ~MF_BENCH;
1715 }
1716
1717 /* --- @machine_report@ --- *
1718  *
1719  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1720  *                       @struct machine_output@
1721  *              @unsigned level@ = message level (@TVLV_...@)
1722  *              @const char *msg@, @va_list *ap@ = format string and
1723  *                      arguments
1724  *
1725  * Returns:     ---
1726  *
1727  * Use:         Report a message to the user.
1728  *
1729  *              Each report level has its own output tag
1730  */
1731
1732 static void machine_report(struct tvec_output *o, unsigned level,
1733                            const char *msg, va_list *ap)
1734 {
1735   struct machine_output *m = (struct machine_output *)o;
1736   struct tvec_state *tv = m->tv;
1737   const char *p;
1738
1739   for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
1740   machine_report_location(m, tv->infile, tv->lno);
1741   fputs(" \"", m->fp); vgprintf(&machine_printops, m, msg, ap);
1742   putc('"', m->fp);
1743   putc('\n', m->fp);
1744 }
1745
1746 /* --- @machine_bbench@ --- *
1747  *
1748  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1749  *                       @struct machine_output@
1750  *              @const char *desc@ = adhoc test description
1751  *              @unsigned unit@ = measurement unit (@BTU_...@)
1752  *
1753  * Returns:     ---
1754  *
1755  * Use:         Report that a benchmark has started.
1756  *
1757  *              The machine driver does nothing here.  All of the reporting
1758  *              happens in @machine_ebench@.
1759  */
1760
1761 static void machine_bbench(struct tvec_output *o,
1762                            const char *desc, unsigned unit)
1763   { ; }
1764
1765 /* --- @machine_ebench@ --- *
1766  *
1767  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1768  *                       @struct machine_output@
1769  *              @const char *desc@ = adhoc test description
1770  *              @unsigned unit@ = measurement unit (@BTU_...@)
1771  *              @const struct bench_timing *t@ = measurement
1772  *
1773  * Returns:     ---
1774  *
1775  * Use:         Report a benchmark's results.
1776  *
1777  *              The machine driver prints the result as a `BENCH' output
1778  *              line, in raw format.
1779  */
1780
1781 static void machine_ebench(struct tvec_output *o,
1782                            const char *desc, unsigned unit,
1783                            const struct bench_timing *t)
1784 {
1785   struct machine_output *m = (struct machine_output *)o;
1786   struct tvec_state *tv = m->tv;
1787
1788   fprintf(m->fp, "BENCH %u", m->testix);
1789   machine_report_location(m, tv->infile, tv->test_lno);
1790   putc(' ', m->fp);
1791   if (desc) machine_maybe_quote(m, desc);
1792   else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
1793   fputs(": ", m->fp);
1794   tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
1795   putc('\n', m->fp); m->f |= MF_BENCH;
1796 }
1797
1798 static const struct tvec_benchoutops machine_benchops =
1799   { machine_bbench, machine_ebench };
1800
1801 /* --- @machine_extend@ --- *
1802  *
1803  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1804  *                       @struct machine_output@
1805  *              @const char *name@ = extension name
1806  *
1807  * Returns:     A pointer to the extension implementation, or null.
1808  */
1809
1810 static const void *machine_extend(struct tvec_output *o, const char *name)
1811 {
1812   if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
1813   else return (0);
1814 }
1815
1816 /* --- @machine_destroy@ --- *
1817  *
1818  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1819  *                       @struct machine_output@
1820  *
1821  * Returns:     ---
1822  *
1823  * Use:         Release the resources held by the output driver.
1824  */
1825
1826 static void machine_destroy(struct tvec_output *o)
1827 {
1828   struct machine_output *m = (struct machine_output *)o;
1829
1830   if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1831   x_free(m->a, m->outbuf); x_free(m->a, m);
1832 }
1833
1834 static const struct tvec_outops machine_ops = {
1835   machine_bsession, machine_esession,
1836   machine_bgroup, machine_skipgroup, machine_egroup,
1837   machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
1838   machine_report, machine_extend, machine_destroy
1839 };
1840
1841 /* --- @tvec_machineoutput@ --- *
1842  *
1843  * Arguments:   @FILE *fp@ = output file to write on
1844  *
1845  * Returns:     An output formatter.
1846  *
1847  * Use:         Return an output formatter which writes on @fp@ in a
1848  *              moderately simple machine-readable format.
1849  */
1850
1851 struct tvec_output *tvec_machineoutput(FILE *fp)
1852 {
1853   struct machine_output *m;
1854
1855   XNEW(m); m->a = arena_global; m->_o.ops = &machine_ops;
1856   m->f = 0; m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
1857   return (&m->_o);
1858 }
1859
1860 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1861
1862 struct tap_output {
1863   struct tvec_output _o;                /* output base class */
1864   struct tvec_state *tv;                /* stashed testing state */
1865   arena *a;                             /* arena for memory allocation */
1866   struct layout lyt;                    /* output layout */
1867   char *outbuf; size_t outsz;           /* buffer for formatted output */
1868   unsigned grpix, testix;               /* group and test indices */
1869   unsigned previx;                      /* previously reported test index */
1870   int maxlen;                           /* longest register name */
1871 };
1872
1873 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1874  *
1875  * Arguments:   @void *go@ = output sink, secretly a @struct tap_output@
1876  *              @int ch@ = character to write
1877  *              @const char *@p@, @size_t sz@ = string (with explicit length)
1878  *                      to write
1879  *              @const char *p, ...@ = format control string and arguments to
1880  *                      write
1881  *
1882  * Returns:     ---
1883  *
1884  * Use:         Write characters, strings, or formatted strings to the
1885  *              output, applying appropriate layout.
1886  *
1887  *              For the TAP output driver, the layout machinery prefixes each
1888  *              line with `    ## ' and strips trailing spaces.
1889  */
1890
1891 static int tap_writech(void *go, int ch)
1892 {
1893   struct tap_output *t = go;
1894
1895   if (layout_char(&t->lyt, ch)) return (-1);
1896   else return (1);
1897 }
1898
1899 static int tap_writem(void *go, const char *p, size_t sz)
1900 {
1901   struct tap_output *t = go;
1902
1903   if (layout_string(&t->lyt, p, sz)) return (-1);
1904   else return (sz);
1905 }
1906
1907 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1908 {
1909   struct tap_output *t = go;
1910   size_t n;
1911   va_list ap;
1912
1913   va_start(ap, p);
1914   n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
1915   va_end(ap);
1916   if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1917   return (n);
1918 }
1919
1920 static const struct gprintf_ops tap_printops =
1921   { tap_writech, tap_writem, tap_nwritef };
1922
1923 /* --- @tap_bsession@ --- *
1924  *
1925  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1926  *                       @struct tap_output@
1927  *              @struct tvec_state *tv@ = the test state producing output
1928  *
1929  * Returns:     ---
1930  *
1931  * Use:         Begin a test session.
1932  *
1933  *              The TAP driver records the test state for later reference,
1934  *              initializes the group index counter, and prints the version
1935  *              number.
1936  */
1937
1938 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1939 {
1940   struct tap_output *t = (struct tap_output *)o;
1941
1942   t->tv = tv; t->grpix = 0;
1943   fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1944 }
1945
1946 /* --- @tap_esession@ --- *
1947  *
1948  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1949  *                       @struct tap_output@
1950  *
1951  * Returns:     Suggested exit code.
1952  *
1953  * Use:         End a test session.
1954  *
1955  *              The TAP driver prints a final summary of the rest results
1956  *              and returns a suitable exit code.  If errors occurred, it
1957  *              instead prints a `Bail out!' line forcing the reader to
1958  *              report a failure.
1959  */
1960
1961 static int tap_esession(struct tvec_output *o)
1962 {
1963   struct tap_output *t = (struct tap_output *)o;
1964   struct tvec_state *tv = t->tv;
1965
1966   if (tv->f&TVSF_ERROR) {
1967     fputs("Bail out!  "
1968           "Errors found in input; tests may not have run correctly\n",
1969           t->lyt.fp);
1970     return (2);
1971   }
1972
1973   fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1974   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1975 }
1976
1977 /* --- @tap_bgroup@ --- *
1978  *
1979  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
1980  *                       @struct tap_output@
1981  *
1982  * Returns:     ---
1983  *
1984  * Use:         Begin a test group.
1985  *
1986  *              The TAP driver determines the length of the longest
1987  *              register name, resets the group progress scoreboard, and
1988  *              activates the progress display.
1989  */
1990
1991 static void tap_bgroup(struct tvec_output *o)
1992 {
1993   struct tap_output *t = (struct tap_output *)o;
1994   struct tvec_state *tv = t->tv;
1995
1996   t->grpix++; t->testix = t->previx = 0;
1997   t->maxlen = register_maxnamelen(t->tv);
1998   fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1999 }
2000
2001 /* --- @tap_skipgroup@ --- *
2002  *
2003  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2004  *                       @struct tap_output@
2005  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
2006  *                      group, or null
2007  *
2008  * Returns:     ---
2009  *
2010  * Use:         Report that a test group is being skipped.
2011  *
2012  *              The TAP driver just reports the situation to its output
2013  *              stream.
2014  */
2015
2016 static void tap_skipgroup(struct tvec_output *o,
2017                           const char *excuse, va_list *ap)
2018 {
2019   struct tap_output *t = (struct tap_output *)o;
2020
2021   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
2022   fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
2023   if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
2024   fputc('\n', t->lyt.fp);
2025 }
2026
2027 /* --- @tap_egroup@ --- *
2028  *
2029  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2030  *                       @struct tap_output@
2031  *
2032  * Returns:     ---
2033  *
2034  * Use:         Report that a test group has finished.
2035  *
2036  *              The TAP driver reports a summary of the group's tests.
2037  */
2038
2039 static void tap_egroup(struct tvec_output *o)
2040 {
2041   struct tap_output *t = (struct tap_output *)o;
2042   struct tvec_state *tv = t->tv;
2043
2044   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
2045   fprintf(t->lyt.fp, "%s %u - %s\n",
2046           tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
2047           t->grpix, tv->test->name);
2048 }
2049
2050 /* --- @tap_btest@ --- *
2051  *
2052  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2053  *                       @struct tap_output@
2054  *
2055  * Returns:     ---
2056  *
2057  * Use:         Report that a test is starting.
2058  *
2059  *              The TAP driver advances its test counter.  (We could do this
2060  *              by adding up up the counters in @tv->curr@, and add on the
2061  *              current test, but it's easier this way.)
2062  */
2063
2064 static void tap_btest(struct tvec_output *o)
2065   { struct tap_output *t = (struct tap_output *)o; t->testix++; }
2066
2067 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2068  *
2069  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2070  *                       @struct tap_output@
2071  *              @const char *head, *tail@ = outcome strings to report
2072  *              @const char *detail@, @va_list *ap@ = a detail message
2073  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
2074  *                      test
2075  *
2076  * Returns:     ---
2077  *
2078  * Use:         Report that a test has been skipped or failed.
2079  *
2080  *              The TAP driver reports the situation on its output stream.
2081  *              TAP only allows us to report a single status for each
2082  *              subtest, so we notice when we've already reported a status
2083  *              for the current test and convert the second report as a
2084  *              comment.  This should only happen in the case of multiple
2085  *              failures.
2086  */
2087
2088 static void tap_outcome(struct tvec_output *o,
2089                         const char *head, const char *tail,
2090                         const char *detail, va_list *ap)
2091 {
2092   struct tap_output *t = (struct tap_output *)o;
2093   struct tvec_state *tv = t->tv;
2094
2095   fprintf(t->lyt.fp, "    %s %u - %s:%u%s",
2096           t->testix == t->previx ? "##" : head,
2097           t->testix, tv->infile, tv->test_lno, tail);
2098   if (detail)
2099     { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2100   fputc('\n', t->lyt.fp);
2101   t->previx = t->testix;
2102 }
2103
2104 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2105   { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
2106 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
2107   { tap_outcome(o, "not ok", "", detail, ap); }
2108
2109 /* --- @tap_dumpreg@ --- *
2110  *
2111  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2112  *                       @struct tap_output@
2113  *              @unsigned disp@ = register disposition
2114  *              @const union tvec_regval *rv@ = register value
2115  *              @const struct tvec_regdef *rd@ = register definition
2116  *
2117  * Returns:     ---
2118  *
2119  * Use:         Dump a register.
2120  *
2121  *              The TAP driver applies highlighting to mismatching output
2122  *              registers, but otherwise delegates to the register type
2123  *              handler and the layout machinery.  The result is that the
2124  *              register dump is marked as a comment and indented.
2125  */
2126
2127 static void tap_dumpreg(struct tvec_output *o,
2128                         unsigned disp, const union tvec_regval *rv,
2129                         const struct tvec_regdef *rd)
2130 {
2131   struct tap_output *t = (struct tap_output *)o;
2132   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2133
2134   set_layout_prefix(&t->lyt, "    ## ");
2135   gprintf(&tap_printops, t, "%*s%s %s = ",
2136           10 + t->maxlen - n, "", ds, rd->name);
2137   if (!rv) gprintf(&tap_printops, t, "#<unset>");
2138   else rd->ty->dump(rv, rd, 0, &tap_printops, t);
2139   layout_char(&t->lyt, '\n');
2140 }
2141
2142 /* --- @tap_etest@ --- *
2143  *
2144  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2145  *                       @struct tap_output@
2146  *              @unsigned outcome@ = the test outcome
2147  *
2148  * Returns:     ---
2149  *
2150  * Use:         Report that a test has finished.
2151  *
2152  *              The TAP driver reports the outcome of the test, if that's not
2153  *              already decided.
2154  */
2155
2156 static void tap_etest(struct tvec_output *o, unsigned outcome)
2157 {
2158   switch (outcome) {
2159     case TVOUT_WIN:
2160       tap_outcome(o, "ok", "", 0, 0);
2161       break;
2162     case TVOUT_XFAIL:
2163       tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2164       break;
2165   }
2166 }
2167
2168 /* --- @tap_report@ --- *
2169  *
2170  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2171  *                       @struct tap_output@
2172  *              @unsigned level@ = message level (@TVLV_...@)
2173  *              @const char *msg@, @va_list *ap@ = format string and
2174  *                      arguments
2175  *
2176  * Returns:     ---
2177  *
2178  * Use:         Report a message to the user.
2179  *
2180  *              Messages are reported as comments, so that they can be
2181  *              accumulated by the reader.  An error will cause a later
2182  *              bailout or, if we crash before then, a missing plan line,
2183  *              either of which will cause the reader to report a serious
2184  *              problem.
2185  */
2186
2187 static void tap_report(struct tvec_output *o, unsigned level,
2188                        const char *msg, va_list *ap)
2189 {
2190   struct tap_output *t = (struct tap_output *)o;
2191   struct tvec_state *tv = t->tv;
2192
2193   if (tv->test) set_layout_prefix(&t->lyt, "    ## ");
2194   else set_layout_prefix(&t->lyt, "## ");
2195
2196   if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
2197   gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
2198   vgprintf(&tap_printops, t, msg, ap);
2199   layout_char(&t->lyt, '\n');
2200 }
2201
2202 /* --- @tap_extend@ --- *
2203  *
2204  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2205  *                       @struct tap_output@
2206  *              @const char *name@ = extension name
2207  *
2208  * Returns:     A pointer to the extension implementation, or null.
2209  */
2210
2211 static const void *tap_extend(struct tvec_output *o, const char *name)
2212   { return (0); }
2213
2214 /* --- @tap_destroy@ --- *
2215  *
2216  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2217  *                       @struct tap_output@
2218  *
2219  * Returns:     ---
2220  *
2221  * Use:         Release the resources held by the output driver.
2222  */
2223
2224 static void tap_destroy(struct tvec_output *o)
2225 {
2226   struct tap_output *t = (struct tap_output *)o;
2227
2228   destroy_layout(&t->lyt,
2229                  t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
2230   x_free(t->a, t->outbuf); x_free(t->a, t);
2231 }
2232
2233 static const struct tvec_outops tap_ops = {
2234   tap_bsession, tap_esession,
2235   tap_bgroup, tap_skipgroup, tap_egroup,
2236   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
2237   tap_report, tap_extend, tap_destroy
2238 };
2239
2240 /* --- @tvec_tapoutput@ --- *
2241  *
2242  * Arguments:   @FILE *fp@ = output file to write on
2243  *
2244  * Returns:     An output formatter.
2245  *
2246  * Use:         Return an output formatter which writes on @fp@ in `TAP'
2247  *              (`Test Anything Protocol') format.
2248  *
2249  *              TAP comes from the Perl community, but has spread rather
2250  *              further.  This driver produces TAP version 14, but pretends
2251  *              to be version 13.  The driver produces a TAP `test point' --
2252  *              i.e., a result reported as `ok' or `not ok' -- for each input
2253  *              test group.  Failure reports and register dumps are produced
2254  *              as diagnostic messages before the final group result.  (TAP
2255  *              permits structuerd YAML data after the test-point result,
2256  *              which could be used to report details, but (a) postponing the
2257  *              details until after the report is inconvenient, and (b) there
2258  *              is no standardization for the YAML anyway, so in practice
2259  *              it's no more useful than the unstructured diagnostics.
2260  */
2261
2262 struct tvec_output *tvec_tapoutput(FILE *fp)
2263 {
2264   struct tap_output *t;
2265
2266   XNEW(t); t->a = arena_global; t->_o.ops = &tap_ops;
2267   init_layout(&t->lyt, fp, 0);
2268   t->outbuf = 0; t->outsz = 0;
2269   return (&t->_o);
2270 }
2271
2272 /*----- Automake support --------------------------------------------------*/
2273
2274 struct automake_output {
2275   struct tvec_output _o;
2276   arena *a;                             /* arena */
2277   struct tvec_state *tv;                /* test-vector state */
2278   struct tvec_output *progress;         /* real-time progress output */
2279   struct tvec_output *log;              /* log file output */
2280   FILE *trs;                            /* test result stream */
2281 };
2282
2283 /* --- @am_bsession@ --- *
2284  *
2285  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2286  *                       @struct automake_output@
2287  *              @struct tvec_state *tv@ = the test state producing output
2288  *
2289  * Returns:     ---
2290  *
2291  * Use:         Begin a test session.
2292  *
2293  *              The Automake driver passes the event on to its subordinates.
2294  */
2295
2296 static void am_bsession(struct tvec_output *o, struct tvec_state *tv)
2297 {
2298   struct automake_output *am = (struct automake_output *)o;
2299
2300   am->tv = tv;
2301   human_bsession(am->progress, tv);
2302   machine_bsession(am->log, tv);
2303 }
2304
2305 /* --- @am_esession@ --- *
2306  *
2307  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2308  *                       @struct automake_output@
2309  *
2310  * Returns:     Suggested exit code.
2311  *
2312  * Use:         End a test session.
2313  *
2314  *              The Automake driver completes the test-results file and
2315  *              passes  the event on to its subordinates.
2316  */
2317
2318 static void am_report_unusual(struct automake_output *am,
2319                               unsigned xfail, unsigned skip)
2320 {
2321   unsigned f = 0;
2322 #define f_any 1u
2323
2324   if (xfail) {
2325     fprintf(am->trs, "%s%u expected %s", f&f_any ? ", " : " (",
2326             xfail, xfail == 1 ? "failure" : "failures");
2327     f |= f_any;
2328   }
2329   if (skip) {
2330     fprintf(am->trs, "%s%u skipped", f&f_any ? ", " : " (", skip);
2331     f |= f_any;
2332   }
2333   if (f&f_any) fputc(')', am->trs);
2334
2335 #undef f_any
2336 }
2337
2338 static int am_esession(struct tvec_output *o)
2339 {
2340   struct automake_output *am = (struct automake_output *)o;
2341   struct tvec_state *tv = am->tv;
2342   unsigned
2343     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
2344     all_xfail = tv->all[TVOUT_XFAIL],
2345     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
2346     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
2347     all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
2348     grps_run = grps_win + grps_lose;
2349
2350   human_esession(am->progress);
2351   machine_esession(am->log);
2352
2353   fputs(":test-global-result: ", am->trs);
2354   if (tv->f&TVSF_ERROR) fputs("ERRORS; ", am->trs);
2355   if (!all_lose) {
2356     fprintf(am->trs, "PASSED %s%u %s",
2357             !(all_skip || grps_skip) ? "all " : "",
2358             all_win, all_win == 1 ? "test" : "tests");
2359     am_report_unusual(am, all_xfail, all_skip);
2360     fprintf(am->trs, " in %u %s",
2361             grps_win, grps_win == 1 ? "group" : "groups");
2362     am_report_unusual(am, 0, grps_skip);
2363   } else {
2364     fprintf(am->trs, "FAILED %u out of %u %s",
2365             all_lose, all_run, all_run == 1 ? "test" : "tests");
2366     am_report_unusual(am, all_xfail, all_skip);
2367     fprintf(am->trs, " in %u out of %u %s",
2368             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
2369     am_report_unusual(am, 0, grps_skip);
2370   }
2371   fputc('\n', am->trs);
2372
2373   fprintf(am->trs, ":copy-in-global-log: %s\n",
2374           !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes");
2375   fprintf(am->trs, ":recheck: %s\n",
2376           !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes");
2377
2378   return (0);
2379 }
2380
2381 /* --- @am_bgroup@ --- *
2382  *
2383  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2384  *                       @struct automake_output@
2385  *
2386  * Returns:     ---
2387  *
2388  * Use:         Begin a test group.
2389  *
2390  *              The Automake driver passes the event on to its subordinates.
2391  */
2392
2393 static void am_bgroup(struct tvec_output *o)
2394 {
2395   struct automake_output *am = (struct automake_output *)o;
2396
2397   human_bgroup(am->progress);
2398   machine_bgroup(am->log);
2399 }
2400
2401 /* --- @am_skipgroup@ --- *
2402  *
2403  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2404  *                       @struct automake_output@
2405  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
2406  *                      group, or null
2407  *
2408  * Returns:     ---
2409  *
2410  * Use:         Report that a test group is being skipped.
2411  *
2412  *              The Automake driver makes a note in the test-results file and
2413  *              passes the event on to its subordinates.
2414  */
2415
2416 static void am_skipgroup(struct tvec_output *o,
2417                          const char *excuse, va_list *ap)
2418 {
2419   struct automake_output *am = (struct automake_output *)o;
2420   struct tvec_state *tv = am->tv;
2421
2422   fprintf(am->trs, ":test-result: SKIP %s\n", tv->test->name);
2423   human_skipgroup(am->progress, excuse, ap);
2424   machine_skipgroup(am->log, excuse, ap);
2425 }
2426
2427 /* --- @am_egroup@ --- *
2428  *
2429  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2430  *                       @struct automake_output@
2431  *
2432  * Returns:     ---
2433  *
2434  * Use:         Report that a test group has finished.
2435  *
2436  *              The Automake driver makes a note in the test-results file and
2437  *              passes the event on to its subordinates.
2438  */
2439
2440 static void am_egroup(struct tvec_output *o)
2441 {
2442   struct automake_output *am = (struct automake_output *)o;
2443   struct tvec_state *tv = am->tv;
2444
2445   fprintf(am->trs, ":test-result: %s %s\n",
2446           tv->curr[TVOUT_LOSE] ? "FAIL" : "PASS", tv->test->name);
2447   human_egroup(am->progress);
2448   machine_egroup(am->log);
2449 }
2450
2451 /* --- @am_btest@ --- *
2452  *
2453  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2454  *                       @struct automake_output@
2455  *
2456  * Returns:     ---
2457  *
2458  * Use:         Report that a test is starting.
2459  *
2460  *              The Automake driver passes the event on to its subordinates.
2461  */
2462
2463 static void am_btest(struct tvec_output *o)
2464 {
2465   struct automake_output *am = (struct automake_output *)o;
2466
2467   human_btest(am->progress);
2468   machine_btest(am->log);
2469 }
2470
2471 /* --- @am_skip@, @am_fail@ --- *
2472  *
2473  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2474  *                       @struct automake_output@
2475  *              @const char *head, *tail@ = outcome strings to report
2476  *              @const char *detail@, @va_list *ap@ = a detail message
2477  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
2478  *                      test
2479  *
2480  * Returns:     ---
2481  *
2482  * Use:         Report that a test has been skipped or failed.
2483  *
2484  *              The Automake driver passes the event on to its subordinates.
2485  */
2486
2487 static void am_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2488 {
2489   struct automake_output *am = (struct automake_output *)o;
2490
2491   human_skip(am->progress, excuse, ap);
2492   machine_skip(am->log, excuse, ap);
2493 }
2494
2495 static void am_fail(struct tvec_output *o, const char *detail, va_list *ap)
2496 {
2497   struct automake_output *am = (struct automake_output *)o;
2498
2499   human_fail(am->progress, detail, ap);
2500   machine_fail(am->log, detail, ap);
2501 }
2502
2503 /* --- @am_dumpreg@ --- *
2504  *
2505  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2506  *                       @struct automake_output@
2507  *              @unsigned disp@ = register disposition
2508  *              @const union tvec_regval *rv@ = register value
2509  *              @const struct tvec_regdef *rd@ = register definition
2510  *
2511  * Returns:     ---
2512  *
2513  * Use:         Dump a register.
2514  *
2515  *              The Automake driver passes the event on to its subordinates.
2516  */
2517
2518 static void am_dumpreg(struct tvec_output *o,
2519                        unsigned disp, const union tvec_regval *rv,
2520                        const struct tvec_regdef *rd)
2521 {
2522   struct automake_output *am = (struct automake_output *)o;
2523
2524   human_dumpreg(am->progress, disp, rv, rd);
2525   machine_dumpreg(am->log, disp, rv, rd);
2526 }
2527
2528 /* --- @am_etest@ --- *
2529  *
2530  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2531  *                       @struct automake_output@
2532  *              @unsigned outcome@ = the test outcome
2533  *
2534  * Returns:     ---
2535  *
2536  * Use:         Report that a test has finished.
2537  *
2538  *              The Automake driver passes the event on to its subordinates.
2539  */
2540
2541 static void am_etest(struct tvec_output *o, unsigned outcome)
2542 {
2543   struct automake_output *am = (struct automake_output *)o;
2544
2545   human_etest(am->progress, outcome);
2546   machine_etest(am->log, outcome);
2547 }
2548
2549 /* --- @am_report@ --- *
2550  *
2551  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2552  *                       @struct automake_output@
2553  *              @unsigned level@ = message level (@TVLV_...@)
2554  *              @const char *msg@, @va_list *ap@ = format string and
2555  *                      arguments
2556  *
2557  * Returns:     ---
2558  *
2559  * Use:         Report a message to the user.
2560  *
2561  *              The Automake driver passes the event on to its subordinates.
2562  */
2563
2564 static void am_report(struct tvec_output *o, unsigned level,
2565                       const char *msg, va_list *ap)
2566 {
2567   struct automake_output *am = (struct automake_output *)o;
2568
2569   human_report(am->progress, level, msg, ap);
2570   machine_report(am->log, level, msg, ap);
2571 }
2572
2573 /* --- @am_bbench@ --- *
2574  *
2575  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2576  *                       @struct automake_output@
2577  *              @const char *desc@ = adhoc test description
2578  *              @unsigned unit@ = measurement unit (@BTU_...@)
2579  *
2580  * Returns:     ---
2581  *
2582  * Use:         Report that a benchmark has started.
2583  *
2584  *              The Automake driver passes the event on to its subordinates.
2585  */
2586
2587 static void am_bbench(struct tvec_output *o,
2588                       const char *desc, unsigned unit)
2589 {
2590   struct automake_output *am = (struct automake_output *)o;
2591
2592   human_bbench(am->progress, desc, unit);
2593   machine_bbench(am->progress, desc, unit);
2594 }
2595
2596 /* --- @am_ebench@ --- *
2597  *
2598  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2599  *                       @struct automake_output@
2600  *              @const char *desc@ = adhoc test description
2601  *              @unsigned unit@ = measurement unit (@BTU_...@)
2602  *              @const struct bench_timing *t@ = measurement
2603  *
2604  * Returns:     ---
2605  *
2606  * Use:         Report a benchmark's results.
2607  *
2608  *              The Automake driver passes the event on to its subordinates.
2609  */
2610
2611 static void am_ebench(struct tvec_output *o,
2612                          const char *desc, unsigned unit,
2613                          const struct bench_timing *t)
2614 {
2615   struct automake_output *am = (struct automake_output *)o;
2616
2617   human_ebench(am->progress, desc, unit, t);
2618   machine_ebench(am->progress, desc, unit, t);
2619 }
2620
2621 static const struct tvec_benchoutops am_benchops =
2622   { am_bbench, am_ebench };
2623
2624 /* --- @am_extend@ --- *
2625  *
2626  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2627  *                       @struct automake_output@
2628  *              @const char *name@ = extension name
2629  *
2630  * Returns:     A pointer to the extension implementation, or null.
2631  */
2632
2633 static const void *am_extend(struct tvec_output *o, const char *name)
2634 {
2635   if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&am_benchops);
2636   else return (0);
2637 }
2638
2639 /* --- @am_destroy@ --- *
2640  *
2641  * Arguments:   @struct tvec_output *o@ = output sink, secretly a
2642  *                       @struct automake_output@
2643  *
2644  * Returns:     ---
2645  *
2646  * Use:         Release the resources held by the output driver.
2647  */
2648
2649 static void am_destroy(struct tvec_output *o)
2650 {
2651   struct automake_output *am = (struct automake_output *)o;
2652
2653   human_destroy(am->progress);
2654   machine_destroy(am->log);
2655   fclose(am->trs); x_free(am->a, am);
2656 }
2657
2658 static const struct tvec_outops automake_ops = {
2659   am_bsession, am_esession,
2660   am_bgroup, am_skipgroup, am_egroup,
2661   am_btest, am_skip, am_fail, am_dumpreg, am_etest,
2662   am_report, am_extend, am_destroy
2663 };
2664
2665 /* --- @tvec_amoutput@ --- *
2666  *
2667  * Arguments:   @const struct tvec_amargs *a@ = arguments from Automake
2668  *                      command-line protocol
2669  *
2670  * Returns:     An output formatter.
2671  *
2672  * Use:         Returns an output formatter which writes on standard output
2673  *              in human format, pretending that the output is to a terminal
2674  *              (in order to cope with %%\manpage{make}{1}%%'s output-
2675  *              buffering behaviour, writes to the log file @a->log@ in
2676  *              machine-readable format, and writes an Automake rST-format
2677  *              test result file to @a->trs@.  The `test name' is currently
2678  *              ignored, because the framework has its own means of
2679  *              determining test names.
2680  */
2681
2682 struct tvec_output *tvec_amoutput(const struct tvec_amargs *a)
2683 {
2684   struct automake_output *am;
2685   unsigned f;
2686
2687   f = TVHF_TTY;
2688   if (a->f&TVAF_COLOUR) f |= TVHF_COLOUR;
2689
2690   XNEW(am); am->a = arena_global; am->_o.ops = &automake_ops;
2691   am->progress = tvec_humanoutput(stdout, f, TVHF_TTY | TVHF_COLOUR);
2692   am->log = tvec_machineoutput(a->log); am->trs = a->trs;
2693   return (&am->_o);
2694 }
2695
2696 /*----- Default output ----------------------------------------------------*/
2697
2698 /* --- @tvec_dfltoutput@ --- *
2699  *
2700  * Arguments:   @FILE *fp@ = output file to write on
2701  *
2702  * Returns:     An output formatter.
2703  *
2704  * Use:         Selects and instantiates an output formatter suitable for
2705  *              writing on @fp@.  The policy is subject to change, but
2706  *              currently the `human' output format is selected if @fp@ is
2707  *              interactive (i.e., if @isatty(fileno(fp))@ is true), and
2708  *              otherwise the `machine' format is used.
2709  */
2710
2711 struct tvec_output *tvec_dfltoutput(FILE *fp)
2712 {
2713   int ttyp = getenv_boolean("MLIB_TVEC_TTY", -1);
2714
2715   if (ttyp == -1) ttyp = isatty(fileno(fp));
2716   if (ttyp) return (tvec_humanoutput(fp, TVHF_TTY, TVHF_TTY));
2717   else return (tvec_machineoutput(fp));
2718 }
2719
2720 /*----- That's all, folks -------------------------------------------------*/