chiark / gitweb /
e92ea695e1107b7b739730f3f7dd70e3f5930816
[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 formatting -------------------------------------------------*/
121
122 /* We have two main jobs in output formatting: 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 format {
129   FILE *fp;                              /* output file */
130   const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
131   dstr w;                               /* trailing whitespace */
132   unsigned f;                           /* flags */
133 #define FMTF_NEWL 1u                    /*   start of output line */
134 };
135
136 /* Support macros.  These assume `fmt' is defined as a pointer to the `struct
137  * format' 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, fmt->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, fmt->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 (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->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 = fmt->w.len;                                                \
174   if (n && fwrite(fmt->w.buf, 1, n, fmt->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 (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail);                \
183 } while (0)
184
185 #define SAVE_PFXTAIL do {                                               \
186   /* Save the trailing blank portion of the prefix. */                  \
187                                                                         \
188   if (fmt->prefix)                                                      \
189     DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail);           \
190 } while (0)
191
192 /* --- @init_fmt@ --- *
193  *
194  * Arguments:   @struct format *fmt@ = formatting 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 formatting state.
201  */
202
203 static void init_fmt(struct format *fmt, FILE *fp, const char *prefix)
204 {
205   const char *q, *l;
206
207   /* Basics. */
208   fmt->fp = fp;
209   fmt->f = FMTF_NEWL;
210   dstr_create(&fmt->w);
211
212   /* Prefix portions. */
213   if (!prefix || !*prefix)
214     fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0;
215   else {
216     fmt->prefix = prefix;
217     l = fmt->pfxlim = prefix + strlen(prefix);
218     SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q;
219   }
220 }
221
222 /* --- @destroy_fmt@ --- *
223  *
224  * Arguments:   @struct format *fmt@ = formatting state
225  *              @unsigned f@ = flags (@DFF_...@)
226  *
227  * Returns:     ---
228  *
229  * Use:         Releases a formatting state and the resources it holds.
230  *              Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave
231  *              it open (in case it's @stderr@ or something).
232  */
233
234 #define DFF_CLOSE 1u
235 static void destroy_fmt(struct format *fmt, unsigned f)
236 {
237   if (f&DFF_CLOSE) fclose(fmt->fp);
238   dstr_destroy(&fmt->w);
239 }
240
241 /* --- @format_char@ --- *
242  *
243  * Arguments:   @struct format *fmt@ = formatting 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 format_char(struct format *fmt, int ch)
252 {
253   if (ch == '\n') {
254     if (fmt->f&FMTF_NEWL) PUT_PFXINB;
255     PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w);
256   } else if (isspace(ch))
257     DPUTC(&fmt->w, ch);
258   else {
259     if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
260     PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w);
261   }
262   return (0);
263 }
264
265 /* --- @format_string@ --- *
266  *
267  * Arguments:   @struct format *fmt@ = formatting 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 format_string(struct format *fmt, 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(&fmt->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    
346      */
347
348     if (r > p) {
349       if (fmt->f&FMTF_NEWL) { PUT_PREFIX; fmt->f &= ~FMTF_NEWL; }
350       PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w);
351     }
352     SAVE_TAIL;
353     return (0);
354   }
355
356   /* There is at least one more segment, so we know that there'll be a line
357    * to output.
358    */
359   if (r > p) {
360     if (fmt->f&FMTF_NEWL) PUT_PREFIX;
361     PUT_SAVED; PUT_NONBLANK;
362   } else if (fmt->f&FMTF_NEWL)
363     PUT_PFXINB;
364   PUT_NEWLINE; DRESET(&fmt->w);
365   SPLIT_SEGMENT;
366
367   /* Main loop over whole segments with trailing newlines.  For each one, we
368    * know that we're starting at the beginning of a line and there's a final
369    * newline, so we write the initial prefix and drop the trailing blanks.
370    */
371   while (q) {
372     if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
373     else PUT_PFXINB;
374     PUT_NEWLINE;
375     SPLIT_SEGMENT;
376   }
377
378   /* At the end, there's no final newline.  If there's nonblank material,
379    * then we can write the prefix and the nonblank stuff.  Otherwise, stash
380    * the blank stuff (including the trailing blanks of the prefix) and leave
381    * the newline flag set.
382    */
383   if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; }
384   else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; }
385   SAVE_TAIL;
386
387 #undef SPLIT_SEGMENT
388 #undef PUT_NONBLANK
389 #undef PUT_NEWLINE
390 #undef SAVE_TAIL
391
392   return (0);
393 }
394
395 #undef SPLIT_RANGE
396 #undef PUT_RANGE
397 #undef PUT_PREFIX
398 #undef PUT_PFXINB
399 #undef PUT_SAVED
400 #undef PUT_CHAR
401 #undef SAVE_PFXTAIL
402
403 /*----- Skeleton ----------------------------------------------------------*/
404 /*
405 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
406 static int ..._esession(struct tvec_output *o)
407 static void ..._bgroup(struct tvec_output *o)
408 static void ..._skipgroup(struct tvec_output *o,
409                           const char *excuse, va_list *ap)
410 static void ..._egroup(struct tvec_output *o)
411 static void ..._btest(struct tvec_output *o)
412 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
413 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
414 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
415                         union tvec_regval *rv, const struct tvec_regdef *rd)
416 static void ..._etest(struct tvec_output *o, unsigned outcome)
417 static void ..._bbench(struct tvec_output *o,
418                        const char *ident, unsigned unit)
419 static void ..._ebench(struct tvec_output *o,
420                        const char *ident, unsigned unit,
421                        const struct tvec_timing *t)
422 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
423 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
424 static void ..._destroy(struct tvec_output *o)
425
426 static const struct tvec_outops ..._ops = {
427   ..._bsession, ..._esession,
428   ..._bgroup, ..._egroup, ..._skip,
429   ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
430   ..._bbench, ..._ebench,
431   ..._error, ..._notice,
432   ..._destroy
433 };
434 */
435 /*----- Human-readable output ---------------------------------------------*/
436
437 #define HAF_FGMASK 0x0f
438 #define HAF_FGSHIFT 0
439 #define HAF_BGMASK 0xf0
440 #define HAF_BGSHIFT 4
441 #define HAF_FG 256u
442 #define HAF_BG 512u
443 #define HAF_BOLD 1024u
444 #define HCOL_BLACK 0u
445 #define HCOL_RED 1u
446 #define HCOL_GREEN 2u
447 #define HCOL_YELLOW 3u
448 #define HCOL_BLUE 4u
449 #define HCOL_MAGENTA 5u
450 #define HCOL_CYAN 6u
451 #define HCOL_WHITE 7u
452 #define HCF_BRIGHT 8u
453 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
454 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
455
456 #define HA_WIN (HFG(GREEN))
457 #define HA_LOSE (HFG(RED) | HAF_BOLD)
458 #define HA_SKIP (HFG(YELLOW))
459 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
460
461 struct human_output {
462   struct tvec_output _o;
463   struct tvec_state *tv;
464   struct format fmt;
465   char *outbuf; size_t outsz;
466   dstr scoreboard;
467   unsigned attr;
468   int maxlen;
469   unsigned f;
470 #define HOF_TTY 1u
471 #define HOF_DUPERR 2u
472 #define HOF_COLOUR 4u
473 #define HOF_PROGRESS 8u
474 };
475
476 static void set_colour(FILE *fp, int *sep_inout,
477                        const char *norm, const char *bright,
478                        unsigned colour)
479 {
480   if (*sep_inout) putc(*sep_inout, fp);
481   fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
482   *sep_inout = ';';
483 }
484
485 static void setattr(struct human_output *h, unsigned attr)
486 {
487   unsigned diff = h->attr ^ attr;
488   int sep = 0;
489
490   if (!diff || !(h->f&HOF_COLOUR)) return;
491   fputs("\x1b[", h->fmt.fp);
492
493   if (diff&HAF_BOLD) {
494     if (attr&HAF_BOLD) putc('1', h->fmt.fp);
495     else { putc('0', h->fmt.fp); diff = h->attr; }
496     sep = ';';
497   }
498   if (diff&(HAF_FG | HAF_FGMASK)) {
499     if (attr&HAF_FG)
500       set_colour(h->fmt.fp, &sep, "3", "9",
501                  (attr&HAF_FGMASK) >> HAF_FGSHIFT);
502     else
503       { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; }
504   }
505   if (diff&(HAF_BG | HAF_BGMASK)) {
506     if (attr&HAF_BG)
507       set_colour(h->fmt.fp, &sep, "4", "10",
508                  (attr&HAF_BGMASK) >> HAF_BGSHIFT);
509     else
510       { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; }
511   }
512
513   putc('m', h->fmt.fp); h->attr = attr;
514
515 #undef f_any
516 }
517
518 static void clear_progress(struct human_output *h)
519 {
520   size_t i, n;
521
522   if (h->f&HOF_PROGRESS) {
523     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
524     for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp);
525     h->f &= ~HOF_PROGRESS;
526   }
527 }
528
529 static void write_scoreboard_char(struct human_output *h, int ch)
530 {
531   switch (ch) {
532     case 'x': setattr(h, HA_LOSE); break;
533     case '_': setattr(h, HA_SKIP); break;
534     default: setattr(h, 0); break;
535   }
536   putc(ch, h->fmt.fp); setattr(h, 0);
537 }
538
539 static void show_progress(struct human_output *h)
540 {
541   struct tvec_state *tv = h->tv;
542   const char *p, *l;
543
544   if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
545     fprintf(h->fmt.fp, "%s: ", tv->test->name);
546     if (!(h->f&HOF_COLOUR))
547       dstr_write(&h->scoreboard, h->fmt.fp);
548     else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
549       write_scoreboard_char(h, *p);
550     fflush(h->fmt.fp); h->f |= HOF_PROGRESS;
551   }
552 }
553
554 static void report_location(struct human_output *h, FILE *fp,
555                             const char *file, unsigned lno)
556 {
557   unsigned f = 0;
558 #define f_flush 1u
559
560 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
561
562   if (fp != h->fmt.fp) f |= f_flush;
563
564   if (file) {
565     setattr(h, HFG(CYAN));      FLUSH(h->fmt.fp);
566     fputs(file, fp);            FLUSH(fp);
567     setattr(h, HFG(BLUE));      FLUSH(h->fmt.fp);
568     fputc(':', fp);             FLUSH(fp);
569     setattr(h, HFG(CYAN));      FLUSH(h->fmt.fp);
570     fprintf(fp, "%u", lno);     FLUSH(fp);
571     setattr(h, HFG(BLUE));      FLUSH(h->fmt.fp);
572     fputc(':', fp);             FLUSH(fp);
573     setattr(h, 0);              FLUSH(h->fmt.fp);
574     fputc(' ', fp);
575   }
576
577 #undef f_flush
578 #undef FLUSH
579 }
580
581 static int human_writech(void *go, int ch)
582   { struct human_output *h = go; return (format_char(&h->fmt, ch)); }
583
584 static int human_writem(void *go, const char *p, size_t sz)
585   { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); }
586
587 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
588 {
589   struct human_output *h = go;
590   size_t n;
591   va_list ap;
592
593   va_start(ap, p);
594   n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
595   va_end(ap);
596   return (format_string(&h->fmt, h->outbuf, n));
597 }
598
599 static const struct gprintf_ops human_printops =
600   { human_writech, human_writem, human_nwritef };
601
602 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
603   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
604
605 static void report_skipped(struct human_output *h, unsigned n)
606 {
607   if (n) {
608     fprintf(h->fmt.fp, " (%u ", n);
609     setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
610     fputc(')', h->fmt.fp);
611   }
612 }
613
614 static int human_esession(struct tvec_output *o)
615 {
616   struct human_output *h = (struct human_output *)o;
617   struct tvec_state *tv = h->tv;
618   unsigned
619     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
620     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
621     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
622     all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
623
624   if (!all_lose) {
625     setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0);
626     fprintf(h->fmt.fp, " %s%u %s",
627             !(all_skip || grps_skip) ? "all " : "",
628             all_win, all_win == 1 ? "test" : "tests");
629     report_skipped(h, all_skip);
630     fprintf(h->fmt.fp, " in %u %s",
631             grps_win, grps_win == 1 ? "group" : "groups");
632     report_skipped(h, grps_skip);
633   } else {
634     setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
635     fprintf(h->fmt.fp, " %u out of %u %s",
636             all_lose, all_run, all_run == 1 ? "test" : "tests");
637     report_skipped(h, all_skip);
638     fprintf(h->fmt.fp, " in %u out of %u %s",
639             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
640     report_skipped(h, grps_skip);
641   }
642   fputc('\n', h->fmt.fp);
643
644   if (tv->f&TVSF_ERROR) {
645     setattr(h, HA_ERR); fputs("ERRORS", h->fmt.fp); setattr(h, 0);
646     fputs(" found in input; tests may not have run correctly\n", h->fmt.fp);
647   }
648
649   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
650 }
651
652 static void human_bgroup(struct tvec_output *o)
653 {
654   struct human_output *h = (struct human_output *)o;
655
656   h->maxlen = register_maxnamelen(h->tv);
657   dstr_reset(&h->scoreboard); show_progress(h);
658 }
659
660 static void human_skipgroup(struct tvec_output *o,
661                             const char *excuse, va_list *ap)
662 {
663   struct human_output *h = (struct human_output *)o;
664
665   if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
666     h->f &= ~HOF_PROGRESS;
667     putc(' ', h->fmt.fp);
668     setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
669   } else {
670     fprintf(h->fmt.fp, "%s: ", h->tv->test->name);
671     setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
672   }
673   if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
674   fputc('\n', h->fmt.fp);
675 }
676
677 static void human_egroup(struct tvec_output *o)
678 {
679   struct human_output *h = (struct human_output *)o;
680   struct tvec_state *tv = h->tv;
681   unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
682     skip = tv->curr[TVOUT_SKIP], run = win + lose;
683
684   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
685   else fprintf(h->fmt.fp, "%s:", h->tv->test->name);
686
687   if (lose) {
688     fprintf(h->fmt.fp, " %u/%u ", lose, run);
689     setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
690     report_skipped(h, skip);
691   } else {
692     fputc(' ', h->fmt.fp); setattr(h, HA_WIN);
693     fputs("ok", h->fmt.fp); setattr(h, 0);
694     report_skipped(h, skip);
695   }
696   fputc('\n', h->fmt.fp);
697 }
698
699 static void human_btest(struct tvec_output *o)
700   { struct human_output *h = (struct human_output *)o; show_progress(h); }
701
702 static void human_skip(struct tvec_output *o,
703                        const char *excuse, va_list *ap)
704 {
705   struct human_output *h = (struct human_output *)o;
706   struct tvec_state *tv = h->tv;
707
708   clear_progress(h);
709   report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
710   fprintf(h->fmt.fp, "`%s' ", tv->test->name);
711   setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
712   if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
713   fputc('\n', h->fmt.fp);
714 }
715
716 static void human_fail(struct tvec_output *o,
717                        const char *detail, va_list *ap)
718 {
719   struct human_output *h = (struct human_output *)o;
720   struct tvec_state *tv = h->tv;
721
722   clear_progress(h);
723   report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
724   fprintf(h->fmt.fp, "`%s' ", tv->test->name);
725   setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
726   if (detail) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, detail, *ap); }
727   fputc('\n', h->fmt.fp);
728 }
729
730 static void human_dumpreg(struct tvec_output *o,
731                           unsigned disp, const union tvec_regval *rv,
732                           const struct tvec_regdef *rd)
733 {
734   struct human_output *h = (struct human_output *)o;
735   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
736
737   clear_progress(h);
738   gprintf(&human_printops, h, "%*s%s %s = ",
739           10 + h->maxlen - n, "", ds, rd->name);
740   if (h->f&HOF_COLOUR) {
741     if (!rv) setattr(h, HFG(YELLOW));
742     else if (disp == TVRD_FOUND) setattr(h, HFG(RED));
743     else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN));
744   }
745   if (!rv) gprintf(&human_printops, h, "#unset");
746   else rd->ty->dump(rv, rd, 0, &human_printops, h);
747   setattr(h, 0); format_char(&h->fmt, '\n');
748 }
749
750 static void human_etest(struct tvec_output *o, unsigned outcome)
751 {
752   struct human_output *h = (struct human_output *)o;
753   int ch;
754
755   if (h->f&HOF_TTY) {
756     show_progress(h);
757     switch (outcome) {
758       case TVOUT_WIN: ch = '.'; break;
759       case TVOUT_LOSE: ch = 'x'; break;
760       case TVOUT_SKIP: ch = '_'; break;
761       default: abort();
762     }
763     dstr_putc(&h->scoreboard, ch);
764     write_scoreboard_char(h, ch); fflush(h->fmt.fp);
765   }
766 }
767
768 static void human_bbench(struct tvec_output *o,
769                          const char *ident, unsigned unit)
770 {
771   struct human_output *h = (struct human_output *)o;
772   struct tvec_state *tv = h->tv;
773
774   clear_progress(h);
775   fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp);
776 }
777
778 static void human_ebench(struct tvec_output *o,
779                          const char *ident, unsigned unit,
780                          const struct bench_timing *tm)
781 {
782   struct human_output *h = (struct human_output *)o;
783
784   tvec_benchreport(&human_printops, h->fmt.fp, unit, tm);
785   fputc('\n', h->fmt.fp);
786 }
787
788 static void human_report(struct tvec_output *o, unsigned level,
789                          const char *msg, va_list *ap)
790 {
791   struct human_output *h = (struct human_output *)o;
792   struct tvec_state *tv = h->tv;
793   dstr d = DSTR_INIT;
794
795   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
796
797   clear_progress(h); fflush(h->fmt.fp);
798   fprintf(stderr, "%s: ", QUIS);
799   report_location(h, stderr, tv->infile, tv->lno);
800   fwrite(d.buf, 1, d.len, stderr);
801
802   if (h->f&HOF_DUPERR) {
803     report_location(h, h->fmt.fp, tv->infile, tv->lno);
804     fwrite(d.buf, 1, d.len, h->fmt.fp);
805   }
806   show_progress(h);
807 }
808
809 static void human_destroy(struct tvec_output *o)
810 {
811   struct human_output *h = (struct human_output *)o;
812
813   destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0);
814   dstr_destroy(&h->scoreboard);
815   xfree(h->outbuf); xfree(h);
816 }
817
818 static const struct tvec_outops human_ops = {
819   human_bsession, human_esession,
820   human_bgroup, human_skipgroup, human_egroup,
821   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
822   human_bbench, human_ebench,
823   human_report,
824   human_destroy
825 };
826
827 struct tvec_output *tvec_humanoutput(FILE *fp)
828 {
829   struct human_output *h;
830   const char *p;
831
832   h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
833   h->f = 0; h->attr = 0;
834
835   init_fmt(&h->fmt, fp, 0);
836   h->outbuf = 0; h->outsz = 0;
837
838   switch (getenv_boolean("TVEC_TTY", -1)) {
839     case 1: h->f |= HOF_TTY; break;
840     case 0: break;
841     default:
842       if (isatty(fileno(fp))) h->f |= HOF_TTY;
843       break;
844   }
845   switch (getenv_boolean("TVEC_COLOUR", -1)) {
846     case 1: h->f |= HOF_COLOUR; break;
847     case 0: break;
848     default:
849       if (h->f&HOF_TTY) {
850         p = getenv("TERM");
851         if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
852       }
853       break;
854   }
855
856   if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
857   dstr_create(&h->scoreboard);
858   return (&h->_o);
859 }
860
861 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
862
863 struct tap_output {
864   struct tvec_output _o;
865   struct tvec_state *tv;
866   struct format fmt;
867   char *outbuf; size_t outsz;
868   int maxlen;
869 };
870
871 static int tap_writech(void *go, int ch)
872   { struct tap_output *t = go; return (format_char(&t->fmt, ch)); }
873
874 static int tap_writem(void *go, const char *p, size_t sz)
875   { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); }
876
877 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
878 {
879   struct human_output *t = go;
880   size_t n;
881   va_list ap;
882
883   va_start(ap, p);
884   n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
885   va_end(ap);
886   return (format_string(&t->fmt, t->outbuf, n));
887 }
888
889 static const struct gprintf_ops tap_printops =
890   { tap_writech, tap_writem, tap_nwritef };
891
892 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
893 {
894   struct tap_output *t = (struct tap_output *)o;
895
896   t->tv = tv;
897   fputs("TAP version 13\n", t->fmt.fp);
898 }
899
900 static unsigned tap_grpix(struct tap_output *t)
901 {
902   struct tvec_state *tv = t->tv;
903
904   return (tv->grps[TVOUT_WIN] +
905           tv->grps[TVOUT_LOSE] +
906           tv->grps[TVOUT_SKIP]);
907 }
908
909 static int tap_esession(struct tvec_output *o)
910 {
911   struct tap_output *t = (struct tap_output *)o;
912   struct tvec_state *tv = t->tv;
913
914   if (tv->f&TVSF_ERROR) {
915     fputs("Bail out!  "
916           "Errors found in input; tests may not have run correctly\n",
917           t->fmt.fp);
918     return (2);
919   }
920
921   fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t));
922   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
923 }
924
925 static void tap_bgroup(struct tvec_output *o)
926 {
927   struct tap_output *t = (struct tap_output *)o;
928   t->maxlen = register_maxnamelen(t->tv);
929 }
930
931 static void tap_skipgroup(struct tvec_output *o,
932                           const char *excuse, va_list *ap)
933 {
934   struct tap_output *t = (struct tap_output *)o;
935
936   fprintf(t->fmt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
937   if (excuse) { fputc(' ', t->fmt.fp); vfprintf(t->fmt.fp, excuse, *ap); }
938   fputc('\n', t->fmt.fp);
939 }
940
941 static void tap_egroup(struct tvec_output *o)
942 {
943   struct tap_output *t = (struct tap_output *)o;
944   struct tvec_state *tv = t->tv;
945   unsigned
946     grpix = tap_grpix(t),
947     win = tv->curr[TVOUT_WIN],
948     lose = tv->curr[TVOUT_LOSE],
949     skip = tv->curr[TVOUT_SKIP];
950
951   if (lose) {
952     fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u",
953             grpix, tv->test->name, lose, win + lose);
954     if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
955   } else {
956     fprintf(t->fmt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
957     if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
958   }
959   fputc('\n', t->fmt.fp);
960 }
961
962 static void tap_btest(struct tvec_output *o) { ; }
963
964 static void tap_outcome(struct tvec_output *o, const char *outcome,
965                         const char *detail, va_list *ap)
966 {
967   struct tap_output *t = (struct tap_output *)o;
968   struct tvec_state *tv = t->tv;
969
970   gprintf(&tap_printops, t, "%s:%u: `%s' %s",
971           tv->infile, tv->test_lno, tv->test->name, outcome);
972   if (detail) {
973     format_string(&t->fmt, ": ", 2);
974     vgprintf(&tap_printops, t, detail, ap);
975   }
976   format_char(&t->fmt, '\n');
977 }
978
979 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
980   { tap_outcome(o, "skipped", excuse, ap); }
981 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
982   { tap_outcome(o, "FAILED", detail, ap); }
983
984 static void tap_dumpreg(struct tvec_output *o,
985                         unsigned disp, const union tvec_regval *rv,
986                         const struct tvec_regdef *rd)
987 {
988   struct tap_output *t = (struct tap_output *)o;
989   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
990
991   gprintf(&tap_printops, t, "%*s%s %s = ",
992           10 + t->maxlen - n, "", ds, rd->name);
993   if (!rv) gprintf(&tap_printops, t, "#<unset>");
994   else rd->ty->dump(rv, rd, 0, &tap_printops, t);
995   format_char(&t->fmt, '\n');
996 }
997
998 static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
999
1000 static void tap_bbench(struct tvec_output *o,
1001                        const char *ident, unsigned unit)
1002   { ; }
1003
1004 static void tap_ebench(struct tvec_output *o,
1005                        const char *ident, unsigned unit,
1006                        const struct bench_timing *tm)
1007 {
1008   struct tap_output *t = (struct tap_output *)o;
1009   struct tvec_state *tv = t->tv;
1010
1011   gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1012   tvec_benchreport(&tap_printops, t, unit, tm);
1013   format_char(&t->fmt, '\n');
1014 }
1015
1016 static void tap_report(struct tvec_output *o, unsigned level,
1017                        const char *msg, va_list *ap)
1018 {
1019   struct tap_output *t = (struct tap_output *)o;
1020   struct tvec_state *tv = t->tv;
1021   const struct gprintf_ops *gops; void *go;
1022
1023   if (level >= TVLEV_ERR) {
1024     fputs("Bail out!  ", t->fmt.fp);
1025     gops = &file_printops; go = t->fmt.fp;
1026   } else {
1027     gops = &tap_printops; go = t;
1028   }
1029   if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
1030   gprintf(gops, go, msg, ap); gops->putch(go, '\n');
1031 }
1032
1033 static void tap_destroy(struct tvec_output *o)
1034 {
1035   struct tap_output *t = (struct tap_output *)o;
1036
1037   destroy_fmt(&t->fmt,
1038               t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE);
1039   xfree(t->outbuf); xfree(t);
1040 }
1041
1042 static const struct tvec_outops tap_ops = {
1043   tap_bsession, tap_esession,
1044   tap_bgroup, tap_skipgroup, tap_egroup,
1045   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
1046   tap_bbench, tap_ebench,
1047   tap_report,
1048   tap_destroy
1049 };
1050
1051 struct tvec_output *tvec_tapoutput(FILE *fp)
1052 {
1053   struct tap_output *t;
1054
1055   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
1056   init_fmt(&t->fmt, fp, "## ");
1057   t->outbuf = 0; t->outsz = 0;
1058   return (&t->_o);
1059 }
1060
1061 /*----- Default output ----------------------------------------------------*/
1062
1063 struct tvec_output *tvec_dfltout(FILE *fp)
1064 {
1065   int ttyp = getenv_boolean("TVEC_TTY", -1);
1066
1067   if (ttyp == -1) ttyp = isatty(fileno(fp));
1068   if (ttyp) return (tvec_humanoutput(fp));
1069   else return (tvec_tapoutput(fp));
1070 }
1071
1072 /*----- That's all, folks -------------------------------------------------*/