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