@@@ more mess
[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/*
96static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
97static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
b64eb60f
MW
98static void ..._bsession(struct tvec_output *o)
99static int ..._esession(struct tvec_output *o)
100static void ..._bgroup(struct tvec_output *o)
101static void ..._egroup(struct tvec_output *o, unsigned outcome)
102static void ..._skipgroup(struct tvec_output *o,
103 const char *excuse, va_list *ap)
104static void ..._btest(struct tvec_output *o)
e63124bc 105static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
b64eb60f 106static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
e63124bc
MW
107static void ..._dumpreg(struct tvec_output *o, unsigned disp,
108 union tvec_regval *rv, const struct tvec_regdef *rd)
b64eb60f 109static void ..._etest(struct tvec_output *o, unsigned outcome)
e63124bc
MW
110static void ..._bbench(struct tvec_output *o,
111 const char *ident, unsigned unit)
112static void ..._ebench(struct tvec_output *o,
113 const char *ident, unsigned unit,
114 const struct tvec_timing *t)
b64eb60f
MW
115static void ..._destroy(struct tvec_output *o)
116
117static const struct tvec_outops ..._ops = {
e63124bc 118 ..._error, ..._notice,
b64eb60f
MW
119 ..._bsession, ..._esession,
120 ..._bgroup, ..._egroup, ..._skip,
e63124bc 121 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
b64eb60f
MW
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))
882a39c1 150#define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
b64eb60f
MW
151
152struct human_output {
153 struct tvec_output _o;
154 FILE *fp;
155 dstr scoreboard;
156 unsigned attr;
e63124bc 157 int maxlen;
b64eb60f
MW
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
165static 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
174static 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
205static 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
216static 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
226static void show_progress(struct human_output *h)
227{
882a39c1 228 struct tvec_state *tv = h->_o.tv;
b64eb60f
MW
229 const char *p, *l;
230
882a39c1 231 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
b64eb60f
MW
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
241static 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
882a39c1 263static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
b64eb60f 264{
882a39c1 265 struct human_output *h = (struct human_output *)o;
b64eb60f 266 struct tvec_state *tv = h->_o.tv;
e63124bc
MW
267 dstr d = DSTR_INIT;
268
269 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
b64eb60f 270
882a39c1 271 clear_progress(h); fflush(h->fp);
b64eb60f
MW
272 fprintf(stderr, "%s: ", QUIS);
273 report_location(h, stderr, tv->infile, tv->lno);
e63124bc 274 fwrite(d.buf, 1, d.len, stderr);
b64eb60f
MW
275
276 if (h->f&HOF_DUPERR) {
e63124bc
MW
277 report_location(h, h->fp, tv->infile, tv->lno);
278 fwrite(d.buf, 1, d.len, h->fp);
b64eb60f 279 }
882a39c1 280 show_progress(h);
b64eb60f
MW
281}
282
b64eb60f
MW
283static void human_bsession(struct tvec_output *o) { ; }
284
285static 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
294static 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
882a39c1
MW
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);
b64eb60f
MW
330}
331
332static void human_bgroup(struct tvec_output *o)
333{
334 struct human_output *h = (struct human_output *)o;
e63124bc
MW
335
336 h->maxlen = register_maxnamelen(h->_o.tv);
b64eb60f
MW
337 dstr_reset(&h->scoreboard); show_progress(h);
338}
339
340static 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
359static 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
368static 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
384static void human_btest(struct tvec_output *o)
385 { struct human_output *h = (struct human_output *)o; show_progress(h); }
386
387static 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
401static 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
e63124bc
MW
415static void human_dumpreg(struct tvec_output *o,
416 unsigned disp, const union tvec_regval *rv,
417 const struct tvec_regdef *rd)
b64eb60f
MW
418{
419 struct human_output *h = (struct human_output *)o;
e63124bc 420 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
b64eb60f 421
e63124bc
MW
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);
b64eb60f
MW
431}
432
433static 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
e63124bc
MW
451static void human_bbench(struct tvec_output *o,
452 const char *ident, unsigned unit)
b64eb60f
MW
453{
454 struct human_output *h = (struct human_output *)o;
455 struct tvec_state *tv = h->_o.tv;
456
457 clear_progress(h);
e63124bc 458 fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp);
b64eb60f
MW
459}
460
461static void human_ebench(struct tvec_output *o,
e63124bc 462 const char *ident, unsigned unit,
b64eb60f
MW
463 const struct bench_timing *tm)
464{
465 struct human_output *h = (struct human_output *)o;
e63124bc 466 tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp);
b64eb60f
MW
467}
468
469static 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
478static const struct tvec_outops human_ops = {
e63124bc 479 human_report, human_report,
b64eb60f
MW
480 human_bsession, human_esession,
481 human_bgroup, human_egroup, human_skipgroup,
e63124bc 482 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
b64eb60f
MW
483 human_bbench, human_ebench,
484 human_destroy
485};
486
487struct 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;
b64eb60f
MW
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
e63124bc 515 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
b64eb60f
MW
516 dstr_create(&h->scoreboard);
517 return (&h->_o);
518}
519
520/*----- Perl's `Test Anything Protocol' -----------------------------------*/
521
522struct tap_output {
523 struct tvec_output _o;
524 FILE *fp;
e63124bc
MW
525 dstr d;
526 int maxlen;
b64eb60f
MW
527 unsigned f;
528#define TOF_FRESHLINE 1u
529};
530
531static 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
539static 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
545static 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
e63124bc 551static int tap_writech(void *go, int ch)
b64eb60f 552{
e63124bc
MW
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
564static int tap_writem(void *go, const char *p, size_t sz)
565{
566 struct tap_output *t = go;
b64eb60f 567 const char *q, *l = p + sz;
e63124bc 568 size_t n;
b64eb60f 569
e63124bc
MW
570 if (p == l) return (0);
571 if (t->f&TOF_FRESHLINE)
572 if (fputs("## ", t->fp) < 0) return (-1);
b64eb60f
MW
573 for (;;) {
574 q = memchr(p, '\n', l - p); if (!q) break;
e63124bc
MW
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);
b64eb60f 579 }
e63124bc
MW
580 n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
581 t->f &= ~TOF_FRESHLINE; return (0);
582}
583
584static 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));
b64eb60f
MW
599}
600
e63124bc
MW
601static const struct gprintf_ops tap_printops =
602 { tap_writech, tap_writem, tap_nwritef };
603
b64eb60f
MW
604static void tap_bsession(struct tvec_output *o) { ; }
605
606static 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
615static int tap_esession(struct tvec_output *o)
616{
617 struct tap_output *t = (struct tap_output *)o;
882a39c1
MW
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 }
b64eb60f
MW
626
627 fprintf(t->fp, "1..%u\n", tap_grpix(t));
882a39c1 628 return (tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
629}
630
e63124bc
MW
631static 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}
b64eb60f
MW
636
637static 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
660static 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
671static void tap_btest(struct tvec_output *o) { ; }
672
673static 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
684static 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
e63124bc
MW
695static 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}
b64eb60f
MW
711
712static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
713
e63124bc
MW
714static void tap_bbench(struct tvec_output *o,
715 const char *ident, unsigned unit)
716 { ; }
b64eb60f
MW
717
718static void tap_ebench(struct tvec_output *o,
e63124bc 719 const char *ident, unsigned unit,
b64eb60f
MW
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
e63124bc
MW
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);
b64eb60f
MW
728}
729
730static 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);
e63124bc 735 dstr_destroy(&t->d);
b64eb60f
MW
736 xfree(t);
737}
738
739static const struct tvec_outops tap_ops = {
e63124bc 740 tap_error, tap_notice,
b64eb60f
MW
741 tap_bsession, tap_esession,
742 tap_bgroup, tap_egroup, tap_skipgroup,
e63124bc 743 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
b64eb60f
MW
744 tap_bbench, tap_ebench,
745 tap_destroy
746};
747
748struct tvec_output *tvec_tapoutput(FILE *fp)
749{
750 struct tap_output *t;
751
752 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
e63124bc
MW
753 dstr_create(&t->d);
754 t->f = 0; t->fp = fp;
b64eb60f
MW
755 return (&t->_o);
756}
757
758/*----- Default output ----------------------------------------------------*/
759
760struct 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 -------------------------------------------------*/