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