chiark / gitweb /
@@@ BROKEN wip
[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 <stdarg.h>
34 #include <stdio.h>
35 #include <string.h>
36
37 #include <unistd.h>
38
39 #include "alloc.h"
40 #include "bench.h"
41 #include "dstr.h"
42 #include "quis.h"
43 #include "report.h"
44 #include "tvec.h"
45
46 /*----- Common machinery --------------------------------------------------*/
47
48 static const char *regdisp(unsigned disp)
49 {
50   switch (disp) {
51     case TVRD_INPUT: return "input";
52     case TVRD_OUTPUT: return "output";
53     case TVRD_MATCH: return "matched";
54     case TVRD_EXPECT: return "expected";
55     case TVRD_FOUND: return "found";
56     default: abort();
57   }
58 }
59
60 static int getenv_boolean(const char *var, int dflt)
61 {
62   const char *p;
63
64   p = getenv(var);
65   if (!p)
66     return (dflt);
67   else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
68            STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
69            STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
70            STRCMP(p, ==, "1"))
71     return (1);
72   else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
73            STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
74            STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
75            STRCMP(p, ==, "0"))
76     return (0);
77   else {
78     moan("unexpected value `%s' for boolean environment variable `%s'",
79          var, p);
80     return (dflt);
81   }
82 }
83
84 static int register_maxnamelen(const struct tvec_state *tv)
85 {
86   const struct tvec_regdef *rd;
87   int maxlen = 6, n;
88
89   for (rd = tv->test->regs; rd->name; rd++)
90     { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
91   return (maxlen);
92 }
93
94 /*----- Skeleton ----------------------------------------------------------*/
95 /*
96 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
97 static int ..._esession(struct tvec_output *o)
98 static void ..._bgroup(struct tvec_output *o)
99 static void ..._skipgroup(struct tvec_output *o,
100                           const char *excuse, va_list *ap)
101 static void ..._egroup(struct tvec_output *o)
102 static void ..._btest(struct tvec_output *o)
103 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
104 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
105 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
106                         union tvec_regval *rv, const struct tvec_regdef *rd)
107 static void ..._etest(struct tvec_output *o, unsigned outcome)
108 static void ..._bbench(struct tvec_output *o,
109                        const char *ident, unsigned unit)
110 static void ..._ebench(struct tvec_output *o,
111                        const char *ident, unsigned unit,
112                        const struct tvec_timing *t)
113 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
114 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
115 static void ..._destroy(struct tvec_output *o)
116
117 static const struct tvec_outops ..._ops = {
118   ..._bsession, ..._esession,
119   ..._bgroup, ..._egroup, ..._skip,
120   ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
121   ..._bbench, ..._ebench,
122   ..._error, ..._notice,
123   ..._destroy
124 };
125 */
126 /*----- Human-readable output ---------------------------------------------*/
127
128 #define HAF_FGMASK 0x0f
129 #define HAF_FGSHIFT 0
130 #define HAF_BGMASK 0xf0
131 #define HAF_BGSHIFT 4
132 #define HAF_FG 256u
133 #define HAF_BG 512u
134 #define HAF_BOLD 1024u
135 #define HCOL_BLACK 0u
136 #define HCOL_RED 1u
137 #define HCOL_GREEN 2u
138 #define HCOL_YELLOW 3u
139 #define HCOL_BLUE 4u
140 #define HCOL_MAGENTA 5u
141 #define HCOL_CYAN 6u
142 #define HCOL_WHITE 7u
143 #define HCF_BRIGHT 8u
144 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
145 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
146
147 #define HA_WIN (HFG(GREEN))
148 #define HA_LOSE (HFG(RED) | HAF_BOLD)
149 #define HA_SKIP (HFG(YELLOW))
150 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
151
152 struct human_output {
153   struct tvec_output _o;
154   struct tvec_state *tv;
155   FILE *fp;
156   dstr scoreboard;
157   unsigned attr;
158   int maxlen;
159   unsigned f;
160 #define HOF_TTY 1u
161 #define HOF_DUPERR 2u
162 #define HOF_COLOUR 4u
163 #define HOF_PROGRESS 8u
164 };
165
166 static void set_colour(FILE *fp, int *sep_inout,
167                        const char *norm, const char *bright,
168                        unsigned colour)
169 {
170   if (*sep_inout) putc(*sep_inout, fp);
171   fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
172   *sep_inout = ';';
173 }
174
175 static void setattr(struct human_output *h, unsigned attr)
176 {
177   unsigned diff = h->attr ^ attr;
178   int sep = 0;
179
180   if (!diff || !(h->f&HOF_COLOUR)) return;
181   fputs("\x1b[", h->fp);
182
183   if (diff&HAF_BOLD) {
184     if (attr&HAF_BOLD) putc('1', h->fp);
185     else { putc('0', h->fp); diff = h->attr; }
186     sep = ';';
187   }
188   if (diff&(HAF_FG | HAF_FGMASK)) {
189     if (attr&HAF_FG)
190       set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT);
191     else
192       { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; }
193   }
194   if (diff&(HAF_BG | HAF_BGMASK)) {
195     if (attr&HAF_BG)
196       set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT);
197     else
198       { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; }
199   }
200
201   putc('m', h->fp); h->attr = attr;
202
203 #undef f_any
204 }
205
206 static void clear_progress(struct human_output *h)
207 {
208   size_t i, n;
209
210   if (h->f&HOF_PROGRESS) {
211     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
212     for (i = 0; i < n; i++) fputs("\b \b", h->fp);
213     h->f &= ~HOF_PROGRESS;
214   }
215 }
216
217 static void write_scoreboard_char(struct human_output *h, int ch)
218 {
219   switch (ch) {
220     case 'x': setattr(h, HA_LOSE); break;
221     case '_': setattr(h, HA_SKIP); break;
222     default: setattr(h, 0); break;
223   }
224   putc(ch, h->fp); setattr(h, 0);
225 }
226
227 static void show_progress(struct human_output *h)
228 {
229   struct tvec_state *tv = h->tv;
230   const char *p, *l;
231
232   if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
233     fprintf(h->fp, "%s: ", tv->test->name);
234     if (!(h->f&HOF_COLOUR))
235       dstr_write(&h->scoreboard, h->fp);
236     else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
237       write_scoreboard_char(h, *p);
238     fflush(h->fp); h->f |= HOF_PROGRESS;
239   }
240 }
241
242 static void report_location(struct human_output *h, FILE *fp,
243                             const char *file, unsigned lno)
244 {
245   unsigned f = 0;
246 #define f_flush 1u
247
248 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
249
250   if (fp != h->fp) f |= f_flush;
251
252   if (file) {
253     setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp);
254     setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
255     setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp);
256     setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
257     setattr(h, 0); FLUSH(h->fp); fputc(' ', fp);
258   }
259
260 #undef f_flush
261 #undef FLUSH
262 }
263
264 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
265   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
266
267 static void report_skipped(struct human_output *h, unsigned n)
268 {
269   if (n) {
270     fprintf(h->fp, " (%u ", n);
271     setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
272     fputc(')', h->fp);
273   }
274 }
275
276 static int human_esession(struct tvec_output *o)
277 {
278   struct human_output *h = (struct human_output *)o;
279   struct tvec_state *tv = h->tv;
280   unsigned
281     all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
282     all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
283     all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
284     all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
285
286   if (!all_lose) {
287     setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0);
288     fprintf(h->fp, " %s%u %s",
289             !(all_skip || grps_skip) ? "all " : "",
290             all_win, all_win == 1 ? "test" : "tests");
291     report_skipped(h, all_skip);
292     fprintf(h->fp, " in %u %s",
293             grps_win, grps_win == 1 ? "group" : "groups");
294     report_skipped(h, grps_skip);
295   } else {
296     setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
297     fprintf(h->fp, " %u out of %u %s",
298             all_lose, all_run, all_run == 1 ? "test" : "tests");
299     report_skipped(h, all_skip);
300     fprintf(h->fp, " in %u out of %u %s",
301             grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
302     report_skipped(h, grps_skip);
303   }
304   fputc('\n', h->fp);
305
306   if (tv->f&TVSF_ERROR) {
307     setattr(h, HA_ERR); fputs("ERRORS", h->fp); setattr(h, 0);
308     fputs(" found in input; tests may not have run correctly\n", h->fp);
309   }
310
311   h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
312 }
313
314 static void human_bgroup(struct tvec_output *o)
315 {
316   struct human_output *h = (struct human_output *)o;
317
318   h->maxlen = register_maxnamelen(h->tv);
319   dstr_reset(&h->scoreboard); show_progress(h);
320 }
321
322 static void human_skipgroup(struct tvec_output *o,
323                             const char *excuse, va_list *ap)
324 {
325   struct human_output *h = (struct human_output *)o;
326
327   if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
328     h->f &= ~HOF_PROGRESS;
329     setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
330   } else {
331     fprintf(h->fp, "%s: ", h->tv->test->name);
332     setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
333   }
334   if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
335   fputc('\n', h->fp);
336 }
337
338 static void human_egroup(struct tvec_output *o)
339 {
340   struct human_output *h = (struct human_output *)o;
341   struct tvec_state *tv = h->tv;
342   unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
343     skip = tv->curr[TVOUT_SKIP], run = win + lose;
344
345   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
346   else fprintf(h->fp, "%s:", h->tv->test->name);
347
348   if (lose) {
349     fprintf(h->fp, " %u/%u ", lose, run);
350     setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
351     report_skipped(h, skip);
352   } else {
353     fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0);
354     report_skipped(h, skip);
355   }
356   fputc('\n', h->fp);
357 }
358
359 static void human_btest(struct tvec_output *o)
360   { struct human_output *h = (struct human_output *)o; show_progress(h); }
361
362 static void human_skip(struct tvec_output *o,
363                        const char *excuse, va_list *ap)
364 {
365   struct human_output *h = (struct human_output *)o;
366   struct tvec_state *tv = h->tv;
367
368   clear_progress(h);
369   report_location(h, h->fp, tv->infile, tv->test_lno);
370   fprintf(h->fp, "`%s' ", tv->test->name);
371   setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
372   if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
373   fputc('\n', h->fp);
374 }
375
376 static void human_fail(struct tvec_output *o,
377                        const char *detail, va_list *ap)
378 {
379   struct human_output *h = (struct human_output *)o;
380   struct tvec_state *tv = h->tv;
381
382   clear_progress(h);
383   report_location(h, h->fp, tv->infile, tv->test_lno);
384   fprintf(h->fp, "`%s' ", tv->test->name);
385   setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
386   if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); }
387   fputc('\n', h->fp);
388 }
389
390 static void human_dumpreg(struct tvec_output *o,
391                           unsigned disp, const union tvec_regval *rv,
392                           const struct tvec_regdef *rd)
393 {
394   struct human_output *h = (struct human_output *)o;
395   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
396
397   fprintf(h->fp, "%*s%s %s = ", 10 + h->maxlen - n, "", ds, rd->name);
398   if (h->f&HOF_COLOUR) {
399     if (!rv) setattr(h, HFG(YELLOW));
400     else if (disp == TVRD_FOUND) setattr(h, HFG(RED));
401     else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN));
402   }
403   if (!rv) fprintf(h->fp, "#<unset>");
404   else rd->ty->dump(rv, rd, 0, &file_printops, h->fp);
405   setattr(h, 0); fputc('\n', h->fp);
406 }
407
408 static void human_etest(struct tvec_output *o, unsigned outcome)
409 {
410   struct human_output *h = (struct human_output *)o;
411   int ch;
412
413   if (h->f&HOF_TTY) {
414     show_progress(h);
415     switch (outcome) {
416       case TVOUT_WIN: ch = '.'; break;
417       case TVOUT_LOSE: ch = 'x'; break;
418       case TVOUT_SKIP: ch = '_'; break;
419       default: abort();
420     }
421     dstr_putc(&h->scoreboard, ch);
422     write_scoreboard_char(h, ch); fflush(h->fp);
423   }
424 }
425
426 static void human_bbench(struct tvec_output *o,
427                          const char *ident, unsigned unit)
428 {
429   struct human_output *h = (struct human_output *)o;
430   struct tvec_state *tv = h->tv;
431
432   clear_progress(h);
433   fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp);
434 }
435
436 static void human_ebench(struct tvec_output *o,
437                          const char *ident, unsigned unit,
438                          const struct bench_timing *tm)
439 {
440   struct human_output *h = (struct human_output *)o;
441   tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp);
442 }
443
444 static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
445 {
446   struct human_output *h = (struct human_output *)o;
447   struct tvec_state *tv = h->tv;
448   dstr d = DSTR_INIT;
449
450   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
451
452   clear_progress(h); fflush(h->fp);
453   fprintf(stderr, "%s: ", QUIS);
454   report_location(h, stderr, tv->infile, tv->lno);
455   fwrite(d.buf, 1, d.len, stderr);
456
457   if (h->f&HOF_DUPERR) {
458     report_location(h, h->fp, tv->infile, tv->lno);
459     fwrite(d.buf, 1, d.len, h->fp);
460   }
461   show_progress(h);
462 }
463
464 static void human_destroy(struct tvec_output *o)
465 {
466   struct human_output *h = (struct human_output *)o;
467
468   if (h->f&HOF_DUPERR) fclose(h->fp);
469   dstr_destroy(&h->scoreboard);
470   xfree(h);
471 }
472
473 static const struct tvec_outops human_ops = {
474   human_bsession, human_esession,
475   human_bgroup, human_skipgroup, human_egroup,
476   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
477   human_bbench, human_ebench,
478   human_report, human_report,
479   human_destroy
480 };
481
482 struct tvec_output *tvec_humanoutput(FILE *fp)
483 {
484   struct human_output *h;
485   const char *p;
486
487   h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
488   h->f = 0; h->attr = 0;
489
490   h->fp = fp;
491
492   switch (getenv_boolean("TVEC_TTY", -1)) {
493     case 1: h->f |= HOF_TTY; break;
494     case 0: break;
495     default:
496       if (isatty(fileno(fp))) h->f |= HOF_TTY;
497       break;
498   }
499   switch (getenv_boolean("TVEC_COLOUR", -1)) {
500     case 1: h->f |= HOF_COLOUR; break;
501     case 0: break;
502     default:
503       if (h->f&HOF_TTY) {
504         p = getenv("TERM");
505         if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
506       }
507       break;
508   }
509
510   if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
511   dstr_create(&h->scoreboard);
512   return (&h->_o);
513 }
514
515 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
516
517 struct tap_output {
518   struct tvec_output _o;
519   struct tvec_state *tv;
520   FILE *fp;
521   dstr d;
522   int maxlen;
523   unsigned f;
524 #define TOF_FRESHLINE 1u
525 };
526
527 static int tap_writech(void *go, int ch)
528 {
529   struct tap_output *t = go;
530
531   if (t->f&TOF_FRESHLINE) {
532     if (fputs("## ", t->fp) < 0) return (-1);
533     t->f &= ~TOF_FRESHLINE;
534   }
535   if (putc(ch, t->fp) < 0) return (-1);
536   if (ch == '\n') t->f |= TOF_FRESHLINE;
537   return (1);
538 }
539
540 static int tap_writem(void *go, const char *p, size_t sz)
541 {
542   struct tap_output *t = go;
543   const char *q, *l = p + sz;
544   size_t n;
545
546   if (p == l) return (0);
547   if (t->f&TOF_FRESHLINE)
548     if (fputs("## ", t->fp) < 0) return (-1);
549   for (;;) {
550     q = memchr(p, '\n', l - p); if (!q) break;
551     n = q + 1 - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
552     p = q + 1;
553     if (p == l) { t->f |= TOF_FRESHLINE; return (sz); }
554     if (fputs("## ", t->fp) < 0) return (-1);
555   }
556   n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
557   t->f &= ~TOF_FRESHLINE; return (0);
558 }
559
560 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
561 {
562   struct tap_output *t = go;
563   va_list ap;
564   int n;
565
566   va_start(ap, p); DRESET(&t->d); DENSURE(&t->d, maxsz + 1);
567 #ifdef HAVE_SNPRINTF
568   n = vsnprintf(t->d.buf, maxsz + 1, p, ap);
569 #else
570   n = vsprintf(t->d.buf, p, ap);
571 #endif
572   assert(0 <= n && n <= maxsz);
573   va_end(ap);
574   return (tap_writem(t, t->d.buf, n));
575 }
576
577 static const struct gprintf_ops tap_printops =
578   { tap_writech, tap_writem, tap_nwritef };
579
580 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
581 {
582   struct tap_output *t = (struct tap_output *)o;
583
584   t->tv = tv;
585   fputs("TAP version 13\n", t->fp);
586 }
587
588 static unsigned tap_grpix(struct tap_output *t)
589 {
590   struct tvec_state *tv = t->tv;
591
592   return (tv->grps[TVOUT_WIN] +
593           tv->grps[TVOUT_LOSE] +
594           tv->grps[TVOUT_SKIP]);
595 }
596
597 static int tap_esession(struct tvec_output *o)
598 {
599   struct tap_output *t = (struct tap_output *)o;
600   struct tvec_state *tv = t->tv;
601
602   if (tv->f&TVSF_ERROR) {
603     fputs("Bail out!  "
604           "Errors found in input; tests may not have run correctly\n",
605           t->fp);
606     return (2);
607   }
608
609   fprintf(t->fp, "1..%u\n", tap_grpix(t));
610   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
611 }
612
613 static void tap_bgroup(struct tvec_output *o)
614 {
615   struct tap_output *t = (struct tap_output *)o;
616   t->maxlen = register_maxnamelen(t->tv);
617 }
618
619 static void tap_skipgroup(struct tvec_output *o,
620                           const char *excuse, va_list *ap)
621 {
622   struct tap_output *t = (struct tap_output *)o;
623
624   fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
625   if (excuse) { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); }
626   fputc('\n', t->fp);
627 }
628
629 static void tap_egroup(struct tvec_output *o)
630 {
631   struct tap_output *t = (struct tap_output *)o;
632   struct tvec_state *tv = t->tv;
633   unsigned
634     grpix = tap_grpix(t),
635     win = tv->curr[TVOUT_WIN],
636     lose = tv->curr[TVOUT_LOSE],
637     skip = tv->curr[TVOUT_SKIP];
638
639   if (lose) {
640     fprintf(t->fp, "not ok %u - %s: FAILED %u/%u",
641             grpix, tv->test->name, lose, win + lose);
642     if (skip) fprintf(t->fp, " (skipped %u)", skip);
643   } else {
644     fprintf(t->fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
645     if (skip) fprintf(t->fp, " (skipped %u)", skip);
646   }
647   fputc('\n', t->fp);
648 }
649
650 static void tap_btest(struct tvec_output *o) { ; }
651
652 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
653 {
654   struct tap_output *t = (struct tap_output *)o;
655   struct tvec_state *tv = t->tv;
656
657   fprintf(t->fp, "## %s:%u: `%s' skipped",
658           tv->infile, tv->test_lno, tv->test->name);
659   if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); }
660   fputc('\n', t->fp);
661 }
662
663 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
664 {
665   struct tap_output *t = (struct tap_output *)o;
666   struct tvec_state *tv = t->tv;
667
668   fprintf(t->fp, "## %s:%u: `%s' FAILED",
669           tv->infile, tv->test_lno, tv->test->name);
670   if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); }
671   fputc('\n', t->fp);
672 }
673
674 static void tap_dumpreg(struct tvec_output *o,
675                         unsigned disp, const union tvec_regval *rv,
676                         const struct tvec_regdef *rd)
677 {
678   struct tap_output *t = (struct tap_output *)o;
679   const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
680
681   fprintf(t->fp, "## %*s%s %s = ", 10 + t->maxlen - n, "", ds, rd->name);
682   if (!rv)
683     fprintf(t->fp, "#<unset>\n");
684   else {
685     t->f &= ~TOF_FRESHLINE;
686     rd->ty->dump(rv, rd, 0, &tap_printops, t);
687     fputc('\n', t->fp);
688   }
689 }
690
691 static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
692
693 static void tap_bbench(struct tvec_output *o,
694                        const char *ident, unsigned unit)
695   { ; }
696
697 static void tap_ebench(struct tvec_output *o,
698                        const char *ident, unsigned unit,
699                        const struct bench_timing *tm)
700 {
701   struct tap_output *t = (struct tap_output *)o;
702   struct tvec_state *tv = t->tv;
703
704   fprintf(t->fp, "## %s: %s: ", tv->test->name, ident);
705   t->f &= ~TOF_FRESHLINE; tvec_benchreport(&tap_printops, t, unit, tm);
706   fputc('\n', t->fp);
707 }
708
709 static void tap_report(struct tap_output *t, const char *msg, va_list *ap)
710 {
711   struct tvec_state *tv = t->tv;
712
713   if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno);
714   vfprintf(t->fp, msg, *ap); fputc('\n', t->fp);
715 }
716
717 static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
718 {
719   struct tap_output *t = (struct tap_output *)o;
720   fputs("Bail out!  ", t->fp); tap_report(t, msg, ap);
721 }
722
723 static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
724 {
725   struct tap_output *t = (struct tap_output *)o;
726   fputs("## ", t->fp); tap_report(t, msg, ap);
727 }
728
729 static void tap_destroy(struct tvec_output *o)
730 {
731   struct tap_output *t = (struct tap_output *)o;
732
733   if (t->fp != stdout && t->fp != stderr) fclose(t->fp);
734   dstr_destroy(&t->d);
735   xfree(t);
736 }
737
738 static const struct tvec_outops tap_ops = {
739   tap_bsession, tap_esession,
740   tap_bgroup, tap_skipgroup, tap_egroup,
741   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
742   tap_bbench, tap_ebench,
743   tap_error, tap_notice,
744   tap_destroy
745 };
746
747 struct tvec_output *tvec_tapoutput(FILE *fp)
748 {
749   struct tap_output *t;
750
751   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
752   dstr_create(&t->d);
753   t->f = 0; t->fp = fp;
754   return (&t->_o);
755 }
756
757 /*----- Default output ----------------------------------------------------*/
758
759 struct tvec_output *tvec_dfltout(FILE *fp)
760 {
761   int ttyp = getenv_boolean("TVEC_TTY", -1);
762
763   if (ttyp == -1) ttyp = isatty(fileno(fp));
764   if (ttyp) return (tvec_humanoutput(fp));
765   else return (tvec_tapoutput(fp));
766 }
767
768 /*----- That's all, folks -------------------------------------------------*/