@@@ fltfmt mess
[mLib] / test / tvec-output.c
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 "config.h"
31
32 #include <assert.h>
33 #include <ctype.h>
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"
43 #include "macros.h"
44 #include "quis.h"
45 #include "report.h"
46
47 #include "tvec.h"
48 #include "tvec-bench.h"
49 #include "tvec-output.h"
50
51 /*----- Common machinery --------------------------------------------------*/
52
53 /* --- @regdisp@ --- *
54 *
55 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
56 *
57 * Returns: A human-readable adjective describing the register
58 * disposition.
59 */
60
61 static const char *regdisp(unsigned disp)
62 {
63 switch (disp) {
64 case TVRD_INPUT: return "input";
65 case TVRD_OUTPUT: return "output";
66 case TVRD_MATCH: return "matched";
67 case TVRD_FOUND: return "found";
68 case TVRD_EXPECT: return "expected";
69 default: abort();
70 }
71 }
72
73 /* --- @getenv_boolean@ --- *
74 *
75 * Arguments: @const char *var@ = environment variable name
76 * @int dflt@ = default value
77 *
78 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
79 * set to something truish, or @dflt@ otherwise.
80 */
81
82 static int getenv_boolean(const char *var, int dflt)
83 {
84 const char *p;
85
86 p = getenv(var);
87 if (!p)
88 return (dflt);
89 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
90 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
91 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
92 STRCMP(p, ==, "1"))
93 return (1);
94 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
95 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
96 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
97 STRCMP(p, ==, "0"))
98 return (0);
99 else {
100 moan("ignoring unexpected value `%s' for environment variable `%s'",
101 var, p);
102 return (dflt);
103 }
104 }
105
106 /* --- @register_maxnamelen@ --- *
107 *
108 * Arguments: @const struct tvec_state *tv@ = test vector state
109 *
110 * Returns: The maximum length of a register name in the current test.
111 */
112
113 static int register_maxnamelen(const struct tvec_state *tv)
114 {
115 const struct tvec_regdef *rd;
116 int maxlen = 10, n;
117
118 for (rd = tv->test->regs; rd->name; rd++)
119 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
120 return (maxlen);
121 }
122
123 /* --- @print_ident@ --- *
124 *
125 * Arguments: @struct tvec_state *tv@ = test-vector state
126 * @unsigned style@ = style to use for register dumps
127 * @const struct gprintf_ops *gops@ = output operations
128 * @void *go@ = output state
129 *
130 * Returns: ---
131 *
132 * Use: Write a benchmark identification to the output.
133 */
134
135 static void print_ident(struct tvec_state *tv, unsigned style,
136 const struct gprintf_ops *gops, void *go)
137 {
138 const struct tvec_regdef *rd;
139 unsigned f = 0;
140
141 #define f_any 1u
142
143 for (rd = tv->test->regs; rd->name; rd++)
144 if (rd->f&TVRF_ID) {
145 if (!(f&f_any)) f |= f_any;
146 else if (style&TVSF_RAW) gops->putch(go, ' ');
147 else gprintf(gops, go, ", ");
148 gprintf(gops, go, "%s", rd->name);
149 if (style&TVSF_RAW) gops->putch(go, '=');
150 else gprintf(gops, go, " = ");
151 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, style, gops, go);
152 }
153
154 #undef f_any
155 }
156
157 /*----- Output layout -----------------------------------------------------*/
158
159 /* We have two main jobs in output layout: trimming trailing blanks; and
160 * adding a prefix to each line.
161 *
162 * This is somehow much more complicated than it ought to be.
163 */
164
165 struct layout {
166 FILE *fp; /* output file */
167 const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
168 dstr w; /* trailing whitespace */
169 unsigned f; /* flags */
170 #define LYTF_NEWL 1u /* start of output line */
171 };
172
173 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
174 * layout' state.
175 */
176
177 #define SPLIT_RANGE(tail, base, limit) do { \
178 /* Set TAIL to point just after the last nonspace character between \
179 * BASE and LIMIT. If there are no nonspace characters, then set \
180 * TAIL to equal BASE. \
181 */ \
182 \
183 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
184 } while (0)
185
186 #define PUT_RANGE(base, limit) do { \
187 /* Write the range of characters between BASE and LIMIT to the output \
188 * file. Return immediately on error. \
189 */ \
190 \
191 size_t _n = limit - base; \
192 if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
193 } while (0)
194
195 #define PUT_CHAR(ch) do { \
196 /* Write CH to the output. Return immediately on error. */ \
197 \
198 if (putc(ch, lyt->fp) == EOF) return (-1); \
199 } while (0)
200
201 #define PUT_PREFIX do { \
202 /* Output the prefix, if there is one. Return immediately on error. */ \
203 \
204 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
205 } while (0)
206
207 #define PUT_SAVED do { \
208 /* Output the saved trailing blank material in the buffer. */ \
209 \
210 size_t _n = lyt->w.len; \
211 if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
212 } while (0)
213
214 #define PUT_PFXINB do { \
215 /* Output the initial nonblank portion of the prefix, if there is \
216 * one. Return immediately on error. \
217 */ \
218 \
219 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
220 } while (0)
221
222 #define SAVE_PFXTAIL do { \
223 /* Save the trailing blank portion of the prefix. */ \
224 \
225 if (lyt->prefix) \
226 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
227 } while (0)
228
229 /* --- @set_layout_prefix@ --- *
230 *
231 * Arguments: @struct layout *lyt@ = layout state
232 * @const char *prefix@ = new prefix string or null
233 *
234 * Returns: ---
235 *
236 * Use: Change the configured prefix string. The change takes effect
237 * at the start of the next line (or the current line if it's
238 * empty or only whitespace so far).
239 */
240
241 static void set_layout_prefix(struct layout *lyt, const char *prefix)
242 {
243 const char *q, *l;
244
245 if (!prefix || !*prefix)
246 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
247 else {
248 lyt->prefix = prefix;
249 l = lyt->pfxlim = prefix + strlen(prefix);
250 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
251 }
252 }
253
254 /* --- @init_layout@ --- *
255 *
256 * Arguments: @struct layout *lyt@ = layout state to initialize
257 * @FILE *fp@ = output file
258 * @const char *prefix@ = prefix string (or null if empty)
259 *
260 * Returns: ---
261 *
262 * Use: Initialize a layout state.
263 */
264
265 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
266 {
267 lyt->fp = fp;
268 lyt->f = LYTF_NEWL;
269 dstr_create(&lyt->w);
270 set_layout_prefix(lyt, prefix);
271 }
272
273 /* --- @destroy_layout@ --- *
274 *
275 * Arguments: @struct layout *lyt@ = layout state
276 * @unsigned f@ = flags (@DLF_...@)
277 *
278 * Returns: ---
279 *
280 * Use: Releases a layout state and the resources it holds.
281 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
282 * it open (in case it's @stderr@ or something).
283 */
284
285 #define DLF_CLOSE 1u
286 static void destroy_layout(struct layout *lyt, unsigned f)
287 {
288 if (f&DLF_CLOSE) fclose(lyt->fp);
289 dstr_destroy(&lyt->w);
290 }
291
292 /* --- @layout_char@ --- *
293 *
294 * Arguments: @struct layout *lyt@ = layout state
295 * @int ch@ = character to write
296 *
297 * Returns: Zero on success, @-1@ on failure.
298 *
299 * Use: Write a single character to the output.
300 */
301
302 static int layout_char(struct layout *lyt, int ch)
303 {
304 if (ch == '\n') {
305 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
306 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
307 } else if (isspace(ch))
308 DPUTC(&lyt->w, ch);
309 else {
310 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
311 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
312 }
313 return (0);
314 }
315
316 /* --- @layout_string@ --- *
317 *
318 * Arguments: @struct layout *lyt@ = layout state
319 * @const char *p@ = string to write
320 * @size_t sz@ = length of string
321 *
322 * Returns: Zero on success, @-1@ on failure.
323 *
324 * Use: Write a string to the output.
325 */
326
327 static int layout_string(struct layout *lyt, const char *p, size_t sz)
328 {
329 const char *q, *r, *l = p + sz;
330
331 /* This is rather vexing. There are a small number of jobs to do, but the
332 * logic for deciding which to do when gets rather hairy if, as I've tried
333 * here, one aims to minimize the number of decisions being checked, so
334 * it's worth canning them into macros.
335 *
336 * Here, a `blank' is a whitespace character other than newline. The input
337 * buffer consists of one or more `segments', each of which consists of:
338 *
339 * * an initial portion, which is either empty or ends with a nonblank
340 * character;
341 *
342 * * a suffix which consists only of blanks; and
343 *
344 * * an optional newline.
345 *
346 * All segments except the last end with a newline.
347 */
348
349 #define SPLIT_SEGMENT do { \
350 /* Determine the bounds of the current segment. If there is a final \
351 * newline, then q is non-null and points to this newline; otherwise, \
352 * q is null. The initial portion of the segment lies between p .. r \
353 * and the blank suffix lies between r .. q (or r .. l if q is null). \
354 * This sounds awkward, but the suffix is only relevant if there is \
355 * no newline. \
356 */ \
357 \
358 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
359 } while (0)
360
361 #define PUT_NONBLANK do { \
362 /* Output the initial portion of the segment. */ \
363 \
364 PUT_RANGE(p, r); \
365 } while (0)
366
367 #define PUT_NEWLINE do { \
368 /* Write a newline, and advance to the next segment. */ \
369 \
370 PUT_CHAR('\n'); p = q + 1; \
371 } while (0)
372
373 #define SAVE_TAIL do { \
374 /* Save the trailing blank portion of the segment in the buffer. \
375 * Assumes that there is no newline, since otherwise the suffix would \
376 * be omitted. \
377 */ \
378 \
379 DPUTM(&lyt->w, r, l - r); \
380 } while (0)
381
382 /* Determine the bounds of the first segment. Handling this is the most
383 * complicated part of this function.
384 */
385 SPLIT_SEGMENT;
386
387 if (!q) {
388 /* This is the only segment. We'll handle the whole thing here.
389 *
390 * If there's an initial nonblank portion, then we need to write that
391 * out. Furthermore, if we're at the start of the line then we'll need
392 * to write the prefix, and if there's saved blank material then we'll
393 * need to write that. Otherwise, there's only blank stuff, which we
394 * accumulate in the buffer.
395 *
396 * If we're at the start of a line here, then put the prefix followed by
397 * any saved whitespace, and then our initial nonblank portion. Then
398 * save our new trailing space.
399 */
400
401 if (r > p) {
402 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
403 PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
404 }
405 SAVE_TAIL;
406 return (0);
407 }
408
409 /* There is at least one more segment, so we know that there'll be a line
410 * to output.
411 */
412 if (r > p) {
413 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
414 PUT_SAVED; PUT_NONBLANK;
415 } else if (lyt->f&LYTF_NEWL)
416 PUT_PFXINB;
417 PUT_NEWLINE; DRESET(&lyt->w);
418 SPLIT_SEGMENT;
419
420 /* Main loop over whole segments with trailing newlines. For each one, we
421 * know that we're starting at the beginning of a line and there's a final
422 * newline, so we write the initial prefix and drop the trailing blanks.
423 */
424 while (q) {
425 if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
426 else PUT_PFXINB;
427 PUT_NEWLINE;
428 SPLIT_SEGMENT;
429 }
430
431 /* At the end, there's no final newline. If there's nonblank material,
432 * then we can write the prefix and the nonblank stuff. Otherwise, stash
433 * the blank stuff (including the trailing blanks of the prefix) and leave
434 * the newline flag set.
435 */
436 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
437 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
438 SAVE_TAIL;
439
440 #undef SPLIT_SEGMENT
441 #undef PUT_NONBLANK
442 #undef PUT_NEWLINE
443 #undef SAVE_TAIL
444
445 return (0);
446 }
447
448 #undef SPLIT_RANGE
449 #undef PUT_RANGE
450 #undef PUT_PREFIX
451 #undef PUT_PFXINB
452 #undef PUT_SAVED
453 #undef PUT_CHAR
454 #undef SAVE_PFXTAIL
455
456 /*----- Human-readable output ---------------------------------------------*/
457
458 /* Attributes for colour output. This should be done better, but @terminfo@
459 * is a disaster.
460 *
461 * An attribute byte holds a foreground colour in the low nibble, a
462 * background colour in the next nibble, and some flags in the next few
463 * bits. A colour is expressed in classic 1-bit-per-channel style, with red,
464 * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
465 */
466 #define HAF_FGMASK 0x0f /* foreground colour mask */
467 #define HAF_FGSHIFT 0 /* foreground colour shift */
468 #define HAF_BGMASK 0xf0 /* background colour mask */
469 #define HAF_BGSHIFT 4 /* background colour shift */
470 #define HAF_FG 256u /* set foreground? */
471 #define HAF_BG 512u /* set background? */
472 #define HAF_BOLD 1024u /* set bold? */
473 #define HCOL_BLACK 0u /* colour codes... */
474 #define HCOL_RED 1u
475 #define HCOL_GREEN 2u
476 #define HCOL_YELLOW 3u
477 #define HCOL_BLUE 4u
478 #define HCOL_MAGENTA 5u
479 #define HCOL_CYAN 6u
480 #define HCOL_WHITE 7u
481 #define HCF_BRIGHT 8u /* bright colour flag */
482 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
483 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
484
485 /* Predefined attributes. */
486 #define HA_PLAIN 0 /* nothing special: terminal defaults */
487 #define HA_LOC (HFG(CYAN)) /* filename or line number */
488 #define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */
489 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
490 #define HA_NOTE (HFG(YELLOW)) /* notices */
491 #define HA_INFO 0 /* information */
492 #define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
493 #define HA_UNSET (HFG(YELLOW)) /* register not set */
494 #define HA_FOUND (HFG(RED)) /* incorrect output value */
495 #define HA_EXPECT (HFG(GREEN)) /* what the value should have been */
496 #define HA_WIN (HFG(GREEN)) /* reporting success */
497 #define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */
498 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
499 #define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */
500
501 /* Scoreboard indicators. */
502 #define HSB_WIN '.' /* test passed */
503 #define HSB_LOSE 'x' /* test failed */
504 #define HSB_XFAIL 'o' /* test failed expectedly */
505 #define HSB_SKIP '_' /* test wasn't run */
506
507 struct human_output {
508 struct tvec_output _o; /* output base class */
509 struct tvec_state *tv; /* stashed testing state */
510 arena *a; /* arena for memory allocation */
511 struct layout lyt; /* output layout */
512 char *outbuf; size_t outsz; /* buffer for formatted output */
513 dstr scoreboard; /* history of test group results */
514 unsigned attr; /* current terminal attributes */
515 int maxlen; /* longest register name */
516 unsigned f; /* flags */
517 #define HOF_TTY 1u /* writing to terminal */
518 #define HOF_DUPERR 2u /* duplicate errors to stderr */
519 #define HOF_COLOUR 4u /* print in angry fruit salad */
520 #define HOF_PROGRESS 8u /* progress display is active */
521 };
522
523 /* --- @set_colour@ --- *
524 *
525 * Arguments: @FILE *fp@ = output stream to write on
526 * @int *sep_inout@ = where to maintain separator
527 * @const char *norm@ = prefix for normal colour
528 * @const char *bright@ = prefix for bright colour
529 * @unsigned colour@ = four bit colour code
530 *
531 * Returns: ---
532 *
533 * Use: Write to the output stream @fp@, the current character at
534 * @*sep_inout@, if that's not zero, followed by either @norm@
535 * or @bright@, according to whether the @HCF_BRIGHT@ flag is
536 * set in @colour@, followed by the plain colour code from
537 * @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
538 *
539 * This is an internal subroutine for @setattr@ below.
540 */
541
542 static void set_colour(FILE *fp, int *sep_inout,
543 const char *norm, const char *bright,
544 unsigned colour)
545 {
546 if (*sep_inout) putc(*sep_inout, fp);
547 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
548 *sep_inout = ';';
549 }
550
551 /* --- @setattr@ --- *
552 *
553 * Arguments: @struct human_output *h@ = output state
554 * @unsigned attr@ = attribute code to set
555 *
556 * Returns: ---
557 *
558 * Use: Send a control sequence to the output stream so that
559 * subsequent text is printed with the given attributes.
560 *
561 * Some effort is taken to avoid unnecessary control sequences.
562 * In particular, if @attr@ matches the current terminal
563 * settings already, then nothing is written.
564 */
565
566 static void setattr(struct human_output *h, unsigned attr)
567 {
568 unsigned diff = h->attr ^ attr;
569 int sep = 0;
570
571 /* If there's nothing to do, we might as well stop now. */
572 if (!diff || !(h->f&HOF_COLOUR)) return;
573
574 /* Start on the control command. */
575 fputs("\x1b[", h->lyt.fp);
576
577 /* Change the boldness if necessary. */
578 if (diff&HAF_BOLD) {
579 if (attr&HAF_BOLD) putc('1', h->lyt.fp);
580 else { putc('0', h->lyt.fp); diff = h->attr; }
581 sep = ';';
582 }
583
584 /* Change the foreground colour if necessary. */
585 if (diff&(HAF_FG | HAF_FGMASK)) {
586 if (attr&HAF_FG)
587 set_colour(h->lyt.fp, &sep, "3", "9",
588 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
589 else {
590 if (sep) putc(sep, h->lyt.fp);
591 fputs("39", h->lyt.fp); sep = ';';
592 }
593 }
594
595 /* Change the background colour if necessary. */
596 if (diff&(HAF_BG | HAF_BGMASK)) {
597 if (attr&HAF_BG)
598 set_colour(h->lyt.fp, &sep, "4", "10",
599 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
600 else {
601 if (sep) putc(sep, h->lyt.fp);
602 fputs("49", h->lyt.fp); sep = ';';
603 }
604 }
605
606 /* Terminate the control command and save the new attributes. */
607 putc('m', h->lyt.fp); h->attr = attr;
608 }
609
610 /* --- @clear_progress@ --- *
611 *
612 * Arguments: @struct human_output *h@ = output state
613 *
614 * Returns: ---
615 *
616 * Use: Remove the progress display from the terminal.
617 *
618 * If the progress display isn't active then do nothing.
619 */
620
621 static void clear_progress(struct human_output *h)
622 {
623 size_t i, n;
624
625 if (h->f&HOF_PROGRESS) {
626 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
627 for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
628 h->f &= ~HOF_PROGRESS;
629 }
630 }
631
632 /* --- @write_scoreboard_char@ --- *
633 *
634 * Arguments: @struct human_output *h@ = output state
635 * @int ch@ = scoreboard character to print
636 *
637 * Returns: ---
638 *
639 * Use: Write a scoreboard character, indicating the outcome of a
640 * test, to the output stream, with appropriate highlighting.
641 */
642
643 static void write_scoreboard_char(struct human_output *h, int ch)
644 {
645 switch (ch) {
646 case HSB_LOSE: setattr(h, HA_LOSE); break;
647 case HSB_SKIP: setattr(h, HA_SKIP); break;
648 case HSB_XFAIL: setattr(h, HA_XFAIL); break;
649 default: setattr(h, HA_PLAIN); break;
650 }
651 putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
652 }
653
654 /* --- @show_progress@ --- *
655 *
656 * Arguments: @struct human_output *h@ = output state
657 *
658 * Returns: ---
659 *
660 * Use: Show the progress display, with the record of outcomes for
661 * the current test group.
662 *
663 * If the progress display is already active, or the output
664 * stream is not interactive, then nothing happens.
665 */
666
667 static void show_progress(struct human_output *h)
668 {
669 struct tvec_state *tv = h->tv;
670 const char *p, *l;
671
672 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
673 fprintf(h->lyt.fp, "%s: ", tv->test->name);
674 if (!(h->f&HOF_COLOUR))
675 dstr_write(&h->scoreboard, h->lyt.fp);
676 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
677 write_scoreboard_char(h, *p);
678 fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
679 }
680 }
681
682 /* --- @human_writech@, @human_write@, @human_writef@ --- *
683 *
684 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
685 * @int ch@ = character to write
686 * @const char *@p@, @size_t sz@ = string (with explicit length)
687 * to write
688 * @const char *p, ...@ = format control string and arguments to
689 * write
690 *
691 * Returns: ---
692 *
693 * Use: Write characters, strings, or formatted strings to the
694 * output, applying appropriate layout.
695 *
696 * For the human output driver, the layout machinery just strips
697 * trailing spaces.
698 */
699
700 static int human_writech(void *go, int ch)
701 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
702
703 static int human_writem(void *go, const char *p, size_t sz)
704 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
705
706 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
707 {
708 struct human_output *h = go;
709 size_t n;
710 va_list ap;
711
712 va_start(ap, p);
713 n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
714 va_end(ap);
715 if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
716 return (n);
717 }
718
719 static const struct gprintf_ops human_printops =
720 { human_writech, human_writem, human_nwritef };
721
722 /* --- @human_bsession@ --- *
723 *
724 * Arguments: @struct tvec_output *o@ = output sink, secretly a
725 * @struct human_output@
726 * @struct tvec_state *tv@ = the test state producing output
727 *
728 * Returns: ---
729 *
730 * Use: Begin a test session.
731 *
732 * The human driver just records the test state for later
733 * reference.
734 */
735
736 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
737 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
738
739 /* --- @report_unusual@ --- *
740 *
741 * Arguments: @struct human_output *h@ = output sink
742 * @unsigned nxfail, nskip@ = number of expected failures and
743 * skipped tests
744 *
745 * Returns: ---
746 *
747 * Use: Write (directly on the output stream) a note about expected
748 * failures and/or skipped tests, if there were any.
749 */
750
751 static void report_unusual(struct human_output *h,
752 unsigned nxfail, unsigned nskip)
753 {
754 const char *sep = " (";
755 unsigned f = 0;
756 #define f_any 1u
757
758 if (nxfail) {
759 fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
760 setattr(h, HA_XFAIL);
761 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
762 setattr(h, HA_PLAIN);
763 sep = ", "; f |= f_any;
764 }
765
766 if (nskip) {
767 fprintf(h->lyt.fp, "%s%u ", sep, nskip);
768 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
769 sep = ", "; f |= f_any;
770 }
771
772 if (f&f_any) fputc(')', h->lyt.fp);
773
774 #undef f_any
775 }
776
777 /* --- @human_esession@ --- *
778 *
779 * Arguments: @struct tvec_output *o@ = output sink, secretly a
780 * @struct human_output@
781 *
782 * Returns: Suggested exit code.
783 *
784 * Use: End a test session.
785 *
786 * The human driver prints a final summary of the rest results
787 * and returns a suitable exit code.
788 */
789
790 static int human_esession(struct tvec_output *o)
791 {
792 struct human_output *h = (struct human_output *)o;
793 struct tvec_state *tv = h->tv;
794 unsigned
795 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
796 all_xfail = tv->all[TVOUT_XFAIL],
797 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
798 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
799 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
800 grps_run = grps_win + grps_lose;
801
802 if (!all_lose) {
803 setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
804 fprintf(h->lyt.fp, " %s%u %s",
805 !(all_skip || grps_skip) ? "all " : "",
806 all_pass, all_pass == 1 ? "test" : "tests");
807 report_unusual(h, all_xfail, all_skip);
808 fprintf(h->lyt.fp, " in %u %s",
809 grps_win, grps_win == 1 ? "group" : "groups");
810 report_unusual(h, 0, grps_skip);
811 } else {
812 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
813 fprintf(h->lyt.fp, " %u out of %u %s",
814 all_lose, all_run, all_run == 1 ? "test" : "tests");
815 report_unusual(h, all_xfail, all_skip);
816 fprintf(h->lyt.fp, " in %u out of %u %s",
817 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
818 report_unusual(h, 0, grps_skip);
819 }
820 fputc('\n', h->lyt.fp);
821
822 if (tv->f&TVSF_ERROR) {
823 setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
824 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
825 }
826
827 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
828 }
829
830 /* --- @human_bgroup@ --- *
831 *
832 * Arguments: @struct tvec_output *o@ = output sink, secretly a
833 * @struct human_output@
834 *
835 * Returns: ---
836 *
837 * Use: Begin a test group.
838 *
839 * The human driver determines the length of the longest
840 * register name, resets the group progress scoreboard, and
841 * activates the progress display.
842 */
843
844 static void human_bgroup(struct tvec_output *o)
845 {
846 struct human_output *h = (struct human_output *)o;
847
848 h->maxlen = register_maxnamelen(h->tv);
849 dstr_reset(&h->scoreboard); show_progress(h);
850 }
851
852 /* --- @human_skipgroup@ --- *
853 *
854 * Arguments: @struct tvec_output *o@ = output sink, secretly a
855 * @struct human_output@
856 * @const char *excuse@, @va_list *ap@ = reason for skipping the
857 * group, or null
858 *
859 * Returns: ---
860 *
861 * Use: Report that a test group is being skipped.
862 *
863 * The human driver just reports the situation to its output
864 * stream.
865 */
866
867 static void human_skipgroup(struct tvec_output *o,
868 const char *excuse, va_list *ap)
869 {
870 struct human_output *h = (struct human_output *)o;
871
872 if (!(h->f&HOF_TTY))
873 fprintf(h->lyt.fp, "%s ", h->tv->test->name);
874 else {
875 show_progress(h); h->f &= ~HOF_PROGRESS;
876 if (h->scoreboard.len) putc(' ', h->lyt.fp);
877 }
878 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
879 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
880 fputc('\n', h->lyt.fp);
881 }
882
883 /* --- @human_egroup@ --- *
884 *
885 * Arguments: @struct tvec_output *o@ = output sink, secretly a
886 * @struct human_output@
887 *
888 * Returns: ---
889 *
890 * Use: Report that a test group has finished.
891 *
892 * The human driver reports a summary of the group's tests.
893 */
894
895 static void human_egroup(struct tvec_output *o)
896 {
897 struct human_output *h = (struct human_output *)o;
898 struct tvec_state *tv = h->tv;
899 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
900 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
901 run = win + lose + xfail;
902
903 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
904 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
905
906 if (lose) {
907 fprintf(h->lyt.fp, " %u/%u ", lose, run);
908 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
909 report_unusual(h, xfail, skip);
910 } else {
911 fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
912 fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
913 report_unusual(h, xfail, skip);
914 }
915 fputc('\n', h->lyt.fp);
916 }
917
918 /* --- @human_btest@ --- *
919 *
920 * Arguments: @struct tvec_output *o@ = output sink, secretly a
921 * @struct human_output@
922 *
923 * Returns: ---
924 *
925 * Use: Report that a test is starting.
926 *
927 * The human driver makes sure the progress display is active.
928 */
929
930 static void human_btest(struct tvec_output *o)
931 { struct human_output *h = (struct human_output *)o; show_progress(h); }
932
933 /* --- @human_report_location@ --- *
934 *
935 * Arguments: @struct human_output *h@ = output state
936 * @FILE *fp@ = stream to write the location on
937 * @const char *file@ = filename
938 * @unsigned lno@ = line number
939 *
940 * Returns: ---
941 *
942 * Use: Print the filename and line number to the output stream @fp@.
943 * Also, if appropriate, print interleaved highlighting control
944 * codes to our usual output stream. If @file@ is null then do
945 * nothing.
946 */
947
948 static void human_report_location(struct human_output *h, FILE *fp,
949 const char *file, unsigned lno)
950 {
951 unsigned f = 0;
952 #define f_flush 1u
953
954 /* We emit highlighting if @fp@ is our usual output stream, or the
955 * duplicate-errors flag is clear indicating that (we assume) they're
956 * secretly going to the same place anyway. If they're different streams,
957 * though, we have to be careful to keep the highlighting and the actual
958 * text synchronized.
959 */
960
961 if (!file)
962 /* nothing to do */;
963 else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
964 fprintf(fp, "%s:%u: ", file, lno);
965 else {
966 if (fp != h->lyt.fp) f |= f_flush;
967
968 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
969
970 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
971 fputs(file, fp); FLUSH(fp);
972 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
973 fputc(':', fp); FLUSH(fp);
974 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
975 fprintf(fp, "%u", lno); FLUSH(fp);
976 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
977 fputc(':', fp); FLUSH(fp);
978 setattr(h, HA_PLAIN); FLUSH(h->lyt.fp);
979 fputc(' ', fp);
980
981 #undef FLUSH
982 }
983
984 #undef f_flush
985 }
986
987 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
988 *
989 * Arguments: @struct tvec_output *o@ = output sink, secretly a
990 * @struct human_output@
991 * @unsigned attr@ = attribute to apply to the outcome
992 * @const char *outcome@ = outcome string to report
993 * @const char *detail@, @va_list *ap@ = a detail message
994 * @const char *excuse@, @va_list *ap@ = reason for skipping the
995 * test
996 *
997 * Returns: ---
998 *
999 * Use: Report that a test has been skipped or failed.
1000 *
1001 * The human driver reports the situation on its output stream.
1002 */
1003
1004 static void human_outcome(struct tvec_output *o,
1005 unsigned attr, const char *outcome,
1006 const char *detail, va_list *ap)
1007 {
1008 struct human_output *h = (struct human_output *)o;
1009 struct tvec_state *tv = h->tv;
1010
1011 clear_progress(h);
1012 human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
1013 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
1014 setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
1015 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
1016 fputc('\n', h->lyt.fp);
1017 }
1018
1019 static void human_skip(struct tvec_output *o,
1020 const char *excuse, va_list *ap)
1021 { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
1022 static void human_fail(struct tvec_output *o,
1023 const char *detail, va_list *ap)
1024 { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
1025
1026 /* --- @human_dumpreg@ --- *
1027 *
1028 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1029 * @struct human_output@
1030 * @unsigned disp@ = register disposition
1031 * @const union tvec_regval *rv@ = register value
1032 * @const struct tvec_regdef *rd@ = register definition
1033 *
1034 * Returns: ---
1035 *
1036 * Use: Dump a register.
1037 *
1038 * The human driver applies highlighting to mismatching output
1039 * registers, but otherwise delegates to the register type
1040 * handler and the layout machinery.
1041 */
1042
1043 static void human_dumpreg(struct tvec_output *o,
1044 unsigned disp, const union tvec_regval *rv,
1045 const struct tvec_regdef *rd)
1046 {
1047 struct human_output *h = (struct human_output *)o;
1048 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1049
1050 clear_progress(h);
1051 gprintf(&human_printops, h, "%*s%s %s = ",
1052 10 + h->maxlen - n, "", ds, rd->name);
1053 if (h->f&HOF_COLOUR) {
1054 if (!rv) setattr(h, HA_UNSET);
1055 else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
1056 else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
1057 }
1058 if (!rv) gprintf(&human_printops, h, "#unset");
1059 else rd->ty->dump(rv, rd, 0, &human_printops, h);
1060 setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
1061 }
1062
1063 /* --- @human_etest@ --- *
1064 *
1065 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1066 * @struct human_output@
1067 * @unsigned outcome@ = the test outcome
1068 *
1069 * Returns: ---
1070 *
1071 * Use: Report that a test has finished.
1072 *
1073 * The human driver reactivates the progress display, if
1074 * necessary, and adds a new character for the completed test.
1075 */
1076
1077 static void human_etest(struct tvec_output *o, unsigned outcome)
1078 {
1079 struct human_output *h = (struct human_output *)o;
1080 int ch;
1081
1082 if (h->f&HOF_TTY) {
1083 show_progress(h);
1084 switch (outcome) {
1085 case TVOUT_WIN: ch = HSB_WIN; break;
1086 case TVOUT_LOSE: ch = HSB_LOSE; break;
1087 case TVOUT_XFAIL: ch = HSB_XFAIL; break;
1088 case TVOUT_SKIP: ch = HSB_SKIP; break;
1089 default: abort();
1090 }
1091 dstr_putc(&h->scoreboard, ch);
1092 write_scoreboard_char(h, ch); fflush(h->lyt.fp);
1093 }
1094 }
1095
1096 /* --- @human_report@ --- *
1097 *
1098 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1099 * @struct human_output@
1100 * @unsigned level@ = message level (@TVLEV_...@)
1101 * @const char *msg@, @va_list *ap@ = format string and
1102 * arguments
1103 *
1104 * Returns: ---
1105 *
1106 * Use: Report a message to the user.
1107 *
1108 * The human driver arranges to show the message on @stderr@ as
1109 * well as the usual output, with a certain amount of
1110 * intelligence in case they're both actually the same device.
1111 */
1112
1113 static void human_report(struct tvec_output *o, unsigned level,
1114 const char *msg, va_list *ap)
1115 {
1116 struct human_output *h = (struct human_output *)o;
1117 struct tvec_state *tv = h->tv;
1118 const char *levstr; unsigned levattr;
1119 dstr d = DSTR_INIT;
1120 unsigned f = 0;
1121 #define f_flush 1u
1122 #define f_progress 2u
1123
1124 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1125
1126 switch (level) {
1127 #define CASE(tag, name, val) \
1128 case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1129 TVEC_LEVELS(CASE)
1130 default: levstr = "??"; levattr = HA_UNKLEV; break;
1131 }
1132
1133 if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush;
1134
1135 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1136
1137 if (h->f^HOF_PROGRESS)
1138 { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
1139 fprintf(stderr, "%s: ", QUIS);
1140 human_report_location(h, stderr, tv->infile, tv->lno);
1141 setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
1142 fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
1143
1144 #undef FLUSH
1145
1146 if (h->f&HOF_DUPERR) {
1147 human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
1148 fprintf(h->lyt.fp, "%s: ", levstr);
1149 fwrite(d.buf, 1, d.len, h->lyt.fp);
1150 }
1151 if (f&f_progress) show_progress(h);
1152
1153 dstr_destroy(&d);
1154
1155 #undef f_flush
1156 #undef f_progress
1157 }
1158
1159 /* --- @human_bbench@ --- *
1160 *
1161 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1162 * @struct human_output@
1163 * @const char *desc@ = adhoc test description
1164 * @unsigned unit@ = measurement unit (@BTU_...@)
1165 *
1166 * Returns: ---
1167 *
1168 * Use: Report that a benchmark has started.
1169 *
1170 * The human driver just prints the start of the benchmark
1171 * report.
1172 */
1173
1174 static void human_bbench(struct tvec_output *o,
1175 const char *desc, unsigned unit)
1176 {
1177 struct human_output *h = (struct human_output *)o;
1178 struct tvec_state *tv = h->tv;
1179
1180 clear_progress(h);
1181 gprintf(&human_printops, h, "%s ", tv->test->name);
1182 if (desc) gprintf(&human_printops, h, "%s", desc);
1183 else print_ident(tv, TVSF_COMPACT, &human_printops, h);
1184 gprintf(&human_printops, h, ": ");
1185 if (h->f&HOF_TTY) fflush(h->lyt.fp);
1186 }
1187
1188 /* --- @human_ebench@ --- *
1189 *
1190 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1191 * @struct human_output@
1192 * @const char *desc@ = adhoc test description
1193 * @unsigned unit@ = measurement unit (@BTU_...@)
1194 * @const struct bench_timing *t@ = measurement
1195 *
1196 * Returns: ---
1197 *
1198 * Use: Report a benchmark's results.
1199 *
1200 * The human driver just delegates to the default benchmark
1201 * reporting, via the layout machinery.
1202 */
1203
1204 static void human_ebench(struct tvec_output *o,
1205 const char *desc, unsigned unit,
1206 const struct bench_timing *t)
1207 {
1208 struct human_output *h = (struct human_output *)o;
1209
1210 tvec_benchreport(&human_printops, h, unit, 0, t);
1211 layout_char(&h->lyt, '\n');
1212 }
1213
1214 static const struct tvec_benchoutops human_benchops =
1215 { human_bbench, human_ebench };
1216
1217 /* --- @human_extend@ --- *
1218 *
1219 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1220 * @struct human_output@
1221 * @const char *name@ = extension name
1222 *
1223 * Returns: A pointer to the extension implementation, or null.
1224 */
1225
1226 static const void *human_extend(struct tvec_output *o, const char *name)
1227 {
1228 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
1229 else return (0);
1230 }
1231
1232 /* --- @human_destroy@ --- *
1233 *
1234 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1235 * @struct human_output@
1236 *
1237 * Returns: ---
1238 *
1239 * Use: Release the resources held by the output driver.
1240 */
1241
1242 static void human_destroy(struct tvec_output *o)
1243 {
1244 struct human_output *h = (struct human_output *)o;
1245
1246 destroy_layout(&h->lyt,
1247 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
1248 dstr_destroy(&h->scoreboard);
1249 x_free(h->a, h->outbuf); x_free(h->a, h);
1250 }
1251
1252 static const struct tvec_outops human_ops = {
1253 human_bsession, human_esession,
1254 human_bgroup, human_skipgroup, human_egroup,
1255 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
1256 human_report, human_extend, human_destroy
1257 };
1258
1259 /* --- @tvec_humanoutput@ --- *
1260 *
1261 * Arguments: @FILE *fp@ = output file to write on
1262 *
1263 * Returns: An output formatter.
1264 *
1265 * Use: Return an output formatter which writes on @fp@ with the
1266 * expectation that a human will be watching and interpreting
1267 * the output. If @fp@ denotes a terminal, the display shows a
1268 * `scoreboard' indicating the outcome of each test case
1269 * attempted, and may in addition use colour and other
1270 * highlighting.
1271 */
1272
1273 struct tvec_output *tvec_humanoutput(FILE *fp)
1274 {
1275 struct human_output *h;
1276 const char *p;
1277
1278 XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
1279 h->f = 0; h->attr = 0;
1280
1281 init_layout(&h->lyt, fp, 0);
1282 h->outbuf = 0; h->outsz = 0;
1283
1284 switch (getenv_boolean("TVEC_TTY", -1)) {
1285 case 1: h->f |= HOF_TTY; break;
1286 case 0: break;
1287 default:
1288 if (isatty(fileno(fp))) h->f |= HOF_TTY;
1289 break;
1290 }
1291 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1292 case 1: h->f |= HOF_COLOUR; break;
1293 case 0: break;
1294 default:
1295 if (h->f&HOF_TTY) {
1296 p = getenv("TERM");
1297 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1298 }
1299 break;
1300 }
1301
1302 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1303 dstr_create(&h->scoreboard);
1304 return (&h->_o);
1305 }
1306
1307 /*----- Machine-readable output -------------------------------------------*/
1308
1309 struct machine_output {
1310 struct tvec_output _o; /* output base class */
1311 struct tvec_state *tv; /* stashed testing state */
1312 arena *a; /* arena for memory allocation */
1313 FILE *fp; /* output stream */
1314 char *outbuf; size_t outsz; /* buffer for formatted output */
1315 unsigned grpix, testix; /* group and test indices */
1316 unsigned f; /* flags */
1317 #define MF_BENCH 1u
1318 };
1319
1320 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1321 *
1322 * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
1323 * @int ch@ = character to write
1324 * @const char *@p@, @size_t sz@ = string (with explicit length)
1325 * to write
1326 * @const char *p, ...@ = format control string and arguments to
1327 * write
1328 *
1329 * Returns: ---
1330 *
1331 * Use: Write characters, strings, or formatted strings to the
1332 * output, applying appropriate quoting.
1333 */
1334
1335 static void machine_escape(struct machine_output *m, int ch)
1336 {
1337 switch (ch) {
1338 case 0: fputs("\\0", m->fp); break;
1339 case '\a': fputs("\\a", m->fp); break;
1340 case '\b': fputs("\\b", m->fp); break;
1341 case '\x1b': fputs("\\e", m->fp); break;
1342 case '\f': fputs("\\f", m->fp); break;
1343 case '\n': fputs("\\n", m->fp); break;
1344 case '\r': fputs("\\r", m->fp); break;
1345 case '\t': fputs("\\t", m->fp); break;
1346 case '\v': fputs("\\v", m->fp); break;
1347 case '"': fputs("\\\"", m->fp); break;
1348 case '\\': fputs("\\\\", m->fp); break;
1349 default: fprintf(m->fp, "\\x{%02x}", ch);
1350 }
1351 }
1352
1353 static int machine_writech(void *go, int ch)
1354 {
1355 struct machine_output *m = go;
1356
1357 if (ISPRINT(ch)) putc(ch, m->fp);
1358 else machine_escape(m, ch);
1359 return (1);
1360 }
1361
1362 static int machine_writem(void *go, const char *p, size_t sz)
1363 {
1364 struct machine_output *m = go;
1365 const char *q, *l = p + sz;
1366
1367 for (;;) {
1368 q = p;
1369 for (;;) {
1370 if (q >= l) goto final;
1371 if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
1372 q++;
1373 }
1374 if (p < q) fwrite(p, 1, q - p, m->fp);
1375 p = q; machine_escape(m, (unsigned char)*p++);
1376 }
1377 final:
1378 if (p < l) fwrite(p, 1, l - p, m->fp);
1379 return (sz);
1380 }
1381
1382 static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
1383 {
1384 struct machine_output *m = go;
1385 int n;
1386 va_list ap;
1387
1388 va_start(ap, p);
1389 n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
1390 va_end(ap);
1391 return (machine_writem(m, m->outbuf, n));
1392 }
1393
1394 static const struct gprintf_ops machine_printops =
1395 { machine_writech, machine_writem, machine_nwritef };
1396
1397 /* --- @machine_maybe_quote@ --- *
1398 *
1399 * Arguments: @struct machine_output *m@ = output sink
1400 * @const char *p@ = pointer to string
1401 *
1402 * Returns: ---
1403 *
1404 * Use: Print the string @p@, quoting it if necessary.
1405 */
1406
1407 static void machine_maybe_quote(struct machine_output *m, const char *p)
1408 {
1409 const char *q;
1410
1411 for (q = p; *q; q++)
1412 if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
1413 fputs(p, m->fp); return;
1414 quote:
1415 putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
1416 }
1417
1418 /* --- @machine_bsession@ --- *
1419 *
1420 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1421 * @struct machine_output@
1422 * @struct tvec_state *tv@ = the test state producing output
1423 *
1424 * Returns: ---
1425 *
1426 * Use: Begin a test session.
1427 *
1428 * The TAP driver records the test state for later reference,
1429 * initializes the group index counter, and prints the version
1430 * number.
1431 */
1432
1433 static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
1434 {
1435 struct machine_output *m = (struct machine_output *)o;
1436
1437 m->tv = tv; m->grpix = 0;
1438 }
1439
1440 /* --- @machine_show_stats@ --- *
1441 *
1442 * Arguments: @struct machine_output *m@ = output sink
1443 * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1444 *
1445 * Returns: ---
1446 *
1447 * Use: Print a machine readable outcome statistics table
1448 */
1449
1450 static void machine_show_stats(struct machine_output *m,
1451 unsigned out[TVOUT_LIMIT])
1452 {
1453 static const char *outtab[] = { "lose", "skip", "xfail", "win" };
1454 unsigned i;
1455
1456 for (i = 0; i < TVOUT_LIMIT; i++) {
1457 if (i) putc(' ', m->fp);
1458 fprintf(m->fp, "%s=%u", outtab[i], out[i]);
1459 }
1460 }
1461
1462 /* --- @machine_esession@ --- *
1463 *
1464 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1465 * @struct machine_output@
1466 *
1467 * Returns: Suggested exit code.
1468 *
1469 * Use: End a test session.
1470 *
1471 * The TAP driver prints a final summary of the rest results
1472 * and returns a suitable exit code. If errors occurred, it
1473 * instead prints a `Bail out!' line forcing the reader to
1474 * report a failure.
1475 */
1476
1477 static int machine_esession(struct tvec_output *o)
1478 {
1479 struct machine_output *m = (struct machine_output *)o;
1480 struct tvec_state *tv = m->tv;
1481
1482 fputs("END groups: ", m->fp);
1483 machine_show_stats(m, tv->grps);
1484 fputs("; tests: ", m->fp);
1485 machine_show_stats(m, tv->all);
1486 putc('\n', m->fp);
1487 m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
1488 }
1489
1490 /* --- @machine_bgroup@ --- *
1491 *
1492 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1493 * @struct machine_output@
1494 *
1495 * Returns: ---
1496 *
1497 * Use: Begin a test group.
1498 *
1499 * The TAP driver determines the length of the longest
1500 * register name, resets the group progress scoreboard, and
1501 * activates the progress display.
1502 */
1503
1504 static void machine_bgroup(struct tvec_output *o)
1505 {
1506 struct machine_output *m = (struct machine_output *)o;
1507 struct tvec_state *tv = m->tv;
1508
1509 fputs("BGROUP ", m->fp);
1510 machine_maybe_quote(m, tv->test->name);
1511 putc('\n', m->fp);
1512 m->grpix++; m->testix = 0;
1513 }
1514
1515 /* --- @machine_skipgroup@ --- *
1516 *
1517 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1518 * @struct machine_output@
1519 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1520 * group, or null
1521 *
1522 * Returns: ---
1523 *
1524 * Use: Report that a test group is being skipped.
1525 *
1526 * The TAP driver just reports the situation to its output
1527 * stream.
1528 */
1529
1530 static void machine_skipgroup(struct tvec_output *o,
1531 const char *excuse, va_list *ap)
1532 {
1533 struct machine_output *m = (struct machine_output *)o;
1534 struct tvec_state *tv = m->tv;
1535
1536 fputs("SKIPGRP ", m->fp);
1537 machine_maybe_quote(m, tv->test->name);
1538 if (excuse) {
1539 fputs(" \"", m->fp);
1540 vgprintf(&machine_printops, m, excuse, ap);
1541 putc('\"', m->fp);
1542 }
1543 putc('\n', m->fp);
1544 }
1545
1546 /* --- @machine_egroup@ --- *
1547 *
1548 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1549 * @struct machine_output@
1550 *
1551 * Returns: ---
1552 *
1553 * Use: Report that a test group has finished.
1554 *
1555 * The TAP driver reports a summary of the group's tests.
1556 */
1557
1558 static void machine_egroup(struct tvec_output *o)
1559 {
1560 struct machine_output *m = (struct machine_output *)o;
1561 struct tvec_state *tv = m->tv;
1562
1563 if (!(tv->f&TVSF_SKIP)) {
1564 fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
1565 putc(' ', m->fp); machine_show_stats(m, tv->curr);
1566 putc('\n', m->fp);
1567 }
1568 }
1569
1570 /* --- @machine_btest@ --- *
1571 *
1572 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1573 * @struct machine_output@
1574 *
1575 * Returns: ---
1576 *
1577 * Use: Report that a test is starting.
1578 *
1579 * The TAP driver advances its test counter. (We could do this
1580 * by adding up up the counters in @tv->curr@, and add on the
1581 * current test, but it's easier this way.)
1582 */
1583
1584 static void machine_btest(struct tvec_output *o) { ; }
1585
1586 /* --- @machine_report_location@ --- *
1587 *
1588 * Arguments: @struct human_output *h@ = output state
1589 * @const char *file@ = filename
1590 * @unsigned lno@ = line number
1591 *
1592 * Returns: ---
1593 *
1594 * Use: Print the filename and line number to the output stream.
1595 */
1596
1597 static void machine_report_location(struct machine_output *m,
1598 const char *file, unsigned lno)
1599 {
1600 if (file) {
1601 putc(' ', m->fp);
1602 machine_maybe_quote(m, file);
1603 fprintf(m->fp, ":%u", lno);
1604 }
1605 }
1606
1607 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1608 *
1609 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1610 * @struct machine_output@
1611 * @const char *outcome@ = outcome string to report
1612 * @const char *detail@, @va_list *ap@ = a detail message
1613 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1614 * test
1615 *
1616 * Returns: ---
1617 *
1618 * Use: Report that a test has been skipped or failed.
1619 */
1620
1621 static void machine_outcome(struct tvec_output *o, const char *outcome,
1622 const char *detail, va_list *ap)
1623 {
1624 struct machine_output *m = (struct machine_output *)o;
1625 struct tvec_state *tv = m->tv;
1626
1627 fprintf(m->fp, "%s %u", outcome, m->testix);
1628 machine_report_location(m, tv->infile, tv->test_lno);
1629 if (detail)
1630 { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
1631 putc('\n', m->fp);
1632 }
1633
1634 static void machine_skip(struct tvec_output *o,
1635 const char *excuse, va_list *ap)
1636 { machine_outcome(o, "SKIP", excuse, ap); }
1637 static void machine_fail(struct tvec_output *o,
1638 const char *detail, va_list *ap)
1639 { machine_outcome(o, "FAIL", detail, ap); }
1640
1641 /* --- @machine_dumpreg@ --- *
1642 *
1643 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1644 * @struct machine_output@
1645 * @unsigned disp@ = register disposition
1646 * @const union tvec_regval *rv@ = register value
1647 * @const struct tvec_regdef *rd@ = register definition
1648 *
1649 * Returns: ---
1650 *
1651 * Use: Dump a register.
1652 *
1653 * The machine driver applies highlighting to mismatching output
1654 * registers, but otherwise delegates to the register type
1655 * handler.
1656 */
1657
1658 static void machine_dumpreg(struct tvec_output *o,
1659 unsigned disp, const union tvec_regval *rv,
1660 const struct tvec_regdef *rd)
1661 {
1662 struct machine_output *m = (struct machine_output *)o;
1663 unsigned f = 0;
1664 #define f_reg 1u
1665 #define f_nl 2u
1666
1667 switch (disp) {
1668 case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
1669 case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
1670 case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
1671 case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
1672 case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
1673 default: abort();
1674 }
1675
1676 if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
1677 if (!rv) fputs("#unset", m->fp);
1678 else rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
1679 if (f&f_nl) putc('\n', m->fp);
1680
1681 #undef f_reg
1682 #undef f_newline
1683 }
1684
1685 /* --- @machine_etest@ --- *
1686 *
1687 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1688 * @struct machine_output@
1689 * @unsigned outcome@ = the test outcome
1690 *
1691 * Returns: ---
1692 *
1693 * Use: Report that a test has finished.
1694 *
1695 * The machine driver reports the outcome of the test, if that's
1696 * not already decided.
1697 */
1698
1699 static void machine_etest(struct tvec_output *o, unsigned outcome)
1700 {
1701 struct machine_output *m = (struct machine_output *)o;
1702
1703 if (!(m->f&MF_BENCH)) switch (outcome) {
1704 case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
1705 case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
1706 }
1707 m->testix++; m->f &= ~MF_BENCH;
1708 }
1709
1710 /* --- @machine_report@ --- *
1711 *
1712 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1713 * @struct machine_output@
1714 * @unsigned level@ = message level (@TVLEV_...@)
1715 * @const char *msg@, @va_list *ap@ = format string and
1716 * arguments
1717 *
1718 * Returns: ---
1719 *
1720 * Use: Report a message to the user.
1721 *
1722 * Each report level has its own output tag
1723 */
1724
1725 static void machine_report(struct tvec_output *o, unsigned level,
1726 const char *msg, va_list *ap)
1727 {
1728 struct machine_output *m = (struct machine_output *)o;
1729 struct tvec_state *tv = m->tv;
1730 const char *p;
1731
1732 for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
1733 machine_report_location(m, tv->infile, tv->lno);
1734 fputs(" \"", m->fp); vgprintf(&machine_printops, m, msg, ap);
1735 putc('"', m->fp);
1736 putc('\n', m->fp);
1737 }
1738
1739 /* --- @machine_bbench@ --- *
1740 *
1741 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1742 * @struct machine_output@
1743 * @const char *desc@ = adhoc test description
1744 * @unsigned unit@ = measurement unit (@BTU_...@)
1745 *
1746 * Returns: ---
1747 *
1748 * Use: Report that a benchmark has started.
1749 *
1750 * The machine driver does nothing here. All of the reporting
1751 * happens in @machine_ebench@.
1752 */
1753
1754 static void machine_bbench(struct tvec_output *o,
1755 const char *desc, unsigned unit)
1756 { ; }
1757
1758 /* --- @machine_ebench@ --- *
1759 *
1760 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1761 * @struct machine_output@
1762 * @const char *desc@ = adhoc test description
1763 * @unsigned unit@ = measurement unit (@BTU_...@)
1764 * @const struct bench_timing *t@ = measurement
1765 *
1766 * Returns: ---
1767 *
1768 * Use: Report a benchmark's results.
1769 *
1770 * The machine driver prints the result as a `BENCH' output
1771 * line, in raw format.
1772 */
1773
1774 static void machine_ebench(struct tvec_output *o,
1775 const char *desc, unsigned unit,
1776 const struct bench_timing *t)
1777 {
1778 struct machine_output *m = (struct machine_output *)o;
1779 struct tvec_state *tv = m->tv;
1780
1781 fprintf(m->fp, "BENCH %u", m->testix);
1782 machine_report_location(m, tv->infile, tv->test_lno);
1783 putc(' ', m->fp);
1784 if (desc) machine_maybe_quote(m, desc);
1785 else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
1786 fputs(": ", m->fp);
1787 tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
1788 putc('\n', m->fp); m->f |= MF_BENCH;
1789 }
1790
1791 static const struct tvec_benchoutops machine_benchops =
1792 { machine_bbench, machine_ebench };
1793
1794 /* --- @machine_extend@ --- *
1795 *
1796 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1797 * @struct machine_output@
1798 * @const char *name@ = extension name
1799 *
1800 * Returns: A pointer to the extension implementation, or null.
1801 */
1802
1803 static const void *machine_extend(struct tvec_output *o, const char *name)
1804 {
1805 if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
1806 else return (0);
1807 }
1808
1809 /* --- @machine_destroy@ --- *
1810 *
1811 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1812 * @struct machine_output@
1813 *
1814 * Returns: ---
1815 *
1816 * Use: Release the resources held by the output driver.
1817 */
1818
1819 static void machine_destroy(struct tvec_output *o)
1820 {
1821 struct machine_output *m = (struct machine_output *)o;
1822
1823 if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
1824 x_free(m->a, m->outbuf); x_free(m->a, m);
1825 }
1826
1827 static const struct tvec_outops machine_ops = {
1828 machine_bsession, machine_esession,
1829 machine_bgroup, machine_skipgroup, machine_egroup,
1830 machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
1831 machine_report, machine_extend, machine_destroy
1832 };
1833
1834 /* --- @tvec_machineoutput@ --- *
1835 *
1836 * Arguments: @FILE *fp@ = output file to write on
1837 *
1838 * Returns: An output formatter.
1839 *
1840 * Use: Return an output formatter which writes on @fp@ in a
1841 * moderately simple machine-readable format.
1842 */
1843
1844 struct tvec_output *tvec_machineoutput(FILE *fp)
1845 {
1846 struct machine_output *m;
1847
1848 XNEW(m); m->a = arena_global; m->_o.ops = &machine_ops;
1849 m->f = 0; m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
1850 return (&m->_o);
1851 }
1852
1853 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1854
1855 struct tap_output {
1856 struct tvec_output _o; /* output base class */
1857 struct tvec_state *tv; /* stashed testing state */
1858 arena *a; /* arena for memory allocation */
1859 struct layout lyt; /* output layout */
1860 char *outbuf; size_t outsz; /* buffer for formatted output */
1861 unsigned grpix, testix; /* group and test indices */
1862 unsigned previx; /* previously reported test index */
1863 int maxlen; /* longest register name */
1864 };
1865
1866 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1867 *
1868 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1869 * @int ch@ = character to write
1870 * @const char *@p@, @size_t sz@ = string (with explicit length)
1871 * to write
1872 * @const char *p, ...@ = format control string and arguments to
1873 * write
1874 *
1875 * Returns: ---
1876 *
1877 * Use: Write characters, strings, or formatted strings to the
1878 * output, applying appropriate layout.
1879 *
1880 * For the TAP output driver, the layout machinery prefixes each
1881 * line with ` ## ' and strips trailing spaces.
1882 */
1883
1884 static int tap_writech(void *go, int ch)
1885 {
1886 struct tap_output *t = go;
1887
1888 if (layout_char(&t->lyt, ch)) return (-1);
1889 else return (1);
1890 }
1891
1892 static int tap_writem(void *go, const char *p, size_t sz)
1893 {
1894 struct tap_output *t = go;
1895
1896 if (layout_string(&t->lyt, p, sz)) return (-1);
1897 else return (sz);
1898 }
1899
1900 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1901 {
1902 struct tap_output *t = go;
1903 size_t n;
1904 va_list ap;
1905
1906 va_start(ap, p);
1907 n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
1908 va_end(ap);
1909 if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
1910 return (n);
1911 }
1912
1913 static const struct gprintf_ops tap_printops =
1914 { tap_writech, tap_writem, tap_nwritef };
1915
1916 /* --- @tap_bsession@ --- *
1917 *
1918 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1919 * @struct tap_output@
1920 * @struct tvec_state *tv@ = the test state producing output
1921 *
1922 * Returns: ---
1923 *
1924 * Use: Begin a test session.
1925 *
1926 * The TAP driver records the test state for later reference,
1927 * initializes the group index counter, and prints the version
1928 * number.
1929 */
1930
1931 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1932 {
1933 struct tap_output *t = (struct tap_output *)o;
1934
1935 t->tv = tv; t->grpix = 0;
1936 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1937 }
1938
1939 /* --- @tap_esession@ --- *
1940 *
1941 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1942 * @struct tap_output@
1943 *
1944 * Returns: Suggested exit code.
1945 *
1946 * Use: End a test session.
1947 *
1948 * The TAP driver prints a final summary of the rest results
1949 * and returns a suitable exit code. If errors occurred, it
1950 * instead prints a `Bail out!' line forcing the reader to
1951 * report a failure.
1952 */
1953
1954 static int tap_esession(struct tvec_output *o)
1955 {
1956 struct tap_output *t = (struct tap_output *)o;
1957 struct tvec_state *tv = t->tv;
1958
1959 if (tv->f&TVSF_ERROR) {
1960 fputs("Bail out! "
1961 "Errors found in input; tests may not have run correctly\n",
1962 t->lyt.fp);
1963 return (2);
1964 }
1965
1966 fprintf(t->lyt.fp, "1..%u\n", t->grpix);
1967 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1968 }
1969
1970 /* --- @tap_bgroup@ --- *
1971 *
1972 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1973 * @struct tap_output@
1974 *
1975 * Returns: ---
1976 *
1977 * Use: Begin a test group.
1978 *
1979 * The TAP driver determines the length of the longest
1980 * register name, resets the group progress scoreboard, and
1981 * activates the progress display.
1982 */
1983
1984 static void tap_bgroup(struct tvec_output *o)
1985 {
1986 struct tap_output *t = (struct tap_output *)o;
1987 struct tvec_state *tv = t->tv;
1988
1989 t->grpix++; t->testix = t->previx = 0;
1990 t->maxlen = register_maxnamelen(t->tv);
1991 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1992 }
1993
1994 /* --- @tap_skipgroup@ --- *
1995 *
1996 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1997 * @struct tap_output@
1998 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1999 * group, or null
2000 *
2001 * Returns: ---
2002 *
2003 * Use: Report that a test group is being skipped.
2004 *
2005 * The TAP driver just reports the situation to its output
2006 * stream.
2007 */
2008
2009 static void tap_skipgroup(struct tvec_output *o,
2010 const char *excuse, va_list *ap)
2011 {
2012 struct tap_output *t = (struct tap_output *)o;
2013
2014 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
2015 fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
2016 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
2017 fputc('\n', t->lyt.fp);
2018 }
2019
2020 /* --- @tap_egroup@ --- *
2021 *
2022 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2023 * @struct tap_output@
2024 *
2025 * Returns: ---
2026 *
2027 * Use: Report that a test group has finished.
2028 *
2029 * The TAP driver reports a summary of the group's tests.
2030 */
2031
2032 static void tap_egroup(struct tvec_output *o)
2033 {
2034 struct tap_output *t = (struct tap_output *)o;
2035 struct tvec_state *tv = t->tv;
2036
2037 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
2038 fprintf(t->lyt.fp, "%s %u - %s\n",
2039 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
2040 t->grpix, tv->test->name);
2041 }
2042
2043 /* --- @tap_btest@ --- *
2044 *
2045 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2046 * @struct tap_output@
2047 *
2048 * Returns: ---
2049 *
2050 * Use: Report that a test is starting.
2051 *
2052 * The TAP driver advances its test counter. (We could do this
2053 * by adding up up the counters in @tv->curr@, and add on the
2054 * current test, but it's easier this way.)
2055 */
2056
2057 static void tap_btest(struct tvec_output *o)
2058 { struct tap_output *t = (struct tap_output *)o; t->testix++; }
2059
2060 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2061 *
2062 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2063 * @struct tap_output@
2064 * @const char *head, *tail@ = outcome strings to report
2065 * @const char *detail@, @va_list *ap@ = a detail message
2066 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2067 * test
2068 *
2069 * Returns: ---
2070 *
2071 * Use: Report that a test has been skipped or failed.
2072 *
2073 * The TAP driver reports the situation on its output stream.
2074 * TAP only allows us to report a single status for each
2075 * subtest, so we notice when we've already reported a status
2076 * for the current test and convert the second report as a
2077 * comment. This should only happen in the case of multiple
2078 * failures.
2079 */
2080
2081 static void tap_outcome(struct tvec_output *o,
2082 const char *head, const char *tail,
2083 const char *detail, va_list *ap)
2084 {
2085 struct tap_output *t = (struct tap_output *)o;
2086 struct tvec_state *tv = t->tv;
2087
2088 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
2089 t->testix == t->previx ? "##" : head,
2090 t->testix, tv->infile, tv->test_lno, tail);
2091 if (detail)
2092 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
2093 fputc('\n', t->lyt.fp);
2094 t->previx = t->testix;
2095 }
2096
2097 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
2098 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
2099 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
2100 { tap_outcome(o, "not ok", "", detail, ap); }
2101
2102 /* --- @tap_dumpreg@ --- *
2103 *
2104 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2105 * @struct tap_output@
2106 * @unsigned disp@ = register disposition
2107 * @const union tvec_regval *rv@ = register value
2108 * @const struct tvec_regdef *rd@ = register definition
2109 *
2110 * Returns: ---
2111 *
2112 * Use: Dump a register.
2113 *
2114 * The TAP driver applies highlighting to mismatching output
2115 * registers, but otherwise delegates to the register type
2116 * handler and the layout machinery. The result is that the
2117 * register dump is marked as a comment and indented.
2118 */
2119
2120 static void tap_dumpreg(struct tvec_output *o,
2121 unsigned disp, const union tvec_regval *rv,
2122 const struct tvec_regdef *rd)
2123 {
2124 struct tap_output *t = (struct tap_output *)o;
2125 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
2126
2127 set_layout_prefix(&t->lyt, " ## ");
2128 gprintf(&tap_printops, t, "%*s%s %s = ",
2129 10 + t->maxlen - n, "", ds, rd->name);
2130 if (!rv) gprintf(&tap_printops, t, "#<unset>");
2131 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
2132 layout_char(&t->lyt, '\n');
2133 }
2134
2135 /* --- @tap_etest@ --- *
2136 *
2137 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2138 * @struct tap_output@
2139 * @unsigned outcome@ = the test outcome
2140 *
2141 * Returns: ---
2142 *
2143 * Use: Report that a test has finished.
2144 *
2145 * The TAP driver reports the outcome of the test, if that's not
2146 * already decided.
2147 */
2148
2149 static void tap_etest(struct tvec_output *o, unsigned outcome)
2150 {
2151 switch (outcome) {
2152 case TVOUT_WIN:
2153 tap_outcome(o, "ok", "", 0, 0);
2154 break;
2155 case TVOUT_XFAIL:
2156 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
2157 break;
2158 }
2159 }
2160
2161 /* --- @tap_report@ --- *
2162 *
2163 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2164 * @struct tap_output@
2165 * @unsigned level@ = message level (@TVLEV_...@)
2166 * @const char *msg@, @va_list *ap@ = format string and
2167 * arguments
2168 *
2169 * Returns: ---
2170 *
2171 * Use: Report a message to the user.
2172 *
2173 * Messages are reported as comments, so that they can be
2174 * accumulated by the reader. An error will cause a later
2175 * bailout or, if we crash before then, a missing plan line,
2176 * either of which will cause the reader to report a serious
2177 * problem.
2178 */
2179
2180 static void tap_report(struct tvec_output *o, unsigned level,
2181 const char *msg, va_list *ap)
2182 {
2183 struct tap_output *t = (struct tap_output *)o;
2184 struct tvec_state *tv = t->tv;
2185
2186 if (tv->test) set_layout_prefix(&t->lyt, " ## ");
2187 else set_layout_prefix(&t->lyt, "## ");
2188
2189 if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
2190 gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
2191 vgprintf(&tap_printops, t, msg, ap);
2192 layout_char(&t->lyt, '\n');
2193 }
2194
2195 /* --- @tap_extend@ --- *
2196 *
2197 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2198 * @struct tap_output@
2199 * @const char *name@ = extension name
2200 *
2201 * Returns: A pointer to the extension implementation, or null.
2202 */
2203
2204 static const void *tap_extend(struct tvec_output *o, const char *name)
2205 { return (0); }
2206
2207 /* --- @tap_destroy@ --- *
2208 *
2209 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2210 * @struct tap_output@
2211 *
2212 * Returns: ---
2213 *
2214 * Use: Release the resources held by the output driver.
2215 */
2216
2217 static void tap_destroy(struct tvec_output *o)
2218 {
2219 struct tap_output *t = (struct tap_output *)o;
2220
2221 destroy_layout(&t->lyt,
2222 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
2223 x_free(t->a, t->outbuf); x_free(t->a, t);
2224 }
2225
2226 static const struct tvec_outops tap_ops = {
2227 tap_bsession, tap_esession,
2228 tap_bgroup, tap_skipgroup, tap_egroup,
2229 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
2230 tap_report, tap_extend, tap_destroy
2231 };
2232
2233 /* --- @tvec_tapoutput@ --- *
2234 *
2235 * Arguments: @FILE *fp@ = output file to write on
2236 *
2237 * Returns: An output formatter.
2238 *
2239 * Use: Return an output formatter which writes on @fp@ in `TAP'
2240 * (`Test Anything Protocol') format.
2241 *
2242 * TAP comes from the Perl community, but has spread rather
2243 * further. This driver produces TAP version 14, but pretends
2244 * to be version 13. The driver produces a TAP `test point' --
2245 * i.e., a result reported as `ok' or `not ok' -- for each input
2246 * test group. Failure reports and register dumps are produced
2247 * as diagnostic messages before the final group result. (TAP
2248 * permits structuerd YAML data after the test-point result,
2249 * which could be used to report details, but (a) postponing the
2250 * details until after the report is inconvenient, and (b) there
2251 * is no standardization for the YAML anyway, so in practice
2252 * it's no more useful than the unstructured diagnostics.
2253 */
2254
2255 struct tvec_output *tvec_tapoutput(FILE *fp)
2256 {
2257 struct tap_output *t;
2258
2259 XNEW(t); t->a = arena_global; t->_o.ops = &tap_ops;
2260 init_layout(&t->lyt, fp, 0);
2261 t->outbuf = 0; t->outsz = 0;
2262 return (&t->_o);
2263 }
2264
2265 /*----- Default output ----------------------------------------------------*/
2266
2267 /* --- @tvec_dfltoutput@ --- *
2268 *
2269 * Arguments: @FILE *fp@ = output file to write on
2270 *
2271 * Returns: An output formatter.
2272 *
2273 * Use: Selects and instantiates an output formatter suitable for
2274 * writing on @fp@. The policy is subject to change, but
2275 * currently the `human' output format is selected if @fp@ is
2276 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
2277 * otherwise the `machine' format is used.
2278 */
2279
2280 struct tvec_output *tvec_dfltout(FILE *fp)
2281 {
2282 int ttyp = getenv_boolean("TVEC_TTY", -1);
2283
2284 if (ttyp == -1) ttyp = isatty(fileno(fp));
2285 if (ttyp) return (tvec_humanoutput(fp));
2286 else return (tvec_machineoutput(fp));
2287 }
2288
2289 /*----- That's all, folks -------------------------------------------------*/