chiark / gitweb /
@@@ wip misc
[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 (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 /* --- @init_layout@ --- *
193  *
194  * Arguments:   @struct layout *lyt@ = layout state to initialize
195  *              @FILE *fp@ = output file
196  *              @const char *prefix@ = prefix string (or null if empty)
197  *
198  * Returns:     ---
199  *
200  * Use:         Initialize a layout state.
201  */
202
203 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
204 {
205   const char *q, *l;
206
207   /* Basics. */
208   lyt->fp = fp;
209   lyt->f = LYTF_NEWL;
210   dstr_create(&lyt->w);
211
212   /* Prefix portions. */
213   if (!prefix || !*prefix)
214     lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
215   else {
216     lyt->prefix = prefix;
217     l = lyt->pfxlim = prefix + strlen(prefix);
218     SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
219   }
220 }
221
222 /* --- @destroy_layout@ --- *
223  *
224  * Arguments:   @struct layout *lyt@ = layout state
225  *              @unsigned f@ = flags (@DLF_...@)
226  *
227  * Returns:     ---
228  *
229  * Use:         Releases a layout state and the resources it holds.
230  *              Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
231  *              it open (in case it's @stderr@ or something).
232  */
233
234 #define DLF_CLOSE 1u
235 static void destroy_layout(struct layout *lyt, unsigned f)
236 {
237   if (f&DLF_CLOSE) fclose(lyt->fp);
238   dstr_destroy(&lyt->w);
239 }
240
241 /* --- @layout_char@ --- *
242  *
243  * Arguments:   @struct layout *lyt@ = layout state
244  *              @int ch@ = character to write
245  *
246  * Returns:     Zero on success, @-1@ on failure.
247  *
248  * Use:         Write a single character to the output.
249  */
250
251 static int layout_char(struct layout *lyt, int ch)
252 {
253   if (ch == '\n') {
254     if (lyt->f&LYTF_NEWL) PUT_PFXINB;
255     PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
256   } else if (isspace(ch))
257     DPUTC(&lyt->w, ch);
258   else {
259     if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
260     PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
261   }
262   return (0);
263 }
264
265 /* --- @layout_string@ --- *
266  *
267  * Arguments:   @struct layout *lyt@ = layout state
268  *              @const char *p@ = string to write
269  *              @size_t sz@ = length of string
270  *
271  * Returns:     Zero on success, @-1@ on failure.
272  *
273  * Use:         Write a string to the output.
274  */
275
276 static int layout_string(struct layout *lyt, const char *p, size_t sz)
277 {
278   const char *q, *r, *l = p + sz;
279
280   /* This is rather vexing.  There are a small number of jobs to do, but the
281    * logic for deciding which to do when gets rather hairy if, as I've tried
282    * here, one aims to minimize the number of decisions being checked, so
283    * it's worth canning them into macros.
284    *
285    * Here, a `blank' is a whitespace character other than newline.  The input
286    * buffer consists of one or more `segments', each of which consists of:
287    *
288    *   * an initial portion, which is either empty or ends with a nonblank
289    *     character;
290    *
291    *   * a suffix which consists only of blanks; and
292    *
293    *   * an optional newline.
294    *
295    * All segments except the last end with a newline.
296    */
297
298 #define SPLIT_SEGMENT do {                                              \
299   /* Determine the bounds of the current segment.  If there is a final  \
300    * newline, then q is non-null and points to this newline; otherwise, \
301    * q is null.  The initial portion of the segment lies between p .. r \
302    * and the blank suffix lies between r .. q (or r .. l if q is null). \
303    * This sounds awkward, but the suffix is only relevant if there is   \
304    * no newline.                                                        \
305    */                                                                   \
306                                                                         \
307   q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l);             \
308 } while (0)
309
310 #define PUT_NONBLANK do {                                               \
311   /* Output the initial portion of the segment. */                      \
312                                                                         \
313   PUT_RANGE(p, r);                                                      \
314 } while (0)
315
316 #define PUT_NEWLINE do {                                                \
317   /* Write a newline, and advance to the next segment. */               \
318                                                                         \
319   PUT_CHAR('\n'); p = q + 1;                                            \
320 } while (0)
321
322 #define SAVE_TAIL do {                                                  \
323   /* Save the trailing blank portion of the segment in the buffer.      \
324    * Assumes that there is no newline, since otherwise the suffix would \
325    * be omitted.                                                        \
326    */                                                                   \
327                                                                         \
328   DPUTM(&lyt->w, r, l - r);                                             \
329 } while (0)
330
331   /* Determine the bounds of the first segment.  Handling this is the most
332    * complicated part of this function.
333    */
334   SPLIT_SEGMENT;
335
336   if (!q) {
337     /* This is the only segment.  We'll handle the whole thing here.
338      *
339      * If there's an initial nonblank portion, then we need to write that
340      * out.  Furthermore, if we're at the start of the line then we'll need
341      * to write the prefix, and if there's saved blank material then we'll
342      * need to write that.  Otherwise, there's only blank stuff, which we
343      * accumulate in the buffer.
344      *
345      * If we're at the start of a line here, then put the prefix followed by
346      * any saved whitespace, and then our initial nonblank portion.  Then
347      * save our new trailing space.
348      */
349
350     if (r > p) {
351       if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
352       PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
353     }
354     SAVE_TAIL;
355     return (0);
356   }
357
358   /* There is at least one more segment, so we know that there'll be a line
359    * to output.
360    */
361   if (r > p) {
362     if (lyt->f&LYTF_NEWL) PUT_PREFIX;
363     PUT_SAVED; PUT_NONBLANK;
364   } else if (lyt->f&LYTF_NEWL)
365     PUT_PFXINB;
366   PUT_NEWLINE; DRESET(&lyt->w);
367   SPLIT_SEGMENT;
368
369   /* Main loop over whole segments with trailing newlines.  For each one, we
370    * know that we're starting at the beginning of a line and there's a final
371    * newline, so we write the initial prefix and drop the trailing blanks.
372    */
373   while (q) {
374     if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
375     else PUT_PFXINB;
376     PUT_NEWLINE;
377     SPLIT_SEGMENT;
378   }
379
380   /* At the end, there's no final newline.  If there's nonblank material,
381    * then we can write the prefix and the nonblank stuff.  Otherwise, stash
382    * the blank stuff (including the trailing blanks of the prefix) and leave
383    * the newline flag set.
384    */
385   if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
386   else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
387   SAVE_TAIL;
388
389 #undef SPLIT_SEGMENT
390 #undef PUT_NONBLANK
391 #undef PUT_NEWLINE
392 #undef SAVE_TAIL
393
394   return (0);
395 }
396
397 #undef SPLIT_RANGE
398 #undef PUT_RANGE
399 #undef PUT_PREFIX
400 #undef PUT_PFXINB
401 #undef PUT_SAVED
402 #undef PUT_CHAR
403 #undef SAVE_PFXTAIL
404
405 /*----- Skeleton ----------------------------------------------------------*/
406 /*
407 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
408 static int ..._esession(struct tvec_output *o)
409 static void ..._bgroup(struct tvec_output *o)
410 static void ..._skipgroup(struct tvec_output *o,
411                           const char *excuse, va_list *ap)
412 static void ..._egroup(struct tvec_output *o)
413 static void ..._btest(struct tvec_output *o)
414 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
415 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
416 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
417                         union tvec_regval *rv, const struct tvec_regdef *rd)
418 static void ..._etest(struct tvec_output *o, unsigned outcome)
419 static void ..._bbench(struct tvec_output *o,
420                        const char *ident, unsigned unit)
421 static void ..._ebench(struct tvec_output *o,
422                        const char *ident, unsigned unit,
423                        const struct tvec_timing *t)
424 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
425 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
426 static void ..._destroy(struct tvec_output *o)
427
428 static const struct tvec_outops ..._ops = {
429   ..._bsession, ..._esession,
430   ..._bgroup, ..._egroup, ..._skip,
431   ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
432   ..._bbench, ..._ebench,
433   ..._error, ..._notice,
434   ..._destroy
435 };
436 */
437 /*----- Human-readable output ---------------------------------------------*/
438
439 /* Attributes for colour output.  This should be done better, but @terminfo@
440  * is a disaster.
441  *
442  * An attribute byte holds a foreground colour in the low nibble, a
443  * background colour in the next nibble, and some flags in the next few
444  * bits.  A colour is expressed in classic 1-bit-per-channel style, with red,
445  * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
446  */
447 #define HAF_FGMASK 0x0f                 /* foreground colour mask */
448 #define HAF_FGSHIFT 0                   /* foreground colour shift */
449 #define HAF_BGMASK 0xf0                 /* background colour mask */
450 #define HAF_BGSHIFT 4                   /* background colour shift */
451 #define HAF_FG 256u                     /* set foreground? */
452 #define HAF_BG 512u                     /* set background? */
453 #define HAF_BOLD 1024u                  /* set bold? */
454 #define HCOL_BLACK 0u                   /* colour codes... */
455 #define HCOL_RED 1u
456 #define HCOL_GREEN 2u
457 #define HCOL_YELLOW 3u
458 #define HCOL_BLUE 4u
459 #define HCOL_MAGENTA 5u
460 #define HCOL_CYAN 6u
461 #define HCOL_WHITE 7u
462 #define HCF_BRIGHT 8u                   /* bright colour flag */
463 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
464 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
465
466 /* Predefined attributes. */
467 #define HA_PLAIN 0                   /* nothing special: terminal defaults */
468 #define HA_LOC (HFG(CYAN))              /* filename or line number */
469 #define HA_LOCSEP (HFG(BLUE))           /* location separator `:' */
470 #define HA_UNSET (HFG(YELLOW))          /* register not set */
471 #define HA_FOUND (HFG(RED))             /* incorrect output value */
472 #define HA_EXPECT (HFG(GREEN))          /* what the value should have been */
473 #define HA_WIN (HFG(GREEN))             /* reporting success */
474 #define HA_LOSE (HFG(RED) | HAF_BOLD)   /* reporting failure */
475 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
476 #define HA_SKIP (HFG(YELLOW))           /* reporting a skipped test/group */
477 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* reporting an error */
478
479 /* Scoreboard indicators. */
480 #define HSB_WIN '.'                     /* test passed */
481 #define HSB_LOSE 'x'                    /* test failed */
482 #define HSB_XFAIL 'o'                   /* test failed expectedly */
483 #define HSB_SKIP '_'                    /* test wasn't run */
484
485 struct human_output {
486   struct tvec_output _o;                /* output base class */
487   struct tvec_state *tv;                /* stashed testing state */
488   struct layout lyt;                    /* output layout */
489   char *outbuf; size_t outsz;           /* buffer for formatted output */
490   dstr scoreboard;                      /* history of test group results */
491   unsigned attr;                        /* current terminal attributes */
492   int maxlen;                           /* longest register name */
493   unsigned f;                           /* flags */
494 #define HOF_TTY 1u                      /*   writing to terminal */
495 #define HOF_DUPERR 2u                   /*   duplicate errors to stderr */
496 #define HOF_COLOUR 4u                   /*   print in angry fruit salad */
497 #define HOF_PROGRESS 8u                 /*   progress display is active */
498 };
499
500 /* --- @set_colour@ --- *
501  *
502  * Arguments:   @FILE *fp@ = output stream to write on
503  *              @int *sep_inout@ = where to maintain separator
504  *              @const char *norm@ = prefix for normal colour
505  *              @const char *bright@ = prefix for bright colour
506  *              @unsigned colour@ = four bit colour code
507  *
508  * Returns:     ---
509  *
510  * Use:         Write to the output stream @fp@, the current character at
511  *              @*sep_inout@, if that's not zero, followed by either @norm@
512  *              or @bright@, according to whether the @HCF_BRIGHT@ flag is
513  *              set in @colour@, followed by the plain colour code from
514  *              @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
515  *
516  *              This is an internal subroutine for @setattr@ below.
517  */
518
519 static void set_colour(FILE *fp, int *sep_inout,
520                        const char *norm, const char *bright,
521                        unsigned colour)
522 {
523   if (*sep_inout) putc(*sep_inout, fp);
524   fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
525   *sep_inout = ';';
526 }
527
528 /* --- @setattr@ --- *
529  *
530  * Arguments:   @struct human_output *h@ = output state
531  *              @unsigned attr@ = attribute code to set
532  *
533  * Returns:     ---
534  *
535  * Use:         Send a control sequence to the output stream so that
536  *              subsequent text is printed with the given attributes.
537  *
538  *              Some effort is taken to avoid unnecessary control sequences.
539  *              In particular, if @attr@ matches the current terminal
540  *              settings already, then nothing is written.
541  */
542
543 static void setattr(struct human_output *h, unsigned attr)
544 {
545   unsigned diff = h->attr ^ attr;
546   int sep = 0;
547
548   /* If there's nothing to do, we might as well stop now. */
549   if (!diff || !(h->f&HOF_COLOUR)) return;
550
551   /* Start on the control command. */
552   fputs("\x1b[", h->lyt.fp);
553
554   /* Change the boldness if necessary. */
555   if (diff&HAF_BOLD) {
556     if (attr&HAF_BOLD) putc('1', h->lyt.fp);
557     else { putc('0', h->lyt.fp); diff = h->attr; }
558     sep = ';';
559   }
560
561   /* Change the foreground colour if necessary. */
562   if (diff&(HAF_FG | HAF_FGMASK)) {
563     if (attr&HAF_FG)
564       set_colour(h->lyt.fp, &sep, "3", "9",
565                  (attr&HAF_FGMASK) >> HAF_FGSHIFT);
566     else {
567       if (sep) putc(sep, h->lyt.fp);
568       fputs("39", h->lyt.fp); sep = ';';
569     }
570   }
571
572   /* Change the background colour if necessary. */
573   if (diff&(HAF_BG | HAF_BGMASK)) {
574     if (attr&HAF_BG)
575       set_colour(h->lyt.fp, &sep, "4", "10",
576                  (attr&HAF_BGMASK) >> HAF_BGSHIFT);
577     else {
578       if (sep) putc(sep, h->lyt.fp);
579       fputs("49", h->lyt.fp); sep = ';';
580     }
581   }
582
583   /* Terminate the control command and save the new attributes. */
584   putc('m', h->lyt.fp); h->attr = attr;
585 }
586
587 /* --- @clear_progress@ --- *
588  *
589  * Arguments:   @struct human_output *h@ = output state
590  *
591  * Returns:     ---
592  *
593  * Use:         Remove the progress display from the terminal.
594  *
595  *              If the progress display isn't active then do nothing.
596  */
597
598 static void clear_progress(struct human_output *h)
599 {
600   size_t i, n;
601
602   if (h->f&HOF_PROGRESS) {
603     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
604     for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
605     h->f &= ~HOF_PROGRESS;
606   }
607 }
608
609 /* --- @write_scoreboard_char@ --- *
610  *
611  * Arguments:   @struct human_output *h@ = output state
612  *              @int ch@ = scoreboard character to print
613  *
614  * Returns:     ---
615  *
616  * Use:         Write a scoreboard character, indicating the outcome of a
617  *              test, to the output stream, with appropriate highlighting.
618  */
619
620 static void write_scoreboard_char(struct human_output *h, int ch)
621 {
622   switch (ch) {
623     case HSB_LOSE: setattr(h, HA_LOSE); break;
624     case HSB_SKIP: setattr(h, HA_SKIP); break;
625     case HSB_XFAIL: setattr(h, HA_XFAIL); break;
626     default: setattr(h, HA_PLAIN); break;
627   }
628   putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
629 }
630
631 /* --- @show_progress@ --- *
632  *
633  * Arguments:   @struct human_output *h@ = output state
634  *
635  * Returns:     ---
636  *
637  * Use:         Show the progress display, with the record of outcomes for
638  *              the current test group.
639  *
640  *              If the progress display is already active, or the output
641  *              stream is not interactive, then nothing happens.
642  */
643
644 static void show_progress(struct human_output *h)
645 {
646   struct tvec_state *tv = h->tv;
647   const char *p, *l;
648
649   if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
650     fprintf(h->lyt.fp, "%s: ", tv->test->name);
651     if (!(h->f&HOF_COLOUR))
652       dstr_write(&h->scoreboard, h->lyt.fp);
653     else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
654       write_scoreboard_char(h, *p);
655     fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
656   }
657 }
658
659 /* --- @report_location@ --- *
660  *
661  * Arguments:   @struct human_output *h@ = output state
662  *              @FILE *fp@ = stream to write the location on
663  *              @const char *file@ = filename
664  *              @unsigned lno@ = line number
665  *
666  * Returns:     ---
667  *
668  * Use:         Print the filename and line number to the output stream @fp@.
669  *              Also, if appropriate, print interleaved highlighting control
670  *              codes to our usual output stream.  If @file@ is null then do
671  *              nothing.
672  */
673
674 static void report_location(struct human_output *h, FILE *fp,
675                             const char *file, unsigned lno)
676 {
677   unsigned f = 0;
678 #define f_flush 1u
679
680   /* We emit highlighting if @fp@ is our usual output stream, or the
681    * duplicate-errors flag is clear indicating that (we assume) they're
682    * secretly going to the same place anyway.  If they're different streams,
683    * though, we have to be careful to keep the highlighting and the actual
684    * text synchronized.
685    */
686
687   if (!file)
688     /* nothing to do */;
689   else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
690     fprintf(fp, "%s:%u: ", file, lno);
691   else {
692     if (fp != h->lyt.fp) f |= f_flush;
693
694 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
695
696     setattr(h, HA_LOC);         FLUSH(h->lyt.fp);
697     fputs(file, fp);            FLUSH(fp);
698     setattr(h, HA_LOCSEP);      FLUSH(h->lyt.fp);
699     fputc(':', fp);             FLUSH(fp);
700     setattr(h, HA_LOC);         FLUSH(h->lyt.fp);
701     fprintf(fp, "%u", lno);     FLUSH(fp);
702     setattr(h, HA_LOCSEP);      FLUSH(h->lyt.fp);
703     fputc(':', fp);             FLUSH(fp);
704     setattr(h, HA_PLAIN);       FLUSH(h->lyt.fp);
705     fputc(' ', fp);
706
707 #undef FLUSH
708   }
709
710 #undef f_flush
711 }
712
713 /* Output layout.  Pass everything along to the layout machinery above. */
714
715 static int human_writech(void *go, int ch)
716   { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
717
718 static int human_writem(void *go, const char *p, size_t sz)
719   { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
720
721 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
722 {
723   struct human_output *h = go;
724   size_t n;
725   va_list ap;
726
727   va_start(ap, p);
728   n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
729   va_end(ap);
730   return (layout_string(&h->lyt, h->outbuf, n));
731 }
732
733 static const struct gprintf_ops human_printops =
734   { human_writech, human_writem, human_nwritef };
735
736 /* Output methods. */
737
738 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
739   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
740
741 static void report_unusual(struct human_output *h,
742                            unsigned nxfail, unsigned nskip)
743 {
744   const char *sep = " (";
745   unsigned f = 0;
746 #define f_any 1u
747
748   if (nxfail) {
749     fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
750     setattr(h, HA_XFAIL);
751     fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
752     setattr(h, HA_PLAIN);
753     sep = ", "; f |= f_any;
754   }
755
756   if (nskip) {
757     fprintf(h->lyt.fp, "%s%u ", sep, nskip);
758     setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
759     sep = ", "; f |= f_any;
760   }
761
762   if (f&f_any) fputc(')', h->lyt.fp);
763
764 #undef f_any
765 }
766
767 static int human_esession(struct tvec_output *o)
768 {
769   struct human_output *h = (struct human_output *)o;
770   struct tvec_state *tv = h->tv;
771   unsigned
772     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
773     all_xfail = tv->all[TVOUT_XFAIL],
774     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
775     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
776     all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
777     grps_run = grps_win + grps_lose;
778
779   if (!all_lose) {
780     setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
781     fprintf(h->lyt.fp, " %s%u %s",
782             !(all_skip || grps_skip) ? "all " : "",
783             all_pass, all_pass == 1 ? "test" : "tests");
784     report_unusual(h, all_xfail, all_skip);
785     fprintf(h->lyt.fp, " in %u %s",
786             grps_win, grps_win == 1 ? "group" : "groups");
787     report_unusual(h, 0, grps_skip);
788   } else {
789     setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
790     fprintf(h->lyt.fp, " %u out of %u %s",
791             all_lose, all_run, all_run == 1 ? "test" : "tests");
792     report_unusual(h, all_xfail, all_skip);
793     fprintf(h->lyt.fp, " in %u out of %u %s",
794             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
795     report_unusual(h, 0, grps_skip);
796   }
797   fputc('\n', h->lyt.fp);
798
799   if (tv->f&TVSF_ERROR) {
800     setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
801     fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
802   }
803
804   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
805 }
806
807 static void human_bgroup(struct tvec_output *o)
808 {
809   struct human_output *h = (struct human_output *)o;
810
811   h->maxlen = register_maxnamelen(h->tv);
812   dstr_reset(&h->scoreboard); show_progress(h);
813 }
814
815 static void human_skipgroup(struct tvec_output *o,
816                             const char *excuse, va_list *ap)
817 {
818   struct human_output *h = (struct human_output *)o;
819
820   if (!(h->f&HOF_TTY))
821     fprintf(h->lyt.fp, "%s ", h->tv->test->name);
822   else {
823     show_progress(h); h->f &= ~HOF_PROGRESS;
824     if (h->scoreboard.len) putc(' ', h->lyt.fp);
825   }
826   setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
827   if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
828   fputc('\n', h->lyt.fp);
829 }
830
831 static void human_egroup(struct tvec_output *o)
832 {
833   struct human_output *h = (struct human_output *)o;
834   struct tvec_state *tv = h->tv;
835   unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
836     lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
837     run = win + lose + xfail;
838
839   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
840   else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
841
842   if (lose) {
843     fprintf(h->lyt.fp, " %u/%u ", lose, run);
844     setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
845     report_unusual(h, xfail, skip);
846   } else {
847     fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
848     fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
849     report_unusual(h, xfail, skip);
850   }
851   fputc('\n', h->lyt.fp);
852 }
853
854 static void human_btest(struct tvec_output *o)
855   { struct human_output *h = (struct human_output *)o; show_progress(h); }
856
857 {
858   struct human_output *h = (struct human_output *)o;
859   struct tvec_state *tv = h->tv;
860
861   clear_progress(h);
862   report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
863   fprintf(h->lyt.fp, "`%s' ", tv->test->name);
864   setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
865   if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
866   fputc('\n', h->lyt.fp);
867 }
868
869 static void human_skip(struct tvec_output *o,
870                        const char *excuse, va_list *ap)
871   { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
872 static void human_fail(struct tvec_output *o,
873                        const char *detail, va_list *ap)
874   { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
875
876
877 static void human_dumpreg(struct tvec_output *o,
878                           unsigned disp, const union tvec_regval *rv,
879                           const struct tvec_regdef *rd)
880 {
881   struct human_output *h = (struct human_output *)o;
882   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
883
884   clear_progress(h);
885   gprintf(&human_printops, h, "%*s%s %s = ",
886           10 + h->maxlen - n, "", ds, rd->name);
887   if (h->f&HOF_COLOUR) {
888     if (!rv) setattr(h, HA_UNSET);
889     else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
890     else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
891   }
892   if (!rv) gprintf(&human_printops, h, "#unset");
893   else rd->ty->dump(rv, rd, 0, &human_printops, h);
894   setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
895 }
896
897 static void human_etest(struct tvec_output *o, unsigned outcome)
898 {
899   struct human_output *h = (struct human_output *)o;
900   int ch;
901
902   if (h->f&HOF_TTY) {
903     show_progress(h);
904     switch (outcome) {
905       case TVOUT_WIN: ch = HSB_WIN; break;
906       case TVOUT_LOSE: ch = HSB_LOSE; break;
907       case TVOUT_XFAIL: ch = HSB_XFAIL; break;
908       case TVOUT_SKIP: ch = HSB_SKIP; break;
909       default: abort();
910     }
911     dstr_putc(&h->scoreboard, ch);
912     write_scoreboard_char(h, ch); fflush(h->lyt.fp);
913   }
914 }
915
916 static void human_bbench(struct tvec_output *o,
917                          const char *ident, unsigned unit)
918 {
919   struct human_output *h = (struct human_output *)o;
920   struct tvec_state *tv = h->tv;
921
922   clear_progress(h);
923   fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
924 }
925
926 static void human_ebench(struct tvec_output *o,
927                          const char *ident, unsigned unit,
928                          const struct bench_timing *tm)
929 {
930   struct human_output *h = (struct human_output *)o;
931
932   tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
933   fputc('\n', h->lyt.fp);
934 }
935
936 static void human_report(struct tvec_output *o, unsigned level,
937                          const char *msg, va_list *ap)
938 {
939   struct human_output *h = (struct human_output *)o;
940   struct tvec_state *tv = h->tv;
941   dstr d = DSTR_INIT;
942
943   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
944
945   clear_progress(h); fflush(h->lyt.fp);
946   fprintf(stderr, "%s: ", QUIS);
947   report_location(h, stderr, tv->infile, tv->lno);
948   fwrite(d.buf, 1, d.len, stderr);
949
950   if (h->f&HOF_DUPERR) {
951     report_location(h, h->lyt.fp, tv->infile, tv->lno);
952     fwrite(d.buf, 1, d.len, h->lyt.fp);
953   }
954   show_progress(h);
955 }
956
957 static void human_destroy(struct tvec_output *o)
958 {
959   struct human_output *h = (struct human_output *)o;
960
961   destroy_layout(&h->lyt,
962                  h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
963   dstr_destroy(&h->scoreboard);
964   xfree(h->outbuf); xfree(h);
965 }
966
967 static const struct tvec_outops human_ops = {
968   human_bsession, human_esession,
969   human_bgroup, human_skipgroup, human_egroup,
970   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
971   human_bbench, human_ebench,
972   human_report,
973   human_destroy
974 };
975
976 struct tvec_output *tvec_humanoutput(FILE *fp)
977 {
978   struct human_output *h;
979   const char *p;
980
981   h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
982   h->f = 0; h->attr = 0;
983
984   init_layout(&h->lyt, fp, 0);
985   h->outbuf = 0; h->outsz = 0;
986
987   switch (getenv_boolean("TVEC_TTY", -1)) {
988     case 1: h->f |= HOF_TTY; break;
989     case 0: break;
990     default:
991       if (isatty(fileno(fp))) h->f |= HOF_TTY;
992       break;
993   }
994   switch (getenv_boolean("TVEC_COLOUR", -1)) {
995     case 1: h->f |= HOF_COLOUR; break;
996     case 0: break;
997     default:
998       if (h->f&HOF_TTY) {
999         p = getenv("TERM");
1000         if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1001       }
1002       break;
1003   }
1004
1005   if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1006   dstr_create(&h->scoreboard);
1007   return (&h->_o);
1008 }
1009
1010 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1011
1012 struct tap_output {
1013   struct tvec_output _o;
1014   struct tvec_state *tv;
1015   struct layout lyt;
1016   unsigned last;
1017   char *outbuf; size_t outsz;
1018   int maxlen;
1019 };
1020
1021 static int tap_writech(void *go, int ch)
1022   { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
1023
1024 static int tap_writem(void *go, const char *p, size_t sz)
1025   { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
1026
1027 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1028 {
1029   struct tap_output *t = go;
1030   size_t n;
1031   va_list ap;
1032
1033   va_start(ap, p);
1034   n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
1035   va_end(ap);
1036   return (layout_string(&t->lyt, t->outbuf, n));
1037 }
1038
1039 static const struct gprintf_ops tap_printops =
1040   { tap_writech, tap_writem, tap_nwritef };
1041
1042 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1043 {
1044   struct tap_output *t = (struct tap_output *)o;
1045
1046   t->tv = tv; t->grpix = 0;
1047   fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1048 }
1049
1050
1051 static int tap_esession(struct tvec_output *o)
1052 {
1053   struct tap_output *t = (struct tap_output *)o;
1054   struct tvec_state *tv = t->tv;
1055
1056   if (tv->f&TVSF_ERROR) {
1057     fputs("Bail out!  "
1058           "Errors found in input; tests may not have run correctly\n",
1059           t->lyt.fp);
1060     return (2);
1061   }
1062
1063   fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1064   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1065 }
1066
1067 static void tap_bgroup(struct tvec_output *o)
1068 {
1069   struct tap_output *t = (struct tap_output *)o;
1070   struct tvec_state *tv = t->tv;
1071
1072   t->grpix++; t->testix = t->previx = 0;
1073   t->maxlen = register_maxnamelen(t->tv);
1074   fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1075 }
1076
1077 static void tap_skipgroup(struct tvec_output *o,
1078                           const char *excuse, va_list *ap)
1079 {
1080   struct tap_output *t = (struct tap_output *)o;
1081
1082   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
1083   fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
1084   if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1085   fputc('\n', t->lyt.fp);
1086 }
1087
1088 static void tap_egroup(struct tvec_output *o)
1089 {
1090   struct tap_output *t = (struct tap_output *)o;
1091   struct tvec_state *tv = t->tv;
1092
1093   fprintf(t->lyt.fp, "    1..%u\n", t->testix);
1094   fprintf(t->lyt.fp, "%s %u - %s\n",
1095           tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
1096           t->grpix, tv->test->name);
1097 }
1098
1099 static void tap_btest(struct tvec_output *o)
1100   { struct tap_output *t = (struct tap_output *)o; t->testix++; }
1101
1102 static void tap_outcome(struct tvec_output *o,
1103                         const char *head, const char *tail,
1104                         const char *detail, va_list *ap)
1105 {
1106   struct tap_output *t = (struct tap_output *)o;
1107   struct tvec_state *tv = t->tv;
1108
1109   fprintf(t->lyt.fp, "    %s %u - %s:%u%s",
1110           t->testix == t->previx ? "##" : head,
1111           t->testix, tv->infile, tv->test_lno, tail);
1112   if (detail)
1113     { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
1114   fputc('\n', t->lyt.fp);
1115   t->previx = t->testix;
1116 }
1117
1118 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
1119   { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
1120 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
1121   { tap_outcome(o, "not ok", "", detail, ap); }
1122
1123 static void tap_dumpreg(struct tvec_output *o,
1124                         unsigned disp, const union tvec_regval *rv,
1125                         const struct tvec_regdef *rd)
1126 {
1127   struct tap_output *t = (struct tap_output *)o;
1128   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1129
1130   gprintf(&tap_printops, t, "%*s%s %s = ",
1131           10 + t->maxlen - n, "", ds, rd->name);
1132   if (!rv) gprintf(&tap_printops, t, "#<unset>");
1133   else rd->ty->dump(rv, rd, 0, &tap_printops, t);
1134   layout_char(&t->lyt, '\n');
1135 }
1136
1137 static void tap_etest(struct tvec_output *o, unsigned outcome)
1138 {
1139   switch (outcome) {
1140     case TVOUT_WIN:
1141       tap_outcome(o, "ok", "", 0, 0);
1142       break;
1143     case TVOUT_XFAIL:
1144       tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
1145       break;
1146   }
1147 }
1148
1149 static void tap_bbench(struct tvec_output *o,
1150                        const char *ident, unsigned unit)
1151   { ; }
1152
1153 static void tap_ebench(struct tvec_output *o,
1154                        const char *ident, unsigned unit,
1155                        const struct bench_timing *tm)
1156 {
1157   struct tap_output *t = (struct tap_output *)o;
1158   struct tvec_state *tv = t->tv;
1159
1160   gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1161   tvec_benchreport(&tap_printops, t, unit, tm);
1162   layout_char(&t->lyt, '\n');
1163 }
1164
1165 static void tap_report(struct tvec_output *o, unsigned level,
1166                        const char *msg, va_list *ap)
1167 {
1168   struct tap_output *t = (struct tap_output *)o;
1169   struct tvec_state *tv = t->tv;
1170   const struct gprintf_ops *gops; void *go;
1171
1172   if (level >= TVLEV_ERR) {
1173     fputs("Bail out!  ", t->lyt.fp);
1174     gops = &file_printops; go = t->lyt.fp;
1175   } else {
1176     gops = &tap_printops; go = t;
1177   }
1178   if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
1179   gprintf(gops, go, msg, ap); gops->putch(go, '\n');
1180 }
1181
1182 static void tap_destroy(struct tvec_output *o)
1183 {
1184   struct tap_output *t = (struct tap_output *)o;
1185
1186   destroy_layout(&t->lyt,
1187                  t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
1188   xfree(t->outbuf); xfree(t);
1189 }
1190
1191 static const struct tvec_outops tap_ops = {
1192   tap_bsession, tap_esession,
1193   tap_bgroup, tap_skipgroup, tap_egroup,
1194   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
1195   tap_bbench, tap_ebench,
1196   tap_report,
1197   tap_destroy
1198 };
1199
1200 struct tvec_output *tvec_tapoutput(FILE *fp)
1201 {
1202   struct tap_output *t;
1203
1204   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
1205   init_layout(&t->lyt, fp, "    ## ");
1206   t->outbuf = 0; t->outsz = 0;
1207   return (&t->_o);
1208 }
1209
1210 /*----- Default output ----------------------------------------------------*/
1211
1212 struct tvec_output *tvec_dfltout(FILE *fp)
1213 {
1214   int ttyp = getenv_boolean("TVEC_TTY", -1);
1215
1216   if (ttyp == -1) ttyp = isatty(fileno(fp));
1217   if (ttyp) return (tvec_humanoutput(fp));
1218   else return (tvec_tapoutput(fp));
1219 }
1220
1221 /*----- That's all, folks -------------------------------------------------*/