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