chiark / gitweb /
5210d11e25b1b63305a3d4c3b359bd8100a9229b
[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_EXPECT: return "expected";
65     case TVRD_FOUND: return "found";
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 /*----- Skeleton ----------------------------------------------------------*/
420 /*
421 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
422 static int ..._esession(struct tvec_output *o)
423 static void ..._bgroup(struct tvec_output *o)
424 static void ..._skipgroup(struct tvec_output *o,
425                           const char *excuse, va_list *ap)
426 static void ..._egroup(struct tvec_output *o)
427 static void ..._btest(struct tvec_output *o)
428 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
429 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
430 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
431                         union tvec_regval *rv, const struct tvec_regdef *rd)
432 static void ..._etest(struct tvec_output *o, unsigned outcome)
433 static void ..._bbench(struct tvec_output *o,
434                        const char *ident, unsigned unit)
435 static void ..._ebench(struct tvec_output *o,
436                        const char *ident, unsigned unit,
437                        const struct tvec_timing *t)
438 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
439 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
440 static void ..._destroy(struct tvec_output *o)
441
442 static const struct tvec_outops ..._ops = {
443   ..._bsession, ..._esession,
444   ..._bgroup, ..._egroup, ..._skip,
445   ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
446   ..._bbench, ..._ebench,
447   ..._error, ..._notice,
448   ..._destroy
449 };
450 */
451 /*----- Human-readable output ---------------------------------------------*/
452
453 /* Attributes for colour output.  This should be done better, but @terminfo@
454  * is a disaster.
455  *
456  * An attribute byte holds a foreground colour in the low nibble, a
457  * background colour in the next nibble, and some flags in the next few
458  * bits.  A colour is expressed in classic 1-bit-per-channel style, with red,
459  * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
460  */
461 #define HAF_FGMASK 0x0f                 /* foreground colour mask */
462 #define HAF_FGSHIFT 0                   /* foreground colour shift */
463 #define HAF_BGMASK 0xf0                 /* background colour mask */
464 #define HAF_BGSHIFT 4                   /* background colour shift */
465 #define HAF_FG 256u                     /* set foreground? */
466 #define HAF_BG 512u                     /* set background? */
467 #define HAF_BOLD 1024u                  /* set bold? */
468 #define HCOL_BLACK 0u                   /* colour codes... */
469 #define HCOL_RED 1u
470 #define HCOL_GREEN 2u
471 #define HCOL_YELLOW 3u
472 #define HCOL_BLUE 4u
473 #define HCOL_MAGENTA 5u
474 #define HCOL_CYAN 6u
475 #define HCOL_WHITE 7u
476 #define HCF_BRIGHT 8u                   /* bright colour flag */
477 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
478 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
479
480 /* Predefined attributes. */
481 #define HA_PLAIN 0                   /* nothing special: terminal defaults */
482 #define HA_LOC (HFG(CYAN))              /* filename or line number */
483 #define HA_LOCSEP (HFG(BLUE))           /* location separator `:' */
484 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
485 #define HA_NOTE (HFG(YELLOW))           /* notices */
486 #define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
487 #define HA_UNSET (HFG(YELLOW))          /* register not set */
488 #define HA_FOUND (HFG(RED))             /* incorrect output value */
489 #define HA_EXPECT (HFG(GREEN))          /* what the value should have been */
490 #define HA_WIN (HFG(GREEN))             /* reporting success */
491 #define HA_LOSE (HFG(RED) | HAF_BOLD)   /* reporting failure */
492 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
493 #define HA_SKIP (HFG(YELLOW))           /* reporting a skipped test/group */
494
495 /* Scoreboard indicators. */
496 #define HSB_WIN '.'                     /* test passed */
497 #define HSB_LOSE 'x'                    /* test failed */
498 #define HSB_XFAIL 'o'                   /* test failed expectedly */
499 #define HSB_SKIP '_'                    /* test wasn't run */
500
501 struct human_output {
502   struct tvec_output _o;                /* output base class */
503   struct tvec_state *tv;                /* stashed testing state */
504   struct layout lyt;                    /* output layout */
505   char *outbuf; size_t outsz;           /* buffer for formatted output */
506   dstr scoreboard;                      /* history of test group results */
507   unsigned attr;                        /* current terminal attributes */
508   int maxlen;                           /* longest register name */
509   unsigned f;                           /* flags */
510 #define HOF_TTY 1u                      /*   writing to terminal */
511 #define HOF_DUPERR 2u                   /*   duplicate errors to stderr */
512 #define HOF_COLOUR 4u                   /*   print in angry fruit salad */
513 #define HOF_PROGRESS 8u                 /*   progress display is active */
514 };
515
516 /* --- @set_colour@ --- *
517  *
518  * Arguments:   @FILE *fp@ = output stream to write on
519  *              @int *sep_inout@ = where to maintain separator
520  *              @const char *norm@ = prefix for normal colour
521  *              @const char *bright@ = prefix for bright colour
522  *              @unsigned colour@ = four bit colour code
523  *
524  * Returns:     ---
525  *
526  * Use:         Write to the output stream @fp@, the current character at
527  *              @*sep_inout@, if that's not zero, followed by either @norm@
528  *              or @bright@, according to whether the @HCF_BRIGHT@ flag is
529  *              set in @colour@, followed by the plain colour code from
530  *              @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
531  *
532  *              This is an internal subroutine for @setattr@ below.
533  */
534
535 static void set_colour(FILE *fp, int *sep_inout,
536                        const char *norm, const char *bright,
537                        unsigned colour)
538 {
539   if (*sep_inout) putc(*sep_inout, fp);
540   fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
541   *sep_inout = ';';
542 }
543
544 /* --- @setattr@ --- *
545  *
546  * Arguments:   @struct human_output *h@ = output state
547  *              @unsigned attr@ = attribute code to set
548  *
549  * Returns:     ---
550  *
551  * Use:         Send a control sequence to the output stream so that
552  *              subsequent text is printed with the given attributes.
553  *
554  *              Some effort is taken to avoid unnecessary control sequences.
555  *              In particular, if @attr@ matches the current terminal
556  *              settings already, then nothing is written.
557  */
558
559 static void setattr(struct human_output *h, unsigned attr)
560 {
561   unsigned diff = h->attr ^ attr;
562   int sep = 0;
563
564   /* If there's nothing to do, we might as well stop now. */
565   if (!diff || !(h->f&HOF_COLOUR)) return;
566
567   /* Start on the control command. */
568   fputs("\x1b[", h->lyt.fp);
569
570   /* Change the boldness if necessary. */
571   if (diff&HAF_BOLD) {
572     if (attr&HAF_BOLD) putc('1', h->lyt.fp);
573     else { putc('0', h->lyt.fp); diff = h->attr; }
574     sep = ';';
575   }
576
577   /* Change the foreground colour if necessary. */
578   if (diff&(HAF_FG | HAF_FGMASK)) {
579     if (attr&HAF_FG)
580       set_colour(h->lyt.fp, &sep, "3", "9",
581                  (attr&HAF_FGMASK) >> HAF_FGSHIFT);
582     else {
583       if (sep) putc(sep, h->lyt.fp);
584       fputs("39", h->lyt.fp); sep = ';';
585     }
586   }
587
588   /* Change the background colour if necessary. */
589   if (diff&(HAF_BG | HAF_BGMASK)) {
590     if (attr&HAF_BG)
591       set_colour(h->lyt.fp, &sep, "4", "10",
592                  (attr&HAF_BGMASK) >> HAF_BGSHIFT);
593     else {
594       if (sep) putc(sep, h->lyt.fp);
595       fputs("49", h->lyt.fp); sep = ';';
596     }
597   }
598
599   /* Terminate the control command and save the new attributes. */
600   putc('m', h->lyt.fp); h->attr = attr;
601 }
602
603 /* --- @clear_progress@ --- *
604  *
605  * Arguments:   @struct human_output *h@ = output state
606  *
607  * Returns:     ---
608  *
609  * Use:         Remove the progress display from the terminal.
610  *
611  *              If the progress display isn't active then do nothing.
612  */
613
614 static void clear_progress(struct human_output *h)
615 {
616   size_t i, n;
617
618   if (h->f&HOF_PROGRESS) {
619     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
620     for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
621     h->f &= ~HOF_PROGRESS;
622   }
623 }
624
625 /* --- @write_scoreboard_char@ --- *
626  *
627  * Arguments:   @struct human_output *h@ = output state
628  *              @int ch@ = scoreboard character to print
629  *
630  * Returns:     ---
631  *
632  * Use:         Write a scoreboard character, indicating the outcome of a
633  *              test, to the output stream, with appropriate highlighting.
634  */
635
636 static void write_scoreboard_char(struct human_output *h, int ch)
637 {
638   switch (ch) {
639     case HSB_LOSE: setattr(h, HA_LOSE); break;
640     case HSB_SKIP: setattr(h, HA_SKIP); break;
641     case HSB_XFAIL: setattr(h, HA_XFAIL); break;
642     default: setattr(h, HA_PLAIN); break;
643   }
644   putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
645 }
646
647 /* --- @show_progress@ --- *
648  *
649  * Arguments:   @struct human_output *h@ = output state
650  *
651  * Returns:     ---
652  *
653  * Use:         Show the progress display, with the record of outcomes for
654  *              the current test group.
655  *
656  *              If the progress display is already active, or the output
657  *              stream is not interactive, then nothing happens.
658  */
659
660 static void show_progress(struct human_output *h)
661 {
662   struct tvec_state *tv = h->tv;
663   const char *p, *l;
664
665   if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
666     fprintf(h->lyt.fp, "%s: ", tv->test->name);
667     if (!(h->f&HOF_COLOUR))
668       dstr_write(&h->scoreboard, h->lyt.fp);
669     else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
670       write_scoreboard_char(h, *p);
671     fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
672   }
673 }
674
675 /* --- @human_writech@, @human_write@, @human_writef@ --- *
676  *
677  * Arguments:   @void *go@ = output sink, secretly a @struct human_output@
678  *              @int ch@ = character to write
679  *              @const char *@p@, @size_t sz@ = string (with explicit length)
680  *                      to write
681  *              @const char *p, ...@ = format control string and arguments to
682  *                      write
683  *
684  * Returns:     ---
685  *
686  * Use:         Write characters, strings, or formatted strings to the
687  *              output, applying appropriate layout.
688  *
689  *              For the human output driver, the layout machinery just strips
690  *              trailing spaces.
691  */
692
693 static int human_writech(void *go, int ch)
694   { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
695
696 static int human_writem(void *go, const char *p, size_t sz)
697   { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
698
699 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
700 {
701   struct human_output *h = go;
702   size_t n;
703   va_list ap;
704
705   va_start(ap, p);
706   n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
707   va_end(ap);
708   return (layout_string(&h->lyt, h->outbuf, n));
709 }
710
711 static const struct gprintf_ops human_printops =
712   { human_writech, human_writem, human_nwritef };
713
714 /* --- @human_bsession@ --- *
715  *
716  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
717  *                      human_output@
718  *              @struct tvec_state *tv@ = the test state producing output
719  *
720  * Returns:     ---
721  *
722  * Use:         Begin a test session.
723  *
724  *              The human driver just records the test state for later
725  *              reference.
726  */
727
728 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
729   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
730
731 /* --- @report_unusual@ --- *
732  *
733  * Arguments:   @struct human_output *h@ = output sink
734  *              @unsigned nxfail, nskip@ = number of expected failures and
735  *                      skipped tests
736  *
737  * Returns:     ---
738  *
739  * Use:         Write (directly on the output stream) a note about expected
740  *              failures and/or skipped tests, if there were any.
741  */
742
743 static void report_unusual(struct human_output *h,
744                            unsigned nxfail, unsigned nskip)
745 {
746   const char *sep = " (";
747   unsigned f = 0;
748 #define f_any 1u
749
750   if (nxfail) {
751     fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
752     setattr(h, HA_XFAIL);
753     fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
754     setattr(h, HA_PLAIN);
755     sep = ", "; f |= f_any;
756   }
757
758   if (nskip) {
759     fprintf(h->lyt.fp, "%s%u ", sep, nskip);
760     setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
761     sep = ", "; f |= f_any;
762   }
763
764   if (f&f_any) fputc(')', h->lyt.fp);
765
766 #undef f_any
767 }
768
769 /* --- @human_esession@ --- *
770  *
771  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
772  *                      human_output@
773  *
774  * Returns:     Suggested exit code.
775  *
776  * Use:         End a test session.
777  *
778  *              The human driver prints a final summary of the rest results
779  *              and returns a suitable exit code.
780  */
781
782 static int human_esession(struct tvec_output *o)
783 {
784   struct human_output *h = (struct human_output *)o;
785   struct tvec_state *tv = h->tv;
786   unsigned
787     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
788     all_xfail = tv->all[TVOUT_XFAIL],
789     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
790     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
791     all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
792     grps_run = grps_win + grps_lose;
793
794   if (!all_lose) {
795     setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
796     fprintf(h->lyt.fp, " %s%u %s",
797             !(all_skip || grps_skip) ? "all " : "",
798             all_pass, all_pass == 1 ? "test" : "tests");
799     report_unusual(h, all_xfail, all_skip);
800     fprintf(h->lyt.fp, " in %u %s",
801             grps_win, grps_win == 1 ? "group" : "groups");
802     report_unusual(h, 0, grps_skip);
803   } else {
804     setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
805     fprintf(h->lyt.fp, " %u out of %u %s",
806             all_lose, all_run, all_run == 1 ? "test" : "tests");
807     report_unusual(h, all_xfail, all_skip);
808     fprintf(h->lyt.fp, " in %u out of %u %s",
809             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
810     report_unusual(h, 0, grps_skip);
811   }
812   fputc('\n', h->lyt.fp);
813
814   if (tv->f&TVSF_ERROR) {
815     setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
816     fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
817   }
818
819   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
820 }
821
822 /* --- @human_bgroup@ --- *
823  *
824  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
825  *                      human_output@
826  *
827  * Returns:     ---
828  *
829  * Use:         Begin a test group.
830  *
831  *              The human driver determines the length of the longest
832  *              register name, resets the group progress scoreboard, and
833  *              activates the progress display.
834  */
835
836 static void human_bgroup(struct tvec_output *o)
837 {
838   struct human_output *h = (struct human_output *)o;
839
840   h->maxlen = register_maxnamelen(h->tv);
841   dstr_reset(&h->scoreboard); show_progress(h);
842 }
843
844 /* --- @human_skipgroup@ --- *
845  *
846  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
847  *                      human_output@
848  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
849  *                      group, or null
850  *
851  * Returns:     ---
852  *
853  * Use:         Report that a test group is being skipped.
854  *
855  *              The human driver just reports the situation to its output
856  *              stream.
857  */
858
859 static void human_skipgroup(struct tvec_output *o,
860                             const char *excuse, va_list *ap)
861 {
862   struct human_output *h = (struct human_output *)o;
863
864   if (!(h->f&HOF_TTY))
865     fprintf(h->lyt.fp, "%s ", h->tv->test->name);
866   else {
867     show_progress(h); h->f &= ~HOF_PROGRESS;
868     if (h->scoreboard.len) putc(' ', h->lyt.fp);
869   }
870   setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
871   if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
872   fputc('\n', h->lyt.fp);
873 }
874
875 /* --- @human_egroup@ --- *
876  *
877  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
878  *                      human_output@
879  *
880  * Returns:     ---
881  *
882  * Use:         Report that a test group has finished.
883  *
884  *              The human driver reports a summary of the group's tests.
885  */
886
887 static void human_egroup(struct tvec_output *o)
888 {
889   struct human_output *h = (struct human_output *)o;
890   struct tvec_state *tv = h->tv;
891   unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
892     lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
893     run = win + lose + xfail;
894
895   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
896   else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
897
898   if (lose) {
899     fprintf(h->lyt.fp, " %u/%u ", lose, run);
900     setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
901     report_unusual(h, xfail, skip);
902   } else {
903     fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
904     fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
905     report_unusual(h, xfail, skip);
906   }
907   fputc('\n', h->lyt.fp);
908 }
909
910 /* --- @human_btest@ --- *
911  *
912  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
913  *                      human_output@
914  *
915  * Returns:     ---
916  *
917  * Use:         Report that a test is starting.
918  *
919  *              The human driver makes sure the progress display is active.
920  */
921
922 static void human_btest(struct tvec_output *o)
923   { struct human_output *h = (struct human_output *)o; show_progress(h); }
924
925 /* --- @report_location@ --- *
926  *
927  * Arguments:   @struct human_output *h@ = output state
928  *              @FILE *fp@ = stream to write the location on
929  *              @const char *file@ = filename
930  *              @unsigned lno@ = line number
931  *
932  * Returns:     ---
933  *
934  * Use:         Print the filename and line number to the output stream @fp@.
935  *              Also, if appropriate, print interleaved highlighting control
936  *              codes to our usual output stream.  If @file@ is null then do
937  *              nothing.
938  */
939
940 static void report_location(struct human_output *h, FILE *fp,
941                             const char *file, unsigned lno)
942 {
943   unsigned f = 0;
944 #define f_flush 1u
945
946   /* We emit highlighting if @fp@ is our usual output stream, or the
947    * duplicate-errors flag is clear indicating that (we assume) they're
948    * secretly going to the same place anyway.  If they're different streams,
949    * though, we have to be careful to keep the highlighting and the actual
950    * text synchronized.
951    */
952
953   if (!file)
954     /* nothing to do */;
955   else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
956     fprintf(fp, "%s:%u: ", file, lno);
957   else {
958     if (fp != h->lyt.fp) f |= f_flush;
959
960 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
961
962     setattr(h, HA_LOC);         FLUSH(h->lyt.fp);
963     fputs(file, fp);            FLUSH(fp);
964     setattr(h, HA_LOCSEP);      FLUSH(h->lyt.fp);
965     fputc(':', fp);             FLUSH(fp);
966     setattr(h, HA_LOC);         FLUSH(h->lyt.fp);
967     fprintf(fp, "%u", lno);     FLUSH(fp);
968     setattr(h, HA_LOCSEP);      FLUSH(h->lyt.fp);
969     fputc(':', fp);             FLUSH(fp);
970     setattr(h, HA_PLAIN);       FLUSH(h->lyt.fp);
971     fputc(' ', fp);
972
973 #undef FLUSH
974   }
975
976 #undef f_flush
977 }
978
979 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
980  *
981  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
982  *                      human_output@
983  *              @unsigned attr@ = attribute to apply to the outcome
984  *              @const char *outcome@ = outcome string to report
985  *              @const char *detail@, @va_list *ap@ = a detail message
986  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
987  *                      test
988  *
989  * Returns:     ---
990  *
991  * Use:         Report that a test has been skipped or failed.
992  *
993  *              The human driver reports the situation on its output stream.
994  */
995
996 static void human_outcome(struct tvec_output *o,
997                           unsigned attr, const char *outcome,
998                           const char *detail, va_list *ap)
999 {
1000   struct human_output *h = (struct human_output *)o;
1001   struct tvec_state *tv = h->tv;
1002
1003   clear_progress(h);
1004   report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
1005   fprintf(h->lyt.fp, "`%s' ", tv->test->name);
1006   setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
1007   if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
1008   fputc('\n', h->lyt.fp);
1009 }
1010
1011 static void human_skip(struct tvec_output *o,
1012                        const char *excuse, va_list *ap)
1013   { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
1014 static void human_fail(struct tvec_output *o,
1015                        const char *detail, va_list *ap)
1016   { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
1017
1018 /* --- @human_dumpreg@ --- *
1019  *
1020  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1021  *                      human_output@
1022  *              @unsigned disp@ = register disposition
1023  *              @const union tvec_regval *rv@ = register value
1024  *              @const struct tvec_regdef *rd@ = register definition
1025  *
1026  * Returns:     ---
1027  *
1028  * Use:         Dump a register.
1029  *
1030  *              The human driver applies highlighting to mismatching output
1031  *              registers, but otherwise delegates to the register type
1032  *              handler and the layout machinery.
1033  */
1034
1035 static void human_dumpreg(struct tvec_output *o,
1036                           unsigned disp, const union tvec_regval *rv,
1037                           const struct tvec_regdef *rd)
1038 {
1039   struct human_output *h = (struct human_output *)o;
1040   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1041
1042   clear_progress(h);
1043   gprintf(&human_printops, h, "%*s%s %s = ",
1044           10 + h->maxlen - n, "", ds, rd->name);
1045   if (h->f&HOF_COLOUR) {
1046     if (!rv) setattr(h, HA_UNSET);
1047     else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
1048     else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
1049   }
1050   if (!rv) gprintf(&human_printops, h, "#unset");
1051   else rd->ty->dump(rv, rd, 0, &human_printops, h);
1052   setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
1053 }
1054
1055 /* --- @human_etest@ --- *
1056  *
1057  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1058  *                      human_output@
1059  *              @unsigned outcome@ = the test outcome
1060  *
1061  * Returns:     ---
1062  *
1063  * Use:         Report that a test has finished.
1064  *
1065  *              The human driver reactivates the progress display, if
1066  *              necessary, and adds a new character for the completed test.
1067  */
1068
1069 static void human_etest(struct tvec_output *o, unsigned outcome)
1070 {
1071   struct human_output *h = (struct human_output *)o;
1072   int ch;
1073
1074   if (h->f&HOF_TTY) {
1075     show_progress(h);
1076     switch (outcome) {
1077       case TVOUT_WIN: ch = HSB_WIN; break;
1078       case TVOUT_LOSE: ch = HSB_LOSE; break;
1079       case TVOUT_XFAIL: ch = HSB_XFAIL; break;
1080       case TVOUT_SKIP: ch = HSB_SKIP; break;
1081       default: abort();
1082     }
1083     dstr_putc(&h->scoreboard, ch);
1084     write_scoreboard_char(h, ch); fflush(h->lyt.fp);
1085   }
1086 }
1087
1088 /* --- @human_bbench@ --- *
1089  *
1090  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1091  *                      human_output@
1092  *              @const char *ident@ = identifying register values
1093  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1094  *
1095  * Returns:     ---
1096  *
1097  * Use:         Report that a benchmark has started.
1098  *
1099  *              The human driver just prints the start of the benchmark
1100  *              report.
1101  */
1102
1103 static void human_bbench(struct tvec_output *o,
1104                          const char *ident, unsigned unit)
1105 {
1106   struct human_output *h = (struct human_output *)o;
1107   struct tvec_state *tv = h->tv;
1108
1109   clear_progress(h);
1110   fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
1111 }
1112
1113 /* --- @human_ebench@ --- *
1114  *
1115  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1116  *                      human_output@
1117  *              @const char *ident@ = identifying register values
1118  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1119  *              @const struct bench_timing *tm@ = measurement
1120  *
1121  * Returns:     ---
1122  *
1123  * Use:         Report a benchmark's results
1124  *
1125  *              The human driver just delegates to the default benchmark
1126  *              reporting, via the layout machinery.
1127  */
1128
1129 static void human_ebench(struct tvec_output *o,
1130                          const char *ident, unsigned unit,
1131                          const struct bench_timing *tm)
1132 {
1133   struct human_output *h = (struct human_output *)o;
1134
1135   tvec_benchreport(&human_printops, h, unit, tm);
1136   fputc('\n', h->lyt.fp);
1137 }
1138
1139 /* --- @human_report@ --- *
1140  *
1141  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1142  *                      human_output@
1143  *              @unsigned level@ = message level (@TVLEV_...@)
1144  *              @const char *msg@, @va_list *ap@ = format string and
1145  *                      arguments
1146  *
1147  * Returns:     ---
1148  *
1149  * Use:         Report a message to the user.
1150  *
1151  *              The human driver arranges to show the message on @stderr@ as
1152  *              well as the usual output, with a certain amount of
1153  *              intelligence in case they're both actually the same device.
1154  */
1155
1156 static void human_report(struct tvec_output *o, unsigned level,
1157                          const char *msg, va_list *ap)
1158 {
1159   struct human_output *h = (struct human_output *)o;
1160   struct tvec_state *tv = h->tv;
1161   const char *levstr; unsigned levattr;
1162   dstr d = DSTR_INIT;
1163   unsigned f = 0;
1164 #define f_flush 1u
1165 #define f_progress 2u
1166
1167   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1168
1169   switch (level) {
1170 #define CASE(tag, name, val)                                            \
1171     case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1172     TVEC_LEVELS(CASE)
1173     default: levstr = "??"; levattr = HA_UNKLEV; break;
1174   }
1175
1176   if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
1177
1178 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1179
1180   if (h->f^HOF_PROGRESS)
1181     { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
1182   fprintf(stderr, "%s: ", QUIS);
1183   report_location(h, stderr, tv->infile, tv->lno);
1184   setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
1185   fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
1186
1187 #undef FLUSH
1188
1189   if (h->f&HOF_DUPERR) {
1190     report_location(h, h->lyt.fp, tv->infile, tv->lno);
1191     fprintf(h->lyt.fp, "%s: ", levstr);
1192     fwrite(d.buf, 1, d.len, h->lyt.fp);
1193   }
1194   if (f&f_progress) show_progress(h);
1195
1196 #undef f_flush
1197 #undef f_progress
1198 }
1199
1200 /* --- @human_destroy@ --- *
1201  *
1202  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1203  *                      human_output@
1204  *
1205  * Returns:     ---
1206  *
1207  * Use:         Release the resources held by the output driver.
1208  */
1209
1210 static void human_destroy(struct tvec_output *o)
1211 {
1212   struct human_output *h = (struct human_output *)o;
1213
1214   destroy_layout(&h->lyt,
1215                  h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1216   dstr_destroy(&h->scoreboard);
1217   xfree(h->outbuf); xfree(h);
1218 }
1219
1220 static const struct tvec_outops human_ops = {
1221   human_bsession, human_esession,
1222   human_bgroup, human_skipgroup, human_egroup,
1223   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1224   human_bbench, human_ebench,
1225   human_report,
1226   human_destroy
1227 };
1228
1229 /* --- @tvec_humanoutput@ --- *
1230  *
1231  * Arguments:   @FILE *fp@ = output file to write on
1232  *
1233  * Returns:     An output formatter.
1234  *
1235  * Use:         Return an output formatter which writes on @fp@ with the
1236  *              expectation that a human will be watching and interpreting
1237  *              the output.  If @fp@ denotes a terminal, the display shows a
1238  *              `scoreboard' indicating the outcome of each test case
1239  *              attempted, and may in addition use colour and other
1240  *              highlighting.
1241  */
1242
1243 struct tvec_output *tvec_humanoutput(FILE *fp)
1244 {
1245   struct human_output *h;
1246   const char *p;
1247
1248   h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
1249   h->f = 0; h->attr = 0;
1250
1251   init_layout(&h->lyt, fp, 0);
1252   h->outbuf = 0; h->outsz = 0;
1253
1254   switch (getenv_boolean("TVEC_TTY", -1)) {
1255     case 1: h->f |= HOF_TTY; break;
1256     case 0: break;
1257     default:
1258       if (isatty(fileno(fp))) h->f |= HOF_TTY;
1259       break;
1260   }
1261   switch (getenv_boolean("TVEC_COLOUR", -1)) {
1262     case 1: h->f |= HOF_COLOUR; break;
1263     case 0: break;
1264     default:
1265       if (h->f&HOF_TTY) {
1266         p = getenv("TERM");
1267         if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1268       }
1269       break;
1270   }
1271
1272   if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1273   dstr_create(&h->scoreboard);
1274   return (&h->_o);
1275 }
1276
1277 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1278
1279 struct tap_output {
1280   struct tvec_output _o;                /* output base class */
1281   struct tvec_state *tv;                /* stashed testing state */
1282   struct layout lyt;                    /* output layout */
1283   char *outbuf; size_t outsz;           /* buffer for formatted output */
1284   unsigned grpix, testix;               /* group and test indices */
1285   unsigned previx;                      /* previously reported test index */
1286   int maxlen;                           /* longest register name */
1287 };
1288
1289 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1290  *
1291  * Arguments:   @void *go@ = output sink, secretly a @struct tap_output@
1292  *              @int ch@ = character to write
1293  *              @const char *@p@, @size_t sz@ = string (with explicit length)
1294  *                      to write
1295  *              @const char *p, ...@ = format control string and arguments to
1296  *                      write
1297  *
1298  * Returns:     ---
1299  *
1300  * Use:         Write characters, strings, or formatted strings to the
1301  *              output, applying appropriate layout.
1302  *
1303  *              For the TAP output driver, the layout machinery prefixes each
1304  *              line with `    ## ' and strips trailing spaces.
1305  */
1306
1307 static int tap_writech(void *go, int ch)
1308   { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
1309
1310 static int tap_writem(void *go, const char *p, size_t sz)
1311   { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
1312
1313 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1314 {
1315   struct tap_output *t = go;
1316   size_t n;
1317   va_list ap;
1318
1319   va_start(ap, p);
1320   n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
1321   va_end(ap);
1322   return (layout_string(&t->lyt, t->outbuf, n));
1323 }
1324
1325 static const struct gprintf_ops tap_printops =
1326   { tap_writech, tap_writem, tap_nwritef };
1327
1328 /* --- @tap_bsession@ --- *
1329  *
1330  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1331  *                      tap_output@
1332  *              @struct tvec_state *tv@ = the test state producing output
1333  *
1334  * Returns:     ---
1335  *
1336  * Use:         Begin a test session.
1337  *
1338  *              The TAP driver records the test state for later reference,
1339  *              initializes the group index counter, and prints the version
1340  *              number.
1341  */
1342
1343 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1344 {
1345   struct tap_output *t = (struct tap_output *)o;
1346
1347   t->tv = tv; t->grpix = 0;
1348   fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1349 }
1350
1351 /* --- @tap_esession@ --- *
1352  *
1353  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1354  *                      tap_output@
1355  *
1356  * Returns:     Suggested exit code.
1357  *
1358  * Use:         End a test session.
1359  *
1360  *              The TAP driver prints a final summary of the rest results
1361  *              and returns a suitable exit code.  If errors occurred, it
1362  *              instead prints a `Bail out!' line forcing the reader to
1363  *              report a failure.
1364  */
1365
1366 static int tap_esession(struct tvec_output *o)
1367 {
1368   struct tap_output *t = (struct tap_output *)o;
1369   struct tvec_state *tv = t->tv;
1370
1371   if (tv->f&TVSF_ERROR) {
1372     fputs("Bail out!  "
1373           "Errors found in input; tests may not have run correctly\n",
1374           t->lyt.fp);
1375     return (2);
1376   }
1377
1378   fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1379   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1380 }
1381
1382 /* --- @tap_bgroup@ --- *
1383  *
1384  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1385  *                      tap_output@
1386  *
1387  * Returns:     ---
1388  *
1389  * Use:         Begin a test group.
1390  *
1391  *              The TAP driver determines the length of the longest
1392  *              register name, resets the group progress scoreboard, and
1393  *              activates the progress display.
1394  */
1395
1396 static void tap_bgroup(struct tvec_output *o)
1397 {
1398   struct tap_output *t = (struct tap_output *)o;
1399   struct tvec_state *tv = t->tv;
1400
1401   t->grpix++; t->testix = t->previx = 0;
1402   t->maxlen = register_maxnamelen(t->tv);
1403   fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1404 }
1405
1406 /* --- @tap_skipgroup@ --- *
1407  *
1408  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1409  *                      tap_output@
1410  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1411  *                      group, or null
1412  *
1413  * Returns:     ---
1414  *
1415  * Use:         Report that a test group is being skipped.
1416  *
1417  *              The TAP driver just reports the situation to its output
1418  *              stream.
1419  */
1420
1421 static void tap_skipgroup(struct tvec_output *o,
1422                           const char *excuse, va_list *ap)
1423 {
1424   struct tap_output *t = (struct tap_output *)o;
1425
1426   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
1427   fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
1428   if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1429   fputc('\n', t->lyt.fp);
1430 }
1431
1432 /* --- @tap_egroup@ --- *
1433  *
1434  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1435  *                      tap_output@
1436  *
1437  * Returns:     ---
1438  *
1439  * Use:         Report that a test group has finished.
1440  *
1441  *              The TAP driver reports a summary of the group's tests.
1442  */
1443
1444 static void tap_egroup(struct tvec_output *o)
1445 {
1446   struct tap_output *t = (struct tap_output *)o;
1447   struct tvec_state *tv = t->tv;
1448
1449   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
1450   fprintf(t->lyt.fp, "%s %u - %s\n",
1451           tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
1452           t->grpix, tv->test->name);
1453 }
1454
1455 /* --- @tap_btest@ --- *
1456  *
1457  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1458  *                      tap_output@
1459  *
1460  * Returns:     ---
1461  *
1462  * Use:         Report that a test is starting.
1463  *
1464  *              The TAP driver advances its test counter.  (We could do this
1465  *              by adding up up the counters in @tv->curr@, and add on the
1466  *              current test, but it's easier this way.)
1467  */
1468
1469 static void tap_btest(struct tvec_output *o)
1470   { struct tap_output *t = (struct tap_output *)o; t->testix++; }
1471
1472 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
1473  *
1474  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1475  *                      tap_output@
1476  *              @unsigned attr@ = attribute to apply to the outcome
1477  *              @const char *outcome@ = outcome string to report
1478  *              @const char *detail@, @va_list *ap@ = a detail message
1479  *              @const char *excuse@, @va_list *ap@ = reason for skipping the
1480  *                      test
1481  *
1482  * Returns:     ---
1483  *
1484  * Use:         Report that a test has been skipped or failed.
1485  *
1486  *              The TAP driver reports the situation on its output stream.
1487  *              TAP only allows us to report a single status for each
1488  *              subtest, so we notice when we've already reported a status
1489  *              for the current test and convert the second report as a
1490  *              comment.  This should only happen in the case of multiple
1491  *              failures.
1492  */
1493
1494 static void tap_outcome(struct tvec_output *o,
1495                         const char *head, const char *tail,
1496                         const char *detail, va_list *ap)
1497 {
1498   struct tap_output *t = (struct tap_output *)o;
1499   struct tvec_state *tv = t->tv;
1500
1501   fprintf(t->lyt.fp, "    %s %u - %s:%u%s",
1502           t->testix == t->previx ? "##" : head,
1503           t->testix, tv->infile, tv->test_lno, tail);
1504   if (detail)
1505     { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
1506   fputc('\n', t->lyt.fp);
1507   t->previx = t->testix;
1508 }
1509
1510 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
1511   { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
1512 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
1513   { tap_outcome(o, "not ok", "", detail, ap); }
1514
1515 /* --- @tap_dumpreg@ --- *
1516  *
1517  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1518  *                      tap_output@
1519  *              @unsigned disp@ = register disposition
1520  *              @const union tvec_regval *rv@ = register value
1521  *              @const struct tvec_regdef *rd@ = register definition
1522  *
1523  * Returns:     ---
1524  *
1525  * Use:         Dump a register.
1526  *
1527  *              The TAP driver applies highlighting to mismatching output
1528  *              registers, but otherwise delegates to the register type
1529  *              handler and the layout machinery.  The result is that the
1530  *              register dump is marked as a comment and indented.
1531  */
1532
1533 static void tap_dumpreg(struct tvec_output *o,
1534                         unsigned disp, const union tvec_regval *rv,
1535                         const struct tvec_regdef *rd)
1536 {
1537   struct tap_output *t = (struct tap_output *)o;
1538   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1539
1540   set_layout_prefix(&t->lyt, "    ## ");
1541   gprintf(&tap_printops, t, "%*s%s %s = ",
1542           10 + t->maxlen - n, "", ds, rd->name);
1543   if (!rv) gprintf(&tap_printops, t, "#<unset>");
1544   else rd->ty->dump(rv, rd, 0, &tap_printops, t);
1545   layout_char(&t->lyt, '\n');
1546 }
1547
1548 /* --- @tap_etest@ --- *
1549  *
1550  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1551  *                      tap_output@
1552  *              @unsigned outcome@ = the test outcome
1553  *
1554  * Returns:     ---
1555  *
1556  * Use:         Report that a test has finished.
1557  *
1558  *              The TAP driver reports the outcome of the test, if that's not
1559  *              already decided.
1560  */
1561
1562 static void tap_etest(struct tvec_output *o, unsigned outcome)
1563 {
1564   switch (outcome) {
1565     case TVOUT_WIN:
1566       tap_outcome(o, "ok", "", 0, 0);
1567       break;
1568     case TVOUT_XFAIL:
1569       tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
1570       break;
1571   }
1572 }
1573
1574 /* --- @tap_bbench@ --- *
1575  *
1576  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1577  *                      tap_output@
1578  *              @const char *ident@ = identifying register values
1579  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1580  *
1581  * Returns:     ---
1582  *
1583  * Use:         Report that a benchmark has started.
1584  *
1585  *              The TAP driver does nothing here.  All of the reporting
1586  *              happens in @tap_ebench@.
1587  */
1588
1589 static void tap_bbench(struct tvec_output *o,
1590                        const char *ident, unsigned unit)
1591   { ; }
1592
1593 /* --- @tap_ebench@ --- *
1594  *
1595  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1596  *                      tap_output@
1597  *              @const char *ident@ = identifying register values
1598  *              @unsigned unit@ = measurement unit (@TVBU_...@)
1599  *              @const struct bench_timing *tm@ = measurement
1600  *
1601  * Returns:     ---
1602  *
1603  * Use:         Report a benchmark's results
1604  *
1605  *              The TAP driver just delegates to the default benchmark
1606  *              reporting, via the layout machinery so that the result is
1607  *              printed as a comment.
1608  */
1609
1610 static void tap_ebench(struct tvec_output *o,
1611                        const char *ident, unsigned unit,
1612                        const struct bench_timing *tm)
1613 {
1614   struct tap_output *t = (struct tap_output *)o;
1615   struct tvec_state *tv = t->tv;
1616
1617   set_layout_prefix(&t->lyt, "    ## ");
1618   gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1619   tvec_benchreport(&tap_printops, t, unit, tm);
1620   layout_char(&t->lyt, '\n');
1621 }
1622
1623 /* --- @tap_report@ --- *
1624  *
1625  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1626  *                      tap_output@
1627  *              @unsigned level@ = message level (@TVLEV_...@)
1628  *              @const char *msg@, @va_list *ap@ = format string and
1629  *                      arguments
1630  *
1631  * Returns:     ---
1632  *
1633  * Use:         Report a message to the user.
1634  *
1635  *              Messages are reported as comments, so that they can be
1636  *              accumulated by the reader.  An error will cause a later
1637  *              bailout or, if we crash before then, a missing plan line,
1638  *              either of which will cause the reader to report a serious
1639  *              problem.
1640  */
1641
1642 static void tap_report(struct tvec_output *o, unsigned level,
1643                        const char *msg, va_list *ap)
1644 {
1645   struct tap_output *t = (struct tap_output *)o;
1646   struct tvec_state *tv = t->tv;
1647
1648   if (tv->test) set_layout_prefix(&t->lyt, "    ## ");
1649   else set_layout_prefix(&t->lyt, "## ");
1650
1651   if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
1652   gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
1653   vgprintf(&tap_printops, t, msg, ap);
1654   layout_char(&t->lyt, '\n');
1655 }
1656
1657 /* --- @tap_destroy@ --- *
1658  *
1659  * Arguments:   @struct tvec_output *o@ = output sink, secretly a @struct
1660  *                      tap_output@
1661  *
1662  * Returns:     ---
1663  *
1664  * Use:         Release the resources held by the output driver.
1665  */
1666
1667 static void tap_destroy(struct tvec_output *o)
1668 {
1669   struct tap_output *t = (struct tap_output *)o;
1670
1671   destroy_layout(&t->lyt,
1672                  t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
1673   xfree(t->outbuf); xfree(t);
1674 }
1675
1676 static const struct tvec_outops tap_ops = {
1677   tap_bsession, tap_esession,
1678   tap_bgroup, tap_skipgroup, tap_egroup,
1679   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
1680   tap_bbench, tap_ebench,
1681   tap_report,
1682   tap_destroy
1683 };
1684
1685 /* --- @tvec_tapoutput@ --- *
1686  *
1687  * Arguments:   @FILE *fp@ = output file to write on
1688  *
1689  * Returns:     An output formatter.
1690  *
1691  * Use:         Return an output formatter which writes on @fp@ in `TAP'
1692  *              (`Test Anything Protocol') format.
1693  *
1694  *              TAP comes from the Perl community, but has spread rather
1695  *              further.  This driver produces TAP version 14, but pretends
1696  *              to be version 13.  The driver produces a TAP `test point' --
1697  *              i.e., a result reported as `ok' or `not ok' -- for each input
1698  *              test group.  Failure reports and register dumps are produced
1699  *              as diagnostic messages before the final group result.  (TAP
1700  *              permits structuerd YAML data after the test-point result,
1701  *              which could be used to report details, but (a) postponing the
1702  *              details until after the report is inconvenient, and (b) there
1703  *              is no standardization for the YAML anyway, so in practice
1704  *              it's no more useful than the unstructured diagnostics.
1705  */
1706
1707 struct tvec_output *tvec_tapoutput(FILE *fp)
1708 {
1709   struct tap_output *t;
1710
1711   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
1712   init_layout(&t->lyt, fp, 0);
1713   t->outbuf = 0; t->outsz = 0;
1714   return (&t->_o);
1715 }
1716
1717 /*----- Default output ----------------------------------------------------*/
1718
1719 /* --- @tvec_dfltoutput@ --- *
1720  *
1721  * Arguments:   @FILE *fp@ = output file to write on
1722  *
1723  * Returns:     An output formatter.
1724  *
1725  * Use:         Selects and instantiates an output formatter suitable for
1726  *              writing on @fp@.  The policy is subject to change, but
1727  *              currently the `human' output format is selected if @fp@ is
1728  *              interactive (i.e., if @isatty(fileno(fp))@ is true), and
1729  *              otherwise the `tap' format is used.
1730  */
1731
1732 struct tvec_output *tvec_dfltout(FILE *fp)
1733 {
1734   int ttyp = getenv_boolean("TVEC_TTY", -1);
1735
1736   if (ttyp == -1) ttyp = isatty(fileno(fp));
1737   if (ttyp) return (tvec_humanoutput(fp));
1738   else return (tvec_tapoutput(fp));
1739 }
1740
1741 /*----- That's all, folks -------------------------------------------------*/