@@@ adjust bench timings
[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
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
46enum { INPUT, OUTPUT, MATCH, EXPECT, FOUND };
47struct 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
56static 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
66static 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
78static 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
101static void basic_report_status(unsigned disp, int st, struct tvec_state *tv)
102 { tvec_write(tv, " %8s status = `%c'\n", stdisp(disp), st); }
103
104static 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
115static const struct mismatchfns basic_mismatchfns =
116 { basic_report_status, basic_report_register };
117
118static 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
122static 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
148static 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
164static 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
181static 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
200static 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/*
239static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
240static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
241static void ..._write(struct tvec_output *o, const char *p, size_t sz)
242static void ..._bsession(struct tvec_output *o)
243static int ..._esession(struct tvec_output *o)
244static void ..._bgroup(struct tvec_output *o)
245static void ..._egroup(struct tvec_output *o, unsigned outcome)
246static void ..._skipgroup(struct tvec_output *o,
247 const char *excuse, va_list *ap)
248static void ..._btest(struct tvec_output *o)
249static void ..._skip(struct tvec_output *o, const char *detail, va_list *ap)
250static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
251static void ..._mismatch(struct tvec_output *o)
252static void ..._etest(struct tvec_output *o, unsigned outcome)
253static void ..._bbench(struct tvec_output *o)
254static void ..._ebench(struct tvec_output *o, const struct tvec_timing *t)
255static void ..._destroy(struct tvec_output *o)
256
257static 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))
882a39c1 290#define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
b64eb60f
MW
291
292struct 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
304static 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
313static 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
344static 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
355static 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
365static void show_progress(struct human_output *h)
366{
882a39c1 367 struct tvec_state *tv = h->_o.tv;
b64eb60f
MW
368 const char *p, *l;
369
882a39c1 370 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
b64eb60f
MW
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
380static 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
882a39c1 402static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
b64eb60f 403{
882a39c1 404 struct human_output *h = (struct human_output *)o;
b64eb60f
MW
405 struct tvec_state *tv = h->_o.tv;
406
882a39c1 407 clear_progress(h); fflush(h->fp);
b64eb60f
MW
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 }
882a39c1 418 show_progress(h);
b64eb60f
MW
419}
420
421static 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
427static void human_bsession(struct tvec_output *o) { ; }
428
429static 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
438static 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
882a39c1
MW
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);
b64eb60f
MW
474}
475
476static 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
482static 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
501static 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
510static 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
526static void human_btest(struct tvec_output *o)
527 { struct human_output *h = (struct human_output *)o; show_progress(h); }
528
529static 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
543static 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
557static 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
566static 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
575static 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
593static const struct mismatchfns human_mismatchfns =
594 { human_report_status, human_report_register };
595
596static 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
604static 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
622static 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
632static 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
639static 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
648static const struct tvec_outops human_ops = {
882a39c1 649 human_report, human_report, human_write,
b64eb60f
MW
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
657struct 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
692struct tap_output {
693 struct tvec_output _o;
694 FILE *fp;
695 unsigned f;
696#define TOF_FRESHLINE 1u
697};
698
699static 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
707static 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
713static 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
719static 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
735static void tap_bsession(struct tvec_output *o) { ; }
736
737static 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
746static int tap_esession(struct tvec_output *o)
747{
748 struct tap_output *t = (struct tap_output *)o;
882a39c1
MW
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 }
b64eb60f
MW
757
758 fprintf(t->fp, "1..%u\n", tap_grpix(t));
882a39c1 759 return (tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
760}
761
762static void tap_bgroup(struct tvec_output *o) { ; }
763
764static 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
787static 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
798static void tap_btest(struct tvec_output *o) { ; }
799
800static 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
811static 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
822static void tap_mismatch(struct tvec_output *o)
823 { mismatch(&basic_mismatchfns, o->tv); }
824
825static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
826
827static void tap_bbench(struct tvec_output *o) { ; }
828
829static 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
839static 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
847static 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
856struct 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
868struct 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 -------------------------------------------------*/