@@@ output doc
[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";
64 case TVRD_EXPECT: return "expected";
65 case TVRD_FOUND: return "found";
b64eb60f
MW
66 default: abort();
67 }
68}
69
67b5031e
MW
70/* --- @getenv_boolean@ --- *
71 *
72 * Arguments: @const char *var@ = environment variable name
73 * @int dflt@ = default value
74 *
75 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
76 * set to something truish, or @dflt@ otherwise.
77 */
78
b64eb60f
MW
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 \
154 size_t n = limit - base; \
20ba6b0b 155 if (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 \
20ba6b0b
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
20ba6b0b 192/* --- @init_layout@ --- *
67b5031e 193 *
20ba6b0b 194 * Arguments: @struct layout *lyt@ = layout state to initialize
67b5031e
MW
195 * @FILE *fp@ = output file
196 * @const char *prefix@ = prefix string (or null if empty)
197 *
198 * Returns: ---
199 *
20ba6b0b 200 * Use: Initialize a layout state.
67b5031e
MW
201 */
202
20ba6b0b 203static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
67b5031e
MW
204{
205 const char *q, *l;
206
207 /* Basics. */
20ba6b0b
MW
208 lyt->fp = fp;
209 lyt->f = LYTF_NEWL;
210 dstr_create(&lyt->w);
67b5031e
MW
211
212 /* Prefix portions. */
213 if (!prefix || !*prefix)
20ba6b0b 214 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
67b5031e 215 else {
20ba6b0b
MW
216 lyt->prefix = prefix;
217 l = lyt->pfxlim = prefix + strlen(prefix);
218 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
67b5031e
MW
219 }
220}
221
20ba6b0b 222/* --- @destroy_layout@ --- *
67b5031e 223 *
20ba6b0b
MW
224 * Arguments: @struct layout *lyt@ = layout state
225 * @unsigned f@ = flags (@DLF_...@)
67b5031e
MW
226 *
227 * Returns: ---
228 *
20ba6b0b
MW
229 * Use: Releases a layout state and the resources it holds.
230 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
67b5031e
MW
231 * it open (in case it's @stderr@ or something).
232 */
233
20ba6b0b
MW
234#define DLF_CLOSE 1u
235static void destroy_layout(struct layout *lyt, unsigned f)
67b5031e 236{
20ba6b0b
MW
237 if (f&DLF_CLOSE) fclose(lyt->fp);
238 dstr_destroy(&lyt->w);
67b5031e
MW
239}
240
20ba6b0b 241/* --- @layout_char@ --- *
67b5031e 242 *
20ba6b0b 243 * Arguments: @struct layout *lyt@ = layout state
67b5031e
MW
244 * @int ch@ = character to write
245 *
246 * Returns: Zero on success, @-1@ on failure.
247 *
248 * Use: Write a single character to the output.
249 */
250
20ba6b0b 251static int layout_char(struct layout *lyt, int ch)
67b5031e
MW
252{
253 if (ch == '\n') {
20ba6b0b
MW
254 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
255 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
67b5031e 256 } else if (isspace(ch))
20ba6b0b 257 DPUTC(&lyt->w, ch);
67b5031e 258 else {
20ba6b0b
MW
259 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
260 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
67b5031e
MW
261 }
262 return (0);
263}
264
20ba6b0b 265/* --- @layout_string@ --- *
67b5031e 266 *
20ba6b0b 267 * Arguments: @struct layout *lyt@ = layout state
67b5031e
MW
268 * @const char *p@ = string to write
269 * @size_t sz@ = length of string
270 *
271 * Returns: Zero on success, @-1@ on failure.
272 *
273 * Use: Write a string to the output.
274 */
275
20ba6b0b 276static int layout_string(struct layout *lyt, const char *p, size_t sz)
67b5031e
MW
277{
278 const char *q, *r, *l = p + sz;
279
280 /* This is rather vexing. There are a small number of jobs to do, but the
281 * logic for deciding which to do when gets rather hairy if, as I've tried
282 * here, one aims to minimize the number of decisions being checked, so
283 * it's worth canning them into macros.
284 *
285 * Here, a `blank' is a whitespace character other than newline. The input
286 * buffer consists of one or more `segments', each of which consists of:
287 *
288 * * an initial portion, which is either empty or ends with a nonblank
289 * character;
290 *
291 * * a suffix which consists only of blanks; and
292 *
293 * * an optional newline.
294 *
295 * All segments except the last end with a newline.
296 */
297
298#define SPLIT_SEGMENT do { \
299 /* Determine the bounds of the current segment. If there is a final \
300 * newline, then q is non-null and points to this newline; otherwise, \
301 * q is null. The initial portion of the segment lies between p .. r \
302 * and the blank suffix lies between r .. q (or r .. l if q is null). \
303 * This sounds awkward, but the suffix is only relevant if there is \
304 * no newline. \
305 */ \
306 \
307 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
308} while (0)
309
310#define PUT_NONBLANK do { \
311 /* Output the initial portion of the segment. */ \
312 \
313 PUT_RANGE(p, r); \
314} while (0)
315
316#define PUT_NEWLINE do { \
317 /* Write a newline, and advance to the next segment. */ \
318 \
319 PUT_CHAR('\n'); p = q + 1; \
320} while (0)
321
322#define SAVE_TAIL do { \
323 /* Save the trailing blank portion of the segment in the buffer. \
324 * Assumes that there is no newline, since otherwise the suffix would \
325 * be omitted. \
326 */ \
327 \
20ba6b0b 328 DPUTM(&lyt->w, r, l - r); \
67b5031e
MW
329} while (0)
330
331 /* Determine the bounds of the first segment. Handling this is the most
332 * complicated part of this function.
333 */
334 SPLIT_SEGMENT;
335
336 if (!q) {
337 /* This is the only segment. We'll handle the whole thing here.
338 *
339 * If there's an initial nonblank portion, then we need to write that
340 * out. Furthermore, if we're at the start of the line then we'll need
341 * to write the prefix, and if there's saved blank material then we'll
342 * need to write that. Otherwise, there's only blank stuff, which we
343 * accumulate in the buffer.
344 *
20ba6b0b
MW
345 * If we're at the start of a line here, then put the prefix followed by
346 * any saved whitespace, and then our initial nonblank portion. Then
347 * save our new trailing space.
67b5031e
MW
348 */
349
350 if (r > p) {
20ba6b0b
MW
351 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
352 PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
67b5031e
MW
353 }
354 SAVE_TAIL;
355 return (0);
356 }
357
358 /* There is at least one more segment, so we know that there'll be a line
359 * to output.
360 */
c91413e6 361 if (r > p) {
20ba6b0b 362 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
c91413e6 363 PUT_SAVED; PUT_NONBLANK;
20ba6b0b 364 } else if (lyt->f&LYTF_NEWL)
c91413e6 365 PUT_PFXINB;
20ba6b0b 366 PUT_NEWLINE; DRESET(&lyt->w);
67b5031e
MW
367 SPLIT_SEGMENT;
368
369 /* Main loop over whole segments with trailing newlines. For each one, we
370 * know that we're starting at the beginning of a line and there's a final
371 * newline, so we write the initial prefix and drop the trailing blanks.
372 */
373 while (q) {
c91413e6
MW
374 if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
375 else PUT_PFXINB;
376 PUT_NEWLINE;
67b5031e
MW
377 SPLIT_SEGMENT;
378 }
379
380 /* At the end, there's no final newline. If there's nonblank material,
381 * then we can write the prefix and the nonblank stuff. Otherwise, stash
382 * the blank stuff (including the trailing blanks of the prefix) and leave
383 * the newline flag set.
384 */
20ba6b0b
MW
385 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
386 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
67b5031e
MW
387 SAVE_TAIL;
388
389#undef SPLIT_SEGMENT
390#undef PUT_NONBLANK
391#undef PUT_NEWLINE
392#undef SAVE_TAIL
393
394 return (0);
395}
396
397#undef SPLIT_RANGE
398#undef PUT_RANGE
399#undef PUT_PREFIX
400#undef PUT_PFXINB
401#undef PUT_SAVED
402#undef PUT_CHAR
403#undef SAVE_PFXTAIL
404
b64eb60f
MW
405/*----- Skeleton ----------------------------------------------------------*/
406/*
3efcfd2d 407static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
b64eb60f
MW
408static int ..._esession(struct tvec_output *o)
409static void ..._bgroup(struct tvec_output *o)
b64eb60f
MW
410static void ..._skipgroup(struct tvec_output *o,
411 const char *excuse, va_list *ap)
3efcfd2d 412static void ..._egroup(struct tvec_output *o)
b64eb60f 413static void ..._btest(struct tvec_output *o)
e63124bc 414static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
b64eb60f 415static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
e63124bc
MW
416static void ..._dumpreg(struct tvec_output *o, unsigned disp,
417 union tvec_regval *rv, const struct tvec_regdef *rd)
b64eb60f 418static void ..._etest(struct tvec_output *o, unsigned outcome)
e63124bc
MW
419static void ..._bbench(struct tvec_output *o,
420 const char *ident, unsigned unit)
421static void ..._ebench(struct tvec_output *o,
422 const char *ident, unsigned unit,
423 const struct tvec_timing *t)
3efcfd2d
MW
424static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
425static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
b64eb60f
MW
426static void ..._destroy(struct tvec_output *o)
427
428static const struct tvec_outops ..._ops = {
b64eb60f
MW
429 ..._bsession, ..._esession,
430 ..._bgroup, ..._egroup, ..._skip,
e63124bc 431 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
b64eb60f 432 ..._bbench, ..._ebench,
3efcfd2d 433 ..._error, ..._notice,
b64eb60f
MW
434 ..._destroy
435};
436*/
437/*----- Human-readable output ---------------------------------------------*/
438
20ba6b0b
MW
439/* Attributes for colour output. This should be done better, but @terminfo@
440 * is a disaster.
441 *
442 * An attribute byte holds a foreground colour in the low nibble, a
443 * background colour in the next nibble, and some flags in the next few
444 * bits. A colour is expressed in classic 1-bit-per-channel style, with red,
445 * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
446 */
447#define HAF_FGMASK 0x0f /* foreground colour mask */
448#define HAF_FGSHIFT 0 /* foreground colour shift */
449#define HAF_BGMASK 0xf0 /* background colour mask */
450#define HAF_BGSHIFT 4 /* background colour shift */
451#define HAF_FG 256u /* set foreground? */
452#define HAF_BG 512u /* set background? */
453#define HAF_BOLD 1024u /* set bold? */
454#define HCOL_BLACK 0u /* colour codes... */
b64eb60f
MW
455#define HCOL_RED 1u
456#define HCOL_GREEN 2u
457#define HCOL_YELLOW 3u
458#define HCOL_BLUE 4u
459#define HCOL_MAGENTA 5u
460#define HCOL_CYAN 6u
461#define HCOL_WHITE 7u
20ba6b0b
MW
462#define HCF_BRIGHT 8u /* bright colour flag */
463#define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
464#define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
465
466/* Predefined attributes. */
467#define HA_PLAIN 0 /* nothing special: terminal defaults */
468#define HA_LOC (HFG(CYAN)) /* filename or line number */
469#define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */
470#define HA_UNSET (HFG(YELLOW)) /* register not set */
471#define HA_FOUND (HFG(RED)) /* incorrect output value */
472#define HA_EXPECT (HFG(GREEN)) /* what the value should have been */
473#define HA_WIN (HFG(GREEN)) /* reporting success */
474#define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */
475#define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
476#define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */
477#define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* reporting an error */
478
479/* Scoreboard indicators. */
480#define HSB_WIN '.' /* test passed */
481#define HSB_LOSE 'x' /* test failed */
482#define HSB_XFAIL 'o' /* test failed expectedly */
483#define HSB_SKIP '_' /* test wasn't run */
b64eb60f
MW
484
485struct human_output {
20ba6b0b
MW
486 struct tvec_output _o; /* output base class */
487 struct tvec_state *tv; /* stashed testing state */
488 struct layout lyt; /* output layout */
489 char *outbuf; size_t outsz; /* buffer for formatted output */
490 dstr scoreboard; /* history of test group results */
491 unsigned attr; /* current terminal attributes */
492 int maxlen; /* longest register name */
493 unsigned f; /* flags */
494#define HOF_TTY 1u /* writing to terminal */
495#define HOF_DUPERR 2u /* duplicate errors to stderr */
496#define HOF_COLOUR 4u /* print in angry fruit salad */
497#define HOF_PROGRESS 8u /* progress display is active */
b64eb60f
MW
498};
499
20ba6b0b
MW
500/* --- @set_colour@ --- *
501 *
502 * Arguments: @FILE *fp@ = output stream to write on
503 * @int *sep_inout@ = where to maintain separator
504 * @const char *norm@ = prefix for normal colour
505 * @const char *bright@ = prefix for bright colour
506 * @unsigned colour@ = four bit colour code
507 *
508 * Returns: ---
509 *
510 * Use: Write to the output stream @fp@, the current character at
511 * @*sep_inout@, if that's not zero, followed by either @norm@
512 * or @bright@, according to whether the @HCF_BRIGHT@ flag is
513 * set in @colour@, followed by the plain colour code from
514 * @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
515 *
516 * This is an internal subroutine for @setattr@ below.
517 */
518
b64eb60f
MW
519static void set_colour(FILE *fp, int *sep_inout,
520 const char *norm, const char *bright,
521 unsigned colour)
522{
523 if (*sep_inout) putc(*sep_inout, fp);
524 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
525 *sep_inout = ';';
526}
527
20ba6b0b
MW
528/* --- @setattr@ --- *
529 *
530 * Arguments: @struct human_output *h@ = output state
531 * @unsigned attr@ = attribute code to set
532 *
533 * Returns: ---
534 *
535 * Use: Send a control sequence to the output stream so that
536 * subsequent text is printed with the given attributes.
537 *
538 * Some effort is taken to avoid unnecessary control sequences.
539 * In particular, if @attr@ matches the current terminal
540 * settings already, then nothing is written.
541 */
542
b64eb60f
MW
543static void setattr(struct human_output *h, unsigned attr)
544{
545 unsigned diff = h->attr ^ attr;
546 int sep = 0;
547
20ba6b0b 548 /* If there's nothing to do, we might as well stop now. */
b64eb60f 549 if (!diff || !(h->f&HOF_COLOUR)) return;
b64eb60f 550
20ba6b0b
MW
551 /* Start on the control command. */
552 fputs("\x1b[", h->lyt.fp);
553
554 /* Change the boldness if necessary. */
b64eb60f 555 if (diff&HAF_BOLD) {
20ba6b0b
MW
556 if (attr&HAF_BOLD) putc('1', h->lyt.fp);
557 else { putc('0', h->lyt.fp); diff = h->attr; }
b64eb60f
MW
558 sep = ';';
559 }
20ba6b0b
MW
560
561 /* Change the foreground colour if necessary. */
b64eb60f
MW
562 if (diff&(HAF_FG | HAF_FGMASK)) {
563 if (attr&HAF_FG)
20ba6b0b 564 set_colour(h->lyt.fp, &sep, "3", "9",
67b5031e 565 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
20ba6b0b
MW
566 else {
567 if (sep) putc(sep, h->lyt.fp);
568 fputs("39", h->lyt.fp); sep = ';';
569 }
b64eb60f 570 }
20ba6b0b
MW
571
572 /* Change the background colour if necessary. */
b64eb60f
MW
573 if (diff&(HAF_BG | HAF_BGMASK)) {
574 if (attr&HAF_BG)
20ba6b0b 575 set_colour(h->lyt.fp, &sep, "4", "10",
67b5031e 576 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
20ba6b0b
MW
577 else {
578 if (sep) putc(sep, h->lyt.fp);
579 fputs("49", h->lyt.fp); sep = ';';
580 }
b64eb60f
MW
581 }
582
20ba6b0b
MW
583 /* Terminate the control command and save the new attributes. */
584 putc('m', h->lyt.fp); h->attr = attr;
b64eb60f
MW
585}
586
20ba6b0b
MW
587/* --- @clear_progress@ --- *
588 *
589 * Arguments: @struct human_output *h@ = output state
590 *
591 * Returns: ---
592 *
593 * Use: Remove the progress display from the terminal.
594 *
595 * If the progress display isn't active then do nothing.
596 */
597
b64eb60f
MW
598static void clear_progress(struct human_output *h)
599{
600 size_t i, n;
601
602 if (h->f&HOF_PROGRESS) {
3efcfd2d 603 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
20ba6b0b 604 for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
b64eb60f
MW
605 h->f &= ~HOF_PROGRESS;
606 }
607}
608
20ba6b0b
MW
609/* --- @write_scoreboard_char@ --- *
610 *
611 * Arguments: @struct human_output *h@ = output state
612 * @int ch@ = scoreboard character to print
613 *
614 * Returns: ---
615 *
616 * Use: Write a scoreboard character, indicating the outcome of a
617 * test, to the output stream, with appropriate highlighting.
618 */
619
b64eb60f
MW
620static void write_scoreboard_char(struct human_output *h, int ch)
621{
622 switch (ch) {
20ba6b0b
MW
623 case HSB_LOSE: setattr(h, HA_LOSE); break;
624 case HSB_SKIP: setattr(h, HA_SKIP); break;
625 case HSB_XFAIL: setattr(h, HA_XFAIL); break;
626 default: setattr(h, HA_PLAIN); break;
b64eb60f 627 }
20ba6b0b 628 putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
b64eb60f
MW
629}
630
20ba6b0b
MW
631/* --- @show_progress@ --- *
632 *
633 * Arguments: @struct human_output *h@ = output state
634 *
635 * Returns: ---
636 *
637 * Use: Show the progress display, with the record of outcomes for
638 * the current test group.
639 *
640 * If the progress display is already active, or the output
641 * stream is not interactive, then nothing happens.
642 */
643
b64eb60f
MW
644static void show_progress(struct human_output *h)
645{
3efcfd2d 646 struct tvec_state *tv = h->tv;
b64eb60f
MW
647 const char *p, *l;
648
882a39c1 649 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
20ba6b0b 650 fprintf(h->lyt.fp, "%s: ", tv->test->name);
b64eb60f 651 if (!(h->f&HOF_COLOUR))
20ba6b0b 652 dstr_write(&h->scoreboard, h->lyt.fp);
b64eb60f
MW
653 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
654 write_scoreboard_char(h, *p);
20ba6b0b 655 fflush(h->lyt.fp); h->f |= HOF_PROGRESS;
b64eb60f
MW
656 }
657}
658
20ba6b0b
MW
659/* --- @report_location@ --- *
660 *
661 * Arguments: @struct human_output *h@ = output state
662 * @FILE *fp@ = stream to write the location on
663 * @const char *file@ = filename
664 * @unsigned lno@ = line number
665 *
666 * Returns: ---
667 *
668 * Use: Print the filename and line number to the output stream @fp@.
669 * Also, if appropriate, print interleaved highlighting control
670 * codes to our usual output stream. If @file@ is null then do
671 * nothing.
672 */
673
b64eb60f
MW
674static void report_location(struct human_output *h, FILE *fp,
675 const char *file, unsigned lno)
676{
677 unsigned f = 0;
678#define f_flush 1u
679
20ba6b0b
MW
680 /* We emit highlighting if @fp@ is our usual output stream, or the
681 * duplicate-errors flag is clear indicating that (we assume) they're
682 * secretly going to the same place anyway. If they're different streams,
683 * though, we have to be careful to keep the highlighting and the actual
684 * text synchronized.
685 */
b64eb60f 686
20ba6b0b
MW
687 if (!file)
688 /* nothing to do */;
689 else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
690 fprintf(fp, "%s:%u: ", file, lno);
691 else {
692 if (fp != h->lyt.fp) f |= f_flush;
693
694#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
b64eb60f 695
20ba6b0b 696 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
67b5031e 697 fputs(file, fp); FLUSH(fp);
20ba6b0b 698 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
67b5031e 699 fputc(':', fp); FLUSH(fp);
20ba6b0b 700 setattr(h, HA_LOC); FLUSH(h->lyt.fp);
67b5031e 701 fprintf(fp, "%u", lno); FLUSH(fp);
20ba6b0b 702 setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp);
67b5031e 703 fputc(':', fp); FLUSH(fp);
20ba6b0b 704 setattr(h, HA_PLAIN); FLUSH(h->lyt.fp);
67b5031e 705 fputc(' ', fp);
20ba6b0b
MW
706
707#undef FLUSH
b64eb60f
MW
708 }
709
710#undef f_flush
b64eb60f
MW
711}
712
bca75e8d
MW
713/* --- @human_writech@, @human_write@, @human_writef@ --- *
714 *
715 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
716 * @int ch@ = character to write
717 * @const char *@p@, @size_t sz@ = string (with explicit length)
718 * to write
719 * @const char *p, ...@ = format control string and arguments to
720 * write
721 *
722 * Returns: ---
723 *
724 * Use: Write characters, strings, or formatted strings to the
725 * output, applying appropriate layout.
726 *
727 * For the human output driver, the layout machinery just strips
728 * trailing spaces.
729 */
20ba6b0b 730
67b5031e 731static int human_writech(void *go, int ch)
20ba6b0b 732 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
67b5031e
MW
733
734static int human_writem(void *go, const char *p, size_t sz)
20ba6b0b 735 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
67b5031e
MW
736
737static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
738{
739 struct human_output *h = go;
740 size_t n;
741 va_list ap;
742
743 va_start(ap, p);
744 n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
745 va_end(ap);
20ba6b0b 746 return (layout_string(&h->lyt, h->outbuf, n));
67b5031e
MW
747}
748
749static const struct gprintf_ops human_printops =
750 { human_writech, human_writem, human_nwritef };
751
bca75e8d
MW
752/* --- @human_bsession@ --- *
753 *
754 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
755 * human_output@
756 * @struct tvec_state *tv@ = the test state producing output
757 *
758 * Returns: ---
759 *
760 * Use: Begin a test session.
761 *
762 * The human driver just records the test state for later
763 * reference.
764 */
20ba6b0b 765
3efcfd2d
MW
766static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
767 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
b64eb60f 768
bca75e8d
MW
769/* --- @report_unusual@ --- *
770 *
771 * Arguments: @struct human_output *h@ = output sink
772 * @unsigned nxfail, nskip@ = number of expected failures and
773 * skipped tests
774 *
775 * Returns: ---
776 *
777 * Use: Write (directly on the output stream) a note about expected
778 * failures and/or skipped tests, if there were any.
779 */
780
5fb354e3
MW
781static void report_unusual(struct human_output *h,
782 unsigned nxfail, unsigned nskip)
b64eb60f 783{
20ba6b0b
MW
784 const char *sep = " (";
785 unsigned f = 0;
786#define f_any 1u
787
788 if (nxfail) {
789 fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
790 setattr(h, HA_XFAIL);
791 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
792 setattr(h, HA_PLAIN);
793 sep = ", "; f |= f_any;
794 }
795
796 if (nskip) {
797 fprintf(h->lyt.fp, "%s%u ", sep, nskip);
798 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
799 sep = ", "; f |= f_any;
b64eb60f 800 }
20ba6b0b
MW
801
802 if (f&f_any) fputc(')', h->lyt.fp);
803
804#undef f_any
b64eb60f
MW
805}
806
bca75e8d
MW
807/* --- @human_esession@ --- *
808 *
809 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
810 * human_output@
811 *
812 * Returns: Suggested exit status.
813 *
814 * Use: End a test session.
815 *
816 * The human driver prints a final summary of the rest results
817 * and returns a suitable exit code.
818 */
819
b64eb60f
MW
820static int human_esession(struct tvec_output *o)
821{
822 struct human_output *h = (struct human_output *)o;
3efcfd2d 823 struct tvec_state *tv = h->tv;
b64eb60f
MW
824 unsigned
825 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
20ba6b0b 826 all_xfail = tv->all[TVOUT_XFAIL],
b64eb60f
MW
827 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
828 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
20ba6b0b
MW
829 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
830 grps_run = grps_win + grps_lose;
b64eb60f
MW
831
832 if (!all_lose) {
20ba6b0b
MW
833 setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
834 fprintf(h->lyt.fp, " %s%u %s",
b64eb60f 835 !(all_skip || grps_skip) ? "all " : "",
20ba6b0b 836 all_pass, all_pass == 1 ? "test" : "tests");
5fb354e3 837 report_unusual(h, all_xfail, all_skip);
20ba6b0b 838 fprintf(h->lyt.fp, " in %u %s",
b64eb60f 839 grps_win, grps_win == 1 ? "group" : "groups");
5fb354e3 840 report_unusual(h, 0, grps_skip);
b64eb60f 841 } else {
20ba6b0b
MW
842 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
843 fprintf(h->lyt.fp, " %u out of %u %s",
b64eb60f 844 all_lose, all_run, all_run == 1 ? "test" : "tests");
5fb354e3 845 report_unusual(h, all_xfail, all_skip);
20ba6b0b 846 fprintf(h->lyt.fp, " in %u out of %u %s",
b64eb60f 847 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
5fb354e3 848 report_unusual(h, 0, grps_skip);
b64eb60f 849 }
20ba6b0b 850 fputc('\n', h->lyt.fp);
b64eb60f 851
882a39c1 852 if (tv->f&TVSF_ERROR) {
20ba6b0b
MW
853 setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
854 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
882a39c1
MW
855 }
856
20ba6b0b 857 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
b64eb60f
MW
858}
859
bca75e8d
MW
860/* --- @human_bgroup@ --- *
861 *
862 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
863 * human_output@
864 *
865 * Returns: ---
866 *
867 * Use: Begin a test group.
868 *
869 * The human driver determines the length of the longest
870 * register name, resets the group progress scoreboard, and
871 * activates the progress display.
872 */
873
b64eb60f
MW
874static void human_bgroup(struct tvec_output *o)
875{
876 struct human_output *h = (struct human_output *)o;
e63124bc 877
3efcfd2d 878 h->maxlen = register_maxnamelen(h->tv);
b64eb60f
MW
879 dstr_reset(&h->scoreboard); show_progress(h);
880}
881
bca75e8d
MW
882/* --- @human_skipgroup@ --- *
883 *
884 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
885 * human_output@
886 * @const char *excuse@, @va_list *ap@ = reason for skipping the
887 * group, or null
888 *
889 * Returns: ---
890 *
891 * Use: Report that a test group is being skipped.
892 *
893 * The human driver just reports the situation to its output
894 * stream.
895 */
896
3efcfd2d
MW
897static void human_skipgroup(struct tvec_output *o,
898 const char *excuse, va_list *ap)
b64eb60f 899{
3efcfd2d 900 struct human_output *h = (struct human_output *)o;
b64eb60f 901
5fb354e3
MW
902 if (!(h->f&HOF_TTY))
903 fprintf(h->lyt.fp, "%s ", h->tv->test->name);
904 else {
905 show_progress(h); h->f &= ~HOF_PROGRESS;
906 if (h->scoreboard.len) putc(' ', h->lyt.fp);
b64eb60f 907 }
5fb354e3 908 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
20ba6b0b
MW
909 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
910 fputc('\n', h->lyt.fp);
b64eb60f
MW
911}
912
bca75e8d
MW
913/* --- @human_egroup@ --- *
914 *
915 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
916 * human_output@
917 *
918 * Returns: ---
919 *
920 * Use: Report that a test group has finished.
921 *
922 * The human driver reports a summary of the group's tests.
923 */
924
3efcfd2d 925static void human_egroup(struct tvec_output *o)
b64eb60f
MW
926{
927 struct human_output *h = (struct human_output *)o;
3efcfd2d 928 struct tvec_state *tv = h->tv;
20ba6b0b
MW
929 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
930 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
931 run = win + lose + xfail;
b64eb60f
MW
932
933 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
20ba6b0b 934 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
b64eb60f 935
3efcfd2d 936 if (lose) {
20ba6b0b
MW
937 fprintf(h->lyt.fp, " %u/%u ", lose, run);
938 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
5fb354e3 939 report_unusual(h, xfail, skip);
b64eb60f 940 } else {
20ba6b0b
MW
941 fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
942 fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
5fb354e3 943 report_unusual(h, xfail, skip);
b64eb60f 944 }
20ba6b0b 945 fputc('\n', h->lyt.fp);
b64eb60f
MW
946}
947
bca75e8d
MW
948/* --- @human_btest@ --- *
949 *
950 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
951 * human_output@
952 *
953 * Returns: ---
954 *
955 * Use: Report that a test is starting.
956 *
957 * The human driver makes sure the progress display is active.
958 */
959
b64eb60f
MW
960static void human_btest(struct tvec_output *o)
961 { struct human_output *h = (struct human_output *)o; show_progress(h); }
962
bca75e8d
MW
963/* --- @human_outcome@, @human_skip@, @human_fail@ --- *
964 *
965 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
966 * human_output@
967 * @unsigned attr@ = attribute to apply to the outcome
968 * @const char *outcome@ = outcome string to report
969 * @const char *detail@, @va_list *ap@ = a detail message
970 * @const char *excuse@, @va_list *ap@ = reason for skipping the
971 * test
972 *
973 * Returns: ---
974 *
975 * Use: Report that a test has been skipped or failed.
976 *
977 * The human driver reports the situation on its output stream.
978 */
979
980static void human_outcome(struct tvec_output *o,
981 unsigned attr, const char *outcome,
982 const char *detail, va_list *ap)
b64eb60f
MW
983{
984 struct human_output *h = (struct human_output *)o;
3efcfd2d 985 struct tvec_state *tv = h->tv;
b64eb60f
MW
986
987 clear_progress(h);
20ba6b0b
MW
988 report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
989 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
5fb354e3
MW
990 setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
991 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
20ba6b0b 992 fputc('\n', h->lyt.fp);
b64eb60f
MW
993}
994
5fb354e3
MW
995static void human_skip(struct tvec_output *o,
996 const char *excuse, va_list *ap)
997 { human_outcome(o, HA_SKIP, "skipped", excuse, ap); }
b64eb60f
MW
998static void human_fail(struct tvec_output *o,
999 const char *detail, va_list *ap)
5fb354e3 1000 { human_outcome(o, HA_LOSE, "FAILED", detail, ap); }
b64eb60f 1001
bca75e8d
MW
1002/* --- @human_dumpreg@ --- *
1003 *
1004 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1005 * human_output@
1006 * @unsigned disp@ = register disposition
1007 * @const union tvec_regval *rv@ = register value
1008 * @const struct tvec_regdef *rd@ = register definition
1009 *
1010 * Returns: ---
1011 *
1012 * Use: Dump a register.
1013 *
1014 * The human driver applies highlighting to mismatching output
1015 * registers, but otherwise delegates to the register type
1016 * handler and the layout machinery.
1017 */
b64eb60f 1018
e63124bc
MW
1019static void human_dumpreg(struct tvec_output *o,
1020 unsigned disp, const union tvec_regval *rv,
1021 const struct tvec_regdef *rd)
b64eb60f
MW
1022{
1023 struct human_output *h = (struct human_output *)o;
e63124bc 1024 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
b64eb60f 1025
67b5031e
MW
1026 clear_progress(h);
1027 gprintf(&human_printops, h, "%*s%s %s = ",
1028 10 + h->maxlen - n, "", ds, rd->name);
e63124bc 1029 if (h->f&HOF_COLOUR) {
20ba6b0b
MW
1030 if (!rv) setattr(h, HA_UNSET);
1031 else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
1032 else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
e63124bc 1033 }
67b5031e
MW
1034 if (!rv) gprintf(&human_printops, h, "#unset");
1035 else rd->ty->dump(rv, rd, 0, &human_printops, h);
20ba6b0b 1036 setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
b64eb60f
MW
1037}
1038
bca75e8d
MW
1039/* --- @human_etest@ --- *
1040 *
1041 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1042 * human_output@
1043 * @unsigned outcome@ = the test outcome
1044 *
1045 * Returns: ---
1046 *
1047 * Use: Report that a test has finished.
1048 *
1049 * The human driver reactivates the progress display, if
1050 * necessary, and adds a new character for the completed test.
1051 */
1052
b64eb60f
MW
1053static void human_etest(struct tvec_output *o, unsigned outcome)
1054{
1055 struct human_output *h = (struct human_output *)o;
1056 int ch;
1057
1058 if (h->f&HOF_TTY) {
1059 show_progress(h);
1060 switch (outcome) {
20ba6b0b
MW
1061 case TVOUT_WIN: ch = HSB_WIN; break;
1062 case TVOUT_LOSE: ch = HSB_LOSE; break;
1063 case TVOUT_XFAIL: ch = HSB_XFAIL; break;
1064 case TVOUT_SKIP: ch = HSB_SKIP; break;
b64eb60f
MW
1065 default: abort();
1066 }
1067 dstr_putc(&h->scoreboard, ch);
20ba6b0b 1068 write_scoreboard_char(h, ch); fflush(h->lyt.fp);
b64eb60f
MW
1069 }
1070}
1071
bca75e8d
MW
1072/* --- @human_bbench@ --- *
1073 *
1074 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1075 * human_output@
1076 * @const char *ident@ = identifying register values
1077 * @unsigned unit@ = measurement unit (@TVBU_...@)
1078 *
1079 * Returns: ---
1080 *
1081 * Use: Report that a benchmark has started.
1082 *
1083 * The human driver just prints the start of the benchmark
1084 * report.
1085 */
1086
e63124bc
MW
1087static void human_bbench(struct tvec_output *o,
1088 const char *ident, unsigned unit)
b64eb60f
MW
1089{
1090 struct human_output *h = (struct human_output *)o;
3efcfd2d 1091 struct tvec_state *tv = h->tv;
b64eb60f
MW
1092
1093 clear_progress(h);
20ba6b0b 1094 fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
b64eb60f
MW
1095}
1096
bca75e8d
MW
1097/* --- @human_ebench@ --- *
1098 *
1099 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1100 * human_output@
1101 * @const char *ident@ = identifying register values
1102 * @unsigned unit@ = measurement unit (@TVBU_...@)
1103 * @const struct bench_timing *tm@ = measurement
1104 *
1105 * Returns: ---
1106 *
1107 * Use: Report a benchmark's results
1108 *
1109 * The human driver just delegates to the default benchmark
1110 * reporting, via the layout machinery.
1111 */
1112
b64eb60f 1113static void human_ebench(struct tvec_output *o,
e63124bc 1114 const char *ident, unsigned unit,
b64eb60f
MW
1115 const struct bench_timing *tm)
1116{
1117 struct human_output *h = (struct human_output *)o;
67b5031e 1118
20ba6b0b
MW
1119 tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
1120 fputc('\n', h->lyt.fp);
b64eb60f
MW
1121}
1122
bca75e8d
MW
1123/* --- @human_report@ --- *
1124 *
1125 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1126 * human_output@
1127 * @unsigned level@ = message level (@TVLEV_...@)
1128 * @const char *msg@, @va_list *ap@ = format string and
1129 * arguments
1130 *
1131 * Returns: ---
1132 *
1133 * Use: Report a message to the user.
1134 *
1135 * The human driver arranges to show the message on @stderr@ as
1136 * well as the usual output, with a certain amount of
1137 * intelligence in case they're both actually the same device.
1138 */
1139
c91413e6
MW
1140static void human_report(struct tvec_output *o, unsigned level,
1141 const char *msg, va_list *ap)
3efcfd2d
MW
1142{
1143 struct human_output *h = (struct human_output *)o;
1144 struct tvec_state *tv = h->tv;
1145 dstr d = DSTR_INIT;
1146
1147 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
1148
20ba6b0b 1149 clear_progress(h); fflush(h->lyt.fp);
3efcfd2d
MW
1150 fprintf(stderr, "%s: ", QUIS);
1151 report_location(h, stderr, tv->infile, tv->lno);
1152 fwrite(d.buf, 1, d.len, stderr);
1153
1154 if (h->f&HOF_DUPERR) {
20ba6b0b
MW
1155 report_location(h, h->lyt.fp, tv->infile, tv->lno);
1156 fwrite(d.buf, 1, d.len, h->lyt.fp);
3efcfd2d
MW
1157 }
1158 show_progress(h);
1159}
1160
bca75e8d
MW
1161/* --- @human_destroy@ --- *
1162 *
1163 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1164 * human_output@
1165 *
1166 * Returns: ---
1167 *
1168 * Use: Release the resources held by the output driver.
1169 */
1170
b64eb60f
MW
1171static void human_destroy(struct tvec_output *o)
1172{
1173 struct human_output *h = (struct human_output *)o;
1174
20ba6b0b
MW
1175 destroy_layout(&h->lyt,
1176 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
b64eb60f 1177 dstr_destroy(&h->scoreboard);
67b5031e 1178 xfree(h->outbuf); xfree(h);
b64eb60f
MW
1179}
1180
1181static const struct tvec_outops human_ops = {
b64eb60f 1182 human_bsession, human_esession,
3efcfd2d 1183 human_bgroup, human_skipgroup, human_egroup,
e63124bc 1184 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
b64eb60f 1185 human_bbench, human_ebench,
c91413e6 1186 human_report,
b64eb60f
MW
1187 human_destroy
1188};
1189
bca75e8d
MW
1190/* --- @tvec_humanoutput@ --- *
1191 *
1192 * Arguments: @FILE *fp@ = output file to write on
1193 *
1194 * Returns: An output formatter.
1195 *
1196 * Use: Return an output formatter which writes on @fp@ with the
1197 * expectation that a human will be watching and interpreting
1198 * the output. If @fp@ denotes a terminal, the display shows a
1199 * `scoreboard' indicating the outcome of each test case
1200 * attempted, and may in addition use colour and other
1201 * highlighting.
1202 */
1203
b64eb60f
MW
1204struct tvec_output *tvec_humanoutput(FILE *fp)
1205{
1206 struct human_output *h;
1207 const char *p;
1208
1209 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
1210 h->f = 0; h->attr = 0;
1211
20ba6b0b 1212 init_layout(&h->lyt, fp, 0);
67b5031e 1213 h->outbuf = 0; h->outsz = 0;
b64eb60f
MW
1214
1215 switch (getenv_boolean("TVEC_TTY", -1)) {
1216 case 1: h->f |= HOF_TTY; break;
1217 case 0: break;
1218 default:
1219 if (isatty(fileno(fp))) h->f |= HOF_TTY;
1220 break;
1221 }
1222 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1223 case 1: h->f |= HOF_COLOUR; break;
1224 case 0: break;
1225 default:
1226 if (h->f&HOF_TTY) {
1227 p = getenv("TERM");
1228 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1229 }
1230 break;
1231 }
1232
e63124bc 1233 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
b64eb60f
MW
1234 dstr_create(&h->scoreboard);
1235 return (&h->_o);
1236}
1237
1238/*----- Perl's `Test Anything Protocol' -----------------------------------*/
1239
1240struct tap_output {
bca75e8d
MW
1241 struct tvec_output _o; /* output base class */
1242 struct tvec_state *tv; /* stashed testing state */
1243 struct layout lyt; /* output layout */
1244 char *outbuf; size_t outsz; /* buffer for formatted output */
1245 unsigned grpix, testix; /* group and test indices */
1246 unsigned previx; /* previously reported test index */
1247 int maxlen; /* longest register name */
b64eb60f
MW
1248};
1249
bca75e8d
MW
1250/* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1251 *
1252 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1253 * @int ch@ = character to write
1254 * @const char *@p@, @size_t sz@ = string (with explicit length)
1255 * to write
1256 * @const char *p, ...@ = format control string and arguments to
1257 * write
1258 *
1259 * Returns: ---
1260 *
1261 * Use: Write characters, strings, or formatted strings to the
1262 * output, applying appropriate layout.
1263 *
1264 * For the TAP output driver, the layout machinery prefixes each
1265 * line with ` ## ' and strips trailing spaces.
1266 */
1267
e63124bc 1268static int tap_writech(void *go, int ch)
20ba6b0b 1269 { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
e63124bc
MW
1270
1271static int tap_writem(void *go, const char *p, size_t sz)
ce896b0b 1272 { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
e63124bc
MW
1273
1274static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1275{
ce896b0b 1276 struct tap_output *t = go;
67b5031e 1277 size_t n;
e63124bc 1278 va_list ap;
67b5031e
MW
1279
1280 va_start(ap, p);
1281 n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
e63124bc 1282 va_end(ap);
20ba6b0b 1283 return (layout_string(&t->lyt, t->outbuf, n));
b64eb60f
MW
1284}
1285
e63124bc
MW
1286static const struct gprintf_ops tap_printops =
1287 { tap_writech, tap_writem, tap_nwritef };
1288
bca75e8d
MW
1289/* --- @tap_bsession@ --- *
1290 *
1291 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1292 * tap_output@
1293 * @struct tvec_state *tv@ = the test state producing output
1294 *
1295 * Returns: ---
1296 *
1297 * Use: Begin a test session.
1298 *
1299 * The TAP driver records the test state for later reference,
1300 * initializes the group index counter, and prints the version
1301 * number.
1302 */
1303
3efcfd2d
MW
1304static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1305{
1306 struct tap_output *t = (struct tap_output *)o;
1307
5fb354e3 1308 t->tv = tv; t->grpix = 0;
ce896b0b 1309 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
3efcfd2d 1310}
b64eb60f 1311
bca75e8d
MW
1312/* --- @tap_esession@ --- *
1313 *
1314 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1315 * tap_output@
1316 *
1317 * Returns: Suggested exit status.
1318 *
1319 * Use: End a test session.
1320 *
1321 * The TAP driver prints a final summary of the rest results
1322 * and returns a suitable exit code.
1323 */
ce896b0b 1324
b64eb60f
MW
1325static int tap_esession(struct tvec_output *o)
1326{
1327 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 1328 struct tvec_state *tv = t->tv;
882a39c1
MW
1329
1330 if (tv->f&TVSF_ERROR) {
1331 fputs("Bail out! "
1332 "Errors found in input; tests may not have run correctly\n",
20ba6b0b 1333 t->lyt.fp);
882a39c1
MW
1334 return (2);
1335 }
b64eb60f 1336
5fb354e3 1337 fprintf(t->lyt.fp, "1..%u\n", t->grpix);
3efcfd2d 1338 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
1339}
1340
bca75e8d
MW
1341/* --- @tap_bgroup@ --- *
1342 *
1343 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1344 * tap_output@
1345 *
1346 * Returns: ---
1347 *
1348 * Use: Begin a test group.
1349 *
1350 * The TAP driver determines the length of the longest
1351 * register name, resets the group progress scoreboard, and
1352 * activates the progress display.
1353 */
1354
e63124bc
MW
1355static void tap_bgroup(struct tvec_output *o)
1356{
1357 struct tap_output *t = (struct tap_output *)o;
ce896b0b
MW
1358 struct tvec_state *tv = t->tv;
1359
5fb354e3 1360 t->grpix++; t->testix = t->previx = 0;
3efcfd2d 1361 t->maxlen = register_maxnamelen(t->tv);
ce896b0b 1362 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
3efcfd2d
MW
1363}
1364
bca75e8d
MW
1365/* --- @tap_skipgroup@ --- *
1366 *
1367 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1368 * tap_output@
1369 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1370 * group, or null
1371 *
1372 * Returns: ---
1373 *
1374 * Use: Report that a test group is being skipped.
1375 *
1376 * The TAP driver just reports the situation to its output
1377 * stream.
1378 */
1379
3efcfd2d
MW
1380static void tap_skipgroup(struct tvec_output *o,
1381 const char *excuse, va_list *ap)
1382{
1383 struct tap_output *t = (struct tap_output *)o;
1384
5fb354e3
MW
1385 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
1386 fprintf(t->lyt.fp, "ok %u %s # SKIP", t->grpix, t->tv->test->name);
20ba6b0b
MW
1387 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1388 fputc('\n', t->lyt.fp);
1389}
1390
bca75e8d
MW
1391/* --- @tap_egroup@ --- *
1392 *
1393 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1394 * tap_output@
1395 *
1396 * Returns: ---
1397 *
1398 * Use: Report that a test group has finished.
1399 *
1400 * The TAP driver reports a summary of the group's tests.
1401 */
1402
3efcfd2d 1403static void tap_egroup(struct tvec_output *o)
b64eb60f
MW
1404{
1405 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 1406 struct tvec_state *tv = t->tv;
ce896b0b 1407
5fb354e3 1408 fprintf(t->lyt.fp, " 1..%u\n", t->testix);
ce896b0b
MW
1409 fprintf(t->lyt.fp, "%s %u - %s\n",
1410 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
5fb354e3 1411 t->grpix, tv->test->name);
b64eb60f
MW
1412}
1413
bca75e8d
MW
1414/* --- @tap_btest@ --- *
1415 *
1416 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1417 * tap_output@
1418 *
1419 * Returns: ---
1420 *
1421 * Use: Report that a test is starting.
1422 *
1423 * The TAP driver advances its test counter. (We could do this
1424 * by adding up up the counters in @tv->curr@, and add on the
1425 * current test, but it's easier this way.)
1426 */
1427
5fb354e3
MW
1428static void tap_btest(struct tvec_output *o)
1429 { struct tap_output *t = (struct tap_output *)o; t->testix++; }
b64eb60f 1430
bca75e8d
MW
1431/* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
1432 *
1433 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1434 * tap_output@
1435 * @unsigned attr@ = attribute to apply to the outcome
1436 * @const char *outcome@ = outcome string to report
1437 * @const char *detail@, @va_list *ap@ = a detail message
1438 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1439 * test
1440 *
1441 * Returns: ---
1442 *
1443 * Use: Report that a test has been skipped or failed.
1444 *
1445 * The TAP driver reports the situation on its output stream.
1446 * TAP only allows us to report a single status for each
1447 * subtest, so we notice when we've already reported a status
1448 * for the current test and convert the second report as a
1449 * comment. This should only happen in the case of multiple
1450 * failures.
1451 */
1452
ce896b0b
MW
1453static void tap_outcome(struct tvec_output *o,
1454 const char *head, const char *tail,
67b5031e 1455 const char *detail, va_list *ap)
b64eb60f
MW
1456{
1457 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 1458 struct tvec_state *tv = t->tv;
b64eb60f 1459
ce896b0b 1460 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
5fb354e3
MW
1461 t->testix == t->previx ? "##" : head,
1462 t->testix, tv->infile, tv->test_lno, tail);
ce896b0b
MW
1463 if (detail)
1464 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
1465 fputc('\n', t->lyt.fp);
5fb354e3 1466 t->previx = t->testix;
b64eb60f
MW
1467}
1468
67b5031e 1469static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
ce896b0b 1470 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
b64eb60f 1471static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
ce896b0b 1472 { tap_outcome(o, "not ok", "", detail, ap); }
b64eb60f 1473
bca75e8d
MW
1474/* --- @tap_dumpreg@ --- *
1475 *
1476 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1477 * tap_output@
1478 * @unsigned disp@ = register disposition
1479 * @const union tvec_regval *rv@ = register value
1480 * @const struct tvec_regdef *rd@ = register definition
1481 *
1482 * Returns: ---
1483 *
1484 * Use: Dump a register.
1485 *
1486 * The TAP driver applies highlighting to mismatching output
1487 * registers, but otherwise delegates to the register type
1488 * handler and the layout machinery. The result is that the
1489 * register dump is marked as a comment and indented.
1490 */
1491
e63124bc
MW
1492static void tap_dumpreg(struct tvec_output *o,
1493 unsigned disp, const union tvec_regval *rv,
1494 const struct tvec_regdef *rd)
1495{
1496 struct tap_output *t = (struct tap_output *)o;
1497 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1498
67b5031e
MW
1499 gprintf(&tap_printops, t, "%*s%s %s = ",
1500 10 + t->maxlen - n, "", ds, rd->name);
1501 if (!rv) gprintf(&tap_printops, t, "#<unset>");
1502 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
20ba6b0b 1503 layout_char(&t->lyt, '\n');
e63124bc 1504}
b64eb60f 1505
bca75e8d
MW
1506/* --- @tap_etest@ --- *
1507 *
1508 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1509 * tap_output@
1510 * @unsigned outcome@ = the test outcome
1511 *
1512 * Returns: ---
1513 *
1514 * Use: Report that a test has finished.
1515 *
1516 * The TAP driver reports the outcome of the test, if that's not
1517 * already decided.
1518 */
1519
20ba6b0b 1520static void tap_etest(struct tvec_output *o, unsigned outcome)
ce896b0b
MW
1521{
1522 switch (outcome) {
1523 case TVOUT_WIN:
1524 tap_outcome(o, "ok", "", 0, 0);
1525 break;
1526 case TVOUT_XFAIL:
1527 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
1528 break;
1529 }
1530}
b64eb60f 1531
bca75e8d
MW
1532/* --- @tap_bbench@ --- *
1533 *
1534 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1535 * tap_output@
1536 * @const char *ident@ = identifying register values
1537 * @unsigned unit@ = measurement unit (@TVBU_...@)
1538 *
1539 * Returns: ---
1540 *
1541 * Use: Report that a benchmark has started.
1542 *
1543 * The TAP driver does nothing here. All of the reporting
1544 * happens in @tap_ebench@.
1545 */
1546
e63124bc
MW
1547static void tap_bbench(struct tvec_output *o,
1548 const char *ident, unsigned unit)
1549 { ; }
b64eb60f 1550
bca75e8d
MW
1551/* --- @tap_ebench@ --- *
1552 *
1553 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1554 * tap_output@
1555 * @const char *ident@ = identifying register values
1556 * @unsigned unit@ = measurement unit (@TVBU_...@)
1557 * @const struct bench_timing *tm@ = measurement
1558 *
1559 * Returns: ---
1560 *
1561 * Use: Report a benchmark's results
1562 *
1563 * The TAP driver just delegates to the default benchmark
1564 * reporting, via the layout machinery so that the result is
1565 * printed as a comment.
1566 */
1567
b64eb60f 1568static void tap_ebench(struct tvec_output *o,
e63124bc 1569 const char *ident, unsigned unit,
b64eb60f
MW
1570 const struct bench_timing *tm)
1571{
1572 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 1573 struct tvec_state *tv = t->tv;
b64eb60f 1574
67b5031e
MW
1575 gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1576 tvec_benchreport(&tap_printops, t, unit, tm);
20ba6b0b 1577 layout_char(&t->lyt, '\n');
b64eb60f
MW
1578}
1579
bca75e8d
MW
1580/* --- @tap_report@ --- *
1581 *
1582 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1583 * tap_output@
1584 * @unsigned level@ = message level (@TVLEV_...@)
1585 * @const char *msg@, @va_list *ap@ = format string and
1586 * arguments
1587 *
1588 * Returns: ---
1589 *
1590 * Use: Report a message to the user.
1591 *
1592 * The TAP driver converts error reports into TAP `Bail out!'
1593 * errors. Less critical notices end up as comments.
1594 */
1595
c91413e6 1596static void tap_report(struct tvec_output *o, unsigned level,
67b5031e 1597 const char *msg, va_list *ap)
3efcfd2d 1598{
c91413e6 1599 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 1600 struct tvec_state *tv = t->tv;
c91413e6 1601 const struct gprintf_ops *gops; void *go;
3efcfd2d 1602
c91413e6 1603 if (level >= TVLEV_ERR) {
20ba6b0b
MW
1604 fputs("Bail out! ", t->lyt.fp);
1605 gops = &file_printops; go = t->lyt.fp;
c91413e6
MW
1606 } else {
1607 gops = &tap_printops; go = t;
1608 }
67b5031e
MW
1609 if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
1610 gprintf(gops, go, msg, ap); gops->putch(go, '\n');
3efcfd2d
MW
1611}
1612
bca75e8d
MW
1613/* --- @tap_destroy@ --- *
1614 *
1615 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1616 * tap_output@
1617 *
1618 * Returns: ---
1619 *
1620 * Use: Release the resources held by the output driver.
1621 */
1622
b64eb60f
MW
1623static void tap_destroy(struct tvec_output *o)
1624{
1625 struct tap_output *t = (struct tap_output *)o;
1626
20ba6b0b
MW
1627 destroy_layout(&t->lyt,
1628 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
67b5031e 1629 xfree(t->outbuf); xfree(t);
b64eb60f
MW
1630}
1631
1632static const struct tvec_outops tap_ops = {
b64eb60f 1633 tap_bsession, tap_esession,
3efcfd2d 1634 tap_bgroup, tap_skipgroup, tap_egroup,
e63124bc 1635 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
b64eb60f 1636 tap_bbench, tap_ebench,
c91413e6 1637 tap_report,
b64eb60f
MW
1638 tap_destroy
1639};
1640
bca75e8d
MW
1641/* --- @tvec_tapoutput@ --- *
1642 *
1643 * Arguments: @FILE *fp@ = output file to write on
1644 *
1645 * Returns: An output formatter.
1646 *
1647 * Use: Return an output formatter which writes on @fp@ in `TAP'
1648 * (`Test Anything Protocol') format.
1649 *
1650 * TAP comes from the Perl community, but has spread rather
1651 * further. This driver produces TAP version 14, but pretends
1652 * to be version 13. The driver produces a TAP `test point' --
1653 * i.e., a result reported as `ok' or `not ok' -- for each input
1654 * test group. Failure reports and register dumps are produced
1655 * as diagnostic messages before the final group result. (TAP
1656 * permits structuerd YAML data after the test-point result,
1657 * which could be used to report details, but (a) postponing the
1658 * details until after the report is inconvenient, and (b) there
1659 * is no standardization for the YAML anyway, so in practice
1660 * it's no more useful than the unstructured diagnostics.
1661 */
1662
b64eb60f
MW
1663struct tvec_output *tvec_tapoutput(FILE *fp)
1664{
1665 struct tap_output *t;
1666
1667 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
ce896b0b 1668 init_layout(&t->lyt, fp, " ## ");
67b5031e 1669 t->outbuf = 0; t->outsz = 0;
b64eb60f
MW
1670 return (&t->_o);
1671}
1672
1673/*----- Default output ----------------------------------------------------*/
1674
bca75e8d
MW
1675/* --- @tvec_dfltoutput@ --- *
1676 *
1677 * Arguments: @FILE *fp@ = output file to write on
1678 *
1679 * Returns: An output formatter.
1680 *
1681 * Use: Selects and instantiates an output formatter suitable for
1682 * writing on @fp@. The policy is subject to change, but
1683 * currently the `human' output format is selected if @fp@ is
1684 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
1685 * otherwise the `tap' format is used.
1686 */
1687
b64eb60f
MW
1688struct tvec_output *tvec_dfltout(FILE *fp)
1689{
1690 int ttyp = getenv_boolean("TVEC_TTY", -1);
1691
1692 if (ttyp == -1) ttyp = isatty(fileno(fp));
1693 if (ttyp) return (tvec_humanoutput(fp));
1694 else return (tvec_tapoutput(fp));
1695}
1696
1697/*----- That's all, folks -------------------------------------------------*/