chiark / gitweb /
@@@ BROKEN wip
[mLib] / test / tvec-output.c
CommitLineData
b64eb60f
MW
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
e63124bc
MW
30#include "config.h"
31
b64eb60f
MW
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
b64eb60f
MW
48static const char *regdisp(unsigned disp)
49{
50 switch (disp) {
e63124bc
MW
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";
b64eb60f
MW
56 default: abort();
57 }
58}
59
60static 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") ||
e63124bc 73 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
b64eb60f
MW
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
e63124bc 84static int register_maxnamelen(const struct tvec_state *tv)
b64eb60f
MW
85{
86 const struct tvec_regdef *rd;
e63124bc 87 int maxlen = 6, n;
b64eb60f
MW
88
89 for (rd = tv->test->regs; rd->name; rd++)
e63124bc
MW
90 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
91 return (maxlen);
b64eb60f
MW
92}
93
94/*----- Skeleton ----------------------------------------------------------*/
95/*
3efcfd2d 96static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
b64eb60f
MW
97static int ..._esession(struct tvec_output *o)
98static void ..._bgroup(struct tvec_output *o)
b64eb60f
MW
99static void ..._skipgroup(struct tvec_output *o,
100 const char *excuse, va_list *ap)
3efcfd2d 101static void ..._egroup(struct tvec_output *o)
b64eb60f 102static void ..._btest(struct tvec_output *o)
e63124bc 103static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
b64eb60f 104static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
e63124bc
MW
105static void ..._dumpreg(struct tvec_output *o, unsigned disp,
106 union tvec_regval *rv, const struct tvec_regdef *rd)
b64eb60f 107static void ..._etest(struct tvec_output *o, unsigned outcome)
e63124bc
MW
108static void ..._bbench(struct tvec_output *o,
109 const char *ident, unsigned unit)
110static void ..._ebench(struct tvec_output *o,
111 const char *ident, unsigned unit,
112 const struct tvec_timing *t)
3efcfd2d
MW
113static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
114static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
b64eb60f
MW
115static void ..._destroy(struct tvec_output *o)
116
117static const struct tvec_outops ..._ops = {
b64eb60f
MW
118 ..._bsession, ..._esession,
119 ..._bgroup, ..._egroup, ..._skip,
e63124bc 120 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
b64eb60f 121 ..._bbench, ..._ebench,
3efcfd2d 122 ..._error, ..._notice,
b64eb60f
MW
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))
882a39c1 150#define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
b64eb60f
MW
151
152struct human_output {
153 struct tvec_output _o;
3efcfd2d 154 struct tvec_state *tv;
b64eb60f
MW
155 FILE *fp;
156 dstr scoreboard;
157 unsigned attr;
e63124bc 158 int maxlen;
b64eb60f
MW
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
166static 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
175static 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
206static void clear_progress(struct human_output *h)
207{
208 size_t i, n;
209
210 if (h->f&HOF_PROGRESS) {
3efcfd2d 211 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
b64eb60f
MW
212 for (i = 0; i < n; i++) fputs("\b \b", h->fp);
213 h->f &= ~HOF_PROGRESS;
214 }
215}
216
217static 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
227static void show_progress(struct human_output *h)
228{
3efcfd2d 229 struct tvec_state *tv = h->tv;
b64eb60f
MW
230 const char *p, *l;
231
882a39c1 232 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
3efcfd2d 233 fprintf(h->fp, "%s: ", tv->test->name);
b64eb60f
MW
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
242static 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
3efcfd2d
MW
264static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
265 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
b64eb60f
MW
266
267static 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
276static int human_esession(struct tvec_output *o)
277{
278 struct human_output *h = (struct human_output *)o;
3efcfd2d 279 struct tvec_state *tv = h->tv;
b64eb60f
MW
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
882a39c1
MW
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
3efcfd2d 311 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
312}
313
314static void human_bgroup(struct tvec_output *o)
315{
316 struct human_output *h = (struct human_output *)o;
e63124bc 317
3efcfd2d 318 h->maxlen = register_maxnamelen(h->tv);
b64eb60f
MW
319 dstr_reset(&h->scoreboard); show_progress(h);
320}
321
3efcfd2d
MW
322static void human_skipgroup(struct tvec_output *o,
323 const char *excuse, va_list *ap)
b64eb60f 324{
3efcfd2d 325 struct human_output *h = (struct human_output *)o;
b64eb60f 326
3efcfd2d
MW
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);
b64eb60f 330 } else {
3efcfd2d
MW
331 fprintf(h->fp, "%s: ", h->tv->test->name);
332 setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
b64eb60f 333 }
3efcfd2d 334 if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
b64eb60f
MW
335 fputc('\n', h->fp);
336}
337
3efcfd2d 338static void human_egroup(struct tvec_output *o)
b64eb60f
MW
339{
340 struct human_output *h = (struct human_output *)o;
3efcfd2d
MW
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;
b64eb60f
MW
344
345 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
3efcfd2d 346 else fprintf(h->fp, "%s:", h->tv->test->name);
b64eb60f 347
3efcfd2d
MW
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);
b64eb60f 352 } else {
3efcfd2d
MW
353 fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0);
354 report_skipped(h, skip);
b64eb60f 355 }
b64eb60f
MW
356 fputc('\n', h->fp);
357}
358
359static void human_btest(struct tvec_output *o)
360 { struct human_output *h = (struct human_output *)o; show_progress(h); }
361
362static void human_skip(struct tvec_output *o,
363 const char *excuse, va_list *ap)
364{
365 struct human_output *h = (struct human_output *)o;
3efcfd2d 366 struct tvec_state *tv = h->tv;
b64eb60f
MW
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
376static void human_fail(struct tvec_output *o,
377 const char *detail, va_list *ap)
378{
379 struct human_output *h = (struct human_output *)o;
3efcfd2d 380 struct tvec_state *tv = h->tv;
b64eb60f
MW
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
e63124bc
MW
390static void human_dumpreg(struct tvec_output *o,
391 unsigned disp, const union tvec_regval *rv,
392 const struct tvec_regdef *rd)
b64eb60f
MW
393{
394 struct human_output *h = (struct human_output *)o;
e63124bc 395 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
b64eb60f 396
e63124bc
MW
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);
b64eb60f
MW
406}
407
408static 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
e63124bc
MW
426static void human_bbench(struct tvec_output *o,
427 const char *ident, unsigned unit)
b64eb60f
MW
428{
429 struct human_output *h = (struct human_output *)o;
3efcfd2d 430 struct tvec_state *tv = h->tv;
b64eb60f
MW
431
432 clear_progress(h);
e63124bc 433 fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp);
b64eb60f
MW
434}
435
436static void human_ebench(struct tvec_output *o,
e63124bc 437 const char *ident, unsigned unit,
b64eb60f
MW
438 const struct bench_timing *tm)
439{
440 struct human_output *h = (struct human_output *)o;
e63124bc 441 tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp);
b64eb60f
MW
442}
443
3efcfd2d
MW
444static 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
b64eb60f
MW
464static 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
473static const struct tvec_outops human_ops = {
b64eb60f 474 human_bsession, human_esession,
3efcfd2d 475 human_bgroup, human_skipgroup, human_egroup,
e63124bc 476 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
b64eb60f 477 human_bbench, human_ebench,
3efcfd2d 478 human_report, human_report,
b64eb60f
MW
479 human_destroy
480};
481
482struct 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;
b64eb60f
MW
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
e63124bc 510 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
b64eb60f
MW
511 dstr_create(&h->scoreboard);
512 return (&h->_o);
513}
514
515/*----- Perl's `Test Anything Protocol' -----------------------------------*/
516
517struct tap_output {
518 struct tvec_output _o;
3efcfd2d 519 struct tvec_state *tv;
b64eb60f 520 FILE *fp;
e63124bc
MW
521 dstr d;
522 int maxlen;
b64eb60f
MW
523 unsigned f;
524#define TOF_FRESHLINE 1u
525};
526
e63124bc 527static int tap_writech(void *go, int ch)
b64eb60f 528{
e63124bc
MW
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
540static int tap_writem(void *go, const char *p, size_t sz)
541{
542 struct tap_output *t = go;
b64eb60f 543 const char *q, *l = p + sz;
e63124bc 544 size_t n;
b64eb60f 545
e63124bc
MW
546 if (p == l) return (0);
547 if (t->f&TOF_FRESHLINE)
548 if (fputs("## ", t->fp) < 0) return (-1);
b64eb60f
MW
549 for (;;) {
550 q = memchr(p, '\n', l - p); if (!q) break;
e63124bc
MW
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);
b64eb60f 555 }
e63124bc
MW
556 n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
557 t->f &= ~TOF_FRESHLINE; return (0);
558}
559
560static 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));
b64eb60f
MW
575}
576
e63124bc
MW
577static const struct gprintf_ops tap_printops =
578 { tap_writech, tap_writem, tap_nwritef };
579
3efcfd2d
MW
580static 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}
b64eb60f
MW
587
588static unsigned tap_grpix(struct tap_output *t)
589{
3efcfd2d 590 struct tvec_state *tv = t->tv;
b64eb60f
MW
591
592 return (tv->grps[TVOUT_WIN] +
593 tv->grps[TVOUT_LOSE] +
594 tv->grps[TVOUT_SKIP]);
595}
596
597static int tap_esession(struct tvec_output *o)
598{
599 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 600 struct tvec_state *tv = t->tv;
882a39c1
MW
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 }
b64eb60f
MW
608
609 fprintf(t->fp, "1..%u\n", tap_grpix(t));
3efcfd2d 610 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
611}
612
e63124bc
MW
613static void tap_bgroup(struct tvec_output *o)
614{
615 struct tap_output *t = (struct tap_output *)o;
3efcfd2d
MW
616 t->maxlen = register_maxnamelen(t->tv);
617}
618
619static 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);
e63124bc 627}
b64eb60f 628
3efcfd2d 629static void tap_egroup(struct tvec_output *o)
b64eb60f
MW
630{
631 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 632 struct tvec_state *tv = t->tv;
b64eb60f
MW
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) {
3efcfd2d 640 fprintf(t->fp, "not ok %u - %s: FAILED %u/%u",
b64eb60f
MW
641 grpix, tv->test->name, lose, win + lose);
642 if (skip) fprintf(t->fp, " (skipped %u)", skip);
643 } else {
3efcfd2d 644 fprintf(t->fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
b64eb60f
MW
645 if (skip) fprintf(t->fp, " (skipped %u)", skip);
646 }
647 fputc('\n', t->fp);
648}
649
b64eb60f
MW
650static void tap_btest(struct tvec_output *o) { ; }
651
652static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
653{
654 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 655 struct tvec_state *tv = t->tv;
b64eb60f
MW
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
663static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
664{
665 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 666 struct tvec_state *tv = t->tv;
b64eb60f
MW
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
e63124bc
MW
674static 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}
b64eb60f
MW
690
691static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
692
e63124bc
MW
693static void tap_bbench(struct tvec_output *o,
694 const char *ident, unsigned unit)
695 { ; }
b64eb60f
MW
696
697static void tap_ebench(struct tvec_output *o,
e63124bc 698 const char *ident, unsigned unit,
b64eb60f
MW
699 const struct bench_timing *tm)
700{
701 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 702 struct tvec_state *tv = t->tv;
b64eb60f 703
e63124bc
MW
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);
b64eb60f
MW
707}
708
3efcfd2d
MW
709static 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
717static 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
723static 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
b64eb60f
MW
729static 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);
e63124bc 734 dstr_destroy(&t->d);
b64eb60f
MW
735 xfree(t);
736}
737
738static const struct tvec_outops tap_ops = {
b64eb60f 739 tap_bsession, tap_esession,
3efcfd2d 740 tap_bgroup, tap_skipgroup, tap_egroup,
e63124bc 741 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
b64eb60f 742 tap_bbench, tap_ebench,
3efcfd2d 743 tap_error, tap_notice,
b64eb60f
MW
744 tap_destroy
745};
746
747struct tvec_output *tvec_tapoutput(FILE *fp)
748{
749 struct tap_output *t;
750
751 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
e63124bc
MW
752 dstr_create(&t->d);
753 t->f = 0; t->fp = fp;
b64eb60f
MW
754 return (&t->_o);
755}
756
757/*----- Default output ----------------------------------------------------*/
758
759struct 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 -------------------------------------------------*/