chiark / gitweb /
46c34420613eb22d7d02ede744569c3cef332e4d
[mLib] / test / tvec-output.c
1 /* -*-c-*-
2  *
3  * Test vector output management
4  *
5  * (c) 2023 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
12  * mLib is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU Library General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or (at
15  * your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
20  * License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with mLib.  If not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <assert.h>
33 #include <ctype.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <unistd.h>
39
40 #include "alloc.h"
41 #include "bench.h"
42 #include "dstr.h"
43 #include "macros.h"
44 #include "quis.h"
45 #include "report.h"
46 #include "tvec.h"
47
48 /*----- Common machinery --------------------------------------------------*/
49
50 /* --- @regdisp@ --- *
51  *
52  * Arguments:   @unsigned disp@ = a @TVRD_...@ disposition code
53  *
54  * Returns:     A human-readable adjective describing the register
55  *              disposition.
56  */
57
58 static const char *regdisp(unsigned disp)
59 {
60   switch (disp) {
61     case TVRD_INPUT: return "input";
62     case TVRD_OUTPUT: return "output";
63     case TVRD_MATCH: return "matched";
64     case TVRD_FOUND: return "found";
65     case TVRD_EXPECT: return "expected";
66     default: abort();
67   }
68 }
69
70 /* --- @getenv_boolean@ --- *
71  *
72  * Arguments:   @const char *var@ = environment variable name
73  *              @int dflt@ = default value
74  *
75  * Returns:     @0@ if the variable is set to something falseish, @1@ if it's
76  *              set to something truish, or @dflt@ otherwise.
77  */
78
79 static int getenv_boolean(const char *var, int dflt)
80 {
81   const char *p;
82
83   p = getenv(var);
84   if (!p)
85     return (dflt);
86   else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
87            STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
88            STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
89            STRCMP(p, ==, "1"))
90     return (1);
91   else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
92            STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
93            STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
94            STRCMP(p, ==, "0"))
95     return (0);
96   else {
97     moan("ignoring unexpected value `%s' for environment variable `%s'",
98          var, p);
99     return (dflt);
100   }
101 }
102
103 /* --- @register_maxnamelen@ --- *
104  *
105  * Arguments:   @const struct tvec_state *tv@ = test vector state
106  *
107  * Returns:     The maximum length of a register name in the current test.
108  */
109
110 static int register_maxnamelen(const struct tvec_state *tv)
111 {
112   const struct tvec_regdef *rd;
113   int maxlen = 10, n;
114
115   for (rd = tv->test->regs; rd->name; rd++)
116     { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
117   return (maxlen);
118 }
119
120 /*----- Output layout -----------------------------------------------------*/
121
122 /* We have two main jobs in output layout: trimming trailing blanks; and
123  * adding a prefix to each line.
124  *
125  * This is somehow much more complicated than it ought to be.
126  */
127
128 struct layout {
129   FILE *fp;                              /* output file */
130   const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
131   dstr w;                               /* trailing whitespace */
132   unsigned f;                           /* flags */
133 #define LYTF_NEWL 1u                    /*   start of output line */
134 };
135
136 /* Support macros.  These assume `lyt' is defined as a pointer to the `struct
137  * layout' state.
138  */
139
140 #define SPLIT_RANGE(tail, base, limit) do {                             \
141   /* Set TAIL to point just after the last nonspace character between   \
142    * BASE and LIMIT.  If there are no nonspace characters, then set     \
143    * TAIL to equal BASE.                                                \
144    */                                                                   \
145                                                                         \
146   for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--);         \
147 } while (0)
148
149 #define PUT_RANGE(base, limit) do {                                     \
150   /* Write the range of characters between BASE and LIMIT to the output \
151    * file.  Return immediately on error.                                \
152    */                                                                   \
153                                                                         \
154   size_t _n = limit - base;                                             \
155   if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1);             \
156 } while (0)
157
158 #define PUT_CHAR(ch) do {                                               \
159   /* Write CH to the output. Return immediately on error. */            \
160                                                                         \
161   if (putc(ch, lyt->fp) == EOF) return (-1);                            \
162 } while (0)
163
164 #define PUT_PREFIX do {                                                 \
165   /* Output the prefix, if there is one.  Return immediately on error. */ \
166                                                                         \
167   if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim);                 \
168 } while (0)
169
170 #define PUT_SAVED do {                                                  \
171   /* Output the saved trailing blank material in the buffer. */         \
172                                                                         \
173   size_t _n = lyt->w.len;                                               \
174   if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1);       \
175 } while (0)
176
177 #define PUT_PFXINB do {                                                 \
178   /* Output the initial nonblank portion of the prefix, if there is     \
179    * one.  Return immediately on error.                                 \
180    */                                                                   \
181                                                                         \
182   if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail);                \
183 } while (0)
184
185 #define SAVE_PFXTAIL do {                                               \
186   /* Save the trailing blank portion of the prefix. */                  \
187                                                                         \
188   if (lyt->prefix)                                                      \
189     DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail);           \
190 } while (0)
191
192 /* --- @set_layout_prefix@ --- *
193  *
194  * Arguments:   @struct layout *lyt@ = layout state
195  *              @const char *prefix@ = new prefix string or null
196  *
197  * Returns:     ---
198  *
199  * Use:         Change the configured prefix string.  The change takes effect
200  *              at the start of the next line (or the current line if it's
201  *              empty or only whitespace so far).
202  */
203
204 static void set_layout_prefix(struct layout *lyt, const char *prefix)
205 {
206   const char *q, *l;
207
208  if (!prefix || !*prefix)
209     lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
210   else {
211     lyt->prefix = prefix;
212     l = lyt->pfxlim = prefix + strlen(prefix);
213     SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
214   }
215 }
216
217 /* --- @init_layout@ --- *
218  *
219  * Arguments:   @struct layout *lyt@ = layout state to initialize
220  *              @FILE *fp@ = output file
221  *              @const char *prefix@ = prefix string (or null if empty)
222  *
223  * Returns:     ---
224  *
225  * Use:         Initialize a layout state.
226  */
227
228 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
229 {
230   lyt->fp = fp;
231   lyt->f = LYTF_NEWL;
232   dstr_create(&lyt->w);
233   set_layout_prefix(lyt, prefix);
234 }
235
236 /* --- @destroy_layout@ --- *
237  *
238  * Arguments:   @struct layout *lyt@ = layout state
239  *              @unsigned f@ = flags (@DLF_...@)
240  *
241  * Returns:     ---
242  *
243  * Use:         Releases a layout state and the resources it holds.
244  *              Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
245  *              it open (in case it's @stderr@ or something).
246  */
247
248 #define DLF_CLOSE 1u
249 static void destroy_layout(struct layout *lyt, unsigned f)
250 {
251   if (f&DLF_CLOSE) fclose(lyt->fp);
252   dstr_destroy(&lyt->w);
253 }
254
255 /* --- @layout_char@ --- *
256  *
257  * Arguments:   @struct layout *lyt@ = layout state
258  *              @int ch@ = character to write
259  *
260  * Returns:     Zero on success, @-1@ on failure.
261  *
262  * Use:         Write a single character to the output.
263  */
264
265 static int layout_char(struct layout *lyt, int ch)
266 {
267   if (ch == '\n') {
268     if (lyt->f&LYTF_NEWL) PUT_PFXINB;
269     PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
270   } else if (isspace(ch))
271     DPUTC(&lyt->w, ch);
272   else {
273     if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
274     PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
275   }
276   return (0);
277 }
278
279 /* --- @layout_string@ --- *
280  *
281  * Arguments:   @struct layout *lyt@ = layout state
282  *              @const char *p@ = string to write
283  *              @size_t sz@ = length of string
284  *
285  * Returns:     Zero on success, @-1@ on failure.
286  *
287  * Use:         Write a string to the output.
288  */
289
290 static int layout_string(struct layout *lyt, const char *p, size_t sz)
291 {
292   const char *q, *r, *l = p + sz;
293
294   /* This is rather vexing.  There are a small number of jobs to do, but the
295    * logic for deciding which to do when gets rather hairy if, as I've tried
296    * here, one aims to minimize the number of decisions being checked, so
297    * it's worth canning them into macros.
298    *
299    * Here, a `blank' is a whitespace character other than newline.  The input
300    * buffer consists of one or more `segments', each of which consists of:
301    *
302    *   * an initial portion, which is either empty or ends with a nonblank
303    *     character;
304    *
305    *   * a suffix which consists only of blanks; and
306    *
307    *   * an optional newline.
308    *
309    * All segments except the last end with a newline.
310    */
311
312 #define SPLIT_SEGMENT do {                                              \
313   /* Determine the bounds of the current segment.  If there is a final  \
314    * newline, then q is non-null and points to this newline; otherwise, \
315    * q is null.  The initial portion of the segment lies between p .. r \
316    * and the blank suffix lies between r .. q (or r .. l if q is null). \
317    * This sounds awkward, but the suffix is only relevant if there is   \
318    * no newline.                                                        \
319    */                                                                   \
320                                                                         \
321   q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l);             \
322 } while (0)
323
324 #define PUT_NONBLANK do {                                               \
325   /* Output the initial portion of the segment. */                      \
326                                                                         \
327   PUT_RANGE(p, r);                                                      \
328 } while (0)
329
330 #define PUT_NEWLINE do {                                                \
331   /* Write a newline, and advance to the next segment. */               \
332                                                                         \
333   PUT_CHAR('\n'); p = q + 1;                                            \
334 } while (0)
335
336 #define SAVE_TAIL do {                                                  \
337   /* Save the trailing blank portion of the segment in the buffer.      \
338    * Assumes that there is no newline, since otherwise the suffix would \
339    * be omitted.                                                        \
340    */                                                                   \
341                                                                         \
342   DPUTM(&lyt->w, r, l - r);                                             \
343 } while (0)
344
345   /* Determine the bounds of the first segment.  Handling this is the most
346    * complicated part of this function.
347    */
348   SPLIT_SEGMENT;
349
350   if (!q) {
351     /* This is the only segment.  We'll handle the whole thing here.
352      *
353      * If there's an initial nonblank portion, then we need to write that
354      * out.  Furthermore, if we're at the start of the line then we'll need
355      * to write the prefix, and if there's saved blank material then we'll
356      * need to write that.  Otherwise, there's only blank stuff, which we
357      * accumulate in the buffer.
358      *
359      * If we're at the start of a line here, then put the prefix followed by
360      * any saved whitespace, and then our initial nonblank portion.  Then
361      * save our new trailing space.
362      */
363
364     if (r > p) {
365       if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
366       PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
367     }
368     SAVE_TAIL;
369     return (0);
370   }
371
372   /* There is at least one more segment, so we know that there'll be a line
373    * to output.
374    */
375   if (r > p) {
376     if (lyt->f&LYTF_NEWL) PUT_PREFIX;
377     PUT_SAVED; PUT_NONBLANK;
378   } else if (lyt->f&LYTF_NEWL)
379     PUT_PFXINB;
380   PUT_NEWLINE; DRESET(&lyt->w);
381   SPLIT_SEGMENT;
382
383   /* Main loop over whole segments with trailing newlines.  For each one, we
384    * know that we're starting at the beginning of a line and there's a final
385    * newline, so we write the initial prefix and drop the trailing blanks.
386    */
387   while (q) {
388     if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
389     else PUT_PFXINB;
390     PUT_NEWLINE;
391     SPLIT_SEGMENT;
392   }
393
394   /* At the end, there's no final newline.  If there's nonblank material,
395    * then we can write the prefix and the nonblank stuff.  Otherwise, stash
396    * the blank stuff (including the trailing blanks of the prefix) and leave
397    * the newline flag set.
398    */
399   if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
400   else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
401   SAVE_TAIL;
402
403 #undef SPLIT_SEGMENT
404 #undef PUT_NONBLANK
405 #undef PUT_NEWLINE
406 #undef SAVE_TAIL
407
408   return (0);
409 }
410
411 #undef SPLIT_RANGE
412 #undef PUT_RANGE
413 #undef PUT_PREFIX
414 #undef PUT_PFXINB
415 #undef PUT_SAVED
416 #undef PUT_CHAR
417 #undef SAVE_PFXTAIL
418
419 /*----- Human-readable output ---------------------------------------------*/
420
421 /* Attributes for colour output.  This should be done better, but @terminfo@
422  * is a disaster.
423  *
424  * An attribute byte holds a foreground colour in the low nibble, a
425  * background colour in the next nibble, and some flags in the next few
426  * bits.  A colour is expressed in classic 1-bit-per-channel style, with red,
427  * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
428  */
429 #define HAF_FGMASK 0x0f                 /* foreground colour mask */
430 #define HAF_FGSHIFT 0                   /* foreground colour shift */
431 #define HAF_BGMASK 0xf0                 /* background colour mask */
432 #define HAF_BGSHIFT 4                   /* background colour shift */
433 #define HAF_FG 256u                     /* set foreground? */
434 #define HAF_BG 512u                     /* set background? */
435 #define HAF_BOLD 1024u                  /* set bold? */
436 #define HCOL_BLACK 0u                   /* colour codes... */
437 #define HCOL_RED 1u
438 #define HCOL_GREEN 2u
439 #define HCOL_YELLOW 3u
440 #define HCOL_BLUE 4u
441 #define HCOL_MAGENTA 5u
442 #define HCOL_CYAN 6u
443 #define HCOL_WHITE 7u
444 #define HCF_BRIGHT 8u                   /* bright colour flag */
445 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
446 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
447
448 /* Predefined attributes. */
449 #define HA_PLAIN 0                   /* nothing special: terminal defaults */
450 #define HA_LOC (HFG(CYAN))              /* filename or line number */
451 #define HA_LOCSEP (HFG(BLUE))           /* location separator `:' */
452 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
453 #define HA_NOTE (HFG(YELLOW))           /* notices */
454 #define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
455 #define HA_UNSET (HFG(YELLOW))          /* register not set */
456 #define HA_FOUND (HFG(RED))             /* incorrect output value */
457 #define HA_EXPECT (HFG(GREEN))          /* what the value should have been */
458 #define HA_WIN (HFG(GREEN))             /* reporting success */
459 #define HA_LOSE (HFG(RED) | HAF_BOLD)   /* reporting failure */
460 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
461 #define HA_SKIP (HFG(YELLOW))           /* reporting a skipped test/group */
462
463 /* Scoreboard indicators. */
464 #define HSB_WIN '.'                     /* test passed */
465 #define HSB_LOSE 'x'                    /* test failed */
466 #define HSB_XFAIL 'o'                   /* test failed expectedly */
467 #define HSB_SKIP '_'                    /* test wasn't run */
468
469 struct human_output {
470   struct tvec_output _o;                /* output base class */
471   struct tvec_state *tv;                /* stashed testing state */
472   struct layout lyt;                    /* output layout */
473   char *outbuf; size_t outsz;           /* buffer for formatted output */
474   dstr scoreboard;                      /* history of test group results */
475   unsigned attr;                        /* current terminal attributes */
476   int maxlen;                           /* longest register name */
477   unsigned f;                           /* flags */
478 #define HOF_TTY 1u                      /*   writing to terminal */
479 #define HOF_DUPERR 2u                   /*   duplicate errors to stderr */
480 #define HOF_COLOUR 4u                   /*   print in angry fruit salad */
481 #define HOF_PROGRESS 8u                 /*   progress display is active */
482 };
483
484 /* --- @set_colour@ --- *
485  *
486  * Arguments:   @FILE *fp@ = output stream to write on
487  *              @int *sep_inout@ = where to maintain separator
488  *              @const char *norm@ = prefix for normal colour
489  *              @const char *bright@ = prefix for bright colour
490  *              @unsigned colour@ = four bit colour code
491  *
492  * Returns:     ---
493  *
494  * Use:         Write to the output stream @fp@, the current character at
495  *              @*sep_inout@, if that's not zero, followed by either @norm@
496  *              or @bright@, according to whether the @HCF_BRIGHT@ flag is
497  *              set in @colour@, followed by the plain colour code from
498  *              @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
499  *
500  *              This is an internal subroutine for @setattr@ below.
501  */
502
503 static void set_colour(FILE *fp, int *sep_inout,
504                        const char *norm, const char *bright,
505                        unsigned colour)
506 {
507   if (*sep_inout) putc(*sep_inout, fp);
508   fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
509   *sep_inout = ';';
510 }
511
512 /* --- @setattr@ --- *
513  *
514  * Arguments:   @struct human_output *h@ = output state
515  *              @unsigned attr@ = attribute code to set
516  *
517  * Returns:     ---
518  *
519  * Use:         Send a control sequence to the output stream so that
520  *              subsequent text is printed with the given attributes.
521  *
522  *              Some effort is taken to avoid unnecessary control sequences.
523  *              In particular, if @attr@ matches the current terminal
524  *              settings already, then nothing is written.
525  */
526
527 static void setattr(struct human_output *h, unsigned attr)
528 {
529   unsigned diff = h->attr ^ attr;
530   int sep = 0;
531
532   /* If there's nothing to do, we might as well stop now. */
533   if (!diff || !(h->f&HOF_COLOUR)) return;
534
535   /* Start on the control command. */
536   fputs("\x1b[", h->lyt.fp);
537
538   /* Change the boldness if necessary. */
539   if (diff&HAF_BOLD) {
540     if (attr&HAF_BOLD) putc('1', h->lyt.fp);
541     else { putc('0', h->lyt.fp); diff = h->attr; }
542     sep = ';';
543   }
544
545   /* Change the foreground colour if necessary. */
546   if (diff&(HAF_FG | HAF_FGMASK)) {
547     if (attr&HAF_FG)
548       set_colour(h->lyt.fp, &sep, "3", "9",
549                  (attr&HAF_FGMASK) >> HAF_FGSHIFT);
550     else {
551       if (sep) putc(sep, h->lyt.fp);
552       fputs("39", h->lyt.fp); sep = ';';
553     }
554   }
555
556   /* Change the background colour if necessary. */
557   if (diff&(HAF_BG | HAF_BGMASK)) {
558     if (attr&HAF_BG)
559       set_colour(h->lyt.fp, &sep, "4", "10",
560                  (attr&HAF_BGMASK) >> HAF_BGSHIFT);
561     else {
562       if (sep) putc(sep, h->lyt.fp);
563       fputs("49", h->lyt.fp); sep = ';';
564     }
565   }
566
567   /* Terminate the control command and save the new attributes. */
568   putc('m', h->lyt.fp); h->attr = attr;
569 }
570
571 /* --- @clear_progress@ --- *
572  *
573  * Arguments:   @struct human_output *h@ = output state
574  *
575  * Returns:     ---
576  *
577  * Use:         Remove the progress display from the terminal.
578  *
579  *              If the progress display isn't active then do nothing.
580  */
581
582 static void clear_progress(struct human_output *h)
583 {
584   size_t i, n;
585
586   if (h->f&HOF_PROGRESS) {
587     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
588     for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
589     h->f &= ~HOF_PROGRESS;
590   }
591 }
592
593 /* --- @write_scoreboard_char@ --- *
594  *
595  * Arguments:   @struct human_output *h@ = output state
596  *              @int ch@ = scoreboard character to print
597  *
598  * Returns:     ---
599  *
600  * Use:         Write a scoreboard character, indicating the outcome of a
601  *              test, to the output stream, with appropriate highlighting.
602  */
603
604 static void write_scoreboard_char(struct human_output *h, int ch)
605 {
606   switch (ch) {
607     case HSB_LOSE: setattr(h, HA_LOSE); break;
608     case HSB_SKIP: setattr(h, HA_SKIP); break;
609     case HSB_XFAIL: setattr(h, HA_XFAIL); break;
610     default: setattr(h, HA_PLAIN); break;
611   }
612   putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
613 }
614
615 /* --- @show_progress@ --- *
616  *
617  * Arguments:   @struct human_output *h@ = output state
618  *
619  * Returns:     ---
620  *
621  * Use:         Show the progress display, with the record of outcomes for
622  *              the current test group.
623  *
624  *              If the progress display is already active, or the output
625  *              stream is not interactive, then nothing happens.
626  */
627
628 static void show_progress(struct human_output *h)
629 {
630   struct tvec_state *tv = h->tv;
631   const char *p, *l;
632
633   if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
634     fprintf(h->lyt.fp, "%s: ", tv->test->name);
635     if (!(h->f&HOF_COLOUR))
636       dstr_write(&h->scoreboard, h->lyt.fp);
637     else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
638       write_scoreboard_char(h, *p);
639     fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
640   }
641 }
642
643 /* --- @human_writech@, @human_write@, @human_writef@ --- *
644  *
645  * Arguments:   @void *go@ = output sink, secretly a @struct human_output@
646  *              @int ch@ = character to write
647  *              @const char *@p@, @size_t sz@ = string (with explicit length)
648  *                      to write
649  *              @const char *p, ...@ = format control string and arguments to
650  *                      write
651  *
652  * Returns:     ---
653  *
654  * Use:         Write characters, strings, or formatted strings to the
655  *              output, applying appropriate layout.
656  *
657  *              For the human output driver, the layout machinery just strips
658  *              trailing spaces.
659  */
660
661 static int human_writech(void *go, int ch)
662   { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
663
664 static int human_writem(void *go, const char *p, size_t sz)
665   { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
666
667 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
668 {
669   struct human_output *h = go;
670   size_t n;
671   va_list ap;
672
673   va_start(ap, p);
674   n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
675   va_end(ap);
676   if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
677   return (n);
678 }
679
680 static const struct gprintf_ops human_printops =
681   { human_writech, human_writem, human_nwritef };
682
683 /* --- @human_bsession@ --- *
684  *
685  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
686  *                      human_output@
687  *              @struct tvec_state *tv@ = the test state producing output
688  *
689  * Returns:     ---
690  *
691  * Use:         Begin a test session.
692  *
693  *              The human driver just records the test state for later
694  *              reference.
695  */
696
697 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
698   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
699
700 /* --- @report_unusual@ --- *
701  *
702  * Arguments:   @struct human_output *h@ = output sink
703  *              @unsigned nxfail, nskip@ = number of expected failures and
704  *                      skipped tests
705  *
706  * Returns:     ---
707  *
708  * Use:         Write (directly on the output stream) a note about expected
709  *              failures and/or skipped tests, if there were any.
710  */
711
712 static void report_unusual(struct human_output *h,
713                            unsigned nxfail, unsigned nskip)
714 {
715   const char *sep = " (";
716   unsigned f = 0;
717 #define f_any 1u
718
719   if (nxfail) {
720     fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
721     setattr(h, HA_XFAIL);
722     fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
723     setattr(h, HA_PLAIN);
724     sep = ", "; f |= f_any;
725   }
726
727   if (nskip) {
728     fprintf(h->lyt.fp, "%s%u ", sep, nskip);
729     setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
730     sep = ", "; f |= f_any;
731   }
732
733   if (f&f_any) fputc(')', h->lyt.fp);
734
735 #undef f_any
736 }
737
738 /* --- @human_esession@ --- *
739  *
740  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
741  *                      human_output@
742  *
743  * Returns:     Suggested exit code.
744  *
745  * Use:         End a test session.
746  *
747  *              The human driver prints a final summary of the rest results
748  *              and returns a suitable exit code.
749  */
750
751 static int human_esession(struct tvec_output *o)
752 {
753   struct human_output *h = (struct human_output *)o;
754   struct tvec_state *tv = h->tv;
755   unsigned
756     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
757     all_xfail = tv->all[TVOUT_XFAIL],
758     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
759     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
760     all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
761     grps_run = grps_win + grps_lose;
762
763   if (!all_lose) {
764     setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
765     fprintf(h->lyt.fp, " %s%u %s",
766             !(all_skip || grps_skip) ? "all " : "",
767             all_pass, all_pass == 1 ? "test" : "tests");
768     report_unusual(h, all_xfail, all_skip);
769     fprintf(h->lyt.fp, " in %u %s",
770             grps_win, grps_win == 1 ? "group" : "groups");
771     report_unusual(h, 0, grps_skip);
772   } else {
773     setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
774     fprintf(h->lyt.fp, " %u out of %u %s",
775             all_lose, all_run, all_run == 1 ? "test" : "tests");
776     report_unusual(h, all_xfail, all_skip);
777     fprintf(h->lyt.fp, " in %u out of %u %s",
778             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
779     report_unusual(h, 0, grps_skip);
780   }
781   fputc('\n', h->lyt.fp);
782
783   if (tv->f&TVSF_ERROR) {
784     setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
785     fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
786   }
787
788   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
789 }
790
791 /* --- @human_bgroup@ --- *
792  *
793  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
794  *                      human_output@
795  *
796  * Returns:     ---
797  *
798  * Use:         Begin a test group.
799  *
800  *              The human driver determines the length of the longest
801  *              register name, resets the group progress scoreboard, and
802  *              activates the progress display.
803  */
804
805 static void human_bgroup(struct tvec_output *o)
806 {
807   struct human_output *h = (struct human_output *)o;
808
809   h->maxlen = register_maxnamelen(h->tv);
810   dstr_reset(&h->scoreboard); show_progress(h);
811 }
812
813 /* --- @human_skipgroup@ --- *
814  *
815  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
816  *                      human_output@
817  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
818  *                      group, or null
819  *
820  * Returns:     ---
821  *
822  * Use:         Report that a test group is being skipped.
823  *
824  *              The human driver just reports the situation to its output
825  *              stream.
826  */
827
828 static void human_skipgroup(struct tvec_output *o,
829                             const char *excuse, va_list *ap)
830 {
831   struct human_output *h = (struct human_output *)o;
832
833   if (!(h->f&HOF_TTY))
834     fprintf(h->lyt.fp, "%s ", h->tv->test->name);
835   else {
836     show_progress(h); h->f &= ~HOF_PROGRESS;
837     if (h->scoreboard.len) putc(' ', h->lyt.fp);
838   }
839   setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
840   if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
841   fputc('\n', h->lyt.fp);
842 }
843
844 /* --- @human_egroup@ --- *
845  *
846  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
847  *                      human_output@
848  *
849  * Returns:     ---
850  *
851  * Use:         Report that a test group has finished.
852  *
853  *              The human driver reports a summary of the group's tests.
854  */
855
856 static void human_egroup(struct tvec_output *o)
857 {
858   struct human_output *h = (struct human_output *)o;
859   struct tvec_state *tv = h->tv;
860   unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
861     lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
862     run = win + lose + xfail;
863
864   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
865   else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
866
867   if (lose) {
868     fprintf(h->lyt.fp, " %u/%u ", lose, run);
869     setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
870     report_unusual(h, xfail, skip);
871   } else {
872     fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
873     fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
874     report_unusual(h, xfail, skip);
875   }
876   fputc('\n', h->lyt.fp);
877 }
878
879 /* --- @human_btest@ --- *
880  *
881  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
882  *                      human_output@
883  *
884  * Returns:     ---
885  *
886  * Use:         Report that a test is starting.
887  *
888  *              The human driver makes sure the progress display is active.
889  */
890
891 static void human_btest(struct tvec_output *o)
892   { struct human_output *h = (struct human_output *)o; show_progress(h); }
893
894 /* --- @human_report_location@ --- *
895  *
896  * Arguments:   @struct human_output *h@ = output state
897  *              @FILE *fp@ = stream to write the location on
898  *              @const char *file@ = filename
899  *              @unsigned lno@ = line number
900  *
901  * Returns:     ---
902  *
903  * Use:         Print the filename and line number to the output stream @fp@.
904  *              Also, if appropriate, print interleaved highlighting control
905  *              codes to our usual output stream.  If @file@ is null then do
906  *              nothing.
907  */
908
909 static void human_report_location(struct human_output *h, FILE *fp,
910                                   const char *file, unsigned lno)
911 {
912   unsigned f = 0;
913 #define f_flush 1u
914
915   /* We emit highlighting if @fp@ is our usual output stream, or the
916    * duplicate-errors flag is clear indicating that (we assume) they're
917    * secretly going to the same place anyway.  If they're different streams,
918    * though, we have to be careful to keep the highlighting and the actual
919    * text synchronized.
920    */
921
922   if (!file)
923     /* nothing to do */;
924   else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
925     fprintf(fp, "%s:%u: ", file, lno);
926   else {
927     if (fp != h->lyt.fp) f |= f_flush;
928
929 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
930
931     setattr(h, HA_LOC);         FLUSH(h->lyt.fp);
932     fputs(file, fp);            FLUSH(fp);
933     setattr(h, HA_LOCSEP);      FLUSH(h->lyt.fp);
934     fputc(':', fp);             FLUSH(fp);
935     setattr(h, HA_LOC);         FLUSH(h->lyt.fp);
936     fprintf(fp, "%u", lno);     FLUSH(fp);
937     setattr(h, HA_LOCSEP);      FLUSH(h->lyt.fp);
938     fputc(':', fp);             FLUSH(fp);
939     setattr(h, HA_PLAIN);       FLUSH(h->lyt.fp);
940     fputc(' ', fp);
941
942 #undef FLUSH
943   }
944
945 #undef f_flush
946 }
947
948 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
949  *
950  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
951  *                      human_output@
952  *              @unsigned attr@ = attribute to apply to the outcome
953  *              @const char *outcome@ = outcome string to report
954  *              @const char *detail@, @va_list *ap@ = a detail message
955  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
956  *                      test
957  *
958  * Returns:     ---
959  *
960  * Use:         Report that a test has been skipped or failed.
961  *
962  *              The human driver reports the situation on its output stream.
963  */
964
965 static void human_outcome(struct tvec_output *o,
966                           unsigned attr, const char *outcome,
967                           const char *detail, va_list *ap)
968 {
969   struct human_output *h = (struct human_output *)o;
970   struct tvec_state *tv = h->tv;
971
972   clear_progress(h);
973   human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
974   fprintf(h->lyt.fp, "`%s' ", tv->test->name);
975   setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
976   if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
977   fputc('\n', h->lyt.fp);
978 }
979
980 static void human_skip(struct tvec_output *o,
981                        const char *excuse, va_list *ap)
982   { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
983 static void human_fail(struct tvec_output *o,
984                        const char *detail, va_list *ap)
985   { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
986
987 /* --- @human_dumpreg@ --- *
988  *
989  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
990  *                      human_output@
991  *              @unsigned disp@ = register disposition
992  *              @const union tvec_regval *rv@ = register value
993  *              @const struct tvec_regdef *rd@ = register definition
994  *
995  * Returns:     ---
996  *
997  * Use:         Dump a register.
998  *
999  *              The human driver applies highlighting to mismatching output
1000  *              registers, but otherwise delegates to the register type
1001  *              handler and the layout machinery.
1002  */
1003
1004 static void human_dumpreg(struct tvec_output *o,
1005                           unsigned disp, const union tvec_regval *rv,
1006                           const struct tvec_regdef *rd)
1007 {
1008   struct human_output *h = (struct human_output *)o;
1009   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1010
1011   clear_progress(h);
1012   gprintf(&human_printops, h, "%*s%s %s = ",
1013           10 + h->maxlen - n, "", ds, rd->name);
1014   if (h->f&HOF_COLOUR) {
1015     if (!rv) setattr(h, HA_UNSET);
1016     else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
1017     else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
1018   }
1019   if (!rv) gprintf(&human_printops, h, "#unset");
1020   else rd->ty->dump(rv, rd, 0, &human_printops, h);
1021   setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
1022 }
1023
1024 /* --- @human_etest@ --- *
1025  *
1026  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1027  *                      human_output@
1028  *              @unsigned outcome@ = the test outcome
1029  *
1030  * Returns:     ---
1031  *
1032  * Use:         Report that a test has finished.
1033  *
1034  *              The human driver reactivates the progress display, if
1035  *              necessary, and adds a new character for the completed test.
1036  */
1037
1038 static void human_etest(struct tvec_output *o, unsigned outcome)
1039 {
1040   struct human_output *h = (struct human_output *)o;
1041   int ch;
1042
1043   if (h->f&HOF_TTY) {
1044     show_progress(h);
1045     switch (outcome) {
1046       case TVOUT_WIN: ch = HSB_WIN; break;
1047       case TVOUT_LOSE: ch = HSB_LOSE; break;
1048       case TVOUT_XFAIL: ch = HSB_XFAIL; break;
1049       case TVOUT_SKIP: ch = HSB_SKIP; break;
1050       default: abort();
1051     }
1052     dstr_putc(&h->scoreboard, ch);
1053     write_scoreboard_char(h, ch); fflush(h->lyt.fp);
1054   }
1055 }
1056
1057 /* --- @human_bbench@ --- *
1058  *
1059  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1060  *                      human_output@
1061  *              @const char *ident@ = identifying register values
1062  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1063  *
1064  * Returns:     ---
1065  *
1066  * Use:         Report that a benchmark has started.
1067  *
1068  *              The human driver just prints the start of the benchmark
1069  *              report.
1070  */
1071
1072 static void human_bbench(struct tvec_output *o,
1073                          const char *ident, unsigned unit)
1074 {
1075   struct human_output *h = (struct human_output *)o;
1076   struct tvec_state *tv = h->tv;
1077
1078   clear_progress(h);
1079   gprintf(&human_printops, h, "%s %s: ", tv->test->name, ident);
1080   if (h->f&HOF_TTY) fflush(h->lyt.fp);
1081 }
1082
1083 /* --- @human_ebench@ --- *
1084  *
1085  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1086  *                      human_output@
1087  *              @const char *ident@ = identifying register values
1088  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1089  *              @const struct bench_timing *tm@ = measurement
1090  *
1091  * Returns:     ---
1092  *
1093  * Use:         Report a benchmark's results
1094  *
1095  *              The human driver just delegates to the default benchmark
1096  *              reporting, via the layout machinery.
1097  */
1098
1099 static void human_ebench(struct tvec_output *o,
1100                          const char *ident, unsigned unit,
1101                          const struct bench_timing *tm)
1102 {
1103   struct human_output *h = (struct human_output *)o;
1104
1105   tvec_benchreport(&human_printops, h, unit, 0, tm);
1106   layout_char(&h->lyt, '\n');
1107 }
1108
1109 /* --- @human_report@ --- *
1110  *
1111  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1112  *                      human_output@
1113  *              @unsigned level@ = message level (@TVLEV_...@)
1114  *              @const char *msg@, @va_list *ap@ = format string and
1115  *                      arguments
1116  *
1117  * Returns:     ---
1118  *
1119  * Use:         Report a message to the user.
1120  *
1121  *              The human driver arranges to show the message on @stderr@ as
1122  *              well as the usual output, with a certain amount of
1123  *              intelligence in case they're both actually the same device.
1124  */
1125
1126 static void human_report(struct tvec_output *o, unsigned level,
1127                          const char *msg, va_list *ap)
1128 {
1129   struct human_output *h = (struct human_output *)o;
1130   struct tvec_state *tv = h->tv;
1131   const char *levstr; unsigned levattr;
1132   dstr d = DSTR_INIT;
1133   unsigned f = 0;
1134 #define f_flush 1u
1135 #define f_progress 2u
1136
1137   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1138
1139   switch (level) {
1140 #define CASE(tag, name, val)                                            \
1141     case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1142     TVEC_LEVELS(CASE)
1143     default: levstr = "??"; levattr = HA_UNKLEV; break;
1144   }
1145
1146   if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
1147
1148 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1149
1150   if (h->f^HOF_PROGRESS)
1151     { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
1152   fprintf(stderr, "%s: ", QUIS);
1153   human_report_location(h, stderr, tv->infile, tv->lno);
1154   setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
1155   fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
1156
1157 #undef FLUSH
1158
1159   if (h->f&HOF_DUPERR) {
1160     human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
1161     fprintf(h->lyt.fp, "%s: ", levstr);
1162     fwrite(d.buf, 1, d.len, h->lyt.fp);
1163   }
1164   if (f&f_progress) show_progress(h);
1165
1166 #undef f_flush
1167 #undef f_progress
1168 }
1169
1170 /* --- @human_destroy@ --- *
1171  *
1172  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1173  *                      human_output@
1174  *
1175  * Returns:     ---
1176  *
1177  * Use:         Release the resources held by the output driver.
1178  */
1179
1180 static void human_destroy(struct tvec_output *o)
1181 {
1182   struct human_output *h = (struct human_output *)o;
1183
1184   destroy_layout(&h->lyt,
1185                  h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1186   dstr_destroy(&h->scoreboard);
1187   xfree(h->outbuf); xfree(h);
1188 }
1189
1190 static const struct tvec_outops human_ops = {
1191   human_bsession, human_esession,
1192   human_bgroup, human_skipgroup, human_egroup,
1193   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1194   human_bbench, human_ebench,
1195   human_report,
1196   human_destroy
1197 };
1198
1199 /* --- @tvec_humanoutput@ --- *
1200  *
1201  * Arguments:   @FILE *fp@ = output file to write on
1202  *
1203  * Returns:     An output formatter.
1204  *
1205  * Use:         Return an output formatter which writes on @fp@ with the
1206  *              expectation that a human will be watching and interpreting
1207  *              the output.  If @fp@ denotes a terminal, the display shows a
1208  *              `scoreboard' indicating the outcome of each test case
1209  *              attempted, and may in addition use colour and other
1210  *              highlighting.
1211  */
1212
1213 struct tvec_output *tvec_humanoutput(FILE *fp)
1214 {
1215   struct human_output *h;
1216   const char *p;
1217
1218   h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
1219   h->f = 0; h->attr = 0;
1220
1221   init_layout(&h->lyt, fp, 0);
1222   h->outbuf = 0; h->outsz = 0;
1223
1224   switch (getenv_boolean("TVEC_TTY", -1)) {
1225     case 1: h->f |= HOF_TTY; break;
1226     case 0: break;
1227     default:
1228       if (isatty(fileno(fp))) h->f |= HOF_TTY;
1229       break;
1230   }
1231   switch (getenv_boolean("TVEC_COLOUR", -1)) {
1232     case 1: h->f |= HOF_COLOUR; break;
1233     case 0: break;
1234     default:
1235       if (h->f&HOF_TTY) {
1236         p = getenv("TERM");
1237         if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1238       }
1239       break;
1240   }
1241
1242   if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1243   dstr_create(&h->scoreboard);
1244   return (&h->_o);
1245 }
1246
1247 /*----- Machine-readable output -------------------------------------------*/
1248
1249 struct machine_output {
1250   struct tvec_output _o;                /* output base class */
1251   struct tvec_state *tv;                /* stashed testing state */
1252   FILE *fp;                             /* output stream */
1253   char *outbuf; size_t outsz;           /* buffer for formatted output */
1254   unsigned grpix, testix;               /* group and test indices */
1255   unsigned f;                           /* flags */
1256 #define MF_BENCH 1u
1257 };
1258
1259 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1260  *
1261  * Arguments:   @void *go@ = output sink, secretly a @struct machine_output@
1262  *              @int ch@ = character to write
1263  *              @const char *@p@, @size_t sz@ = string (with explicit length)
1264  *                      to write
1265  *              @const char *p, ...@ = format control string and arguments to
1266  *                      write
1267  *
1268  * Returns:     ---
1269  *
1270  * Use:         Write characters, strings, or formatted strings to the
1271  *              output, applying appropriate quoting.
1272  */
1273
1274 static void machine_escape(struct machine_output *m, int ch)
1275 {
1276   switch (ch) {
1277     case 0: fputs("\\0", m->fp); break;
1278     case '\a': fputs("\\a", m->fp); break;
1279     case '\b': fputs("\\b", m->fp); break;
1280     case '\x1b': fputs("\\e", m->fp); break;
1281     case '\f': fputs("\\f", m->fp); break;
1282     case '\n': fputs("\\n", m->fp); break;
1283     case '\r': fputs("\\r", m->fp); break;
1284     case '\t': fputs("\\t", m->fp); break;
1285     case '\v': fputs("\\v", m->fp); break;
1286     case '"': fputs("\\\"", m->fp); break;
1287     case '\\': fputs("\\\\", m->fp); break;
1288     default: fprintf(m->fp, "\\x{%02x}", ch);
1289   }
1290 }
1291
1292 static int machine_writech(void *go, int ch)
1293 {
1294   struct machine_output *m = go;
1295
1296   if (ISPRINT(ch)) putc(ch, m->fp);
1297   else machine_escape(m, ch);
1298   return (1);
1299 }
1300
1301 static int machine_writem(void *go, const char *p, size_t sz)
1302 {
1303   struct machine_output *m = go;
1304   const char *q, *l = p + sz;
1305
1306   for (;;) {
1307     q = p;
1308     for (;;) {
1309       if (q >= l) goto final;
1310       if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1311       q++;
1312     }
1313     if (p < q) fwrite(p, 1, q - p, m->fp);
1314     p = q; machine_escape(m, (unsigned char)*p++);
1315   }
1316 final:
1317   if (p < l) fwrite(p, 1, l - p, m->fp);
1318   return (sz);
1319 }
1320
1321 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1322 {
1323   struct machine_output *m = go;
1324   int n;
1325   va_list ap;
1326
1327   va_start(ap, p);
1328   n = gprintf_memputf(&m->outbuf, &m->outsz, maxsz, p, ap);
1329   va_end(ap);
1330   return (machine_writem(m, m->outbuf, n));
1331 }
1332
1333 static const struct gprintf_ops machine_printops =
1334   { machine_writech, machine_writem, machine_nwritef };
1335
1336 /* --- @machine_maybe_quote@ --- *
1337  *
1338  * Arguments:   @struct machine_output *m@ = output sink
1339  *              @const char *p@ = pointer to string
1340  *
1341  * Returns:     ---
1342  *
1343  * Use:         Print the string @p@, quoting it if necessary.
1344  */
1345
1346 static void machine_maybe_quote(struct machine_output *m, const char *p)
1347 {
1348   const char *q;
1349
1350   for (q = p; *q; q++)
1351     if (!ISPRINT(*q)) goto quote;
1352   fputs(p, m->fp); return;
1353 quote:
1354   putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1355 }
1356
1357 /* --- @machine_bsession@ --- *
1358  *
1359  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1360  *                      machine_output@
1361  *              @struct tvec_state *tv@ = the test state producing output
1362  *
1363  * Returns:     ---
1364  *
1365  * Use:         Begin a test session.
1366  *
1367  *              The TAP driver records the test state for later reference,
1368  *              initializes the group index counter, and prints the version
1369  *              number.
1370  */
1371
1372 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1373 {
1374   struct machine_output *m = (struct machine_output *)o;
1375
1376   m->tv = tv; m->grpix = 0;
1377 }
1378
1379 /* --- @machine_show_stats@ --- *
1380  *
1381  * Arguments:   @struct machine_output *m@ = output sink
1382  *              @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1383  *
1384  * Returns:     ---
1385  *
1386  * Use:         Print a machine readable outcome statistics table
1387  */
1388
1389 static void machine_show_stats(struct machine_output *m,
1390                                unsigned out[TVOUT_LIMIT])
1391 {
1392   static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1393   unsigned i;
1394
1395   for (i = 0; i < TVOUT_LIMIT; i++) {
1396     if (i) putc(' ', m->fp);
1397     fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1398   }
1399 }
1400
1401 /* --- @machine_esession@ --- *
1402  *
1403  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1404  *                      machine_output@
1405  *
1406  * Returns:     Suggested exit code.
1407  *
1408  * Use:         End a test session.
1409  *
1410  *              The TAP driver prints a final summary of the rest results
1411  *              and returns a suitable exit code.  If errors occurred, it
1412  *              instead prints a `Bail out!' line forcing the reader to
1413  *              report a failure.
1414  */
1415
1416 static int machine_esession(struct tvec_output *o)
1417 {
1418   struct machine_output *m = (struct machine_output *)o;
1419   struct tvec_state *tv = m->tv;
1420
1421   fputs("END groups: ", m->fp);
1422   machine_show_stats(m, tv->grps);
1423   fputs("; tests: ", m->fp);
1424   machine_show_stats(m, tv->all);
1425   putc('\n', m->fp);
1426   m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1427 }
1428
1429 /* --- @machine_bgroup@ --- *
1430  *
1431  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1432  *                      machine_output@
1433  *
1434  * Returns:     ---
1435  *
1436  * Use:         Begin a test group.
1437  *
1438  *              The TAP driver determines the length of the longest
1439  *              register name, resets the group progress scoreboard, and
1440  *              activates the progress display.
1441  */
1442
1443 static void machine_bgroup(struct tvec_output *o)
1444 {
1445   struct machine_output *m = (struct machine_output *)o;
1446   struct tvec_state *tv = m->tv;
1447
1448   fputs("BGROUP ", m->fp);
1449   machine_maybe_quote(m, tv->test->name);
1450   putc('\n', m->fp);
1451   m->grpix++; m->testix = 0;
1452 }
1453
1454 /* --- @machine_skipgroup@ --- *
1455  *
1456  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1457  *                      machine_output@
1458  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1459  *                      group, or null
1460  *
1461  * Returns:     ---
1462  *
1463  * Use:         Report that a test group is being skipped.
1464  *
1465  *              The TAP driver just reports the situation to its output
1466  *              stream.
1467  */
1468
1469 static void machine_skipgroup(struct tvec_output *o,
1470                           const char *excuse, va_list *ap)
1471 {
1472   struct machine_output *m = (struct machine_output *)o;
1473   struct tvec_state *tv = m->tv;
1474
1475   fputs("BGROUP ", m->fp);
1476   machine_maybe_quote(m, tv->test->name);
1477   if (excuse) {
1478     fputs(" \"", m->fp);
1479     vgprintf(&machine_printops, m, excuse, ap);
1480     putc('\"', m->fp);
1481   }
1482   putc('\n', m->fp);
1483 }
1484
1485 /* --- @machine_egroup@ --- *
1486  *
1487  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1488  *                      machine_output@
1489  *
1490  * Returns:     ---
1491  *
1492  * Use:         Report that a test group has finished.
1493  *
1494  *              The TAP driver reports a summary of the group's tests.
1495  */
1496
1497 static void machine_egroup(struct tvec_output *o)
1498 {
1499   struct machine_output *m = (struct machine_output *)o;
1500   struct tvec_state *tv = m->tv;
1501
1502   fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
1503   putc(' ', m->fp); machine_show_stats(m, tv->curr);
1504   putc('\n', m->fp);
1505 }
1506
1507 /* --- @machine_btest@ --- *
1508  *
1509  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1510  *                      machine_output@
1511  *
1512  * Returns:     ---
1513  *
1514  * Use:         Report that a test is starting.
1515  *
1516  *              The TAP driver advances its test counter.  (We could do this
1517  *              by adding up up the counters in @tv->curr@, and add on the
1518  *              current test, but it's easier this way.)
1519  */
1520
1521 static void machine_btest(struct tvec_output *o) { ; }
1522
1523 /* --- @machine_report_location@ --- *
1524  *
1525  * Arguments:   @struct human_output *h@ = output state
1526  *              @const char *file@ = filename
1527  *              @unsigned lno@ = line number
1528  *
1529  * Returns:     ---
1530  *
1531  * Use:         Print the filename and line number to the output stream.
1532  */
1533
1534 static void machine_report_location(struct machine_output *m,
1535                                     const char *file, unsigned lno)
1536   { machine_maybe_quote(m, file); fprintf(m->fp, ":%u", lno); }
1537
1538 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1539  *
1540  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1541  *                      machine_output@
1542  *              @const char *outcome@ = outcome string to report
1543  *              @const char *detail@, @va_list *ap@ = a detail message
1544  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1545  *                      test
1546  *
1547  * Returns:     ---
1548  *
1549  * Use:         Report that a test has been skipped or failed.
1550  */
1551
1552 static void machine_outcome(struct tvec_output *o, const char *outcome,
1553                             const char *detail, va_list *ap)
1554 {
1555   struct machine_output *m = (struct machine_output *)o;
1556   struct tvec_state *tv = m->tv;
1557
1558   fprintf(m->fp, "%s %u ", outcome, m->testix);
1559   machine_report_location(m, tv->infile, tv->test_lno);
1560   if (detail)
1561     { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1562   putc('\n', m->fp);
1563 }
1564
1565 static void machine_skip(struct tvec_output *o,
1566                          const char *excuse, va_list *ap)
1567   { machine_outcome(o, "SKIP", excuse, ap); }
1568 static void machine_fail(struct tvec_output *o,
1569                          const char *detail, va_list *ap)
1570   { machine_outcome(o, "FAIL", detail, ap); }
1571
1572 /* --- @machine_dumpreg@ --- *
1573  *
1574  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1575  *                      machine_output@
1576  *              @unsigned disp@ = register disposition
1577  *              @const union tvec_regval *rv@ = register value
1578  *              @const struct tvec_regdef *rd@ = register definition
1579  *
1580  * Returns:     ---
1581  *
1582  * Use:         Dump a register.
1583  *
1584  *              The TAP driver applies highlighting to mismatching output
1585  *              registers, but otherwise delegates to the register type
1586  *              handler and the layout machinery.  The result is that the
1587  *              register dump is marked as a comment and indented.
1588  */
1589
1590 static void machine_dumpreg(struct tvec_output *o,
1591                         unsigned disp, const union tvec_regval *rv,
1592                         const struct tvec_regdef *rd)
1593 {
1594   struct machine_output *m = (struct machine_output *)o;
1595   unsigned f = 0;
1596 #define f_reg 1u
1597 #define f_nl 2u
1598
1599   switch (disp) {
1600     case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
1601     case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
1602     case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
1603     case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
1604     case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
1605     default: abort();
1606   }
1607
1608   if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
1609   rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
1610   if (f&f_nl) putc('\n', m->fp);
1611
1612 #undef f_reg
1613 #undef f_newline
1614 }
1615
1616 /* --- @machine_etest@ --- *
1617  *
1618  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1619  *                      machine_output@
1620  *              @unsigned outcome@ = the test outcome
1621  *
1622  * Returns:     ---
1623  *
1624  * Use:         Report that a test has finished.
1625  *
1626  *              The TAP driver reports the outcome of the test, if that's not
1627  *              already decided.
1628  */
1629
1630 static void machine_etest(struct tvec_output *o, unsigned outcome)
1631 {
1632   struct machine_output *m = (struct machine_output *)o;
1633
1634   if (!(m->f&MF_BENCH)) switch (outcome) {
1635     case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
1636     case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
1637   }
1638   m->testix++; m->f &= ~MF_BENCH;
1639 }
1640
1641 /* --- @machine_bbench@ --- *
1642  *
1643  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1644  *                      machine_output@
1645  *              @const char *ident@ = identifying register values
1646  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1647  *
1648  * Returns:     ---
1649  *
1650  * Use:         Report that a benchmark has started.
1651  *
1652  *              The TAP driver does nothing here.  All of the reporting
1653  *              happens in @machine_ebench@.
1654  */
1655
1656 static void machine_bbench(struct tvec_output *o,
1657                            const char *ident, unsigned unit)
1658   { ; }
1659
1660 /* --- @machine_ebench@ --- *
1661  *
1662  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1663  *                      machine_output@
1664  *              @const char *ident@ = identifying register values
1665  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1666  *              @const struct bench_timing *tm@ = measurement
1667  *
1668  * Returns:     ---
1669  *
1670  * Use:         Report a benchmark's results
1671  *
1672  *              The TAP driver just delegates to the default benchmark
1673  *              reporting, via the layout machinery so that the result is
1674  *              printed as a comment.
1675  */
1676
1677 static void machine_ebench(struct tvec_output *o,
1678                            const char *ident, unsigned unit,
1679                            const struct bench_timing *tm)
1680 {
1681   struct machine_output *m = (struct machine_output *)o;
1682   struct tvec_state *tv = m->tv;
1683
1684   fprintf(m->fp, "BENCH %u ", m->testix);
1685   machine_report_location(m, tv->infile, tv->test_lno);
1686   fprintf(m->fp, " %s: ", ident);
1687   tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, tm);
1688   putc('\n', m->fp); m->f |= MF_BENCH;
1689 }
1690
1691 /* --- @machine_report@ --- *
1692  *
1693  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1694  *                      machine_output@
1695  *              @unsigned level@ = message level (@TVLEV_...@)
1696  *              @const char *msg@, @va_list *ap@ = format string and
1697  *                      arguments
1698  *
1699  * Returns:     ---
1700  *
1701  * Use:         Report a message to the user.
1702  *
1703  *              Messages are reported as comments, so that they can be
1704  *              accumulated by the reader.  An error will cause a later
1705  *              bailout or, if we crash before then, a missing plan line,
1706  *              either of which will cause the reader to report a serious
1707  *              problem.
1708  */
1709
1710 static void machine_report(struct tvec_output *o, unsigned level,
1711                            const char *msg, va_list *ap)
1712 {
1713   struct machine_output *m = (struct machine_output *)o;
1714   const char *p;
1715
1716   for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
1717   putc(' ', m->fp);
1718   putc('"', m->fp);
1719   vgprintf(&machine_printops, m, msg, ap);
1720   putc('"', m->fp);
1721   putc('\n', m->fp);
1722 }
1723
1724 /* --- @machine_destroy@ --- *
1725  *
1726  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1727  *                      machine_output@
1728  *
1729  * Returns:     ---
1730  *
1731  * Use:         Release the resources held by the output driver.
1732  */
1733
1734 static void machine_destroy(struct tvec_output *o)
1735 {
1736   struct machine_output *m = (struct machine_output *)o;
1737
1738   if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1739   xfree(m->outbuf); xfree(m);
1740 }
1741
1742 static const struct tvec_outops machine_ops = {
1743   machine_bsession, machine_esession,
1744   machine_bgroup, machine_skipgroup, machine_egroup,
1745   machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
1746   machine_bbench, machine_ebench,
1747   machine_report,
1748   machine_destroy
1749 };
1750
1751 /* --- @tvec_machineoutput@ --- *
1752  *
1753  * Arguments:   @FILE *fp@ = output file to write on
1754  *
1755  * Returns:     An output formatter.
1756  *
1757  * Use:         Return an output formatter which writes on @fp@ in a
1758  *              moderately simple machine-readable format.
1759  */
1760
1761 struct tvec_output *tvec_machineoutput(FILE *fp)
1762 {
1763   struct machine_output *m;
1764
1765   m = xmalloc(sizeof(*m)); m->_o.ops = &machine_ops;
1766   m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
1767   return (&m->_o);
1768 }
1769
1770 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1771
1772 struct tap_output {
1773   struct tvec_output _o;                /* output base class */
1774   struct tvec_state *tv;                /* stashed testing state */
1775   struct layout lyt;                    /* output layout */
1776   char *outbuf; size_t outsz;           /* buffer for formatted output */
1777   unsigned grpix, testix;               /* group and test indices */
1778   unsigned previx;                      /* previously reported test index */
1779   int maxlen;                           /* longest register name */
1780 };
1781
1782 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1783  *
1784  * Arguments:   @void *go@ = output sink, secretly a @struct tap_output@
1785  *              @int ch@ = character to write
1786  *              @const char *@p@, @size_t sz@ = string (with explicit length)
1787  *                      to write
1788  *              @const char *p, ...@ = format control string and arguments to
1789  *                      write
1790  *
1791  * Returns:     ---
1792  *
1793  * Use:         Write characters, strings, or formatted strings to the
1794  *              output, applying appropriate layout.
1795  *
1796  *              For the TAP output driver, the layout machinery prefixes each
1797  *              line with `    ## ' and strips trailing spaces.
1798  */
1799
1800 static int tap_writech(void *go, int ch)
1801 {
1802   struct tap_output *t = go;
1803
1804   if (layout_char(&t->lyt, ch)) return (-1);
1805   else return (1);
1806 }
1807
1808 static int tap_writem(void *go, const char *p, size_t sz)
1809 {
1810   struct tap_output *t = go;
1811
1812   if (layout_string(&t->lyt, p, sz)) return (-1);
1813   else return (sz);
1814 }
1815
1816 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1817 {
1818   struct tap_output *t = go;
1819   size_t n;
1820   va_list ap;
1821
1822   va_start(ap, p);
1823   n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
1824   va_end(ap);
1825   if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1826   return (n);
1827 }
1828
1829 static const struct gprintf_ops tap_printops =
1830   { tap_writech, tap_writem, tap_nwritef };
1831
1832 /* --- @tap_bsession@ --- *
1833  *
1834  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1835  *                      tap_output@
1836  *              @struct tvec_state *tv@ = the test state producing output
1837  *
1838  * Returns:     ---
1839  *
1840  * Use:         Begin a test session.
1841  *
1842  *              The TAP driver records the test state for later reference,
1843  *              initializes the group index counter, and prints the version
1844  *              number.
1845  */
1846
1847 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1848 {
1849   struct tap_output *t = (struct tap_output *)o;
1850
1851   t->tv = tv; t->grpix = 0;
1852   fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1853 }
1854
1855 /* --- @tap_esession@ --- *
1856  *
1857  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1858  *                      tap_output@
1859  *
1860  * Returns:     Suggested exit code.
1861  *
1862  * Use:         End a test session.
1863  *
1864  *              The TAP driver prints a final summary of the rest results
1865  *              and returns a suitable exit code.  If errors occurred, it
1866  *              instead prints a `Bail out!' line forcing the reader to
1867  *              report a failure.
1868  */
1869
1870 static int tap_esession(struct tvec_output *o)
1871 {
1872   struct tap_output *t = (struct tap_output *)o;
1873   struct tvec_state *tv = t->tv;
1874
1875   if (tv->f&TVSF_ERROR) {
1876     fputs("Bail out!  "
1877           "Errors found in input; tests may not have run correctly\n",
1878           t->lyt.fp);
1879     return (2);
1880   }
1881
1882   fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1883   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1884 }
1885
1886 /* --- @tap_bgroup@ --- *
1887  *
1888  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1889  *                      tap_output@
1890  *
1891  * Returns:     ---
1892  *
1893  * Use:         Begin a test group.
1894  *
1895  *              The TAP driver determines the length of the longest
1896  *              register name, resets the group progress scoreboard, and
1897  *              activates the progress display.
1898  */
1899
1900 static void tap_bgroup(struct tvec_output *o)
1901 {
1902   struct tap_output *t = (struct tap_output *)o;
1903   struct tvec_state *tv = t->tv;
1904
1905   t->grpix++; t->testix = t->previx = 0;
1906   t->maxlen = register_maxnamelen(t->tv);
1907   fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1908 }
1909
1910 /* --- @tap_skipgroup@ --- *
1911  *
1912  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1913  *                      tap_output@
1914  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1915  *                      group, or null
1916  *
1917  * Returns:     ---
1918  *
1919  * Use:         Report that a test group is being skipped.
1920  *
1921  *              The TAP driver just reports the situation to its output
1922  *              stream.
1923  */
1924
1925 static void tap_skipgroup(struct tvec_output *o,
1926                           const char *excuse, va_list *ap)
1927 {
1928   struct tap_output *t = (struct tap_output *)o;
1929
1930   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
1931   fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
1932   if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1933   fputc('\n', t->lyt.fp);
1934 }
1935
1936 /* --- @tap_egroup@ --- *
1937  *
1938  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1939  *                      tap_output@
1940  *
1941  * Returns:     ---
1942  *
1943  * Use:         Report that a test group has finished.
1944  *
1945  *              The TAP driver reports a summary of the group's tests.
1946  */
1947
1948 static void tap_egroup(struct tvec_output *o)
1949 {
1950   struct tap_output *t = (struct tap_output *)o;
1951   struct tvec_state *tv = t->tv;
1952
1953   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
1954   fprintf(t->lyt.fp, "%s %u - %s\n",
1955           tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
1956           t->grpix, tv->test->name);
1957 }
1958
1959 /* --- @tap_btest@ --- *
1960  *
1961  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1962  *                      tap_output@
1963  *
1964  * Returns:     ---
1965  *
1966  * Use:         Report that a test is starting.
1967  *
1968  *              The TAP driver advances its test counter.  (We could do this
1969  *              by adding up up the counters in @tv->curr@, and add on the
1970  *              current test, but it's easier this way.)
1971  */
1972
1973 static void tap_btest(struct tvec_output *o)
1974   { struct tap_output *t = (struct tap_output *)o; t->testix++; }
1975
1976 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
1977  *
1978  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1979  *                      tap_output@
1980  *              @const char *head, *tail@ = outcome strings to report
1981  *              @const char *detail@, @va_list *ap@ = a detail message
1982  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1983  *                      test
1984  *
1985  * Returns:     ---
1986  *
1987  * Use:         Report that a test has been skipped or failed.
1988  *
1989  *              The TAP driver reports the situation on its output stream.
1990  *              TAP only allows us to report a single status for each
1991  *              subtest, so we notice when we've already reported a status
1992  *              for the current test and convert the second report as a
1993  *              comment.  This should only happen in the case of multiple
1994  *              failures.
1995  */
1996
1997 static void tap_outcome(struct tvec_output *o,
1998                         const char *head, const char *tail,
1999                         const char *detail, va_list *ap)
2000 {
2001   struct tap_output *t = (struct tap_output *)o;
2002   struct tvec_state *tv = t->tv;
2003
2004   fprintf(t->lyt.fp, "    %s %u - %s:%u%s",
2005           t->testix == t->previx ? "##" : head,
2006           t->testix, tv->infile, tv->test_lno, tail);
2007   if (detail)
2008     { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2009   fputc('\n', t->lyt.fp);
2010   t->previx = t->testix;
2011 }
2012
2013 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2014   { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
2015 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
2016   { tap_outcome(o, "not ok", "", detail, ap); }
2017
2018 /* --- @tap_dumpreg@ --- *
2019  *
2020  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
2021  *                      tap_output@
2022  *              @unsigned disp@ = register disposition
2023  *              @const union tvec_regval *rv@ = register value
2024  *              @const struct tvec_regdef *rd@ = register definition
2025  *
2026  * Returns:     ---
2027  *
2028  * Use:         Dump a register.
2029  *
2030  *              The TAP driver applies highlighting to mismatching output
2031  *              registers, but otherwise delegates to the register type
2032  *              handler and the layout machinery.  The result is that the
2033  *              register dump is marked as a comment and indented.
2034  */
2035
2036 static void tap_dumpreg(struct tvec_output *o,
2037                         unsigned disp, const union tvec_regval *rv,
2038                         const struct tvec_regdef *rd)
2039 {
2040   struct tap_output *t = (struct tap_output *)o;
2041   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2042
2043   set_layout_prefix(&t->lyt, "    ## ");
2044   gprintf(&tap_printops, t, "%*s%s %s = ",
2045           10 + t->maxlen - n, "", ds, rd->name);
2046   if (!rv) gprintf(&tap_printops, t, "#<unset>");
2047   else rd->ty->dump(rv, rd, 0, &tap_printops, t);
2048   layout_char(&t->lyt, '\n');
2049 }
2050
2051 /* --- @tap_etest@ --- *
2052  *
2053  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
2054  *                      tap_output@
2055  *              @unsigned outcome@ = the test outcome
2056  *
2057  * Returns:     ---
2058  *
2059  * Use:         Report that a test has finished.
2060  *
2061  *              The TAP driver reports the outcome of the test, if that's not
2062  *              already decided.
2063  */
2064
2065 static void tap_etest(struct tvec_output *o, unsigned outcome)
2066 {
2067   switch (outcome) {
2068     case TVOUT_WIN:
2069       tap_outcome(o, "ok", "", 0, 0);
2070       break;
2071     case TVOUT_XFAIL:
2072       tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2073       break;
2074   }
2075 }
2076
2077 /* --- @tap_bbench@ --- *
2078  *
2079  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
2080  *                      tap_output@
2081  *              @const char *ident@ = identifying register values
2082  *              @unsigned unit@ = measurement unit (@TVBU_...@)
2083  *
2084  * Returns:     ---
2085  *
2086  * Use:         Report that a benchmark has started.
2087  *
2088  *              The TAP driver does nothing here.  All of the reporting
2089  *              happens in @tap_ebench@.
2090  */
2091
2092 static void tap_bbench(struct tvec_output *o,
2093                        const char *ident, unsigned unit)
2094   { ; }
2095
2096 /* --- @tap_ebench@ --- *
2097  *
2098  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
2099  *                      tap_output@
2100  *              @const char *ident@ = identifying register values
2101  *              @unsigned unit@ = measurement unit (@TVBU_...@)
2102  *              @const struct bench_timing *tm@ = measurement
2103  *
2104  * Returns:     ---
2105  *
2106  * Use:         Report a benchmark's results
2107  *
2108  *              The TAP driver just delegates to the default benchmark
2109  *              reporting, via the layout machinery so that the result is
2110  *              printed as a comment.
2111  */
2112
2113 static void tap_ebench(struct tvec_output *o,
2114                        const char *ident, unsigned unit,
2115                        const struct bench_timing *tm)
2116 {
2117   struct tap_output *t = (struct tap_output *)o;
2118   struct tvec_state *tv = t->tv;
2119
2120   set_layout_prefix(&t->lyt, "    ## ");
2121   gprintf(&tap_printops, t, "%s %s: ", tv->test->name, ident);
2122   tvec_benchreport(&tap_printops, t, unit, 0, tm);
2123   layout_char(&t->lyt, '\n');
2124 }
2125
2126 /* --- @tap_report@ --- *
2127  *
2128  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
2129  *                      tap_output@
2130  *              @unsigned level@ = message level (@TVLEV_...@)
2131  *              @const char *msg@, @va_list *ap@ = format string and
2132  *                      arguments
2133  *
2134  * Returns:     ---
2135  *
2136  * Use:         Report a message to the user.
2137  *
2138  *              Messages are reported as comments, so that they can be
2139  *              accumulated by the reader.  An error will cause a later
2140  *              bailout or, if we crash before then, a missing plan line,
2141  *              either of which will cause the reader to report a serious
2142  *              problem.
2143  */
2144
2145 static void tap_report(struct tvec_output *o, unsigned level,
2146                        const char *msg, va_list *ap)
2147 {
2148   struct tap_output *t = (struct tap_output *)o;
2149   struct tvec_state *tv = t->tv;
2150
2151   if (tv->test) set_layout_prefix(&t->lyt, "    ## ");
2152   else set_layout_prefix(&t->lyt, "## ");
2153
2154   if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
2155   gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
2156   vgprintf(&tap_printops, t, msg, ap);
2157   layout_char(&t->lyt, '\n');
2158 }
2159
2160 /* --- @tap_destroy@ --- *
2161  *
2162  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
2163  *                      tap_output@
2164  *
2165  * Returns:     ---
2166  *
2167  * Use:         Release the resources held by the output driver.
2168  */
2169
2170 static void tap_destroy(struct tvec_output *o)
2171 {
2172   struct tap_output *t = (struct tap_output *)o;
2173
2174   destroy_layout(&t->lyt,
2175                  t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
2176   xfree(t->outbuf); xfree(t);
2177 }
2178
2179 static const struct tvec_outops tap_ops = {
2180   tap_bsession, tap_esession,
2181   tap_bgroup, tap_skipgroup, tap_egroup,
2182   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
2183   tap_bbench, tap_ebench,
2184   tap_report,
2185   tap_destroy
2186 };
2187
2188 /* --- @tvec_tapoutput@ --- *
2189  *
2190  * Arguments:   @FILE *fp@ = output file to write on
2191  *
2192  * Returns:     An output formatter.
2193  *
2194  * Use:         Return an output formatter which writes on @fp@ in `TAP'
2195  *              (`Test Anything Protocol') format.
2196  *
2197  *              TAP comes from the Perl community, but has spread rather
2198  *              further.  This driver produces TAP version 14, but pretends
2199  *              to be version 13.  The driver produces a TAP `test point' --
2200  *              i.e., a result reported as `ok' or `not ok' -- for each input
2201  *              test group.  Failure reports and register dumps are produced
2202  *              as diagnostic messages before the final group result.  (TAP
2203  *              permits structuerd YAML data after the test-point result,
2204  *              which could be used to report details, but (a) postponing the
2205  *              details until after the report is inconvenient, and (b) there
2206  *              is no standardization for the YAML anyway, so in practice
2207  *              it's no more useful than the unstructured diagnostics.
2208  */
2209
2210 struct tvec_output *tvec_tapoutput(FILE *fp)
2211 {
2212   struct tap_output *t;
2213
2214   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
2215   init_layout(&t->lyt, fp, 0);
2216   t->outbuf = 0; t->outsz = 0;
2217   return (&t->_o);
2218 }
2219
2220 /*----- Default output ----------------------------------------------------*/
2221
2222 /* --- @tvec_dfltoutput@ --- *
2223  *
2224  * Arguments:   @FILE *fp@ = output file to write on
2225  *
2226  * Returns:     An output formatter.
2227  *
2228  * Use:         Selects and instantiates an output formatter suitable for
2229  *              writing on @fp@.  The policy is subject to change, but
2230  *              currently the `human' output format is selected if @fp@ is
2231  *              interactive (i.e., if @isatty(fileno(fp))@ is true), and
2232  *              otherwise the `tap' format is used.
2233  */
2234
2235 struct tvec_output *tvec_dfltout(FILE *fp)
2236 {
2237   int ttyp = getenv_boolean("TVEC_TTY", -1);
2238
2239   if (ttyp == -1) ttyp = isatty(fileno(fp));
2240   if (ttyp) return (tvec_humanoutput(fp));
2241   else return (tvec_tapoutput(fp));
2242 }
2243
2244 /*----- That's all, folks -------------------------------------------------*/