3 * Test vector output management
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
44 /*----- Common machinery --------------------------------------------------*/
46 enum { INPUT
, OUTPUT
, MATCH
, EXPECT
, FOUND
};
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*/
);
56 static const char *stdisp(unsigned disp
)
59 case MATCH
: return "final";
60 case EXPECT
: return "expected";
61 case FOUND
: return "actual";
66 static const char *regdisp(unsigned 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";
78 static int getenv_boolean(const char *var
, int dflt
)
85 else if (STRCMP(p
, ==, "y") || STRCMP(p
, ==, "yes") ||
86 STRCMP(p
, ==, "t") || STRCMP(p
, ==, "true") ||
87 STRCMP(p
, ==, "on") || STRCMP(p
, ==, "force") ||
90 else if (STRCMP(p
, ==, "n") || STRCMP(p
, ==, "no") ||
91 STRCMP(p
, ==, "nil") || STRCMP(p
, ==, "off") ||
95 moan("unexpected value `%s' for boolean environment variable `%s'",
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
); }
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
)
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");
115 static const struct mismatchfns basic_mismatchfns
=
116 { basic_report_status
, basic_report_register
};
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
); }
122 static void dump_outreg(const struct tvec_regdef
*rd
,
123 const struct mismatchfns
*fns
, struct tvec_state
*tv
)
125 const struct tvec_reg
126 *rin
= TVEC_REG(tv
, in
, rd
->i
), *rout
= TVEC_REG(tv
, out
, rd
->i
);
129 if (!(rout
->f
&TVRF_LIVE
)) {
130 if (!(rin
->f
&TVRF_LIVE
))
131 fns
->report_register(INPUT
, rin
, rd
, tv
);
133 fns
->report_register(FOUND
, rout
, rd
, tv
);
134 fns
->report_register(EXPECT
, rin
, rd
, tv
);
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
);
141 fns
->report_register(FOUND
, rout
, rd
, tv
);
142 fns
->report_register(EXPECT
, rin
, rd
, tv
);
148 static void mismatch(const struct mismatchfns
*fns
, struct tvec_state
*tv
)
150 const struct tvec_regdef
*rd
;
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
);
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
);
164 static void bench_summary(struct tvec_state
*tv
)
166 const struct tvec_regdef
*rd
;
170 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
172 if (f
&f_any
) tvec_write(tv
, ", ");
174 tvec_write(tv
, "%s = ", rd
->name
);
175 rd
->ty
->dump(&TVEC_REG(tv
, in
, rd
->i
)->v
, rd
, tv
, TVSF_COMPACT
);
181 static void normalize(double *x_inout
, const char **unit_out
, double scale
)
185 *const big
[] = { "k", "M", "G", "T", "P", "E", 0 },
186 *const little
[] = { "m", "ยต", "n", "p", "f", "a", 0 };
187 const char *const *u
;
191 for (u
= little
, x
*= scale
; x
< 1 && u
[1]; u
++, x
*= scale
);
193 for (u
= big
, x
/= scale
; x
>= scale
&& u
[1]; u
++, x
/= scale
);
197 *x_inout
= x
; *unit_out
= *u
;
200 static void bench_report(struct tvec_state
*tv
,
201 const struct bench_timing
*tm
)
203 const struct tvec_bench
*b
= tv
->test
->arg
.p
;
204 double n
= (double)tm
->n
*b
->niter
;
206 const char *u
, *what
, *whats
;
208 assert(tm
->f
&BTF_TIMEOK
);
211 tvec_write(tv
, " -- %.0f iterations ", n
);
212 what
= "op"; whats
= "ops"; scale
= 1000;
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;
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
);
224 tvec_write(tv
, ": ");
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
);
234 tvec_write(tv
, "\n");
237 /*----- Skeleton ----------------------------------------------------------*/
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)
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,
266 /*----- Human-readable output ---------------------------------------------*/
268 #define HAF_FGMASK 0x0f
269 #define HAF_FGSHIFT 0
270 #define HAF_BGMASK 0xf0
271 #define HAF_BGSHIFT 4
274 #define HAF_BOLD 1024u
275 #define HCOL_BLACK 0u
277 #define HCOL_GREEN 2u
278 #define HCOL_YELLOW 3u
280 #define HCOL_MAGENTA 5u
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)
287 #define HA_WIN (HFG(GREEN))
288 #define HA_LOSE (HFG(RED) | HAF_BOLD)
289 #define HA_SKIP (HFG(YELLOW))
291 struct human_output
{
292 struct tvec_output _o
;
298 #define HOF_DUPERR 2u
299 #define HOF_COLOUR 4u
300 #define HOF_PROGRESS 8u
303 static void set_colour(FILE *fp
, int *sep_inout
,
304 const char *norm
, const char *bright
,
307 if (*sep_inout
) putc(*sep_inout
, fp
);
308 fprintf(fp
, "%s%d", colour
&HCF_BRIGHT ? bright
: norm
, colour
&7);
312 static void setattr(struct human_output
*h
, unsigned attr
)
314 unsigned diff
= h
->attr
^ attr
;
317 if (!diff
|| !(h
->f
&HOF_COLOUR
)) return;
318 fputs("\x1b[", h
->fp
);
321 if (attr
&HAF_BOLD
) putc('1', h
->fp
);
322 else { putc('0', h
->fp
); diff
= h
->attr
; }
325 if (diff
&(HAF_FG
| HAF_FGMASK
)) {
327 set_colour(h
->fp
, &sep
, "3", "9", (attr
&HAF_FGMASK
) >> HAF_FGSHIFT
);
329 { if (sep
) putc(sep
, h
->fp
); fputs("39", h
->fp
); sep
= ';'; }
331 if (diff
&(HAF_BG
| HAF_BGMASK
)) {
333 set_colour(h
->fp
, &sep
, "4", "10", (attr
&HAF_BGMASK
) >> HAF_BGSHIFT
);
335 { if (sep
) putc(sep
, h
->fp
); fputs("49", h
->fp
); sep
= ';'; }
338 putc('m', h
->fp
); h
->attr
= attr
;
343 static void clear_progress(struct human_output
*h
)
347 if (h
->f
&HOF_PROGRESS
) {
348 n
= strlen(h
->_o
.tv
->test
->name
) + 2 + h
->scoreboard
.len
;
349 for (i
= 0; i
< n
; i
++) fputs("\b \b", h
->fp
);
350 h
->f
&= ~HOF_PROGRESS
;
354 static void write_scoreboard_char(struct human_output
*h
, int ch
)
357 case 'x': setattr(h
, HA_LOSE
); break;
358 case '_': setattr(h
, HA_SKIP
); break;
359 default: setattr(h
, 0); break;
361 putc(ch
, h
->fp
); setattr(h
, 0);
364 static void show_progress(struct human_output
*h
)
368 if ((h
->f
&HOF_TTY
) && !(h
->f
&HOF_PROGRESS
)) {
369 fprintf(h
->fp
, "%s: ", h
->_o
.tv
->test
->name
);
370 if (!(h
->f
&HOF_COLOUR
))
371 dstr_write(&h
->scoreboard
, h
->fp
);
372 else for (p
= h
->scoreboard
.buf
, l
= p
+ h
->scoreboard
.len
; p
< l
; p
++)
373 write_scoreboard_char(h
, *p
);
374 fflush(h
->fp
); h
->f
|= HOF_PROGRESS
;
378 static void report_location(struct human_output
*h
, FILE *fp
,
379 const char *file
, unsigned lno
)
384 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
386 if (fp
!= h
->fp
) f
|= f_flush
;
389 setattr(h
, HFG(CYAN
)); FLUSH(h
->fp
); fputs(file
, fp
); FLUSH(fp
);
390 setattr(h
, HFG(BLUE
)); FLUSH(h
->fp
); fputc(':', fp
); FLUSH(fp
);
391 setattr(h
, HFG(CYAN
)); FLUSH(h
->fp
); fprintf(fp
, "%u", lno
); FLUSH(fp
);
392 setattr(h
, HFG(BLUE
)); FLUSH(h
->fp
); fputc(':', fp
); FLUSH(fp
);
393 setattr(h
, 0); FLUSH(h
->fp
); fputc(' ', fp
);
400 static void human_report(struct human_output
*h
,
401 const char *msg
, va_list *ap
)
403 struct tvec_state
*tv
= h
->_o
.tv
;
405 fprintf(stderr
, "%s: ", QUIS
);
406 report_location(h
, stderr
, tv
->infile
, tv
->lno
);
407 vfprintf(stderr
, msg
, *ap
);
410 if (h
->f
&HOF_DUPERR
) {
411 report_location(h
, stderr
, tv
->infile
, tv
->lno
);
412 vfprintf(h
->fp
, msg
, *ap
);
417 static void human_error(struct tvec_output
*o
, const char *msg
, va_list *ap
)
419 struct human_output
*h
= (struct human_output
*)o
;
421 if (h
->f
&HOF_PROGRESS
) fputc('\n', h
->fp
);
422 human_report(h
, msg
, ap
);
425 static void human_notice(struct tvec_output
*o
, const char *msg
, va_list *ap
)
427 struct human_output
*h
= (struct human_output
*)o
;
428 clear_progress(h
); human_report(h
, msg
, ap
); show_progress(h
);
431 static void human_write(struct tvec_output
*o
, const char *p
, size_t sz
)
433 struct human_output
*h
= (struct human_output
*)o
;
434 fwrite(p
, 1, sz
, h
->fp
);
437 static void human_bsession(struct tvec_output
*o
) { ; }
439 static void report_skipped(struct human_output
*h
, unsigned n
)
442 fprintf(h
->fp
, " (%u ", n
);
443 setattr(h
, HA_SKIP
); fputs("skipped", h
->fp
); setattr(h
, 0);
448 static int human_esession(struct tvec_output
*o
)
450 struct human_output
*h
= (struct human_output
*)o
;
451 struct tvec_state
*tv
= h
->_o
.tv
;
453 all_win
= tv
->all
[TVOUT_WIN
], grps_win
= tv
->grps
[TVOUT_WIN
],
454 all_lose
= tv
->all
[TVOUT_LOSE
], grps_lose
= tv
->grps
[TVOUT_LOSE
],
455 all_skip
= tv
->all
[TVOUT_SKIP
], grps_skip
= tv
->grps
[TVOUT_SKIP
],
456 all_run
= all_win
+ all_lose
, grps_run
= grps_win
+ grps_lose
;
459 setattr(h
, HA_WIN
); fputs("PASSED", h
->fp
); setattr(h
, 0);
460 fprintf(h
->fp
, " %s%u %s",
461 !(all_skip
|| grps_skip
) ?
"all " : "",
462 all_win
, all_win
== 1 ?
"test" : "tests");
463 report_skipped(h
, all_skip
);
464 fprintf(h
->fp
, " in %u %s",
465 grps_win
, grps_win
== 1 ?
"group" : "groups");
466 report_skipped(h
, grps_skip
);
468 setattr(h
, HA_LOSE
); fputs("FAILED", h
->fp
); setattr(h
, 0);
469 fprintf(h
->fp
, " %u out of %u %s",
470 all_lose
, all_run
, all_run
== 1 ?
"test" : "tests");
471 report_skipped(h
, all_skip
);
472 fprintf(h
->fp
, " in %u out of %u %s",
473 grps_lose
, grps_run
, grps_run
== 1 ?
"group" : "groups");
474 report_skipped(h
, grps_skip
);
478 return (tv
->all
[TVOUT_LOSE
] ?
1 : 0);
481 static void human_bgroup(struct tvec_output
*o
)
483 struct human_output
*h
= (struct human_output
*)o
;
484 dstr_reset(&h
->scoreboard
); show_progress(h
);
487 static void human_grpsumm(struct human_output
*h
, unsigned outcome
)
489 struct tvec_state
*tv
= h
->_o
.tv
;
490 unsigned win
= tv
->curr
[TVOUT_WIN
], lose
= tv
->curr
[TVOUT_LOSE
],
491 skip
= tv
->curr
[TVOUT_SKIP
], run
= win
+ lose
;
494 assert(outcome
== TVOUT_LOSE
);
495 fprintf(h
->fp
, " %u/%u ", lose
, run
);
496 setattr(h
, HA_LOSE
); fputs("FAILED", h
->fp
); setattr(h
, 0);
497 report_skipped(h
, skip
);
499 assert(outcome
== TVOUT_WIN
);
500 fputc(' ', h
->fp
); setattr(h
, HA_WIN
); fputs("ok", h
->fp
); setattr(h
, 0);
501 report_skipped(h
, skip
);
506 static void human_egroup(struct tvec_output
*o
, unsigned outcome
)
508 struct human_output
*h
= (struct human_output
*)o
;
510 if (h
->f
&HOF_TTY
) h
->f
&= ~HOF_PROGRESS
;
511 else fprintf(h
->fp
, "%s:", h
->_o
.tv
->test
->name
);
512 human_grpsumm(h
, outcome
);
515 static void human_skipgroup(struct tvec_output
*o
,
516 const char *excuse
, va_list *ap
)
518 struct human_output
*h
= (struct human_output
*)o
;
520 if (!(~h
->f
&(HOF_TTY
| HOF_PROGRESS
))) {
521 h
->f
&= ~HOF_PROGRESS
;
522 setattr(h
, HA_SKIP
); fputs("skipped", h
->fp
); setattr(h
, 0);
524 fprintf(h
->fp
, "%s: ", h
->_o
.tv
->test
->name
);
525 setattr(h
, HA_SKIP
); fputs("skipped", h
->fp
); setattr(h
, 0);
527 if (excuse
) { fputs(": ", h
->fp
); vfprintf(h
->fp
, excuse
, *ap
); }
531 static void human_btest(struct tvec_output
*o
)
532 { struct human_output
*h
= (struct human_output
*)o
; show_progress(h
); }
534 static void human_skip(struct tvec_output
*o
,
535 const char *excuse
, va_list *ap
)
537 struct human_output
*h
= (struct human_output
*)o
;
538 struct tvec_state
*tv
= h
->_o
.tv
;
541 report_location(h
, h
->fp
, tv
->infile
, tv
->test_lno
);
542 fprintf(h
->fp
, "`%s' ", tv
->test
->name
);
543 setattr(h
, HA_SKIP
); fputs("skipped", h
->fp
); setattr(h
, 0);
544 if (excuse
) { fputs(": ", h
->fp
); vfprintf(h
->fp
, excuse
, *ap
); }
548 static void human_fail(struct tvec_output
*o
,
549 const char *detail
, va_list *ap
)
551 struct human_output
*h
= (struct human_output
*)o
;
552 struct tvec_state
*tv
= h
->_o
.tv
;
555 report_location(h
, h
->fp
, tv
->infile
, tv
->test_lno
);
556 fprintf(h
->fp
, "`%s' ", tv
->test
->name
);
557 setattr(h
, HA_LOSE
); fputs("FAILED", h
->fp
); setattr(h
, 0);
558 if (detail
) { fputs(": ", h
->fp
); vfprintf(h
->fp
, detail
, *ap
); }
562 static void set_dispattr(struct human_output
*h
, unsigned disp
)
565 case EXPECT
: setattr(h
, HFG(GREEN
)); break;
566 case FOUND
: setattr(h
, HFG(RED
)); break;
567 default: setattr(h
, 0); break;
571 static void human_report_status(unsigned disp
, int st
, struct tvec_state
*tv
)
573 struct human_output
*h
= (struct human_output
*)tv
->output
;
575 fprintf(h
->fp
, " %8s status = ", stdisp(disp
));
576 set_dispattr(h
, disp
); fprintf(h
->fp
, "`%c'", st
); setattr(h
, 0);
580 static void human_report_register(unsigned disp
,
581 const struct tvec_reg
*r
,
582 const struct tvec_regdef
*rd
,
583 struct tvec_state
*tv
)
585 struct human_output
*h
= (struct human_output
*)tv
->output
;
587 fprintf(h
->fp
, " %8s %s = ", regdisp(disp
), rd
->name
);
588 if (!(r
->f
&TVRF_LIVE
))
589 tvec_write(tv
, "#<unset>");
591 set_dispattr(h
, disp
);
592 rd
->ty
->dump(&r
->v
, rd
, tv
, 0);
595 tvec_write(tv
, "\n");
598 static const struct mismatchfns human_mismatchfns
=
599 { human_report_status
, human_report_register
};
601 static void human_mismatch(struct tvec_output
*o
)
603 struct human_output
*h
= (struct human_output
*)o
;
605 if (h
->f
&HOF_COLOUR
) mismatch(&human_mismatchfns
, h
->_o
.tv
);
606 else mismatch(&basic_mismatchfns
, h
->_o
.tv
);
609 static void human_etest(struct tvec_output
*o
, unsigned outcome
)
611 struct human_output
*h
= (struct human_output
*)o
;
617 case TVOUT_WIN
: ch
= '.'; break;
618 case TVOUT_LOSE
: ch
= 'x'; break;
619 case TVOUT_SKIP
: ch
= '_'; break;
622 dstr_putc(&h
->scoreboard
, ch
);
623 write_scoreboard_char(h
, ch
); fflush(h
->fp
);
627 static void human_bbench(struct tvec_output
*o
)
629 struct human_output
*h
= (struct human_output
*)o
;
630 struct tvec_state
*tv
= h
->_o
.tv
;
633 fprintf(h
->fp
, "%s: ", tv
->test
->name
);
634 bench_summary(tv
); fflush(h
->fp
);
637 static void human_ebench(struct tvec_output
*o
,
638 const struct bench_timing
*tm
)
640 struct human_output
*h
= (struct human_output
*)o
;
641 bench_report(h
->_o
.tv
, tm
);
644 static void human_destroy(struct tvec_output
*o
)
646 struct human_output
*h
= (struct human_output
*)o
;
648 if (h
->f
&HOF_DUPERR
) fclose(h
->fp
);
649 dstr_destroy(&h
->scoreboard
);
653 static const struct tvec_outops human_ops
= {
654 human_error
, human_notice
, human_write
,
655 human_bsession
, human_esession
,
656 human_bgroup
, human_egroup
, human_skipgroup
,
657 human_btest
, human_skip
, human_fail
, human_mismatch
, human_etest
,
658 human_bbench
, human_ebench
,
662 struct tvec_output
*tvec_humanoutput(FILE *fp
)
664 struct human_output
*h
;
667 h
= xmalloc(sizeof(*h
)); h
->_o
.ops
= &human_ops
;
668 h
->f
= 0; h
->attr
= 0;
671 if (fp
!= stdout
&& fp
!= stderr
) h
->f
|= HOF_DUPERR
;
673 switch (getenv_boolean("TVEC_TTY", -1)) {
674 case 1: h
->f
|= HOF_TTY
; break;
677 if (isatty(fileno(fp
))) h
->f
|= HOF_TTY
;
680 switch (getenv_boolean("TVEC_COLOUR", -1)) {
681 case 1: h
->f
|= HOF_COLOUR
; break;
686 if (p
&& STRCMP(p
, !=, "dumb")) h
->f
|= HOF_COLOUR
;
691 dstr_create(&h
->scoreboard
);
695 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
698 struct tvec_output _o
;
701 #define TOF_FRESHLINE 1u
704 static void tap_report(struct tap_output
*t
, const char *msg
, va_list *ap
)
706 struct tvec_state
*tv
= t
->_o
.tv
;
708 if (tv
->infile
) fprintf(t
->fp
, "%s:%u: ", tv
->infile
, tv
->lno
);
709 vfprintf(t
->fp
, msg
, *ap
); fputc('\n', t
->fp
);
712 static void tap_error(struct tvec_output
*o
, const char *msg
, va_list *ap
)
714 struct tap_output
*t
= (struct tap_output
*)o
;
715 fputs("Bail out! ", t
->fp
); tap_report(t
, msg
, ap
);
718 static void tap_notice(struct tvec_output
*o
, const char *msg
, va_list *ap
)
720 struct tap_output
*t
= (struct tap_output
*)o
;
721 fputs("## ", t
->fp
); tap_report(t
, msg
, ap
);
724 static void tap_write(struct tvec_output
*o
, const char *p
, size_t sz
)
726 struct tap_output
*t
= (struct tap_output
*)o
;
727 const char *q
, *l
= p
+ sz
;
730 if (t
->f
&TOF_FRESHLINE
) fputs("## ", t
->fp
);
732 q
= memchr(p
, '\n', l
- p
); if (!q
) break;
733 fwrite(p
, 1, q
+ 1 - p
, t
->fp
); p
= q
+ 1;
734 if (p
== l
) { t
->f
|= TOF_FRESHLINE
; return; }
737 fwrite(p
, 1, l
- p
, t
->fp
); t
->f
&= ~TOF_FRESHLINE
;
740 static void tap_bsession(struct tvec_output
*o
) { ; }
742 static unsigned tap_grpix(struct tap_output
*t
)
744 struct tvec_state
*tv
= t
->_o
.tv
;
746 return (tv
->grps
[TVOUT_WIN
] +
747 tv
->grps
[TVOUT_LOSE
] +
748 tv
->grps
[TVOUT_SKIP
]);
751 static int tap_esession(struct tvec_output
*o
)
753 struct tap_output
*t
= (struct tap_output
*)o
;
755 fprintf(t
->fp
, "1..%u\n", tap_grpix(t
));
759 static void tap_bgroup(struct tvec_output
*o
) { ; }
761 static void tap_egroup(struct tvec_output
*o
, unsigned outcome
)
763 struct tap_output
*t
= (struct tap_output
*)o
;
764 struct tvec_state
*tv
= t
->_o
.tv
;
766 grpix
= tap_grpix(t
),
767 win
= tv
->curr
[TVOUT_WIN
],
768 lose
= tv
->curr
[TVOUT_LOSE
],
769 skip
= tv
->curr
[TVOUT_SKIP
];
772 assert(outcome
== TVOUT_LOSE
);
773 fprintf(t
->fp
, "not ok %u %s: FAILED %u/%u",
774 grpix
, tv
->test
->name
, lose
, win
+ lose
);
775 if (skip
) fprintf(t
->fp
, " (skipped %u)", skip
);
777 assert(outcome
== TVOUT_WIN
);
778 fprintf(t
->fp
, "ok %u %s: passed %u", grpix
, tv
->test
->name
, win
);
779 if (skip
) fprintf(t
->fp
, " (skipped %u)", skip
);
784 static void tap_skipgroup(struct tvec_output
*o
,
785 const char *excuse
, va_list *ap
)
787 struct tap_output
*t
= (struct tap_output
*)o
;
789 fprintf(t
->fp
, "ok %u %s # SKIP", tap_grpix(t
), t
->_o
.tv
->test
->name
);
791 { fputc(' ', t
->fp
); vfprintf(t
->fp
, excuse
, *ap
); }
795 static void tap_btest(struct tvec_output
*o
) { ; }
797 static void tap_skip(struct tvec_output
*o
, const char *excuse
, va_list *ap
)
799 struct tap_output
*t
= (struct tap_output
*)o
;
800 struct tvec_state
*tv
= t
->_o
.tv
;
802 fprintf(t
->fp
, "## %s:%u: `%s' skipped",
803 tv
->infile
, tv
->test_lno
, tv
->test
->name
);
804 if (excuse
) { fputs(": ", t
->fp
); vfprintf(t
->fp
, excuse
, *ap
); }
808 static void tap_fail(struct tvec_output
*o
, const char *detail
, va_list *ap
)
810 struct tap_output
*t
= (struct tap_output
*)o
;
811 struct tvec_state
*tv
= t
->_o
.tv
;
813 fprintf(t
->fp
, "## %s:%u: `%s' FAILED",
814 tv
->infile
, tv
->test_lno
, tv
->test
->name
);
815 if (detail
) { fputs(": ", t
->fp
); vfprintf(t
->fp
, detail
, *ap
); }
819 static void tap_mismatch(struct tvec_output
*o
)
820 { mismatch(&basic_mismatchfns
, o
->tv
); }
822 static void tap_etest(struct tvec_output
*o
, unsigned outcome
) { ; }
824 static void tap_bbench(struct tvec_output
*o
) { ; }
826 static void tap_ebench(struct tvec_output
*o
,
827 const struct bench_timing
*tm
)
829 struct tap_output
*t
= (struct tap_output
*)o
;
830 struct tvec_state
*tv
= t
->_o
.tv
;
832 tvec_write(tv
, "%s: ", tv
->test
->name
); bench_summary(tv
);
833 bench_report(tv
, tm
);
836 static void tap_destroy(struct tvec_output
*o
)
838 struct tap_output
*t
= (struct tap_output
*)o
;
840 if (t
->fp
!= stdout
&& t
->fp
!= stderr
) fclose(t
->fp
);
844 static const struct tvec_outops tap_ops
= {
845 tap_error
, tap_notice
, tap_write
,
846 tap_bsession
, tap_esession
,
847 tap_bgroup
, tap_egroup
, tap_skipgroup
,
848 tap_btest
, tap_skip
, tap_fail
, tap_mismatch
, tap_etest
,
849 tap_bbench
, tap_ebench
,
853 struct tvec_output
*tvec_tapoutput(FILE *fp
)
855 struct tap_output
*t
;
857 t
= xmalloc(sizeof(*t
)); t
->_o
.ops
= &tap_ops
;
858 t
->f
= TOF_FRESHLINE
;
863 /*----- Default output ----------------------------------------------------*/
865 struct tvec_output
*tvec_dfltout(FILE *fp
)
867 int ttyp
= getenv_boolean("TVEC_TTY", -1);
869 if (ttyp
== -1) ttyp
= isatty(fileno(fp
));
870 if (ttyp
) return (tvec_humanoutput(fp
));
871 else return (tvec_tapoutput(fp
));
874 /*----- That's all, folks -------------------------------------------------*/