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 | 32 | #include <assert.h> |
67b5031e | 33 | #include <ctype.h> |
b64eb60f MW |
34 | #include <stdarg.h> |
35 | #include <stdio.h> | |
36 | #include <string.h> | |
37 | ||
38 | #include <unistd.h> | |
39 | ||
40 | #include "alloc.h" | |
41 | #include "bench.h" | |
42 | #include "dstr.h" | |
67b5031e | 43 | #include "macros.h" |
b64eb60f MW |
44 | #include "quis.h" |
45 | #include "report.h" | |
46 | #include "tvec.h" | |
47 | ||
48 | /*----- Common machinery --------------------------------------------------*/ | |
49 | ||
67b5031e MW |
50 | /* --- @regdisp@ --- * |
51 | * | |
52 | * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code | |
53 | * | |
54 | * Returns: A human-readable adjective describing the register | |
55 | * disposition. | |
56 | */ | |
57 | ||
b64eb60f MW |
58 | static const char *regdisp(unsigned disp) |
59 | { | |
60 | switch (disp) { | |
e63124bc MW |
61 | case TVRD_INPUT: return "input"; |
62 | case TVRD_OUTPUT: return "output"; | |
63 | case TVRD_MATCH: return "matched"; | |
64 | case TVRD_EXPECT: return "expected"; | |
65 | case TVRD_FOUND: return "found"; | |
b64eb60f MW |
66 | default: abort(); |
67 | } | |
68 | } | |
69 | ||
67b5031e MW |
70 | /* --- @getenv_boolean@ --- * |
71 | * | |
72 | * Arguments: @const char *var@ = environment variable name | |
73 | * @int dflt@ = default value | |
74 | * | |
75 | * Returns: @0@ if the variable is set to something falseish, @1@ if it's | |
76 | * set to something truish, or @dflt@ otherwise. | |
77 | */ | |
78 | ||
b64eb60f MW |
79 | static int getenv_boolean(const char *var, int dflt) |
80 | { | |
81 | const char *p; | |
82 | ||
83 | p = getenv(var); | |
84 | if (!p) | |
85 | return (dflt); | |
86 | else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") || | |
87 | STRCMP(p, ==, "t") || STRCMP(p, ==, "true") || | |
88 | STRCMP(p, ==, "on") || STRCMP(p, ==, "force") || | |
89 | STRCMP(p, ==, "1")) | |
90 | return (1); | |
91 | else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") || | |
e63124bc | 92 | STRCMP(p, ==, "f") || STRCMP(p, ==, "false") || |
b64eb60f MW |
93 | STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") || |
94 | STRCMP(p, ==, "0")) | |
95 | return (0); | |
96 | else { | |
67b5031e | 97 | moan("ignoring unexpected value `%s' for environment variable `%s'", |
b64eb60f MW |
98 | var, p); |
99 | return (dflt); | |
100 | } | |
101 | } | |
102 | ||
67b5031e MW |
103 | /* --- @register_maxnamelen@ --- * |
104 | * | |
105 | * Arguments: @const struct tvec_state *tv@ = test vector state | |
106 | * | |
107 | * Returns: The maximum length of a register name in the current test. | |
108 | */ | |
109 | ||
e63124bc | 110 | static int register_maxnamelen(const struct tvec_state *tv) |
b64eb60f MW |
111 | { |
112 | const struct tvec_regdef *rd; | |
e63124bc | 113 | int maxlen = 6, n; |
b64eb60f MW |
114 | |
115 | for (rd = tv->test->regs; rd->name; rd++) | |
e63124bc MW |
116 | { n = strlen(rd->name); if (n > maxlen) maxlen = n; } |
117 | return (maxlen); | |
b64eb60f MW |
118 | } |
119 | ||
67b5031e MW |
120 | /*----- Output formatting -------------------------------------------------*/ |
121 | ||
122 | /* We have two main jobs in output formatting: trimming trailing blanks; and | |
123 | * adding a prefix to each line. | |
124 | * | |
125 | * This is somehow much more complicated than it ought to be. | |
126 | */ | |
127 | ||
128 | struct format { | |
129 | FILE *fp; /* output file */ | |
130 | const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */ | |
131 | dstr w; /* trailing whitespace */ | |
132 | unsigned f; /* flags */ | |
133 | #define FMTF_NEWL 1u /* start of output line */ | |
134 | }; | |
135 | ||
136 | /* Support macros. These assume `fmt' is defined as a pointer to the `struct | |
137 | * format' state. | |
138 | */ | |
139 | ||
140 | #define SPLIT_RANGE(tail, base, limit) do { \ | |
141 | /* Set TAIL to point just after the last nonspace character between \ | |
142 | * BASE and LIMIT. If there are no nonspace characters, then set \ | |
143 | * TAIL to equal BASE. \ | |
144 | */ \ | |
145 | \ | |
146 | for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \ | |
147 | } while (0) | |
148 | ||
149 | #define PUT_RANGE(base, limit) do { \ | |
150 | /* Write the range of characters between BASE and LIMIT to the output \ | |
151 | * file. Return immediately on error. \ | |
152 | */ \ | |
153 | \ | |
154 | size_t n = limit - base; \ | |
155 | if (fwrite(base, 1, n, fmt->fp) < n) return (-1); \ | |
156 | } while (0) | |
157 | ||
158 | #define PUT_CHAR(ch) do { \ | |
159 | /* Write CH to the output. Return immediately on error. */ \ | |
160 | \ | |
161 | if (putc(ch, fmt->fp) == EOF) return (-1); \ | |
162 | } while (0) | |
163 | ||
164 | #define PUT_PREFIX do { \ | |
165 | /* Output the prefix, if there is one. Return immediately on error. */ \ | |
166 | \ | |
167 | if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim); \ | |
168 | } while (0) | |
169 | ||
170 | #define PUT_SAVED do { \ | |
171 | /* Output the saved trailing blank material in the buffer. */ \ | |
172 | \ | |
173 | size_t n = fmt->w.len; \ | |
174 | if (n && fwrite(fmt->w.buf, 1, n, fmt->fp) < n) return (-1); \ | |
175 | } while (0) | |
176 | ||
177 | #define PUT_PFXINB do { \ | |
178 | /* Output the initial nonblank portion of the prefix, if there is \ | |
179 | * one. Return immediately on error. \ | |
180 | */ \ | |
181 | \ | |
182 | if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail); \ | |
183 | } while (0) | |
184 | ||
185 | #define SAVE_PFXTAIL do { \ | |
186 | /* Save the trailing blank portion of the prefix. */ \ | |
187 | \ | |
188 | if (fmt->prefix) \ | |
189 | DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail); \ | |
190 | } while (0) | |
191 | ||
192 | /* --- @init_fmt@ --- * | |
193 | * | |
194 | * Arguments: @struct format *fmt@ = formatting state to initialize | |
195 | * @FILE *fp@ = output file | |
196 | * @const char *prefix@ = prefix string (or null if empty) | |
197 | * | |
198 | * Returns: --- | |
199 | * | |
200 | * Use: Initialize a formatting state. | |
201 | */ | |
202 | ||
203 | static void init_fmt(struct format *fmt, FILE *fp, const char *prefix) | |
204 | { | |
205 | const char *q, *l; | |
206 | ||
207 | /* Basics. */ | |
208 | fmt->fp = fp; | |
209 | fmt->f = FMTF_NEWL; | |
210 | dstr_create(&fmt->w); | |
211 | ||
212 | /* Prefix portions. */ | |
213 | if (!prefix || !*prefix) | |
214 | fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0; | |
215 | else { | |
216 | fmt->prefix = prefix; | |
217 | l = fmt->pfxlim = prefix + strlen(prefix); | |
218 | SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q; | |
219 | DPUTM(&fmt->w, q, l - q); | |
220 | } | |
221 | } | |
222 | ||
223 | /* --- @destroy_fmt@ --- * | |
224 | * | |
225 | * Arguments: @struct format *fmt@ = formatting state | |
226 | * @unsigned f@ = flags (@DFF_...@) | |
227 | * | |
228 | * Returns: --- | |
229 | * | |
230 | * Use: Releases a formatting state and the resources it holds. | |
231 | * Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave | |
232 | * it open (in case it's @stderr@ or something). | |
233 | */ | |
234 | ||
235 | #define DFF_CLOSE 1u | |
236 | static void destroy_fmt(struct format *fmt, unsigned f) | |
237 | { | |
238 | if (f&DFF_CLOSE) fclose(fmt->fp); | |
239 | dstr_destroy(&fmt->w); | |
240 | } | |
241 | ||
242 | /* --- @format_char@ --- * | |
243 | * | |
244 | * Arguments: @struct format *fmt@ = formatting state | |
245 | * @int ch@ = character to write | |
246 | * | |
247 | * Returns: Zero on success, @-1@ on failure. | |
248 | * | |
249 | * Use: Write a single character to the output. | |
250 | */ | |
251 | ||
252 | static int format_char(struct format *fmt, int ch) | |
253 | { | |
254 | if (ch == '\n') { | |
255 | if (fmt->f&FMTF_NEWL) PUT_PFXINB; | |
256 | PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w); | |
257 | } else if (isspace(ch)) | |
258 | DPUTC(&fmt->w, ch); | |
259 | else { | |
260 | if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; } | |
261 | PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w); | |
262 | } | |
263 | return (0); | |
264 | } | |
265 | ||
266 | /* --- @format_string@ --- * | |
267 | * | |
268 | * Arguments: @struct format *fmt@ = formatting state | |
269 | * @const char *p@ = string to write | |
270 | * @size_t sz@ = length of string | |
271 | * | |
272 | * Returns: Zero on success, @-1@ on failure. | |
273 | * | |
274 | * Use: Write a string to the output. | |
275 | */ | |
276 | ||
277 | static int format_string(struct format *fmt, const char *p, size_t sz) | |
278 | { | |
279 | const char *q, *r, *l = p + sz; | |
280 | ||
281 | /* This is rather vexing. There are a small number of jobs to do, but the | |
282 | * logic for deciding which to do when gets rather hairy if, as I've tried | |
283 | * here, one aims to minimize the number of decisions being checked, so | |
284 | * it's worth canning them into macros. | |
285 | * | |
286 | * Here, a `blank' is a whitespace character other than newline. The input | |
287 | * buffer consists of one or more `segments', each of which consists of: | |
288 | * | |
289 | * * an initial portion, which is either empty or ends with a nonblank | |
290 | * character; | |
291 | * | |
292 | * * a suffix which consists only of blanks; and | |
293 | * | |
294 | * * an optional newline. | |
295 | * | |
296 | * All segments except the last end with a newline. | |
297 | */ | |
298 | ||
299 | #define SPLIT_SEGMENT do { \ | |
300 | /* Determine the bounds of the current segment. If there is a final \ | |
301 | * newline, then q is non-null and points to this newline; otherwise, \ | |
302 | * q is null. The initial portion of the segment lies between p .. r \ | |
303 | * and the blank suffix lies between r .. q (or r .. l if q is null). \ | |
304 | * This sounds awkward, but the suffix is only relevant if there is \ | |
305 | * no newline. \ | |
306 | */ \ | |
307 | \ | |
308 | q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \ | |
309 | } while (0) | |
310 | ||
311 | #define PUT_NONBLANK do { \ | |
312 | /* Output the initial portion of the segment. */ \ | |
313 | \ | |
314 | PUT_RANGE(p, r); \ | |
315 | } while (0) | |
316 | ||
317 | #define PUT_NEWLINE do { \ | |
318 | /* Write a newline, and advance to the next segment. */ \ | |
319 | \ | |
320 | PUT_CHAR('\n'); p = q + 1; \ | |
321 | } while (0) | |
322 | ||
323 | #define SAVE_TAIL do { \ | |
324 | /* Save the trailing blank portion of the segment in the buffer. \ | |
325 | * Assumes that there is no newline, since otherwise the suffix would \ | |
326 | * be omitted. \ | |
327 | */ \ | |
328 | \ | |
329 | DPUTM(&fmt->w, r, l - r); \ | |
330 | } while (0) | |
331 | ||
332 | /* Determine the bounds of the first segment. Handling this is the most | |
333 | * complicated part of this function. | |
334 | */ | |
335 | SPLIT_SEGMENT; | |
336 | ||
337 | if (!q) { | |
338 | /* This is the only segment. We'll handle the whole thing here. | |
339 | * | |
340 | * If there's an initial nonblank portion, then we need to write that | |
341 | * out. Furthermore, if we're at the start of the line then we'll need | |
342 | * to write the prefix, and if there's saved blank material then we'll | |
343 | * need to write that. Otherwise, there's only blank stuff, which we | |
344 | * accumulate in the buffer. | |
345 | * | |
346 | * If we're at the start of a line here, then | |
347 | */ | |
348 | ||
349 | if (r > p) { | |
350 | if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; } | |
351 | PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w); | |
352 | } | |
353 | SAVE_TAIL; | |
354 | return (0); | |
355 | } | |
356 | ||
357 | /* There is at least one more segment, so we know that there'll be a line | |
358 | * to output. | |
359 | */ | |
360 | if (fmt->f&FMTF_NEWL) PUT_PFXINB; | |
361 | if (r > p) { PUT_SAVED; PUT_NONBLANK; } | |
362 | PUT_NEWLINE; DRESET(&fmt->w); | |
363 | SPLIT_SEGMENT; | |
364 | ||
365 | /* Main loop over whole segments with trailing newlines. For each one, we | |
366 | * know that we're starting at the beginning of a line and there's a final | |
367 | * newline, so we write the initial prefix and drop the trailing blanks. | |
368 | */ | |
369 | while (q) { | |
370 | PUT_PREFIX; PUT_NONBLANK; PUT_NEWLINE; | |
371 | SPLIT_SEGMENT; | |
372 | } | |
373 | ||
374 | /* At the end, there's no final newline. If there's nonblank material, | |
375 | * then we can write the prefix and the nonblank stuff. Otherwise, stash | |
376 | * the blank stuff (including the trailing blanks of the prefix) and leave | |
377 | * the newline flag set. | |
378 | */ | |
379 | if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; } | |
380 | else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; } | |
381 | SAVE_TAIL; | |
382 | ||
383 | #undef SPLIT_SEGMENT | |
384 | #undef PUT_NONBLANK | |
385 | #undef PUT_NEWLINE | |
386 | #undef SAVE_TAIL | |
387 | ||
388 | return (0); | |
389 | } | |
390 | ||
391 | #undef SPLIT_RANGE | |
392 | #undef PUT_RANGE | |
393 | #undef PUT_PREFIX | |
394 | #undef PUT_PFXINB | |
395 | #undef PUT_SAVED | |
396 | #undef PUT_CHAR | |
397 | #undef SAVE_PFXTAIL | |
398 | ||
b64eb60f MW |
399 | /*----- Skeleton ----------------------------------------------------------*/ |
400 | /* | |
3efcfd2d | 401 | static void ..._bsession(struct tvec_output *o, struct tvec_state *tv) |
b64eb60f MW |
402 | static int ..._esession(struct tvec_output *o) |
403 | static void ..._bgroup(struct tvec_output *o) | |
b64eb60f MW |
404 | static void ..._skipgroup(struct tvec_output *o, |
405 | const char *excuse, va_list *ap) | |
3efcfd2d | 406 | static void ..._egroup(struct tvec_output *o) |
b64eb60f | 407 | static void ..._btest(struct tvec_output *o) |
e63124bc | 408 | static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap) |
b64eb60f | 409 | static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap) |
e63124bc MW |
410 | static void ..._dumpreg(struct tvec_output *o, unsigned disp, |
411 | union tvec_regval *rv, const struct tvec_regdef *rd) | |
b64eb60f | 412 | static void ..._etest(struct tvec_output *o, unsigned outcome) |
e63124bc MW |
413 | static void ..._bbench(struct tvec_output *o, |
414 | const char *ident, unsigned unit) | |
415 | static void ..._ebench(struct tvec_output *o, | |
416 | const char *ident, unsigned unit, | |
417 | const struct tvec_timing *t) | |
3efcfd2d MW |
418 | static void ..._error(struct tvec_output *o, const char *msg, va_list *ap) |
419 | static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap) | |
b64eb60f MW |
420 | static void ..._destroy(struct tvec_output *o) |
421 | ||
422 | static const struct tvec_outops ..._ops = { | |
b64eb60f MW |
423 | ..._bsession, ..._esession, |
424 | ..._bgroup, ..._egroup, ..._skip, | |
e63124bc | 425 | ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest, |
b64eb60f | 426 | ..._bbench, ..._ebench, |
3efcfd2d | 427 | ..._error, ..._notice, |
b64eb60f MW |
428 | ..._destroy |
429 | }; | |
430 | */ | |
431 | /*----- Human-readable output ---------------------------------------------*/ | |
432 | ||
433 | #define HAF_FGMASK 0x0f | |
434 | #define HAF_FGSHIFT 0 | |
435 | #define HAF_BGMASK 0xf0 | |
436 | #define HAF_BGSHIFT 4 | |
437 | #define HAF_FG 256u | |
438 | #define HAF_BG 512u | |
439 | #define HAF_BOLD 1024u | |
440 | #define HCOL_BLACK 0u | |
441 | #define HCOL_RED 1u | |
442 | #define HCOL_GREEN 2u | |
443 | #define HCOL_YELLOW 3u | |
444 | #define HCOL_BLUE 4u | |
445 | #define HCOL_MAGENTA 5u | |
446 | #define HCOL_CYAN 6u | |
447 | #define HCOL_WHITE 7u | |
448 | #define HCF_BRIGHT 8u | |
449 | #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) | |
450 | #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) | |
451 | ||
452 | #define HA_WIN (HFG(GREEN)) | |
453 | #define HA_LOSE (HFG(RED) | HAF_BOLD) | |
454 | #define HA_SKIP (HFG(YELLOW)) | |
882a39c1 | 455 | #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) |
b64eb60f MW |
456 | |
457 | struct human_output { | |
458 | struct tvec_output _o; | |
3efcfd2d | 459 | struct tvec_state *tv; |
67b5031e MW |
460 | struct format fmt; |
461 | char *outbuf; size_t outsz; | |
b64eb60f MW |
462 | dstr scoreboard; |
463 | unsigned attr; | |
e63124bc | 464 | int maxlen; |
b64eb60f MW |
465 | unsigned f; |
466 | #define HOF_TTY 1u | |
467 | #define HOF_DUPERR 2u | |
468 | #define HOF_COLOUR 4u | |
469 | #define HOF_PROGRESS 8u | |
470 | }; | |
471 | ||
472 | static void set_colour(FILE *fp, int *sep_inout, | |
473 | const char *norm, const char *bright, | |
474 | unsigned colour) | |
475 | { | |
476 | if (*sep_inout) putc(*sep_inout, fp); | |
477 | fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7); | |
478 | *sep_inout = ';'; | |
479 | } | |
480 | ||
481 | static void setattr(struct human_output *h, unsigned attr) | |
482 | { | |
483 | unsigned diff = h->attr ^ attr; | |
484 | int sep = 0; | |
485 | ||
486 | if (!diff || !(h->f&HOF_COLOUR)) return; | |
67b5031e | 487 | fputs("\x1b[", h->fmt.fp); |
b64eb60f MW |
488 | |
489 | if (diff&HAF_BOLD) { | |
67b5031e MW |
490 | if (attr&HAF_BOLD) putc('1', h->fmt.fp); |
491 | else { putc('0', h->fmt.fp); diff = h->attr; } | |
b64eb60f MW |
492 | sep = ';'; |
493 | } | |
494 | if (diff&(HAF_FG | HAF_FGMASK)) { | |
495 | if (attr&HAF_FG) | |
67b5031e MW |
496 | set_colour(h->fmt.fp, &sep, "3", "9", |
497 | (attr&HAF_FGMASK) >> HAF_FGSHIFT); | |
b64eb60f | 498 | else |
67b5031e | 499 | { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; } |
b64eb60f MW |
500 | } |
501 | if (diff&(HAF_BG | HAF_BGMASK)) { | |
502 | if (attr&HAF_BG) | |
67b5031e MW |
503 | set_colour(h->fmt.fp, &sep, "4", "10", |
504 | (attr&HAF_BGMASK) >> HAF_BGSHIFT); | |
b64eb60f | 505 | else |
67b5031e | 506 | { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; } |
b64eb60f MW |
507 | } |
508 | ||
67b5031e | 509 | putc('m', h->fmt.fp); h->attr = attr; |
b64eb60f MW |
510 | |
511 | #undef f_any | |
512 | } | |
513 | ||
514 | static void clear_progress(struct human_output *h) | |
515 | { | |
516 | size_t i, n; | |
517 | ||
518 | if (h->f&HOF_PROGRESS) { | |
3efcfd2d | 519 | n = strlen(h->tv->test->name) + 2 + h->scoreboard.len; |
67b5031e | 520 | for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp); |
b64eb60f MW |
521 | h->f &= ~HOF_PROGRESS; |
522 | } | |
523 | } | |
524 | ||
525 | static void write_scoreboard_char(struct human_output *h, int ch) | |
526 | { | |
527 | switch (ch) { | |
528 | case 'x': setattr(h, HA_LOSE); break; | |
529 | case '_': setattr(h, HA_SKIP); break; | |
530 | default: setattr(h, 0); break; | |
531 | } | |
67b5031e | 532 | putc(ch, h->fmt.fp); setattr(h, 0); |
b64eb60f MW |
533 | } |
534 | ||
535 | static void show_progress(struct human_output *h) | |
536 | { | |
3efcfd2d | 537 | struct tvec_state *tv = h->tv; |
b64eb60f MW |
538 | const char *p, *l; |
539 | ||
882a39c1 | 540 | if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) { |
67b5031e | 541 | fprintf(h->fmt.fp, "%s: ", tv->test->name); |
b64eb60f | 542 | if (!(h->f&HOF_COLOUR)) |
67b5031e | 543 | dstr_write(&h->scoreboard, h->fmt.fp); |
b64eb60f MW |
544 | else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++) |
545 | write_scoreboard_char(h, *p); | |
67b5031e | 546 | fflush(h->fmt.fp); h->f |= HOF_PROGRESS; |
b64eb60f MW |
547 | } |
548 | } | |
549 | ||
550 | static void report_location(struct human_output *h, FILE *fp, | |
551 | const char *file, unsigned lno) | |
552 | { | |
553 | unsigned f = 0; | |
554 | #define f_flush 1u | |
555 | ||
556 | #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0) | |
557 | ||
67b5031e | 558 | if (fp != h->fmt.fp) f |= f_flush; |
b64eb60f MW |
559 | |
560 | if (file) { | |
67b5031e MW |
561 | setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp); |
562 | fputs(file, fp); FLUSH(fp); | |
563 | setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp); | |
564 | fputc(':', fp); FLUSH(fp); | |
565 | setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp); | |
566 | fprintf(fp, "%u", lno); FLUSH(fp); | |
567 | setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp); | |
568 | fputc(':', fp); FLUSH(fp); | |
569 | setattr(h, 0); FLUSH(h->fmt.fp); | |
570 | fputc(' ', fp); | |
b64eb60f MW |
571 | } |
572 | ||
573 | #undef f_flush | |
574 | #undef FLUSH | |
575 | } | |
576 | ||
67b5031e MW |
577 | static int human_writech(void *go, int ch) |
578 | { struct human_output *h = go; return (format_char(&h->fmt, ch)); } | |
579 | ||
580 | static int human_writem(void *go, const char *p, size_t sz) | |
581 | { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); } | |
582 | ||
583 | static int human_nwritef(void *go, size_t maxsz, const char *p, ...) | |
584 | { | |
585 | struct human_output *h = go; | |
586 | size_t n; | |
587 | va_list ap; | |
588 | ||
589 | va_start(ap, p); | |
590 | n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap); | |
591 | va_end(ap); | |
592 | return (format_string(&h->fmt, h->outbuf, n)); | |
593 | } | |
594 | ||
595 | static const struct gprintf_ops human_printops = | |
596 | { human_writech, human_writem, human_nwritef }; | |
597 | ||
3efcfd2d MW |
598 | static void human_bsession(struct tvec_output *o, struct tvec_state *tv) |
599 | { struct human_output *h = (struct human_output *)o; h->tv = tv; } | |
b64eb60f MW |
600 | |
601 | static void report_skipped(struct human_output *h, unsigned n) | |
602 | { | |
603 | if (n) { | |
67b5031e MW |
604 | fprintf(h->fmt.fp, " (%u ", n); |
605 | setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); | |
606 | fputc(')', h->fmt.fp); | |
b64eb60f MW |
607 | } |
608 | } | |
609 | ||
610 | static int human_esession(struct tvec_output *o) | |
611 | { | |
612 | struct human_output *h = (struct human_output *)o; | |
3efcfd2d | 613 | struct tvec_state *tv = h->tv; |
b64eb60f MW |
614 | unsigned |
615 | all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN], | |
616 | all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE], | |
617 | all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP], | |
618 | all_run = all_win + all_lose, grps_run = grps_win + grps_lose; | |
619 | ||
620 | if (!all_lose) { | |
67b5031e MW |
621 | setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0); |
622 | fprintf(h->fmt.fp, " %s%u %s", | |
b64eb60f MW |
623 | !(all_skip || grps_skip) ? "all " : "", |
624 | all_win, all_win == 1 ? "test" : "tests"); | |
625 | report_skipped(h, all_skip); | |
67b5031e | 626 | fprintf(h->fmt.fp, " in %u %s", |
b64eb60f MW |
627 | grps_win, grps_win == 1 ? "group" : "groups"); |
628 | report_skipped(h, grps_skip); | |
629 | } else { | |
67b5031e MW |
630 | setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0); |
631 | fprintf(h->fmt.fp, " %u out of %u %s", | |
b64eb60f MW |
632 | all_lose, all_run, all_run == 1 ? "test" : "tests"); |
633 | report_skipped(h, all_skip); | |
67b5031e | 634 | fprintf(h->fmt.fp, " in %u out of %u %s", |
b64eb60f MW |
635 | grps_lose, grps_run, grps_run == 1 ? "group" : "groups"); |
636 | report_skipped(h, grps_skip); | |
637 | } | |
67b5031e | 638 | fputc('\n', h->fmt.fp); |
b64eb60f | 639 | |
882a39c1 | 640 | if (tv->f&TVSF_ERROR) { |
67b5031e MW |
641 | setattr(h, HA_ERR); fputs("ERRORS", h->fmt.fp); setattr(h, 0); |
642 | fputs(" found in input; tests may not have run correctly\n", h->fmt.fp); | |
882a39c1 MW |
643 | } |
644 | ||
3efcfd2d | 645 | h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0); |
b64eb60f MW |
646 | } |
647 | ||
648 | static void human_bgroup(struct tvec_output *o) | |
649 | { | |
650 | struct human_output *h = (struct human_output *)o; | |
e63124bc | 651 | |
3efcfd2d | 652 | h->maxlen = register_maxnamelen(h->tv); |
b64eb60f MW |
653 | dstr_reset(&h->scoreboard); show_progress(h); |
654 | } | |
655 | ||
3efcfd2d MW |
656 | static void human_skipgroup(struct tvec_output *o, |
657 | const char *excuse, va_list *ap) | |
b64eb60f | 658 | { |
3efcfd2d | 659 | struct human_output *h = (struct human_output *)o; |
b64eb60f | 660 | |
3efcfd2d MW |
661 | if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) { |
662 | h->f &= ~HOF_PROGRESS; | |
67b5031e | 663 | setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); |
b64eb60f | 664 | } else { |
67b5031e MW |
665 | fprintf(h->fmt.fp, "%s: ", h->tv->test->name); |
666 | setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); | |
b64eb60f | 667 | } |
67b5031e MW |
668 | if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); } |
669 | fputc('\n', h->fmt.fp); | |
b64eb60f MW |
670 | } |
671 | ||
3efcfd2d | 672 | static void human_egroup(struct tvec_output *o) |
b64eb60f MW |
673 | { |
674 | struct human_output *h = (struct human_output *)o; | |
3efcfd2d MW |
675 | struct tvec_state *tv = h->tv; |
676 | unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE], | |
677 | skip = tv->curr[TVOUT_SKIP], run = win + lose; | |
b64eb60f MW |
678 | |
679 | if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS; | |
67b5031e | 680 | else fprintf(h->fmt.fp, "%s:", h->tv->test->name); |
b64eb60f | 681 | |
3efcfd2d | 682 | if (lose) { |
67b5031e MW |
683 | fprintf(h->fmt.fp, " %u/%u ", lose, run); |
684 | setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0); | |
3efcfd2d | 685 | report_skipped(h, skip); |
b64eb60f | 686 | } else { |
67b5031e MW |
687 | fputc(' ', h->fmt.fp); setattr(h, HA_WIN); |
688 | fputs("ok", h->fmt.fp); setattr(h, 0); | |
3efcfd2d | 689 | report_skipped(h, skip); |
b64eb60f | 690 | } |
67b5031e | 691 | fputc('\n', h->fmt.fp); |
b64eb60f MW |
692 | } |
693 | ||
694 | static void human_btest(struct tvec_output *o) | |
695 | { struct human_output *h = (struct human_output *)o; show_progress(h); } | |
696 | ||
697 | static void human_skip(struct tvec_output *o, | |
698 | const char *excuse, va_list *ap) | |
699 | { | |
700 | struct human_output *h = (struct human_output *)o; | |
3efcfd2d | 701 | struct tvec_state *tv = h->tv; |
b64eb60f MW |
702 | |
703 | clear_progress(h); | |
67b5031e MW |
704 | report_location(h, h->fmt.fp, tv->infile, tv->test_lno); |
705 | fprintf(h->fmt.fp, "`%s' ", tv->test->name); | |
706 | setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); | |
707 | if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); } | |
708 | fputc('\n', h->fmt.fp); | |
b64eb60f MW |
709 | } |
710 | ||
711 | static void human_fail(struct tvec_output *o, | |
712 | const char *detail, va_list *ap) | |
713 | { | |
714 | struct human_output *h = (struct human_output *)o; | |
3efcfd2d | 715 | struct tvec_state *tv = h->tv; |
b64eb60f MW |
716 | |
717 | clear_progress(h); | |
67b5031e MW |
718 | report_location(h, h->fmt.fp, tv->infile, tv->test_lno); |
719 | fprintf(h->fmt.fp, "`%s' ", tv->test->name); | |
720 | setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0); | |
721 | if (detail) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, detail, *ap); } | |
722 | fputc('\n', h->fmt.fp); | |
b64eb60f MW |
723 | } |
724 | ||
e63124bc MW |
725 | static void human_dumpreg(struct tvec_output *o, |
726 | unsigned disp, const union tvec_regval *rv, | |
727 | const struct tvec_regdef *rd) | |
b64eb60f MW |
728 | { |
729 | struct human_output *h = (struct human_output *)o; | |
e63124bc | 730 | const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); |
b64eb60f | 731 | |
67b5031e MW |
732 | clear_progress(h); |
733 | gprintf(&human_printops, h, "%*s%s %s = ", | |
734 | 10 + h->maxlen - n, "", ds, rd->name); | |
e63124bc MW |
735 | if (h->f&HOF_COLOUR) { |
736 | if (!rv) setattr(h, HFG(YELLOW)); | |
737 | else if (disp == TVRD_FOUND) setattr(h, HFG(RED)); | |
738 | else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN)); | |
739 | } | |
67b5031e MW |
740 | if (!rv) gprintf(&human_printops, h, "#unset"); |
741 | else rd->ty->dump(rv, rd, 0, &human_printops, h); | |
742 | setattr(h, 0); format_char(&h->fmt, '\n'); | |
b64eb60f MW |
743 | } |
744 | ||
745 | static void human_etest(struct tvec_output *o, unsigned outcome) | |
746 | { | |
747 | struct human_output *h = (struct human_output *)o; | |
748 | int ch; | |
749 | ||
750 | if (h->f&HOF_TTY) { | |
751 | show_progress(h); | |
752 | switch (outcome) { | |
753 | case TVOUT_WIN: ch = '.'; break; | |
754 | case TVOUT_LOSE: ch = 'x'; break; | |
755 | case TVOUT_SKIP: ch = '_'; break; | |
756 | default: abort(); | |
757 | } | |
758 | dstr_putc(&h->scoreboard, ch); | |
67b5031e | 759 | write_scoreboard_char(h, ch); fflush(h->fmt.fp); |
b64eb60f MW |
760 | } |
761 | } | |
762 | ||
e63124bc MW |
763 | static void human_bbench(struct tvec_output *o, |
764 | const char *ident, unsigned unit) | |
b64eb60f MW |
765 | { |
766 | struct human_output *h = (struct human_output *)o; | |
3efcfd2d | 767 | struct tvec_state *tv = h->tv; |
b64eb60f MW |
768 | |
769 | clear_progress(h); | |
67b5031e | 770 | fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp); |
b64eb60f MW |
771 | } |
772 | ||
773 | static void human_ebench(struct tvec_output *o, | |
e63124bc | 774 | const char *ident, unsigned unit, |
b64eb60f MW |
775 | const struct bench_timing *tm) |
776 | { | |
777 | struct human_output *h = (struct human_output *)o; | |
67b5031e MW |
778 | |
779 | tvec_benchreport(&human_printops, h->fmt.fp, unit, tm); | |
780 | fputc('\n', h->fmt.fp); | |
b64eb60f MW |
781 | } |
782 | ||
3efcfd2d MW |
783 | static void human_report(struct tvec_output *o, const char *msg, va_list *ap) |
784 | { | |
785 | struct human_output *h = (struct human_output *)o; | |
786 | struct tvec_state *tv = h->tv; | |
787 | dstr d = DSTR_INIT; | |
788 | ||
789 | dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); | |
790 | ||
67b5031e | 791 | clear_progress(h); fflush(h->fmt.fp); |
3efcfd2d MW |
792 | fprintf(stderr, "%s: ", QUIS); |
793 | report_location(h, stderr, tv->infile, tv->lno); | |
794 | fwrite(d.buf, 1, d.len, stderr); | |
795 | ||
796 | if (h->f&HOF_DUPERR) { | |
67b5031e MW |
797 | report_location(h, h->fmt.fp, tv->infile, tv->lno); |
798 | fwrite(d.buf, 1, d.len, h->fmt.fp); | |
3efcfd2d MW |
799 | } |
800 | show_progress(h); | |
801 | } | |
802 | ||
b64eb60f MW |
803 | static void human_destroy(struct tvec_output *o) |
804 | { | |
805 | struct human_output *h = (struct human_output *)o; | |
806 | ||
67b5031e | 807 | destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0); |
b64eb60f | 808 | dstr_destroy(&h->scoreboard); |
67b5031e | 809 | xfree(h->outbuf); xfree(h); |
b64eb60f MW |
810 | } |
811 | ||
812 | static const struct tvec_outops human_ops = { | |
b64eb60f | 813 | human_bsession, human_esession, |
3efcfd2d | 814 | human_bgroup, human_skipgroup, human_egroup, |
e63124bc | 815 | human_btest, human_skip, human_fail, human_dumpreg, human_etest, |
b64eb60f | 816 | human_bbench, human_ebench, |
3efcfd2d | 817 | human_report, human_report, |
b64eb60f MW |
818 | human_destroy |
819 | }; | |
820 | ||
821 | struct tvec_output *tvec_humanoutput(FILE *fp) | |
822 | { | |
823 | struct human_output *h; | |
824 | const char *p; | |
825 | ||
826 | h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops; | |
827 | h->f = 0; h->attr = 0; | |
828 | ||
67b5031e MW |
829 | init_fmt(&h->fmt, fp, 0); |
830 | h->outbuf = 0; h->outsz = 0; | |
b64eb60f MW |
831 | |
832 | switch (getenv_boolean("TVEC_TTY", -1)) { | |
833 | case 1: h->f |= HOF_TTY; break; | |
834 | case 0: break; | |
835 | default: | |
836 | if (isatty(fileno(fp))) h->f |= HOF_TTY; | |
837 | break; | |
838 | } | |
839 | switch (getenv_boolean("TVEC_COLOUR", -1)) { | |
840 | case 1: h->f |= HOF_COLOUR; break; | |
841 | case 0: break; | |
842 | default: | |
843 | if (h->f&HOF_TTY) { | |
844 | p = getenv("TERM"); | |
845 | if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR; | |
846 | } | |
847 | break; | |
848 | } | |
849 | ||
e63124bc | 850 | if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR; |
b64eb60f MW |
851 | dstr_create(&h->scoreboard); |
852 | return (&h->_o); | |
853 | } | |
854 | ||
855 | /*----- Perl's `Test Anything Protocol' -----------------------------------*/ | |
856 | ||
857 | struct tap_output { | |
858 | struct tvec_output _o; | |
3efcfd2d | 859 | struct tvec_state *tv; |
67b5031e MW |
860 | struct format fmt; |
861 | char *outbuf; size_t outsz; | |
e63124bc | 862 | int maxlen; |
b64eb60f MW |
863 | }; |
864 | ||
e63124bc | 865 | static int tap_writech(void *go, int ch) |
67b5031e | 866 | { struct tap_output *t = go; return (format_char(&t->fmt, ch)); } |
e63124bc MW |
867 | |
868 | static int tap_writem(void *go, const char *p, size_t sz) | |
67b5031e | 869 | { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); } |
e63124bc MW |
870 | |
871 | static int tap_nwritef(void *go, size_t maxsz, const char *p, ...) | |
872 | { | |
67b5031e MW |
873 | struct human_output *t = go; |
874 | size_t n; | |
e63124bc | 875 | va_list ap; |
67b5031e MW |
876 | |
877 | va_start(ap, p); | |
878 | n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap); | |
e63124bc | 879 | va_end(ap); |
67b5031e | 880 | return (format_string(&t->fmt, t->outbuf, n)); |
b64eb60f MW |
881 | } |
882 | ||
e63124bc MW |
883 | static const struct gprintf_ops tap_printops = |
884 | { tap_writech, tap_writem, tap_nwritef }; | |
885 | ||
3efcfd2d MW |
886 | static void tap_bsession(struct tvec_output *o, struct tvec_state *tv) |
887 | { | |
888 | struct tap_output *t = (struct tap_output *)o; | |
889 | ||
890 | t->tv = tv; | |
67b5031e | 891 | fputs("TAP version 13\n", t->fmt.fp); |
3efcfd2d | 892 | } |
b64eb60f MW |
893 | |
894 | static unsigned tap_grpix(struct tap_output *t) | |
895 | { | |
3efcfd2d | 896 | struct tvec_state *tv = t->tv; |
b64eb60f MW |
897 | |
898 | return (tv->grps[TVOUT_WIN] + | |
899 | tv->grps[TVOUT_LOSE] + | |
900 | tv->grps[TVOUT_SKIP]); | |
901 | } | |
902 | ||
903 | static int tap_esession(struct tvec_output *o) | |
904 | { | |
905 | struct tap_output *t = (struct tap_output *)o; | |
3efcfd2d | 906 | struct tvec_state *tv = t->tv; |
882a39c1 MW |
907 | |
908 | if (tv->f&TVSF_ERROR) { | |
909 | fputs("Bail out! " | |
910 | "Errors found in input; tests may not have run correctly\n", | |
67b5031e | 911 | t->fmt.fp); |
882a39c1 MW |
912 | return (2); |
913 | } | |
b64eb60f | 914 | |
67b5031e | 915 | fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t)); |
3efcfd2d | 916 | t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0); |
b64eb60f MW |
917 | } |
918 | ||
e63124bc MW |
919 | static void tap_bgroup(struct tvec_output *o) |
920 | { | |
921 | struct tap_output *t = (struct tap_output *)o; | |
3efcfd2d MW |
922 | t->maxlen = register_maxnamelen(t->tv); |
923 | } | |
924 | ||
925 | static void tap_skipgroup(struct tvec_output *o, | |
926 | const char *excuse, va_list *ap) | |
927 | { | |
928 | struct tap_output *t = (struct tap_output *)o; | |
929 | ||
67b5031e MW |
930 | fprintf(t->fmt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name); |
931 | if (excuse) { fputc(' ', t->fmt.fp); vfprintf(t->fmt.fp, excuse, *ap); } | |
932 | fputc('\n', t->fmt.fp); | |
e63124bc | 933 | } |
b64eb60f | 934 | |
3efcfd2d | 935 | static void tap_egroup(struct tvec_output *o) |
b64eb60f MW |
936 | { |
937 | struct tap_output *t = (struct tap_output *)o; | |
3efcfd2d | 938 | struct tvec_state *tv = t->tv; |
b64eb60f MW |
939 | unsigned |
940 | grpix = tap_grpix(t), | |
941 | win = tv->curr[TVOUT_WIN], | |
942 | lose = tv->curr[TVOUT_LOSE], | |
943 | skip = tv->curr[TVOUT_SKIP]; | |
944 | ||
945 | if (lose) { | |
67b5031e | 946 | fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u", |
b64eb60f | 947 | grpix, tv->test->name, lose, win + lose); |
67b5031e | 948 | if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip); |
b64eb60f | 949 | } else { |
67b5031e MW |
950 | fprintf(t->fmt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, win); |
951 | if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip); | |
b64eb60f | 952 | } |
67b5031e | 953 | fputc('\n', t->fmt.fp); |
b64eb60f MW |
954 | } |
955 | ||
b64eb60f MW |
956 | static void tap_btest(struct tvec_output *o) { ; } |
957 | ||
67b5031e MW |
958 | static void tap_outcome(struct tvec_output *o, const char *outcome, |
959 | const char *detail, va_list *ap) | |
b64eb60f MW |
960 | { |
961 | struct tap_output *t = (struct tap_output *)o; | |
3efcfd2d | 962 | struct tvec_state *tv = t->tv; |
b64eb60f | 963 | |
67b5031e MW |
964 | gprintf(&tap_printops, t, "%s:%u: `%s' %s", |
965 | tv->infile, tv->test_lno, tv->test->name, outcome); | |
966 | if (detail) { | |
967 | format_string(&t->fmt, ": ", 2); | |
968 | vgprintf(&tap_printops, t, detail, ap); | |
969 | } | |
970 | format_char(&t->fmt, '\n'); | |
b64eb60f MW |
971 | } |
972 | ||
67b5031e MW |
973 | static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap) |
974 | { tap_outcome(o, "skipped", excuse, ap); } | |
b64eb60f | 975 | static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap) |
67b5031e | 976 | { tap_outcome(o, "FAILED", detail, ap); } |
b64eb60f | 977 | |
e63124bc MW |
978 | static void tap_dumpreg(struct tvec_output *o, |
979 | unsigned disp, const union tvec_regval *rv, | |
980 | const struct tvec_regdef *rd) | |
981 | { | |
982 | struct tap_output *t = (struct tap_output *)o; | |
983 | const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); | |
984 | ||
67b5031e MW |
985 | gprintf(&tap_printops, t, "%*s%s %s = ", |
986 | 10 + t->maxlen - n, "", ds, rd->name); | |
987 | if (!rv) gprintf(&tap_printops, t, "#<unset>"); | |
988 | else rd->ty->dump(rv, rd, 0, &tap_printops, t); | |
989 | format_char(&t->fmt, '\n'); | |
e63124bc | 990 | } |
b64eb60f MW |
991 | |
992 | static void tap_etest(struct tvec_output *o, unsigned outcome) { ; } | |
993 | ||
e63124bc MW |
994 | static void tap_bbench(struct tvec_output *o, |
995 | const char *ident, unsigned unit) | |
996 | { ; } | |
b64eb60f MW |
997 | |
998 | static void tap_ebench(struct tvec_output *o, | |
e63124bc | 999 | const char *ident, unsigned unit, |
b64eb60f MW |
1000 | const struct bench_timing *tm) |
1001 | { | |
1002 | struct tap_output *t = (struct tap_output *)o; | |
3efcfd2d | 1003 | struct tvec_state *tv = t->tv; |
b64eb60f | 1004 | |
67b5031e MW |
1005 | gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident); |
1006 | tvec_benchreport(&tap_printops, t, unit, tm); | |
1007 | format_char(&t->fmt, '\n'); | |
b64eb60f MW |
1008 | } |
1009 | ||
67b5031e MW |
1010 | static void tap_report(struct tap_output *t, |
1011 | const struct gprintf_ops *gops, void *go, | |
1012 | const char *msg, va_list *ap) | |
3efcfd2d MW |
1013 | { |
1014 | struct tvec_state *tv = t->tv; | |
1015 | ||
67b5031e MW |
1016 | if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno); |
1017 | gprintf(gops, go, msg, ap); gops->putch(go, '\n'); | |
3efcfd2d MW |
1018 | } |
1019 | ||
1020 | static void tap_error(struct tvec_output *o, const char *msg, va_list *ap) | |
1021 | { | |
1022 | struct tap_output *t = (struct tap_output *)o; | |
67b5031e MW |
1023 | |
1024 | fputs("Bail out! ", t->fmt.fp); | |
1025 | tap_report(t, &file_printops, t->fmt.fp, msg, ap); | |
3efcfd2d MW |
1026 | } |
1027 | ||
1028 | static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) | |
1029 | { | |
1030 | struct tap_output *t = (struct tap_output *)o; | |
67b5031e MW |
1031 | |
1032 | tap_report(t, &tap_printops, t, msg, ap); | |
3efcfd2d MW |
1033 | } |
1034 | ||
b64eb60f MW |
1035 | static void tap_destroy(struct tvec_output *o) |
1036 | { | |
1037 | struct tap_output *t = (struct tap_output *)o; | |
1038 | ||
67b5031e MW |
1039 | destroy_fmt(&t->fmt, |
1040 | t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE); | |
1041 | xfree(t->outbuf); xfree(t); | |
b64eb60f MW |
1042 | } |
1043 | ||
1044 | static const struct tvec_outops tap_ops = { | |
b64eb60f | 1045 | tap_bsession, tap_esession, |
3efcfd2d | 1046 | tap_bgroup, tap_skipgroup, tap_egroup, |
e63124bc | 1047 | tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest, |
b64eb60f | 1048 | tap_bbench, tap_ebench, |
3efcfd2d | 1049 | tap_error, tap_notice, |
b64eb60f MW |
1050 | tap_destroy |
1051 | }; | |
1052 | ||
1053 | struct tvec_output *tvec_tapoutput(FILE *fp) | |
1054 | { | |
1055 | struct tap_output *t; | |
1056 | ||
1057 | t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops; | |
67b5031e MW |
1058 | init_fmt(&t->fmt, fp, "## "); |
1059 | t->outbuf = 0; t->outsz = 0; | |
b64eb60f MW |
1060 | return (&t->_o); |
1061 | } | |
1062 | ||
1063 | /*----- Default output ----------------------------------------------------*/ | |
1064 | ||
1065 | struct tvec_output *tvec_dfltout(FILE *fp) | |
1066 | { | |
1067 | int ttyp = getenv_boolean("TVEC_TTY", -1); | |
1068 | ||
1069 | if (ttyp == -1) ttyp = isatty(fileno(fp)); | |
1070 | if (ttyp) return (tvec_humanoutput(fp)); | |
1071 | else return (tvec_tapoutput(fp)); | |
1072 | } | |
1073 | ||
1074 | /*----- That's all, folks -------------------------------------------------*/ |