3 * Test vector output management
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
48 #include "tvec-bench.h"
49 #include "tvec-output.h"
51 /*----- Common machinery --------------------------------------------------*/
53 /* --- @regdisp@ --- *
55 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
57 * Returns: A human-readable adjective describing the register
61 static const char *regdisp(unsigned disp
)
64 case TVRD_INPUT
: return "input";
65 case TVRD_OUTPUT
: return "output";
66 case TVRD_MATCH
: return "matched";
67 case TVRD_FOUND
: return "found";
68 case TVRD_EXPECT
: return "expected";
73 /* --- @getenv_boolean@ --- *
75 * Arguments: @const char *var@ = environment variable name
76 * @int dflt@ = default value
78 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
79 * set to something truish, or @dflt@ otherwise.
82 static int getenv_boolean(const char *var
, int dflt
)
89 else if (STRCMP(p
, ==, "y") || STRCMP(p
, ==, "yes") ||
90 STRCMP(p
, ==, "t") || STRCMP(p
, ==, "true") ||
91 STRCMP(p
, ==, "on") || STRCMP(p
, ==, "force") ||
94 else if (STRCMP(p
, ==, "n") || STRCMP(p
, ==, "no") ||
95 STRCMP(p
, ==, "f") || STRCMP(p
, ==, "false") ||
96 STRCMP(p
, ==, "nil") || STRCMP(p
, ==, "off") ||
100 moan("ignoring unexpected value `%s' for environment variable `%s'",
106 /* --- @register_maxnamelen@ --- *
108 * Arguments: @const struct tvec_state *tv@ = test vector state
110 * Returns: The maximum length of a register name in the current test.
113 static int register_maxnamelen(const struct tvec_state
*tv
)
115 const struct tvec_regdef
*rd
;
118 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
119 { n
= strlen(rd
->name
); if (n
> maxlen
) maxlen
= n
; }
123 /* --- @print_ident@ --- *
125 * Arguments: @struct tvec_state *tv@ = test-vector state
126 * @unsigned style@ = style to use for register dumps
127 * @const struct gprintf_ops *gops@ = output operations
128 * @void *go@ = output state
132 * Use: Write a benchmark identification to the output.
135 static void print_ident(struct tvec_state
*tv
, unsigned style
,
136 const struct gprintf_ops
*gops
, void *go
)
138 const struct tvec_regdef
*rd
;
143 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
145 if (!(f
&f_any
)) f
|= f_any
;
146 else if (style
&TVSF_RAW
) gops
->putch(go
, ' ');
147 else gprintf(gops
, go
, ", ");
148 gprintf(gops
, go
, "%s", rd
->name
);
149 if (style
&TVSF_RAW
) gops
->putch(go
, '=');
150 else gprintf(gops
, go
, " = ");
151 rd
->ty
->dump(&TVEC_REG(tv
, in
, rd
->i
)->v
, rd
, style
, gops
, go
);
157 /*----- Output layout -----------------------------------------------------*/
159 /* We have two main jobs in output layout: trimming trailing blanks; and
160 * adding a prefix to each line.
162 * This is somehow much more complicated than it ought to be.
166 FILE *fp
; /* output file */
167 const char *prefix
, *pfxtail
, *pfxlim
; /* prefix pointers */
168 dstr w
; /* trailing whitespace */
169 unsigned f
; /* flags */
170 #define LYTF_NEWL 1u /* start of output line */
173 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
177 #define SPLIT_RANGE(tail, base, limit) do { \
178 /* Set TAIL to point just after the last nonspace character between \
179 * BASE and LIMIT. If there are no nonspace characters, then set \
180 * TAIL to equal BASE. \
183 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
186 #define PUT_RANGE(base, limit) do { \
187 /* Write the range of characters between BASE and LIMIT to the output \
188 * file. Return immediately on error. \
191 size_t _n = limit - base; \
192 if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
195 #define PUT_CHAR(ch) do { \
196 /* Write CH to the output. Return immediately on error. */ \
198 if (putc(ch, lyt->fp) == EOF) return (-1); \
201 #define PUT_PREFIX do { \
202 /* Output the prefix, if there is one. Return immediately on error. */ \
204 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
207 #define PUT_SAVED do { \
208 /* Output the saved trailing blank material in the buffer. */ \
210 size_t _n = lyt->w.len; \
211 if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \
214 #define PUT_PFXINB do { \
215 /* Output the initial nonblank portion of the prefix, if there is \
216 * one. Return immediately on error. \
219 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
222 #define SAVE_PFXTAIL do { \
223 /* Save the trailing blank portion of the prefix. */ \
226 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
229 /* --- @set_layout_prefix@ --- *
231 * Arguments: @struct layout *lyt@ = layout state
232 * @const char *prefix@ = new prefix string or null
236 * Use: Change the configured prefix string. The change takes effect
237 * at the start of the next line (or the current line if it's
238 * empty or only whitespace so far).
241 static void set_layout_prefix(struct layout
*lyt
, const char *prefix
)
245 if (!prefix
|| !*prefix
)
246 lyt
->prefix
= lyt
->pfxtail
= lyt
->pfxlim
= 0;
248 lyt
->prefix
= prefix
;
249 l
= lyt
->pfxlim
= prefix
+ strlen(prefix
);
250 SPLIT_RANGE(q
, prefix
, l
); lyt
->pfxtail
= q
;
254 /* --- @init_layout@ --- *
256 * Arguments: @struct layout *lyt@ = layout state to initialize
257 * @FILE *fp@ = output file
258 * @const char *prefix@ = prefix string (or null if empty)
262 * Use: Initialize a layout state.
265 static void init_layout(struct layout
*lyt
, FILE *fp
, const char *prefix
)
269 dstr_create(&lyt
->w
);
270 set_layout_prefix(lyt
, prefix
);
273 /* --- @destroy_layout@ --- *
275 * Arguments: @struct layout *lyt@ = layout state
276 * @unsigned f@ = flags (@DLF_...@)
280 * Use: Releases a layout state and the resources it holds.
281 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
282 * it open (in case it's @stderr@ or something).
286 static void destroy_layout(struct layout
*lyt
, unsigned f
)
288 if (f
&DLF_CLOSE
) fclose(lyt
->fp
);
289 dstr_destroy(&lyt
->w
);
292 /* --- @layout_char@ --- *
294 * Arguments: @struct layout *lyt@ = layout state
295 * @int ch@ = character to write
297 * Returns: Zero on success, @-1@ on failure.
299 * Use: Write a single character to the output.
302 static int layout_char(struct layout
*lyt
, int ch
)
305 if (lyt
->f
&LYTF_NEWL
) PUT_PFXINB
;
306 PUT_CHAR('\n'); lyt
->f
|= LYTF_NEWL
; DRESET(&lyt
->w
);
307 } else if (isspace(ch
))
310 if (lyt
->f
&LYTF_NEWL
) { PUT_PFXINB
; lyt
->f
&= ~LYTF_NEWL
; }
311 PUT_SAVED
; PUT_CHAR(ch
); DRESET(&lyt
->w
);
316 /* --- @layout_string@ --- *
318 * Arguments: @struct layout *lyt@ = layout state
319 * @const char *p@ = string to write
320 * @size_t sz@ = length of string
322 * Returns: Zero on success, @-1@ on failure.
324 * Use: Write a string to the output.
327 static int layout_string(struct layout
*lyt
, const char *p
, size_t sz
)
329 const char *q
, *r
, *l
= p
+ sz
;
331 /* This is rather vexing. There are a small number of jobs to do, but the
332 * logic for deciding which to do when gets rather hairy if, as I've tried
333 * here, one aims to minimize the number of decisions being checked, so
334 * it's worth canning them into macros.
336 * Here, a `blank' is a whitespace character other than newline. The input
337 * buffer consists of one or more `segments', each of which consists of:
339 * * an initial portion, which is either empty or ends with a nonblank
342 * * a suffix which consists only of blanks; and
344 * * an optional newline.
346 * All segments except the last end with a newline.
349 #define SPLIT_SEGMENT do { \
350 /* Determine the bounds of the current segment. If there is a final \
351 * newline, then q is non-null and points to this newline; otherwise, \
352 * q is null. The initial portion of the segment lies between p .. r \
353 * and the blank suffix lies between r .. q (or r .. l if q is null). \
354 * This sounds awkward, but the suffix is only relevant if there is \
358 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
361 #define PUT_NONBLANK do { \
362 /* Output the initial portion of the segment. */ \
367 #define PUT_NEWLINE do { \
368 /* Write a newline, and advance to the next segment. */ \
370 PUT_CHAR('\n'); p = q + 1; \
373 #define SAVE_TAIL do { \
374 /* Save the trailing blank portion of the segment in the buffer. \
375 * Assumes that there is no newline, since otherwise the suffix would \
379 DPUTM(&lyt->w, r, l - r); \
382 /* Determine the bounds of the first segment. Handling this is the most
383 * complicated part of this function.
388 /* This is the only segment. We'll handle the whole thing here.
390 * If there's an initial nonblank portion, then we need to write that
391 * out. Furthermore, if we're at the start of the line then we'll need
392 * to write the prefix, and if there's saved blank material then we'll
393 * need to write that. Otherwise, there's only blank stuff, which we
394 * accumulate in the buffer.
396 * If we're at the start of a line here, then put the prefix followed by
397 * any saved whitespace, and then our initial nonblank portion. Then
398 * save our new trailing space.
402 if (lyt
->f
&LYTF_NEWL
) { PUT_PREFIX
; lyt
->f
&= ~LYTF_NEWL
; }
403 PUT_SAVED
; PUT_NONBLANK
; DRESET(&lyt
->w
);
409 /* There is at least one more segment, so we know that there'll be a line
413 if (lyt
->f
&LYTF_NEWL
) PUT_PREFIX
;
414 PUT_SAVED
; PUT_NONBLANK
;
415 } else if (lyt
->f
&LYTF_NEWL
)
417 PUT_NEWLINE
; DRESET(&lyt
->w
);
420 /* Main loop over whole segments with trailing newlines. For each one, we
421 * know that we're starting at the beginning of a line and there's a final
422 * newline, so we write the initial prefix and drop the trailing blanks.
425 if (r
> p
) { PUT_PREFIX
; PUT_NONBLANK
; }
431 /* At the end, there's no final newline. If there's nonblank material,
432 * then we can write the prefix and the nonblank stuff. Otherwise, stash
433 * the blank stuff (including the trailing blanks of the prefix) and leave
434 * the newline flag set.
436 if (r
> p
) { PUT_PREFIX
; PUT_NONBLANK
; lyt
->f
&= ~LYTF_NEWL
; }
437 else { lyt
->f
|= LYTF_NEWL
; SAVE_PFXTAIL
; }
456 /*----- Human-readable output ---------------------------------------------*/
458 /* Attributes for colour output. This should be done better, but @terminfo@
461 * An attribute byte holds a foreground colour in the low nibble, a
462 * background colour in the next nibble, and some flags in the next few
463 * bits. A colour is expressed in classic 1-bit-per-channel style, with red,
464 * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
466 #define HAF_FGMASK 0x0f /* foreground colour mask */
467 #define HAF_FGSHIFT 0 /* foreground colour shift */
468 #define HAF_BGMASK 0xf0 /* background colour mask */
469 #define HAF_BGSHIFT 4 /* background colour shift */
470 #define HAF_FG 256u /* set foreground? */
471 #define HAF_BG 512u /* set background? */
472 #define HAF_BOLD 1024u /* set bold? */
473 #define HCOL_BLACK 0u /* colour codes... */
475 #define HCOL_GREEN 2u
476 #define HCOL_YELLOW 3u
478 #define HCOL_MAGENTA 5u
480 #define HCOL_WHITE 7u
481 #define HCF_BRIGHT 8u /* bright colour flag */
482 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
483 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
485 /* Predefined attributes. */
486 #define HA_PLAIN 0 /* nothing special: terminal defaults */
487 #define HA_LOC (HFG(CYAN)) /* filename or line number */
488 #define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */
489 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */
490 #define HA_NOTE (HFG(YELLOW)) /* notices */
491 #define HA_INFO 0 /* information */
492 #define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */
493 #define HA_UNSET (HFG(YELLOW)) /* register not set */
494 #define HA_FOUND (HFG(RED)) /* incorrect output value */
495 #define HA_EXPECT (HFG(GREEN)) /* what the value should have been */
496 #define HA_WIN (HFG(GREEN)) /* reporting success */
497 #define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */
498 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
499 #define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */
501 /* Scoreboard indicators. */
502 #define HSB_WIN '.' /* test passed */
503 #define HSB_LOSE 'x' /* test failed */
504 #define HSB_XFAIL 'o' /* test failed expectedly */
505 #define HSB_SKIP '_' /* test wasn't run */
507 struct human_output
{
508 struct tvec_output _o
; /* output base class */
509 struct tvec_state
*tv
; /* stashed testing state */
510 arena
*a
; /* arena for memory allocation */
511 struct layout lyt
; /* output layout */
512 char *outbuf
; size_t outsz
; /* buffer for formatted output */
513 dstr scoreboard
; /* history of test group results */
514 unsigned attr
; /* current terminal attributes */
515 int maxlen
; /* longest register name */
516 unsigned f
; /* flags */
517 #define HOF_TTY 1u /* writing to terminal */
518 #define HOF_DUPERR 2u /* duplicate errors to stderr */
519 #define HOF_COLOUR 4u /* print in angry fruit salad */
520 #define HOF_PROGRESS 8u /* progress display is active */
523 /* --- @set_colour@ --- *
525 * Arguments: @FILE *fp@ = output stream to write on
526 * @int *sep_inout@ = where to maintain separator
527 * @const char *norm@ = prefix for normal colour
528 * @const char *bright@ = prefix for bright colour
529 * @unsigned colour@ = four bit colour code
533 * Use: Write to the output stream @fp@, the current character at
534 * @*sep_inout@, if that's not zero, followed by either @norm@
535 * or @bright@, according to whether the @HCF_BRIGHT@ flag is
536 * set in @colour@, followed by the plain colour code from
537 * @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
539 * This is an internal subroutine for @setattr@ below.
542 static void set_colour(FILE *fp
, int *sep_inout
,
543 const char *norm
, const char *bright
,
546 if (*sep_inout
) putc(*sep_inout
, fp
);
547 fprintf(fp
, "%s%d", colour
&HCF_BRIGHT ? bright
: norm
, colour
&7);
551 /* --- @setattr@ --- *
553 * Arguments: @struct human_output *h@ = output state
554 * @unsigned attr@ = attribute code to set
558 * Use: Send a control sequence to the output stream so that
559 * subsequent text is printed with the given attributes.
561 * Some effort is taken to avoid unnecessary control sequences.
562 * In particular, if @attr@ matches the current terminal
563 * settings already, then nothing is written.
566 static void setattr(struct human_output
*h
, unsigned attr
)
568 unsigned diff
= h
->attr
^ attr
;
571 /* If there's nothing to do, we might as well stop now. */
572 if (!diff
|| !(h
->f
&HOF_COLOUR
)) return;
574 /* Start on the control command. */
575 fputs("\x1b[", h
->lyt
.fp
);
577 /* Change the boldness if necessary. */
579 if (attr
&HAF_BOLD
) putc('1', h
->lyt
.fp
);
580 else { putc('0', h
->lyt
.fp
); diff
= h
->attr
; }
584 /* Change the foreground colour if necessary. */
585 if (diff
&(HAF_FG
| HAF_FGMASK
)) {
587 set_colour(h
->lyt
.fp
, &sep
, "3", "9",
588 (attr
&HAF_FGMASK
) >> HAF_FGSHIFT
);
590 if (sep
) putc(sep
, h
->lyt
.fp
);
591 fputs("39", h
->lyt
.fp
); sep
= ';';
595 /* Change the background colour if necessary. */
596 if (diff
&(HAF_BG
| HAF_BGMASK
)) {
598 set_colour(h
->lyt
.fp
, &sep
, "4", "10",
599 (attr
&HAF_BGMASK
) >> HAF_BGSHIFT
);
601 if (sep
) putc(sep
, h
->lyt
.fp
);
602 fputs("49", h
->lyt
.fp
); sep
= ';';
606 /* Terminate the control command and save the new attributes. */
607 putc('m', h
->lyt
.fp
); h
->attr
= attr
;
610 /* --- @clear_progress@ --- *
612 * Arguments: @struct human_output *h@ = output state
616 * Use: Remove the progress display from the terminal.
618 * If the progress display isn't active then do nothing.
621 static void clear_progress(struct human_output
*h
)
625 if (h
->f
&HOF_PROGRESS
) {
626 n
= strlen(h
->tv
->test
->name
) + 2 + h
->scoreboard
.len
;
627 for (i
= 0; i
< n
; i
++) fputs("\b \b", h
->lyt
.fp
);
628 h
->f
&= ~HOF_PROGRESS
;
632 /* --- @write_scoreboard_char@ --- *
634 * Arguments: @struct human_output *h@ = output state
635 * @int ch@ = scoreboard character to print
639 * Use: Write a scoreboard character, indicating the outcome of a
640 * test, to the output stream, with appropriate highlighting.
643 static void write_scoreboard_char(struct human_output
*h
, int ch
)
646 case HSB_LOSE
: setattr(h
, HA_LOSE
); break;
647 case HSB_SKIP
: setattr(h
, HA_SKIP
); break;
648 case HSB_XFAIL
: setattr(h
, HA_XFAIL
); break;
649 default: setattr(h
, HA_PLAIN
); break;
651 putc(ch
, h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
654 /* --- @show_progress@ --- *
656 * Arguments: @struct human_output *h@ = output state
660 * Use: Show the progress display, with the record of outcomes for
661 * the current test group.
663 * If the progress display is already active, or the output
664 * stream is not interactive, then nothing happens.
667 static void show_progress(struct human_output
*h
)
669 struct tvec_state
*tv
= h
->tv
;
672 if (tv
->test
&& (h
->f
&HOF_TTY
) && !(h
->f
&HOF_PROGRESS
)) {
673 fprintf(h
->lyt
.fp
, "%s: ", tv
->test
->name
);
674 if (!(h
->f
&HOF_COLOUR
))
675 dstr_write(&h
->scoreboard
, h
->lyt
.fp
);
676 else for (p
= h
->scoreboard
.buf
, l
= p
+ h
->scoreboard
.len
; p
< l
; p
++)
677 write_scoreboard_char(h
, *p
);
678 fflush(h
->lyt
.fp
); h
->f
|= HOF_PROGRESS
;
682 /* --- @human_writech@, @human_write@, @human_writef@ --- *
684 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
685 * @int ch@ = character to write
686 * @const char *@p@, @size_t sz@ = string (with explicit length)
688 * @const char *p, ...@ = format control string and arguments to
693 * Use: Write characters, strings, or formatted strings to the
694 * output, applying appropriate layout.
696 * For the human output driver, the layout machinery just strips
700 static int human_writech(void *go
, int ch
)
701 { struct human_output
*h
= go
; return (layout_char(&h
->lyt
, ch
)); }
703 static int human_writem(void *go
, const char *p
, size_t sz
)
704 { struct human_output
*h
= go
; return (layout_string(&h
->lyt
, p
, sz
)); }
706 static int human_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
708 struct human_output
*h
= go
;
713 n
= gprintf_memputf(h
->a
, &h
->outbuf
, &h
->outsz
, maxsz
, p
, ap
);
715 if (layout_string(&h
->lyt
, h
->outbuf
, n
)) return (-1);
719 static const struct gprintf_ops human_printops
=
720 { human_writech
, human_writem
, human_nwritef
};
722 /* --- @human_bsession@ --- *
724 * Arguments: @struct tvec_output *o@ = output sink, secretly a
725 * @struct human_output@
726 * @struct tvec_state *tv@ = the test state producing output
730 * Use: Begin a test session.
732 * The human driver just records the test state for later
736 static void human_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
737 { struct human_output
*h
= (struct human_output
*)o
; h
->tv
= tv
; }
739 /* --- @report_unusual@ --- *
741 * Arguments: @struct human_output *h@ = output sink
742 * @unsigned nxfail, nskip@ = number of expected failures and
747 * Use: Write (directly on the output stream) a note about expected
748 * failures and/or skipped tests, if there were any.
751 static void report_unusual(struct human_output
*h
,
752 unsigned nxfail
, unsigned nskip
)
754 const char *sep
= " (";
759 fprintf(h
->lyt
.fp
, "%s%u ", sep
, nxfail
);
760 setattr(h
, HA_XFAIL
);
761 fprintf(h
->lyt
.fp
, "expected %s", nxfail
== 1 ?
"failure" : "failures");
762 setattr(h
, HA_PLAIN
);
763 sep
= ", "; f
|= f_any
;
767 fprintf(h
->lyt
.fp
, "%s%u ", sep
, nskip
);
768 setattr(h
, HA_SKIP
); fputs("skipped", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
769 sep
= ", "; f
|= f_any
;
772 if (f
&f_any
) fputc(')', h
->lyt
.fp
);
777 /* --- @human_esession@ --- *
779 * Arguments: @struct tvec_output *o@ = output sink, secretly a
780 * @struct human_output@
782 * Returns: Suggested exit code.
784 * Use: End a test session.
786 * The human driver prints a final summary of the rest results
787 * and returns a suitable exit code.
790 static int human_esession(struct tvec_output
*o
)
792 struct human_output
*h
= (struct human_output
*)o
;
793 struct tvec_state
*tv
= h
->tv
;
795 all_win
= tv
->all
[TVOUT_WIN
], grps_win
= tv
->grps
[TVOUT_WIN
],
796 all_xfail
= tv
->all
[TVOUT_XFAIL
],
797 all_lose
= tv
->all
[TVOUT_LOSE
], grps_lose
= tv
->grps
[TVOUT_LOSE
],
798 all_skip
= tv
->all
[TVOUT_SKIP
], grps_skip
= tv
->grps
[TVOUT_SKIP
],
799 all_pass
= all_win
+ all_xfail
, all_run
= all_pass
+ all_lose
,
800 grps_run
= grps_win
+ grps_lose
;
803 setattr(h
, HA_WIN
); fputs("PASSED", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
804 fprintf(h
->lyt
.fp
, " %s%u %s",
805 !(all_skip
|| grps_skip
) ?
"all " : "",
806 all_pass
, all_pass
== 1 ?
"test" : "tests");
807 report_unusual(h
, all_xfail
, all_skip
);
808 fprintf(h
->lyt
.fp
, " in %u %s",
809 grps_win
, grps_win
== 1 ?
"group" : "groups");
810 report_unusual(h
, 0, grps_skip
);
812 setattr(h
, HA_LOSE
); fputs("FAILED", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
813 fprintf(h
->lyt
.fp
, " %u out of %u %s",
814 all_lose
, all_run
, all_run
== 1 ?
"test" : "tests");
815 report_unusual(h
, all_xfail
, all_skip
);
816 fprintf(h
->lyt
.fp
, " in %u out of %u %s",
817 grps_lose
, grps_run
, grps_run
== 1 ?
"group" : "groups");
818 report_unusual(h
, 0, grps_skip
);
820 fputc('\n', h
->lyt
.fp
);
822 if (tv
->f
&TVSF_ERROR
) {
823 setattr(h
, HA_ERR
); fputs("ERRORS", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
824 fputs(" found in input; tests may not have run correctly\n", h
->lyt
.fp
);
827 h
->tv
= 0; return (tv
->f
&TVSF_ERROR ?
2 : all_lose ?
1 : 0);
830 /* --- @human_bgroup@ --- *
832 * Arguments: @struct tvec_output *o@ = output sink, secretly a
833 * @struct human_output@
837 * Use: Begin a test group.
839 * The human driver determines the length of the longest
840 * register name, resets the group progress scoreboard, and
841 * activates the progress display.
844 static void human_bgroup(struct tvec_output
*o
)
846 struct human_output
*h
= (struct human_output
*)o
;
848 h
->maxlen
= register_maxnamelen(h
->tv
);
849 dstr_reset(&h
->scoreboard
); show_progress(h
);
852 /* --- @human_skipgroup@ --- *
854 * Arguments: @struct tvec_output *o@ = output sink, secretly a
855 * @struct human_output@
856 * @const char *excuse@, @va_list *ap@ = reason for skipping the
861 * Use: Report that a test group is being skipped.
863 * The human driver just reports the situation to its output
867 static void human_skipgroup(struct tvec_output
*o
,
868 const char *excuse
, va_list *ap
)
870 struct human_output
*h
= (struct human_output
*)o
;
873 fprintf(h
->lyt
.fp
, "%s ", h
->tv
->test
->name
);
875 show_progress(h
); h
->f
&= ~HOF_PROGRESS
;
876 if (h
->scoreboard
.len
) putc(' ', h
->lyt
.fp
);
878 setattr(h
, HA_SKIP
); fputs("skipped", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
879 if (excuse
) { fputs(": ", h
->lyt
.fp
); vfprintf(h
->lyt
.fp
, excuse
, *ap
); }
880 fputc('\n', h
->lyt
.fp
);
883 /* --- @human_egroup@ --- *
885 * Arguments: @struct tvec_output *o@ = output sink, secretly a
886 * @struct human_output@
890 * Use: Report that a test group has finished.
892 * The human driver reports a summary of the group's tests.
895 static void human_egroup(struct tvec_output
*o
)
897 struct human_output
*h
= (struct human_output
*)o
;
898 struct tvec_state
*tv
= h
->tv
;
899 unsigned win
= tv
->curr
[TVOUT_WIN
], xfail
= tv
->curr
[TVOUT_XFAIL
],
900 lose
= tv
->curr
[TVOUT_LOSE
], skip
= tv
->curr
[TVOUT_SKIP
],
901 run
= win
+ lose
+ xfail
;
903 if (h
->f
&HOF_TTY
) h
->f
&= ~HOF_PROGRESS
;
904 else fprintf(h
->lyt
.fp
, "%s:", h
->tv
->test
->name
);
907 fprintf(h
->lyt
.fp
, " %u/%u ", lose
, run
);
908 setattr(h
, HA_LOSE
); fputs("FAILED", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
909 report_unusual(h
, xfail
, skip
);
911 fputc(' ', h
->lyt
.fp
); setattr(h
, HA_WIN
);
912 fputs("ok", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
913 report_unusual(h
, xfail
, skip
);
915 fputc('\n', h
->lyt
.fp
);
918 /* --- @human_btest@ --- *
920 * Arguments: @struct tvec_output *o@ = output sink, secretly a
921 * @struct human_output@
925 * Use: Report that a test is starting.
927 * The human driver makes sure the progress display is active.
930 static void human_btest(struct tvec_output
*o
)
931 { struct human_output
*h
= (struct human_output
*)o
; show_progress(h
); }
933 /* --- @human_report_location@ --- *
935 * Arguments: @struct human_output *h@ = output state
936 * @FILE *fp@ = stream to write the location on
937 * @const char *file@ = filename
938 * @unsigned lno@ = line number
942 * Use: Print the filename and line number to the output stream @fp@.
943 * Also, if appropriate, print interleaved highlighting control
944 * codes to our usual output stream. If @file@ is null then do
948 static void human_report_location(struct human_output
*h
, FILE *fp
,
949 const char *file
, unsigned lno
)
954 /* We emit highlighting if @fp@ is our usual output stream, or the
955 * duplicate-errors flag is clear indicating that (we assume) they're
956 * secretly going to the same place anyway. If they're different streams,
957 * though, we have to be careful to keep the highlighting and the actual
963 else if (fp
!= h
->lyt
.fp
&& (h
->f
&HOF_DUPERR
))
964 fprintf(fp
, "%s:%u: ", file
, lno
);
966 if (fp
!= h
->lyt
.fp
) f
|= f_flush
;
968 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
970 setattr(h
, HA_LOC
); FLUSH(h
->lyt
.fp
);
971 fputs(file
, fp
); FLUSH(fp
);
972 setattr(h
, HA_LOCSEP
); FLUSH(h
->lyt
.fp
);
973 fputc(':', fp
); FLUSH(fp
);
974 setattr(h
, HA_LOC
); FLUSH(h
->lyt
.fp
);
975 fprintf(fp
, "%u", lno
); FLUSH(fp
);
976 setattr(h
, HA_LOCSEP
); FLUSH(h
->lyt
.fp
);
977 fputc(':', fp
); FLUSH(fp
);
978 setattr(h
, HA_PLAIN
); FLUSH(h
->lyt
.fp
);
987 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
989 * Arguments: @struct tvec_output *o@ = output sink, secretly a
990 * @struct human_output@
991 * @unsigned attr@ = attribute to apply to the outcome
992 * @const char *outcome@ = outcome string to report
993 * @const char *detail@, @va_list *ap@ = a detail message
994 * @const char *excuse@, @va_list *ap@ = reason for skipping the
999 * Use: Report that a test has been skipped or failed.
1001 * The human driver reports the situation on its output stream.
1004 static void human_outcome(struct tvec_output
*o
,
1005 unsigned attr
, const char *outcome
,
1006 const char *detail
, va_list *ap
)
1008 struct human_output
*h
= (struct human_output
*)o
;
1009 struct tvec_state
*tv
= h
->tv
;
1012 human_report_location(h
, h
->lyt
.fp
, tv
->infile
, tv
->test_lno
);
1013 fprintf(h
->lyt
.fp
, "`%s' ", tv
->test
->name
);
1014 setattr(h
, attr
); fputs(outcome
, h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
1015 if (detail
) { fputs(": ", h
->lyt
.fp
); vfprintf(h
->lyt
.fp
, detail
, *ap
); }
1016 fputc('\n', h
->lyt
.fp
);
1019 static void human_skip(struct tvec_output
*o
,
1020 const char *excuse
, va_list *ap
)
1021 { human_outcome(o
, HA_SKIP
, "skipped", excuse
, ap
); }
1022 static void human_fail(struct tvec_output
*o
,
1023 const char *detail
, va_list *ap
)
1024 { human_outcome(o
, HA_LOSE
, "FAILED", detail
, ap
); }
1026 /* --- @human_dumpreg@ --- *
1028 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1029 * @struct human_output@
1030 * @unsigned disp@ = register disposition
1031 * @const union tvec_regval *rv@ = register value
1032 * @const struct tvec_regdef *rd@ = register definition
1036 * Use: Dump a register.
1038 * The human driver applies highlighting to mismatching output
1039 * registers, but otherwise delegates to the register type
1040 * handler and the layout machinery.
1043 static void human_dumpreg(struct tvec_output
*o
,
1044 unsigned disp
, const union tvec_regval
*rv
,
1045 const struct tvec_regdef
*rd
)
1047 struct human_output
*h
= (struct human_output
*)o
;
1048 const char *ds
= regdisp(disp
); int n
= strlen(ds
) + strlen(rd
->name
);
1051 gprintf(&human_printops
, h
, "%*s%s %s = ",
1052 10 + h
->maxlen
- n
, "", ds
, rd
->name
);
1053 if (h
->f
&HOF_COLOUR
) {
1054 if (!rv
) setattr(h
, HA_UNSET
);
1055 else if (disp
== TVRD_FOUND
) setattr(h
, HA_FOUND
);
1056 else if (disp
== TVRD_EXPECT
) setattr(h
, HA_EXPECT
);
1058 if (!rv
) gprintf(&human_printops
, h
, "#unset");
1059 else rd
->ty
->dump(rv
, rd
, 0, &human_printops
, h
);
1060 setattr(h
, HA_PLAIN
); layout_char(&h
->lyt
, '\n');
1063 /* --- @human_etest@ --- *
1065 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1066 * @struct human_output@
1067 * @unsigned outcome@ = the test outcome
1071 * Use: Report that a test has finished.
1073 * The human driver reactivates the progress display, if
1074 * necessary, and adds a new character for the completed test.
1077 static void human_etest(struct tvec_output
*o
, unsigned outcome
)
1079 struct human_output
*h
= (struct human_output
*)o
;
1085 case TVOUT_WIN
: ch
= HSB_WIN
; break;
1086 case TVOUT_LOSE
: ch
= HSB_LOSE
; break;
1087 case TVOUT_XFAIL
: ch
= HSB_XFAIL
; break;
1088 case TVOUT_SKIP
: ch
= HSB_SKIP
; break;
1091 dstr_putc(&h
->scoreboard
, ch
);
1092 write_scoreboard_char(h
, ch
); fflush(h
->lyt
.fp
);
1096 /* --- @human_report@ --- *
1098 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1099 * @struct human_output@
1100 * @unsigned level@ = message level (@TVLEV_...@)
1101 * @const char *msg@, @va_list *ap@ = format string and
1106 * Use: Report a message to the user.
1108 * The human driver arranges to show the message on @stderr@ as
1109 * well as the usual output, with a certain amount of
1110 * intelligence in case they're both actually the same device.
1113 static void human_report(struct tvec_output
*o
, unsigned level
,
1114 const char *msg
, va_list *ap
)
1116 struct human_output
*h
= (struct human_output
*)o
;
1117 struct tvec_state
*tv
= h
->tv
;
1118 const char *levstr
; unsigned levattr
;
1122 #define f_progress 2u
1124 dstr_vputf(&d
, msg
, ap
); dstr_putc(&d
, '\n');
1127 #define CASE(tag, name, val) \
1128 case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
1130 default: levstr
= "??"; levattr
= HA_UNKLEV
; break;
1133 if (h
->lyt
.fp
!= stderr
&& !(h
->f
&HOF_DUPERR
)) f
|= f_flush
;
1135 #define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0)
1137 if (h
->f
^HOF_PROGRESS
)
1138 { clear_progress(h
); fflush(h
->lyt
.fp
); f
|= f_progress
; }
1139 fprintf(stderr
, "%s: ", QUIS
);
1140 human_report_location(h
, stderr
, tv
->infile
, tv
->lno
);
1141 setattr(h
, levattr
); FLUSH
; fputs(levstr
, stderr
); setattr(h
, 0); FLUSH
;
1142 fputs(": ", stderr
); fwrite(d
.buf
, 1, d
.len
, stderr
);
1146 if (h
->f
&HOF_DUPERR
) {
1147 human_report_location(h
, h
->lyt
.fp
, tv
->infile
, tv
->lno
);
1148 fprintf(h
->lyt
.fp
, "%s: ", levstr
);
1149 fwrite(d
.buf
, 1, d
.len
, h
->lyt
.fp
);
1151 if (f
&f_progress
) show_progress(h
);
1159 /* --- @human_bbench@ --- *
1161 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1162 * @struct human_output@
1163 * @const char *desc@ = adhoc test description
1164 * @unsigned unit@ = measurement unit (@BTU_...@)
1168 * Use: Report that a benchmark has started.
1170 * The human driver just prints the start of the benchmark
1174 static void human_bbench(struct tvec_output
*o
,
1175 const char *desc
, unsigned unit
)
1177 struct human_output
*h
= (struct human_output
*)o
;
1178 struct tvec_state
*tv
= h
->tv
;
1181 gprintf(&human_printops
, h
, "%s ", tv
->test
->name
);
1182 if (desc
) gprintf(&human_printops
, h
, "%s", desc
);
1183 else print_ident(tv
, TVSF_COMPACT
, &human_printops
, h
);
1184 gprintf(&human_printops
, h
, ": ");
1185 if (h
->f
&HOF_TTY
) fflush(h
->lyt
.fp
);
1188 /* --- @human_ebench@ --- *
1190 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1191 * @struct human_output@
1192 * @const char *desc@ = adhoc test description
1193 * @unsigned unit@ = measurement unit (@BTU_...@)
1194 * @const struct bench_timing *t@ = measurement
1198 * Use: Report a benchmark's results.
1200 * The human driver just delegates to the default benchmark
1201 * reporting, via the layout machinery.
1204 static void human_ebench(struct tvec_output
*o
,
1205 const char *desc
, unsigned unit
,
1206 const struct bench_timing
*t
)
1208 struct human_output
*h
= (struct human_output
*)o
;
1210 tvec_benchreport(&human_printops
, h
, unit
, 0, t
);
1211 layout_char(&h
->lyt
, '\n');
1214 static const struct tvec_benchoutops human_benchops
=
1215 { human_bbench
, human_ebench
};
1217 /* --- @human_extend@ --- *
1219 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1220 * @struct human_output@
1221 * @const char *name@ = extension name
1223 * Returns: A pointer to the extension implementation, or null.
1226 static const void *human_extend(struct tvec_output
*o
, const char *name
)
1228 if (STRCMP(name
, ==, TVEC_BENCHOUTEXT
)) return (&human_benchops
);
1232 /* --- @human_destroy@ --- *
1234 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1235 * @struct human_output@
1239 * Use: Release the resources held by the output driver.
1242 static void human_destroy(struct tvec_output
*o
)
1244 struct human_output
*h
= (struct human_output
*)o
;
1246 destroy_layout(&h
->lyt
,
1247 h
->lyt
.fp
== stdout
|| h
->lyt
.fp
== stderr ?
0 : DLF_CLOSE
);
1248 dstr_destroy(&h
->scoreboard
);
1249 x_free(h
->a
, h
->outbuf
); x_free(h
->a
, h
);
1252 static const struct tvec_outops human_ops
= {
1253 human_bsession
, human_esession
,
1254 human_bgroup
, human_skipgroup
, human_egroup
,
1255 human_btest
, human_skip
, human_fail
, human_dumpreg
, human_etest
,
1256 human_report
, human_extend
, human_destroy
1259 /* --- @tvec_humanoutput@ --- *
1261 * Arguments: @FILE *fp@ = output file to write on
1263 * Returns: An output formatter.
1265 * Use: Return an output formatter which writes on @fp@ with the
1266 * expectation that a human will be watching and interpreting
1267 * the output. If @fp@ denotes a terminal, the display shows a
1268 * `scoreboard' indicating the outcome of each test case
1269 * attempted, and may in addition use colour and other
1273 struct tvec_output
*tvec_humanoutput(FILE *fp
)
1275 struct human_output
*h
;
1278 XNEW(h
); h
->a
= arena_global
; h
->_o
.ops
= &human_ops
;
1279 h
->f
= 0; h
->attr
= 0;
1281 init_layout(&h
->lyt
, fp
, 0);
1282 h
->outbuf
= 0; h
->outsz
= 0;
1284 switch (getenv_boolean("TVEC_TTY", -1)) {
1285 case 1: h
->f
|= HOF_TTY
; break;
1288 if (isatty(fileno(fp
))) h
->f
|= HOF_TTY
;
1291 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1292 case 1: h
->f
|= HOF_COLOUR
; break;
1297 if (p
&& STRCMP(p
, !=, "dumb")) h
->f
|= HOF_COLOUR
;
1302 if (fp
!= stderr
&& (fp
!= stdout
|| !(h
->f
&HOF_TTY
))) h
->f
|= HOF_DUPERR
;
1303 dstr_create(&h
->scoreboard
);
1307 /*----- Machine-readable output -------------------------------------------*/
1309 struct machine_output
{
1310 struct tvec_output _o
; /* output base class */
1311 struct tvec_state
*tv
; /* stashed testing state */
1312 arena
*a
; /* arena for memory allocation */
1313 FILE *fp
; /* output stream */
1314 char *outbuf
; size_t outsz
; /* buffer for formatted output */
1315 unsigned grpix
, testix
; /* group and test indices */
1316 unsigned f
; /* flags */
1320 /* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
1322 * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
1323 * @int ch@ = character to write
1324 * @const char *@p@, @size_t sz@ = string (with explicit length)
1326 * @const char *p, ...@ = format control string and arguments to
1331 * Use: Write characters, strings, or formatted strings to the
1332 * output, applying appropriate quoting.
1335 static void machine_escape(struct machine_output
*m
, int ch
)
1338 case 0: fputs("\\0", m
->fp
); break;
1339 case '\a': fputs("\\a", m
->fp
); break;
1340 case '\b': fputs("\\b", m
->fp
); break;
1341 case '\x1b': fputs("\\e", m
->fp
); break;
1342 case '\f': fputs("\\f", m
->fp
); break;
1343 case '\n': fputs("\\n", m
->fp
); break;
1344 case '\r': fputs("\\r", m
->fp
); break;
1345 case '\t': fputs("\\t", m
->fp
); break;
1346 case '\v': fputs("\\v", m
->fp
); break;
1347 case '"': fputs("\\\"", m
->fp
); break;
1348 case '\\': fputs("\\\\", m
->fp
); break;
1349 default: fprintf(m
->fp
, "\\x{%02x}", ch
);
1353 static int machine_writech(void *go
, int ch
)
1355 struct machine_output
*m
= go
;
1357 if (ISPRINT(ch
)) putc(ch
, m
->fp
);
1358 else machine_escape(m
, ch
);
1362 static int machine_writem(void *go
, const char *p
, size_t sz
)
1364 struct machine_output
*m
= go
;
1365 const char *q
, *l
= p
+ sz
;
1370 if (q
>= l
) goto final
;
1371 if (!ISPRINT(*q
) || *q
== '\\' || *q
== '"') break;
1374 if (p
< q
) fwrite(p
, 1, q
- p
, m
->fp
);
1375 p
= q
; machine_escape(m
, (unsigned char)*p
++);
1378 if (p
< l
) fwrite(p
, 1, l
- p
, m
->fp
);
1382 static int machine_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
1384 struct machine_output
*m
= go
;
1389 n
= gprintf_memputf(m
->a
, &m
->outbuf
, &m
->outsz
, maxsz
, p
, ap
);
1391 return (machine_writem(m
, m
->outbuf
, n
));
1394 static const struct gprintf_ops machine_printops
=
1395 { machine_writech
, machine_writem
, machine_nwritef
};
1397 /* --- @machine_maybe_quote@ --- *
1399 * Arguments: @struct machine_output *m@ = output sink
1400 * @const char *p@ = pointer to string
1404 * Use: Print the string @p@, quoting it if necessary.
1407 static void machine_maybe_quote(struct machine_output
*m
, const char *p
)
1411 for (q
= p
; *q
; q
++)
1412 if (!ISPRINT(*q
) || ISSPACE(*q
)) goto quote
;
1413 fputs(p
, m
->fp
); return;
1415 putc('"', m
->fp
); machine_writem(m
, p
, strlen(p
)); putc('"', m
->fp
);
1418 /* --- @machine_bsession@ --- *
1420 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1421 * @struct machine_output@
1422 * @struct tvec_state *tv@ = the test state producing output
1426 * Use: Begin a test session.
1428 * The TAP driver records the test state for later reference,
1429 * initializes the group index counter, and prints the version
1433 static void machine_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
1435 struct machine_output
*m
= (struct machine_output
*)o
;
1437 m
->tv
= tv
; m
->grpix
= 0;
1440 /* --- @machine_show_stats@ --- *
1442 * Arguments: @struct machine_output *m@ = output sink
1443 * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
1447 * Use: Print a machine readable outcome statistics table
1450 static void machine_show_stats(struct machine_output
*m
,
1451 unsigned out
[TVOUT_LIMIT
])
1453 static const char *outtab
[] = { "lose", "skip", "xfail", "win" };
1456 for (i
= 0; i
< TVOUT_LIMIT
; i
++) {
1457 if (i
) putc(' ', m
->fp
);
1458 fprintf(m
->fp
, "%s=%u", outtab
[i
], out
[i
]);
1462 /* --- @machine_esession@ --- *
1464 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1465 * @struct machine_output@
1467 * Returns: Suggested exit code.
1469 * Use: End a test session.
1471 * The TAP driver prints a final summary of the rest results
1472 * and returns a suitable exit code. If errors occurred, it
1473 * instead prints a `Bail out!' line forcing the reader to
1477 static int machine_esession(struct tvec_output
*o
)
1479 struct machine_output
*m
= (struct machine_output
*)o
;
1480 struct tvec_state
*tv
= m
->tv
;
1482 fputs("END groups: ", m
->fp
);
1483 machine_show_stats(m
, tv
->grps
);
1484 fputs("; tests: ", m
->fp
);
1485 machine_show_stats(m
, tv
->all
);
1487 m
->tv
= 0; return (tv
->f
&TVSF_ERROR ?
2 : tv
->all
[TVOUT_LOSE
] ?
1 : 0);
1490 /* --- @machine_bgroup@ --- *
1492 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1493 * @struct machine_output@
1497 * Use: Begin a test group.
1499 * The TAP driver determines the length of the longest
1500 * register name, resets the group progress scoreboard, and
1501 * activates the progress display.
1504 static void machine_bgroup(struct tvec_output
*o
)
1506 struct machine_output
*m
= (struct machine_output
*)o
;
1507 struct tvec_state
*tv
= m
->tv
;
1509 fputs("BGROUP ", m
->fp
);
1510 machine_maybe_quote(m
, tv
->test
->name
);
1512 m
->grpix
++; m
->testix
= 0;
1515 /* --- @machine_skipgroup@ --- *
1517 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1518 * @struct machine_output@
1519 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1524 * Use: Report that a test group is being skipped.
1526 * The TAP driver just reports the situation to its output
1530 static void machine_skipgroup(struct tvec_output
*o
,
1531 const char *excuse
, va_list *ap
)
1533 struct machine_output
*m
= (struct machine_output
*)o
;
1534 struct tvec_state
*tv
= m
->tv
;
1536 fputs("SKIPGRP ", m
->fp
);
1537 machine_maybe_quote(m
, tv
->test
->name
);
1539 fputs(" \"", m
->fp
);
1540 vgprintf(&machine_printops
, m
, excuse
, ap
);
1546 /* --- @machine_egroup@ --- *
1548 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1549 * @struct machine_output@
1553 * Use: Report that a test group has finished.
1555 * The TAP driver reports a summary of the group's tests.
1558 static void machine_egroup(struct tvec_output
*o
)
1560 struct machine_output
*m
= (struct machine_output
*)o
;
1561 struct tvec_state
*tv
= m
->tv
;
1563 if (!(tv
->f
&TVSF_SKIP
)) {
1564 fputs("EGROUP ", m
->fp
); machine_maybe_quote(m
, tv
->test
->name
);
1565 putc(' ', m
->fp
); machine_show_stats(m
, tv
->curr
);
1570 /* --- @machine_btest@ --- *
1572 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1573 * @struct machine_output@
1577 * Use: Report that a test is starting.
1579 * The TAP driver advances its test counter. (We could do this
1580 * by adding up up the counters in @tv->curr@, and add on the
1581 * current test, but it's easier this way.)
1584 static void machine_btest(struct tvec_output
*o
) { ; }
1586 /* --- @machine_report_location@ --- *
1588 * Arguments: @struct human_output *h@ = output state
1589 * @const char *file@ = filename
1590 * @unsigned lno@ = line number
1594 * Use: Print the filename and line number to the output stream.
1597 static void machine_report_location(struct machine_output
*m
,
1598 const char *file
, unsigned lno
)
1602 machine_maybe_quote(m
, file
);
1603 fprintf(m
->fp
, ":%u", lno
);
1607 /* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
1609 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1610 * @struct machine_output@
1611 * @const char *outcome@ = outcome string to report
1612 * @const char *detail@, @va_list *ap@ = a detail message
1613 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1618 * Use: Report that a test has been skipped or failed.
1621 static void machine_outcome(struct tvec_output
*o
, const char *outcome
,
1622 const char *detail
, va_list *ap
)
1624 struct machine_output
*m
= (struct machine_output
*)o
;
1625 struct tvec_state
*tv
= m
->tv
;
1627 fprintf(m
->fp
, "%s %u", outcome
, m
->testix
);
1628 machine_report_location(m
, tv
->infile
, tv
->test_lno
);
1630 { putc(' ', m
->fp
); vgprintf(&machine_printops
, m
, detail
, ap
); }
1634 static void machine_skip(struct tvec_output
*o
,
1635 const char *excuse
, va_list *ap
)
1636 { machine_outcome(o
, "SKIP", excuse
, ap
); }
1637 static void machine_fail(struct tvec_output
*o
,
1638 const char *detail
, va_list *ap
)
1639 { machine_outcome(o
, "FAIL", detail
, ap
); }
1641 /* --- @machine_dumpreg@ --- *
1643 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1644 * @struct machine_output@
1645 * @unsigned disp@ = register disposition
1646 * @const union tvec_regval *rv@ = register value
1647 * @const struct tvec_regdef *rd@ = register definition
1651 * Use: Dump a register.
1653 * The machine driver applies highlighting to mismatching output
1654 * registers, but otherwise delegates to the register type
1658 static void machine_dumpreg(struct tvec_output
*o
,
1659 unsigned disp
, const union tvec_regval
*rv
,
1660 const struct tvec_regdef
*rd
)
1662 struct machine_output
*m
= (struct machine_output
*)o
;
1668 case TVRD_INPUT
: fputs("\tINPUT ", m
->fp
); f
|= f_reg
| f_nl
; break;
1669 case TVRD_OUTPUT
: fputs("\tOUTPUT ", m
->fp
); f
|= f_reg
| f_nl
; break;
1670 case TVRD_MATCH
: fputs("\tMATCH ", m
->fp
); f
|= f_reg
| f_nl
; break;
1671 case TVRD_FOUND
: fputs("\tMISMATCH ", m
->fp
); f
|= f_reg
; break;
1672 case TVRD_EXPECT
: fputs(" /= ", m
->fp
); f
|= f_nl
; break;
1676 if (f
&f_reg
) fprintf(m
->fp
, "%s = ", rd
->name
);
1677 if (!rv
) fputs("#unset", m
->fp
);
1678 else rd
->ty
->dump(rv
, rd
, TVSF_RAW
, &file_printops
, m
->fp
);
1679 if (f
&f_nl
) putc('\n', m
->fp
);
1685 /* --- @machine_etest@ --- *
1687 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1688 * @struct machine_output@
1689 * @unsigned outcome@ = the test outcome
1693 * Use: Report that a test has finished.
1695 * The machine driver reports the outcome of the test, if that's
1696 * not already decided.
1699 static void machine_etest(struct tvec_output
*o
, unsigned outcome
)
1701 struct machine_output
*m
= (struct machine_output
*)o
;
1703 if (!(m
->f
&MF_BENCH
)) switch (outcome
) {
1704 case TVOUT_WIN
: machine_outcome(o
, "WIN", 0, 0); break;
1705 case TVOUT_XFAIL
: machine_outcome(o
, "XFAIL", 0, 0); break;
1707 m
->testix
++; m
->f
&= ~MF_BENCH
;
1710 /* --- @machine_report@ --- *
1712 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1713 * @struct machine_output@
1714 * @unsigned level@ = message level (@TVLEV_...@)
1715 * @const char *msg@, @va_list *ap@ = format string and
1720 * Use: Report a message to the user.
1722 * Each report level has its own output tag
1725 static void machine_report(struct tvec_output
*o
, unsigned level
,
1726 const char *msg
, va_list *ap
)
1728 struct machine_output
*m
= (struct machine_output
*)o
;
1729 struct tvec_state
*tv
= m
->tv
;
1732 for (p
= tvec_strlevel(level
); *p
; p
++) putc(TOUPPER(*p
), m
->fp
);
1733 machine_report_location(m
, tv
->infile
, tv
->lno
);
1734 fputs(" \"", m
->fp
); vgprintf(&machine_printops
, m
, msg
, ap
);
1739 /* --- @machine_bbench@ --- *
1741 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1742 * @struct machine_output@
1743 * @const char *desc@ = adhoc test description
1744 * @unsigned unit@ = measurement unit (@BTU_...@)
1748 * Use: Report that a benchmark has started.
1750 * The machine driver does nothing here. All of the reporting
1751 * happens in @machine_ebench@.
1754 static void machine_bbench(struct tvec_output
*o
,
1755 const char *desc
, unsigned unit
)
1758 /* --- @machine_ebench@ --- *
1760 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1761 * @struct machine_output@
1762 * @const char *desc@ = adhoc test description
1763 * @unsigned unit@ = measurement unit (@BTU_...@)
1764 * @const struct bench_timing *t@ = measurement
1768 * Use: Report a benchmark's results.
1770 * The machine driver prints the result as a `BENCH' output
1771 * line, in raw format.
1774 static void machine_ebench(struct tvec_output
*o
,
1775 const char *desc
, unsigned unit
,
1776 const struct bench_timing
*t
)
1778 struct machine_output
*m
= (struct machine_output
*)o
;
1779 struct tvec_state
*tv
= m
->tv
;
1781 fprintf(m
->fp
, "BENCH %u", m
->testix
);
1782 machine_report_location(m
, tv
->infile
, tv
->test_lno
);
1784 if (desc
) machine_maybe_quote(m
, desc
);
1785 else print_ident(tv
, TVSF_RAW
, &file_printops
, m
->fp
);
1787 tvec_benchreport(&file_printops
, m
->fp
, unit
, TVSF_RAW
, t
);
1788 putc('\n', m
->fp
); m
->f
|= MF_BENCH
;
1791 static const struct tvec_benchoutops machine_benchops
=
1792 { machine_bbench
, machine_ebench
};
1794 /* --- @machine_extend@ --- *
1796 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1797 * @struct machine_output@
1798 * @const char *name@ = extension name
1800 * Returns: A pointer to the extension implementation, or null.
1803 static const void *machine_extend(struct tvec_output
*o
, const char *name
)
1805 if (STRCMP(name
, ==, TVEC_BENCHOUTEXT
)) return (&machine_benchops
);
1809 /* --- @machine_destroy@ --- *
1811 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1812 * @struct machine_output@
1816 * Use: Release the resources held by the output driver.
1819 static void machine_destroy(struct tvec_output
*o
)
1821 struct machine_output
*m
= (struct machine_output
*)o
;
1823 if (m
->fp
!= stdout
&& m
->fp
!= stderr
) fclose(m
->fp
);
1824 x_free(m
->a
, m
->outbuf
); x_free(m
->a
, m
);
1827 static const struct tvec_outops machine_ops
= {
1828 machine_bsession
, machine_esession
,
1829 machine_bgroup
, machine_skipgroup
, machine_egroup
,
1830 machine_btest
, machine_skip
, machine_fail
, machine_dumpreg
, machine_etest
,
1831 machine_report
, machine_extend
, machine_destroy
1834 /* --- @tvec_machineoutput@ --- *
1836 * Arguments: @FILE *fp@ = output file to write on
1838 * Returns: An output formatter.
1840 * Use: Return an output formatter which writes on @fp@ in a
1841 * moderately simple machine-readable format.
1844 struct tvec_output
*tvec_machineoutput(FILE *fp
)
1846 struct machine_output
*m
;
1848 XNEW(m
); m
->a
= arena_global
; m
->_o
.ops
= &machine_ops
;
1849 m
->f
= 0; m
->fp
= fp
; m
->outbuf
= 0; m
->outsz
= 0; m
->testix
= 0;
1853 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1856 struct tvec_output _o
; /* output base class */
1857 struct tvec_state
*tv
; /* stashed testing state */
1858 arena
*a
; /* arena for memory allocation */
1859 struct layout lyt
; /* output layout */
1860 char *outbuf
; size_t outsz
; /* buffer for formatted output */
1861 unsigned grpix
, testix
; /* group and test indices */
1862 unsigned previx
; /* previously reported test index */
1863 int maxlen
; /* longest register name */
1866 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1868 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1869 * @int ch@ = character to write
1870 * @const char *@p@, @size_t sz@ = string (with explicit length)
1872 * @const char *p, ...@ = format control string and arguments to
1877 * Use: Write characters, strings, or formatted strings to the
1878 * output, applying appropriate layout.
1880 * For the TAP output driver, the layout machinery prefixes each
1881 * line with ` ## ' and strips trailing spaces.
1884 static int tap_writech(void *go
, int ch
)
1886 struct tap_output
*t
= go
;
1888 if (layout_char(&t
->lyt
, ch
)) return (-1);
1892 static int tap_writem(void *go
, const char *p
, size_t sz
)
1894 struct tap_output
*t
= go
;
1896 if (layout_string(&t
->lyt
, p
, sz
)) return (-1);
1900 static int tap_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
1902 struct tap_output
*t
= go
;
1907 n
= gprintf_memputf(t
->a
, &t
->outbuf
, &t
->outsz
, maxsz
, p
, ap
);
1909 if (layout_string(&t
->lyt
, t
->outbuf
, n
)) return (-1);
1913 static const struct gprintf_ops tap_printops
=
1914 { tap_writech
, tap_writem
, tap_nwritef
};
1916 /* --- @tap_bsession@ --- *
1918 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1919 * @struct tap_output@
1920 * @struct tvec_state *tv@ = the test state producing output
1924 * Use: Begin a test session.
1926 * The TAP driver records the test state for later reference,
1927 * initializes the group index counter, and prints the version
1931 static void tap_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
1933 struct tap_output
*t
= (struct tap_output
*)o
;
1935 t
->tv
= tv
; t
->grpix
= 0;
1936 fputs("TAP version 13\n", t
->lyt
.fp
); /* but secretly 14 really */
1939 /* --- @tap_esession@ --- *
1941 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1942 * @struct tap_output@
1944 * Returns: Suggested exit code.
1946 * Use: End a test session.
1948 * The TAP driver prints a final summary of the rest results
1949 * and returns a suitable exit code. If errors occurred, it
1950 * instead prints a `Bail out!' line forcing the reader to
1954 static int tap_esession(struct tvec_output
*o
)
1956 struct tap_output
*t
= (struct tap_output
*)o
;
1957 struct tvec_state
*tv
= t
->tv
;
1959 if (tv
->f
&TVSF_ERROR
) {
1961 "Errors found in input; tests may not have run correctly\n",
1966 fprintf(t
->lyt
.fp
, "1..%u\n", t
->grpix
);
1967 t
->tv
= 0; return (tv
->all
[TVOUT_LOSE
] ?
1 : 0);
1970 /* --- @tap_bgroup@ --- *
1972 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1973 * @struct tap_output@
1977 * Use: Begin a test group.
1979 * The TAP driver determines the length of the longest
1980 * register name, resets the group progress scoreboard, and
1981 * activates the progress display.
1984 static void tap_bgroup(struct tvec_output
*o
)
1986 struct tap_output
*t
= (struct tap_output
*)o
;
1987 struct tvec_state
*tv
= t
->tv
;
1989 t
->grpix
++; t
->testix
= t
->previx
= 0;
1990 t
->maxlen
= register_maxnamelen(t
->tv
);
1991 fprintf(t
->lyt
.fp
, "# Subtest: %s\n", tv
->test
->name
);
1994 /* --- @tap_skipgroup@ --- *
1996 * Arguments: @struct tvec_output *o@ = output sink, secretly a
1997 * @struct tap_output@
1998 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2003 * Use: Report that a test group is being skipped.
2005 * The TAP driver just reports the situation to its output
2009 static void tap_skipgroup(struct tvec_output
*o
,
2010 const char *excuse
, va_list *ap
)
2012 struct tap_output
*t
= (struct tap_output
*)o
;
2014 fprintf(t
->lyt
.fp
, " 1..%u\n", t
->testix
);
2015 fprintf(t
->lyt
.fp
, "ok %u %s # SKIP", t
->grpix
, t
->tv
->test
->name
);
2016 if (excuse
) { fputc(' ', t
->lyt
.fp
); vfprintf(t
->lyt
.fp
, excuse
, *ap
); }
2017 fputc('\n', t
->lyt
.fp
);
2020 /* --- @tap_egroup@ --- *
2022 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2023 * @struct tap_output@
2027 * Use: Report that a test group has finished.
2029 * The TAP driver reports a summary of the group's tests.
2032 static void tap_egroup(struct tvec_output
*o
)
2034 struct tap_output
*t
= (struct tap_output
*)o
;
2035 struct tvec_state
*tv
= t
->tv
;
2037 fprintf(t
->lyt
.fp
, " 1..%u\n", t
->testix
);
2038 fprintf(t
->lyt
.fp
, "%s %u - %s\n",
2039 tv
->curr
[TVOUT_LOSE
] ?
"not ok" : "ok",
2040 t
->grpix
, tv
->test
->name
);
2043 /* --- @tap_btest@ --- *
2045 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2046 * @struct tap_output@
2050 * Use: Report that a test is starting.
2052 * The TAP driver advances its test counter. (We could do this
2053 * by adding up up the counters in @tv->curr@, and add on the
2054 * current test, but it's easier this way.)
2057 static void tap_btest(struct tvec_output
*o
)
2058 { struct tap_output
*t
= (struct tap_output
*)o
; t
->testix
++; }
2060 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
2062 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2063 * @struct tap_output@
2064 * @const char *head, *tail@ = outcome strings to report
2065 * @const char *detail@, @va_list *ap@ = a detail message
2066 * @const char *excuse@, @va_list *ap@ = reason for skipping the
2071 * Use: Report that a test has been skipped or failed.
2073 * The TAP driver reports the situation on its output stream.
2074 * TAP only allows us to report a single status for each
2075 * subtest, so we notice when we've already reported a status
2076 * for the current test and convert the second report as a
2077 * comment. This should only happen in the case of multiple
2081 static void tap_outcome(struct tvec_output
*o
,
2082 const char *head
, const char *tail
,
2083 const char *detail
, va_list *ap
)
2085 struct tap_output
*t
= (struct tap_output
*)o
;
2086 struct tvec_state
*tv
= t
->tv
;
2088 fprintf(t
->lyt
.fp
, " %s %u - %s:%u%s",
2089 t
->testix
== t
->previx ?
"##" : head
,
2090 t
->testix
, tv
->infile
, tv
->test_lno
, tail
);
2092 { fputc(' ', t
->lyt
.fp
); vfprintf(t
->lyt
.fp
, detail
, *ap
); }
2093 fputc('\n', t
->lyt
.fp
);
2094 t
->previx
= t
->testix
;
2097 static void tap_skip(struct tvec_output
*o
, const char *excuse
, va_list *ap
)
2098 { tap_outcome(o
, "ok", " # SKIP", excuse
, ap
); }
2099 static void tap_fail(struct tvec_output
*o
, const char *detail
, va_list *ap
)
2100 { tap_outcome(o
, "not ok", "", detail
, ap
); }
2102 /* --- @tap_dumpreg@ --- *
2104 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2105 * @struct tap_output@
2106 * @unsigned disp@ = register disposition
2107 * @const union tvec_regval *rv@ = register value
2108 * @const struct tvec_regdef *rd@ = register definition
2112 * Use: Dump a register.
2114 * The TAP driver applies highlighting to mismatching output
2115 * registers, but otherwise delegates to the register type
2116 * handler and the layout machinery. The result is that the
2117 * register dump is marked as a comment and indented.
2120 static void tap_dumpreg(struct tvec_output
*o
,
2121 unsigned disp
, const union tvec_regval
*rv
,
2122 const struct tvec_regdef
*rd
)
2124 struct tap_output
*t
= (struct tap_output
*)o
;
2125 const char *ds
= regdisp(disp
); int n
= strlen(ds
) + strlen(rd
->name
);
2127 set_layout_prefix(&t
->lyt
, " ## ");
2128 gprintf(&tap_printops
, t
, "%*s%s %s = ",
2129 10 + t
->maxlen
- n
, "", ds
, rd
->name
);
2130 if (!rv
) gprintf(&tap_printops
, t
, "#<unset>");
2131 else rd
->ty
->dump(rv
, rd
, 0, &tap_printops
, t
);
2132 layout_char(&t
->lyt
, '\n');
2135 /* --- @tap_etest@ --- *
2137 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2138 * @struct tap_output@
2139 * @unsigned outcome@ = the test outcome
2143 * Use: Report that a test has finished.
2145 * The TAP driver reports the outcome of the test, if that's not
2149 static void tap_etest(struct tvec_output
*o
, unsigned outcome
)
2153 tap_outcome(o
, "ok", "", 0, 0);
2156 tap_outcome(o
, "not ok", " # TODO expected failure", 0, 0);
2161 /* --- @tap_report@ --- *
2163 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2164 * @struct tap_output@
2165 * @unsigned level@ = message level (@TVLEV_...@)
2166 * @const char *msg@, @va_list *ap@ = format string and
2171 * Use: Report a message to the user.
2173 * Messages are reported as comments, so that they can be
2174 * accumulated by the reader. An error will cause a later
2175 * bailout or, if we crash before then, a missing plan line,
2176 * either of which will cause the reader to report a serious
2180 static void tap_report(struct tvec_output
*o
, unsigned level
,
2181 const char *msg
, va_list *ap
)
2183 struct tap_output
*t
= (struct tap_output
*)o
;
2184 struct tvec_state
*tv
= t
->tv
;
2186 if (tv
->test
) set_layout_prefix(&t
->lyt
, " ## ");
2187 else set_layout_prefix(&t
->lyt
, "## ");
2189 if (tv
->infile
) gprintf(&tap_printops
, t
, "%s:%u: ", tv
->infile
, tv
->lno
);
2190 gprintf(&tap_printops
, t
, "%s: ", tvec_strlevel(level
));
2191 vgprintf(&tap_printops
, t
, msg
, ap
);
2192 layout_char(&t
->lyt
, '\n');
2195 /* --- @tap_extend@ --- *
2197 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2198 * @struct tap_output@
2199 * @const char *name@ = extension name
2201 * Returns: A pointer to the extension implementation, or null.
2204 static const void *tap_extend(struct tvec_output
*o
, const char *name
)
2207 /* --- @tap_destroy@ --- *
2209 * Arguments: @struct tvec_output *o@ = output sink, secretly a
2210 * @struct tap_output@
2214 * Use: Release the resources held by the output driver.
2217 static void tap_destroy(struct tvec_output
*o
)
2219 struct tap_output
*t
= (struct tap_output
*)o
;
2221 destroy_layout(&t
->lyt
,
2222 t
->lyt
.fp
== stdout
|| t
->lyt
.fp
== stderr ?
0 : DLF_CLOSE
);
2223 x_free(t
->a
, t
->outbuf
); x_free(t
->a
, t
);
2226 static const struct tvec_outops tap_ops
= {
2227 tap_bsession
, tap_esession
,
2228 tap_bgroup
, tap_skipgroup
, tap_egroup
,
2229 tap_btest
, tap_skip
, tap_fail
, tap_dumpreg
, tap_etest
,
2230 tap_report
, tap_extend
, tap_destroy
2233 /* --- @tvec_tapoutput@ --- *
2235 * Arguments: @FILE *fp@ = output file to write on
2237 * Returns: An output formatter.
2239 * Use: Return an output formatter which writes on @fp@ in `TAP'
2240 * (`Test Anything Protocol') format.
2242 * TAP comes from the Perl community, but has spread rather
2243 * further. This driver produces TAP version 14, but pretends
2244 * to be version 13. The driver produces a TAP `test point' --
2245 * i.e., a result reported as `ok' or `not ok' -- for each input
2246 * test group. Failure reports and register dumps are produced
2247 * as diagnostic messages before the final group result. (TAP
2248 * permits structuerd YAML data after the test-point result,
2249 * which could be used to report details, but (a) postponing the
2250 * details until after the report is inconvenient, and (b) there
2251 * is no standardization for the YAML anyway, so in practice
2252 * it's no more useful than the unstructured diagnostics.
2255 struct tvec_output
*tvec_tapoutput(FILE *fp
)
2257 struct tap_output
*t
;
2259 XNEW(t
); t
->a
= arena_global
; t
->_o
.ops
= &tap_ops
;
2260 init_layout(&t
->lyt
, fp
, 0);
2261 t
->outbuf
= 0; t
->outsz
= 0;
2265 /*----- Default output ----------------------------------------------------*/
2267 /* --- @tvec_dfltoutput@ --- *
2269 * Arguments: @FILE *fp@ = output file to write on
2271 * Returns: An output formatter.
2273 * Use: Selects and instantiates an output formatter suitable for
2274 * writing on @fp@. The policy is subject to change, but
2275 * currently the `human' output format is selected if @fp@ is
2276 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
2277 * otherwise the `machine' format is used.
2280 struct tvec_output
*tvec_dfltout(FILE *fp
)
2282 int ttyp
= getenv_boolean("TVEC_TTY", -1);
2284 if (ttyp
== -1) ttyp
= isatty(fileno(fp
));
2285 if (ttyp
) return (tvec_humanoutput(fp
));
2286 else return (tvec_machineoutput(fp
));
2289 /*----- That's all, folks -------------------------------------------------*/