Commit | Line | Data |
---|---|---|
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 |
48 | static 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 | ||
60 | static 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 | 84 | static 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 | 96 | static void ..._bsession(struct tvec_output *o, struct tvec_state *tv) |
b64eb60f MW |
97 | static int ..._esession(struct tvec_output *o) |
98 | static void ..._bgroup(struct tvec_output *o) | |
b64eb60f MW |
99 | static void ..._skipgroup(struct tvec_output *o, |
100 | const char *excuse, va_list *ap) | |
3efcfd2d | 101 | static void ..._egroup(struct tvec_output *o) |
b64eb60f | 102 | static void ..._btest(struct tvec_output *o) |
e63124bc | 103 | static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap) |
b64eb60f | 104 | static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap) |
e63124bc MW |
105 | static void ..._dumpreg(struct tvec_output *o, unsigned disp, |
106 | union tvec_regval *rv, const struct tvec_regdef *rd) | |
b64eb60f | 107 | static void ..._etest(struct tvec_output *o, unsigned outcome) |
e63124bc MW |
108 | static void ..._bbench(struct tvec_output *o, |
109 | const char *ident, unsigned unit) | |
110 | static void ..._ebench(struct tvec_output *o, | |
111 | const char *ident, unsigned unit, | |
112 | const struct tvec_timing *t) | |
3efcfd2d MW |
113 | static void ..._error(struct tvec_output *o, const char *msg, va_list *ap) |
114 | static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap) | |
b64eb60f MW |
115 | static void ..._destroy(struct tvec_output *o) |
116 | ||
117 | static 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 | |
152 | struct 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 | ||
166 | static 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 | ||
175 | static 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 | ||
206 | static 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 | ||
217 | static 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 | ||
227 | static 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 | ||
242 | static 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 |
264 | static 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 | |
267 | static 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 | ||
276 | static 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 | ||
314 | static 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 |
322 | static 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 | 338 | static 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 | ||
359 | static void human_btest(struct tvec_output *o) | |
360 | { struct human_output *h = (struct human_output *)o; show_progress(h); } | |
361 | ||
362 | static 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 | ||
376 | static 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 |
390 | static 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 | ||
408 | static 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 |
426 | static 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 | ||
436 | static 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 |
444 | static 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 |
464 | static 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 | ||
473 | static 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 | ||
482 | struct 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 | ||
517 | struct 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 | 527 | static 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 | ||
540 | static 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 | ||
560 | static 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 |
577 | static const struct gprintf_ops tap_printops = |
578 | { tap_writech, tap_writem, tap_nwritef }; | |
579 | ||
3efcfd2d MW |
580 | static 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 | |
588 | static 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 | ||
597 | static 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 |
613 | static 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 | ||
619 | static 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 | 629 | static 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 |
650 | static void tap_btest(struct tvec_output *o) { ; } |
651 | ||
652 | static 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 | ||
663 | static 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 |
674 | static 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 | |
691 | static void tap_etest(struct tvec_output *o, unsigned outcome) { ; } | |
692 | ||
e63124bc MW |
693 | static void tap_bbench(struct tvec_output *o, |
694 | const char *ident, unsigned unit) | |
695 | { ; } | |
b64eb60f MW |
696 | |
697 | static 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 |
709 | static 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 | ||
717 | static 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 | ||
723 | static 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 |
729 | static 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 | ||
738 | static 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 | ||
747 | struct 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 | ||
759 | struct 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 -------------------------------------------------*/ |