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