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 /*----- Common machinery --------------------------------------------------*/
50 /* --- @regdisp@ --- *
52 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
54 * Returns: A human-readable adjective describing the register
58 static const char *regdisp(unsigned disp
)
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";
70 /* --- @getenv_boolean@ --- *
72 * Arguments: @const char *var@ = environment variable name
73 * @int dflt@ = default value
75 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
76 * set to something truish, or @dflt@ otherwise.
79 static int getenv_boolean(const char *var
, int dflt
)
86 else if (STRCMP(p
, ==, "y") || STRCMP(p
, ==, "yes") ||
87 STRCMP(p
, ==, "t") || STRCMP(p
, ==, "true") ||
88 STRCMP(p
, ==, "on") || STRCMP(p
, ==, "force") ||
91 else if (STRCMP(p
, ==, "n") || STRCMP(p
, ==, "no") ||
92 STRCMP(p
, ==, "f") || STRCMP(p
, ==, "false") ||
93 STRCMP(p
, ==, "nil") || STRCMP(p
, ==, "off") ||
97 moan("ignoring unexpected value `%s' for environment variable `%s'",
103 /* --- @register_maxnamelen@ --- *
105 * Arguments: @const struct tvec_state *tv@ = test vector state
107 * Returns: The maximum length of a register name in the current test.
110 static int register_maxnamelen(const struct tvec_state
*tv
)
112 const struct tvec_regdef
*rd
;
115 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
116 { n
= strlen(rd
->name
); if (n
> maxlen
) maxlen
= n
; }
120 /*----- Output layout -----------------------------------------------------*/
122 /* We have two main jobs in output layout: trimming trailing blanks; and
123 * adding a prefix to each line.
125 * This is somehow much more complicated than it ought to be.
129 FILE *fp
; /* output file */
130 const char *prefix
, *pfxtail
, *pfxlim
; /* prefix pointers */
131 dstr w
; /* trailing whitespace */
132 unsigned f
; /* flags */
133 #define LYTF_NEWL 1u /* start of output line */
136 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
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. \
146 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
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. \
154 size_t n = limit - base; \
155 if (fwrite(base, 1, n, lyt->fp) < n) return (-1); \
158 #define PUT_CHAR(ch) do { \
159 /* Write CH to the output. Return immediately on error. */ \
161 if (putc(ch, lyt->fp) == EOF) return (-1); \
164 #define PUT_PREFIX do { \
165 /* Output the prefix, if there is one. Return immediately on error. */ \
167 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim); \
170 #define PUT_SAVED do { \
171 /* Output the saved trailing blank material in the buffer. */ \
173 size_t n = lyt->w.len; \
174 if (n && fwrite(lyt->w.buf, 1, n, lyt->fp) < n) return (-1); \
177 #define PUT_PFXINB do { \
178 /* Output the initial nonblank portion of the prefix, if there is \
179 * one. Return immediately on error. \
182 if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
185 #define SAVE_PFXTAIL do { \
186 /* Save the trailing blank portion of the prefix. */ \
189 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
192 /* --- @init_layout@ --- *
194 * Arguments: @struct layout *lyt@ = layout state to initialize
195 * @FILE *fp@ = output file
196 * @const char *prefix@ = prefix string (or null if empty)
200 * Use: Initialize a layout state.
203 static void init_layout(struct layout
*lyt
, FILE *fp
, const char *prefix
)
210 dstr_create(&lyt
->w
);
212 /* Prefix portions. */
213 if (!prefix
|| !*prefix
)
214 lyt
->prefix
= lyt
->pfxtail
= lyt
->pfxlim
= 0;
216 lyt
->prefix
= prefix
;
217 l
= lyt
->pfxlim
= prefix
+ strlen(prefix
);
218 SPLIT_RANGE(q
, prefix
, l
); lyt
->pfxtail
= q
;
222 /* --- @destroy_layout@ --- *
224 * Arguments: @struct layout *lyt@ = layout state
225 * @unsigned f@ = flags (@DLF_...@)
229 * Use: Releases a layout state and the resources it holds.
230 * Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
231 * it open (in case it's @stderr@ or something).
235 static void destroy_layout(struct layout
*lyt
, unsigned f
)
237 if (f
&DLF_CLOSE
) fclose(lyt
->fp
);
238 dstr_destroy(&lyt
->w
);
241 /* --- @layout_char@ --- *
243 * Arguments: @struct layout *lyt@ = layout state
244 * @int ch@ = character to write
246 * Returns: Zero on success, @-1@ on failure.
248 * Use: Write a single character to the output.
251 static int layout_char(struct layout
*lyt
, int ch
)
254 if (lyt
->f
&LYTF_NEWL
) PUT_PFXINB
;
255 PUT_CHAR('\n'); lyt
->f
|= LYTF_NEWL
; DRESET(&lyt
->w
);
256 } else if (isspace(ch
))
259 if (lyt
->f
&LYTF_NEWL
) { PUT_PFXINB
; lyt
->f
&= ~LYTF_NEWL
; }
260 PUT_SAVED
; PUT_CHAR(ch
); DRESET(&lyt
->w
);
265 /* --- @layout_string@ --- *
267 * Arguments: @struct layout *lyt@ = layout state
268 * @const char *p@ = string to write
269 * @size_t sz@ = length of string
271 * Returns: Zero on success, @-1@ on failure.
273 * Use: Write a string to the output.
276 static int layout_string(struct layout
*lyt
, const char *p
, size_t sz
)
278 const char *q
, *r
, *l
= p
+ sz
;
280 /* This is rather vexing. There are a small number of jobs to do, but the
281 * logic for deciding which to do when gets rather hairy if, as I've tried
282 * here, one aims to minimize the number of decisions being checked, so
283 * it's worth canning them into macros.
285 * Here, a `blank' is a whitespace character other than newline. The input
286 * buffer consists of one or more `segments', each of which consists of:
288 * * an initial portion, which is either empty or ends with a nonblank
291 * * a suffix which consists only of blanks; and
293 * * an optional newline.
295 * All segments except the last end with a newline.
298 #define SPLIT_SEGMENT do { \
299 /* Determine the bounds of the current segment. If there is a final \
300 * newline, then q is non-null and points to this newline; otherwise, \
301 * q is null. The initial portion of the segment lies between p .. r \
302 * and the blank suffix lies between r .. q (or r .. l if q is null). \
303 * This sounds awkward, but the suffix is only relevant if there is \
307 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
310 #define PUT_NONBLANK do { \
311 /* Output the initial portion of the segment. */ \
316 #define PUT_NEWLINE do { \
317 /* Write a newline, and advance to the next segment. */ \
319 PUT_CHAR('\n'); p = q + 1; \
322 #define SAVE_TAIL do { \
323 /* Save the trailing blank portion of the segment in the buffer. \
324 * Assumes that there is no newline, since otherwise the suffix would \
328 DPUTM(&lyt->w, r, l - r); \
331 /* Determine the bounds of the first segment. Handling this is the most
332 * complicated part of this function.
337 /* This is the only segment. We'll handle the whole thing here.
339 * If there's an initial nonblank portion, then we need to write that
340 * out. Furthermore, if we're at the start of the line then we'll need
341 * to write the prefix, and if there's saved blank material then we'll
342 * need to write that. Otherwise, there's only blank stuff, which we
343 * accumulate in the buffer.
345 * If we're at the start of a line here, then put the prefix followed by
346 * any saved whitespace, and then our initial nonblank portion. Then
347 * save our new trailing space.
351 if (lyt
->f
&LYTF_NEWL
) { PUT_PREFIX
; lyt
->f
&= ~LYTF_NEWL
; }
352 PUT_SAVED
; PUT_NONBLANK
; DRESET(&lyt
->w
);
358 /* There is at least one more segment, so we know that there'll be a line
362 if (lyt
->f
&LYTF_NEWL
) PUT_PREFIX
;
363 PUT_SAVED
; PUT_NONBLANK
;
364 } else if (lyt
->f
&LYTF_NEWL
)
366 PUT_NEWLINE
; DRESET(&lyt
->w
);
369 /* Main loop over whole segments with trailing newlines. For each one, we
370 * know that we're starting at the beginning of a line and there's a final
371 * newline, so we write the initial prefix and drop the trailing blanks.
374 if (r
> p
) { PUT_PREFIX
; PUT_NONBLANK
; }
380 /* At the end, there's no final newline. If there's nonblank material,
381 * then we can write the prefix and the nonblank stuff. Otherwise, stash
382 * the blank stuff (including the trailing blanks of the prefix) and leave
383 * the newline flag set.
385 if (r
> p
) { PUT_PREFIX
; PUT_NONBLANK
; lyt
->f
&= ~LYTF_NEWL
; }
386 else { lyt
->f
|= LYTF_NEWL
; SAVE_PFXTAIL
; }
405 /*----- Skeleton ----------------------------------------------------------*/
407 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
408 static int ..._esession(struct tvec_output *o)
409 static void ..._bgroup(struct tvec_output *o)
410 static void ..._skipgroup(struct tvec_output *o,
411 const char *excuse, va_list *ap)
412 static void ..._egroup(struct tvec_output *o)
413 static void ..._btest(struct tvec_output *o)
414 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
415 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
416 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
417 union tvec_regval *rv, const struct tvec_regdef *rd)
418 static void ..._etest(struct tvec_output *o, unsigned outcome)
419 static void ..._bbench(struct tvec_output *o,
420 const char *ident, unsigned unit)
421 static void ..._ebench(struct tvec_output *o,
422 const char *ident, unsigned unit,
423 const struct tvec_timing *t)
424 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
425 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
426 static void ..._destroy(struct tvec_output *o)
428 static const struct tvec_outops ..._ops = {
429 ..._bsession, ..._esession,
430 ..._bgroup, ..._egroup, ..._skip,
431 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
432 ..._bbench, ..._ebench,
433 ..._error, ..._notice,
437 /*----- Human-readable output ---------------------------------------------*/
439 /* Attributes for colour output. This should be done better, but @terminfo@
442 * An attribute byte holds a foreground colour in the low nibble, a
443 * background colour in the next nibble, and some flags in the next few
444 * bits. A colour is expressed in classic 1-bit-per-channel style, with red,
445 * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3.
447 #define HAF_FGMASK 0x0f /* foreground colour mask */
448 #define HAF_FGSHIFT 0 /* foreground colour shift */
449 #define HAF_BGMASK 0xf0 /* background colour mask */
450 #define HAF_BGSHIFT 4 /* background colour shift */
451 #define HAF_FG 256u /* set foreground? */
452 #define HAF_BG 512u /* set background? */
453 #define HAF_BOLD 1024u /* set bold? */
454 #define HCOL_BLACK 0u /* colour codes... */
456 #define HCOL_GREEN 2u
457 #define HCOL_YELLOW 3u
459 #define HCOL_MAGENTA 5u
461 #define HCOL_WHITE 7u
462 #define HCF_BRIGHT 8u /* bright colour flag */
463 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */
464 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */
466 /* Predefined attributes. */
467 #define HA_PLAIN 0 /* nothing special: terminal defaults */
468 #define HA_LOC (HFG(CYAN)) /* filename or line number */
469 #define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */
470 #define HA_UNSET (HFG(YELLOW)) /* register not set */
471 #define HA_FOUND (HFG(RED)) /* incorrect output value */
472 #define HA_EXPECT (HFG(GREEN)) /* what the value should have been */
473 #define HA_WIN (HFG(GREEN)) /* reporting success */
474 #define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */
475 #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */
476 #define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */
477 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* reporting an error */
479 /* Scoreboard indicators. */
480 #define HSB_WIN '.' /* test passed */
481 #define HSB_LOSE 'x' /* test failed */
482 #define HSB_XFAIL 'o' /* test failed expectedly */
483 #define HSB_SKIP '_' /* test wasn't run */
485 struct human_output
{
486 struct tvec_output _o
; /* output base class */
487 struct tvec_state
*tv
; /* stashed testing state */
488 struct layout lyt
; /* output layout */
489 char *outbuf
; size_t outsz
; /* buffer for formatted output */
490 dstr scoreboard
; /* history of test group results */
491 unsigned attr
; /* current terminal attributes */
492 int maxlen
; /* longest register name */
493 unsigned f
; /* flags */
494 #define HOF_TTY 1u /* writing to terminal */
495 #define HOF_DUPERR 2u /* duplicate errors to stderr */
496 #define HOF_COLOUR 4u /* print in angry fruit salad */
497 #define HOF_PROGRESS 8u /* progress display is active */
500 /* --- @set_colour@ --- *
502 * Arguments: @FILE *fp@ = output stream to write on
503 * @int *sep_inout@ = where to maintain separator
504 * @const char *norm@ = prefix for normal colour
505 * @const char *bright@ = prefix for bright colour
506 * @unsigned colour@ = four bit colour code
510 * Use: Write to the output stream @fp@, the current character at
511 * @*sep_inout@, if that's not zero, followed by either @norm@
512 * or @bright@, according to whether the @HCF_BRIGHT@ flag is
513 * set in @colour@, followed by the plain colour code from
514 * @colour@; finally, update @*sep_inout@ to be a `%|;|%'.
516 * This is an internal subroutine for @setattr@ below.
519 static void set_colour(FILE *fp
, int *sep_inout
,
520 const char *norm
, const char *bright
,
523 if (*sep_inout
) putc(*sep_inout
, fp
);
524 fprintf(fp
, "%s%d", colour
&HCF_BRIGHT ? bright
: norm
, colour
&7);
528 /* --- @setattr@ --- *
530 * Arguments: @struct human_output *h@ = output state
531 * @unsigned attr@ = attribute code to set
535 * Use: Send a control sequence to the output stream so that
536 * subsequent text is printed with the given attributes.
538 * Some effort is taken to avoid unnecessary control sequences.
539 * In particular, if @attr@ matches the current terminal
540 * settings already, then nothing is written.
543 static void setattr(struct human_output
*h
, unsigned attr
)
545 unsigned diff
= h
->attr
^ attr
;
548 /* If there's nothing to do, we might as well stop now. */
549 if (!diff
|| !(h
->f
&HOF_COLOUR
)) return;
551 /* Start on the control command. */
552 fputs("\x1b[", h
->lyt
.fp
);
554 /* Change the boldness if necessary. */
556 if (attr
&HAF_BOLD
) putc('1', h
->lyt
.fp
);
557 else { putc('0', h
->lyt
.fp
); diff
= h
->attr
; }
561 /* Change the foreground colour if necessary. */
562 if (diff
&(HAF_FG
| HAF_FGMASK
)) {
564 set_colour(h
->lyt
.fp
, &sep
, "3", "9",
565 (attr
&HAF_FGMASK
) >> HAF_FGSHIFT
);
567 if (sep
) putc(sep
, h
->lyt
.fp
);
568 fputs("39", h
->lyt
.fp
); sep
= ';';
572 /* Change the background colour if necessary. */
573 if (diff
&(HAF_BG
| HAF_BGMASK
)) {
575 set_colour(h
->lyt
.fp
, &sep
, "4", "10",
576 (attr
&HAF_BGMASK
) >> HAF_BGSHIFT
);
578 if (sep
) putc(sep
, h
->lyt
.fp
);
579 fputs("49", h
->lyt
.fp
); sep
= ';';
583 /* Terminate the control command and save the new attributes. */
584 putc('m', h
->lyt
.fp
); h
->attr
= attr
;
587 /* --- @clear_progress@ --- *
589 * Arguments: @struct human_output *h@ = output state
593 * Use: Remove the progress display from the terminal.
595 * If the progress display isn't active then do nothing.
598 static void clear_progress(struct human_output
*h
)
602 if (h
->f
&HOF_PROGRESS
) {
603 n
= strlen(h
->tv
->test
->name
) + 2 + h
->scoreboard
.len
;
604 for (i
= 0; i
< n
; i
++) fputs("\b \b", h
->lyt
.fp
);
605 h
->f
&= ~HOF_PROGRESS
;
609 /* --- @write_scoreboard_char@ --- *
611 * Arguments: @struct human_output *h@ = output state
612 * @int ch@ = scoreboard character to print
616 * Use: Write a scoreboard character, indicating the outcome of a
617 * test, to the output stream, with appropriate highlighting.
620 static void write_scoreboard_char(struct human_output
*h
, int ch
)
623 case HSB_LOSE
: setattr(h
, HA_LOSE
); break;
624 case HSB_SKIP
: setattr(h
, HA_SKIP
); break;
625 case HSB_XFAIL
: setattr(h
, HA_XFAIL
); break;
626 default: setattr(h
, HA_PLAIN
); break;
628 putc(ch
, h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
631 /* --- @show_progress@ --- *
633 * Arguments: @struct human_output *h@ = output state
637 * Use: Show the progress display, with the record of outcomes for
638 * the current test group.
640 * If the progress display is already active, or the output
641 * stream is not interactive, then nothing happens.
644 static void show_progress(struct human_output
*h
)
646 struct tvec_state
*tv
= h
->tv
;
649 if (tv
->test
&& (h
->f
&HOF_TTY
) && !(h
->f
&HOF_PROGRESS
)) {
650 fprintf(h
->lyt
.fp
, "%s: ", tv
->test
->name
);
651 if (!(h
->f
&HOF_COLOUR
))
652 dstr_write(&h
->scoreboard
, h
->lyt
.fp
);
653 else for (p
= h
->scoreboard
.buf
, l
= p
+ h
->scoreboard
.len
; p
< l
; p
++)
654 write_scoreboard_char(h
, *p
);
655 fflush(h
->lyt
.fp
); h
->f
|= HOF_PROGRESS
;
659 /* --- @report_location@ --- *
661 * Arguments: @struct human_output *h@ = output state
662 * @FILE *fp@ = stream to write the location on
663 * @const char *file@ = filename
664 * @unsigned lno@ = line number
668 * Use: Print the filename and line number to the output stream @fp@.
669 * Also, if appropriate, print interleaved highlighting control
670 * codes to our usual output stream. If @file@ is null then do
674 static void report_location(struct human_output
*h
, FILE *fp
,
675 const char *file
, unsigned lno
)
680 /* We emit highlighting if @fp@ is our usual output stream, or the
681 * duplicate-errors flag is clear indicating that (we assume) they're
682 * secretly going to the same place anyway. If they're different streams,
683 * though, we have to be careful to keep the highlighting and the actual
689 else if (fp
!= h
->lyt
.fp
&& (h
->f
&HOF_DUPERR
))
690 fprintf(fp
, "%s:%u: ", file
, lno
);
692 if (fp
!= h
->lyt
.fp
) f
|= f_flush
;
694 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
696 setattr(h
, HA_LOC
); FLUSH(h
->lyt
.fp
);
697 fputs(file
, fp
); FLUSH(fp
);
698 setattr(h
, HA_LOCSEP
); FLUSH(h
->lyt
.fp
);
699 fputc(':', fp
); FLUSH(fp
);
700 setattr(h
, HA_LOC
); FLUSH(h
->lyt
.fp
);
701 fprintf(fp
, "%u", lno
); FLUSH(fp
);
702 setattr(h
, HA_LOCSEP
); FLUSH(h
->lyt
.fp
);
703 fputc(':', fp
); FLUSH(fp
);
704 setattr(h
, HA_PLAIN
); FLUSH(h
->lyt
.fp
);
713 /* --- @human_writech@, @human_write@, @human_writef@ --- *
715 * Arguments: @void *go@ = output sink, secretly a @struct human_output@
716 * @int ch@ = character to write
717 * @const char *@p@, @size_t sz@ = string (with explicit length)
719 * @const char *p, ...@ = format control string and arguments to
724 * Use: Write characters, strings, or formatted strings to the
725 * output, applying appropriate layout.
727 * For the human output driver, the layout machinery just strips
731 static int human_writech(void *go
, int ch
)
732 { struct human_output
*h
= go
; return (layout_char(&h
->lyt
, ch
)); }
734 static int human_writem(void *go
, const char *p
, size_t sz
)
735 { struct human_output
*h
= go
; return (layout_string(&h
->lyt
, p
, sz
)); }
737 static int human_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
739 struct human_output
*h
= go
;
744 n
= gprintf_memputf(&h
->outbuf
, &h
->outsz
, maxsz
, p
, ap
);
746 return (layout_string(&h
->lyt
, h
->outbuf
, n
));
749 static const struct gprintf_ops human_printops
=
750 { human_writech
, human_writem
, human_nwritef
};
752 /* --- @human_bsession@ --- *
754 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
756 * @struct tvec_state *tv@ = the test state producing output
760 * Use: Begin a test session.
762 * The human driver just records the test state for later
766 static void human_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
767 { struct human_output
*h
= (struct human_output
*)o
; h
->tv
= tv
; }
769 /* --- @report_unusual@ --- *
771 * Arguments: @struct human_output *h@ = output sink
772 * @unsigned nxfail, nskip@ = number of expected failures and
777 * Use: Write (directly on the output stream) a note about expected
778 * failures and/or skipped tests, if there were any.
781 static void report_unusual(struct human_output
*h
,
782 unsigned nxfail
, unsigned nskip
)
784 const char *sep
= " (";
789 fprintf(h
->lyt
.fp
, "%s%u ", sep
, nxfail
);
790 setattr(h
, HA_XFAIL
);
791 fprintf(h
->lyt
.fp
, "expected %s", nxfail
== 1 ?
"failure" : "failures");
792 setattr(h
, HA_PLAIN
);
793 sep
= ", "; f
|= f_any
;
797 fprintf(h
->lyt
.fp
, "%s%u ", sep
, nskip
);
798 setattr(h
, HA_SKIP
); fputs("skipped", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
799 sep
= ", "; f
|= f_any
;
802 if (f
&f_any
) fputc(')', h
->lyt
.fp
);
807 /* --- @human_esession@ --- *
809 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
812 * Returns: Suggested exit status.
814 * Use: End a test session.
816 * The human driver prints a final summary of the rest results
817 * and returns a suitable exit code.
820 static int human_esession(struct tvec_output
*o
)
822 struct human_output
*h
= (struct human_output
*)o
;
823 struct tvec_state
*tv
= h
->tv
;
825 all_win
= tv
->all
[TVOUT_WIN
], grps_win
= tv
->grps
[TVOUT_WIN
],
826 all_xfail
= tv
->all
[TVOUT_XFAIL
],
827 all_lose
= tv
->all
[TVOUT_LOSE
], grps_lose
= tv
->grps
[TVOUT_LOSE
],
828 all_skip
= tv
->all
[TVOUT_SKIP
], grps_skip
= tv
->grps
[TVOUT_SKIP
],
829 all_pass
= all_win
+ all_xfail
, all_run
= all_pass
+ all_lose
,
830 grps_run
= grps_win
+ grps_lose
;
833 setattr(h
, HA_WIN
); fputs("PASSED", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
834 fprintf(h
->lyt
.fp
, " %s%u %s",
835 !(all_skip
|| grps_skip
) ?
"all " : "",
836 all_pass
, all_pass
== 1 ?
"test" : "tests");
837 report_unusual(h
, all_xfail
, all_skip
);
838 fprintf(h
->lyt
.fp
, " in %u %s",
839 grps_win
, grps_win
== 1 ?
"group" : "groups");
840 report_unusual(h
, 0, grps_skip
);
842 setattr(h
, HA_LOSE
); fputs("FAILED", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
843 fprintf(h
->lyt
.fp
, " %u out of %u %s",
844 all_lose
, all_run
, all_run
== 1 ?
"test" : "tests");
845 report_unusual(h
, all_xfail
, all_skip
);
846 fprintf(h
->lyt
.fp
, " in %u out of %u %s",
847 grps_lose
, grps_run
, grps_run
== 1 ?
"group" : "groups");
848 report_unusual(h
, 0, grps_skip
);
850 fputc('\n', h
->lyt
.fp
);
852 if (tv
->f
&TVSF_ERROR
) {
853 setattr(h
, HA_ERR
); fputs("ERRORS", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
854 fputs(" found in input; tests may not have run correctly\n", h
->lyt
.fp
);
857 h
->tv
= 0; return (tv
->f
&TVSF_ERROR ?
2 : all_lose ?
1 : 0);
860 /* --- @human_bgroup@ --- *
862 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
867 * Use: Begin a test group.
869 * The human driver determines the length of the longest
870 * register name, resets the group progress scoreboard, and
871 * activates the progress display.
874 static void human_bgroup(struct tvec_output
*o
)
876 struct human_output
*h
= (struct human_output
*)o
;
878 h
->maxlen
= register_maxnamelen(h
->tv
);
879 dstr_reset(&h
->scoreboard
); show_progress(h
);
882 /* --- @human_skipgroup@ --- *
884 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
886 * @const char *excuse@, @va_list *ap@ = reason for skipping the
891 * Use: Report that a test group is being skipped.
893 * The human driver just reports the situation to its output
897 static void human_skipgroup(struct tvec_output
*o
,
898 const char *excuse
, va_list *ap
)
900 struct human_output
*h
= (struct human_output
*)o
;
903 fprintf(h
->lyt
.fp
, "%s ", h
->tv
->test
->name
);
905 show_progress(h
); h
->f
&= ~HOF_PROGRESS
;
906 if (h
->scoreboard
.len
) putc(' ', h
->lyt
.fp
);
908 setattr(h
, HA_SKIP
); fputs("skipped", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
909 if (excuse
) { fputs(": ", h
->lyt
.fp
); vfprintf(h
->lyt
.fp
, excuse
, *ap
); }
910 fputc('\n', h
->lyt
.fp
);
913 /* --- @human_egroup@ --- *
915 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
920 * Use: Report that a test group has finished.
922 * The human driver reports a summary of the group's tests.
925 static void human_egroup(struct tvec_output
*o
)
927 struct human_output
*h
= (struct human_output
*)o
;
928 struct tvec_state
*tv
= h
->tv
;
929 unsigned win
= tv
->curr
[TVOUT_WIN
], xfail
= tv
->curr
[TVOUT_XFAIL
],
930 lose
= tv
->curr
[TVOUT_LOSE
], skip
= tv
->curr
[TVOUT_SKIP
],
931 run
= win
+ lose
+ xfail
;
933 if (h
->f
&HOF_TTY
) h
->f
&= ~HOF_PROGRESS
;
934 else fprintf(h
->lyt
.fp
, "%s:", h
->tv
->test
->name
);
937 fprintf(h
->lyt
.fp
, " %u/%u ", lose
, run
);
938 setattr(h
, HA_LOSE
); fputs("FAILED", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
939 report_unusual(h
, xfail
, skip
);
941 fputc(' ', h
->lyt
.fp
); setattr(h
, HA_WIN
);
942 fputs("ok", h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
943 report_unusual(h
, xfail
, skip
);
945 fputc('\n', h
->lyt
.fp
);
948 /* --- @human_btest@ --- *
950 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
955 * Use: Report that a test is starting.
957 * The human driver makes sure the progress display is active.
960 static void human_btest(struct tvec_output
*o
)
961 { struct human_output
*h
= (struct human_output
*)o
; show_progress(h
); }
963 /* --- @human_outcome@, @human_skip@, @human_fail@ --- *
965 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
967 * @unsigned attr@ = attribute to apply to the outcome
968 * @const char *outcome@ = outcome string to report
969 * @const char *detail@, @va_list *ap@ = a detail message
970 * @const char *excuse@, @va_list *ap@ = reason for skipping the
975 * Use: Report that a test has been skipped or failed.
977 * The human driver reports the situation on its output stream.
980 static void human_outcome(struct tvec_output
*o
,
981 unsigned attr
, const char *outcome
,
982 const char *detail
, va_list *ap
)
984 struct human_output
*h
= (struct human_output
*)o
;
985 struct tvec_state
*tv
= h
->tv
;
988 report_location(h
, h
->lyt
.fp
, tv
->infile
, tv
->test_lno
);
989 fprintf(h
->lyt
.fp
, "`%s' ", tv
->test
->name
);
990 setattr(h
, attr
); fputs(outcome
, h
->lyt
.fp
); setattr(h
, HA_PLAIN
);
991 if (detail
) { fputs(": ", h
->lyt
.fp
); vfprintf(h
->lyt
.fp
, detail
, *ap
); }
992 fputc('\n', h
->lyt
.fp
);
995 static void human_skip(struct tvec_output
*o
,
996 const char *excuse
, va_list *ap
)
997 { human_outcome(o
, HA_SKIP
, "skipped", excuse
, ap
); }
998 static void human_fail(struct tvec_output
*o
,
999 const char *detail
, va_list *ap
)
1000 { human_outcome(o
, HA_LOSE
, "FAILED", detail
, ap
); }
1002 /* --- @human_dumpreg@ --- *
1004 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1006 * @unsigned disp@ = register disposition
1007 * @const union tvec_regval *rv@ = register value
1008 * @const struct tvec_regdef *rd@ = register definition
1012 * Use: Dump a register.
1014 * The human driver applies highlighting to mismatching output
1015 * registers, but otherwise delegates to the register type
1016 * handler and the layout machinery.
1019 static void human_dumpreg(struct tvec_output
*o
,
1020 unsigned disp
, const union tvec_regval
*rv
,
1021 const struct tvec_regdef
*rd
)
1023 struct human_output
*h
= (struct human_output
*)o
;
1024 const char *ds
= regdisp(disp
); int n
= strlen(ds
) + strlen(rd
->name
);
1027 gprintf(&human_printops
, h
, "%*s%s %s = ",
1028 10 + h
->maxlen
- n
, "", ds
, rd
->name
);
1029 if (h
->f
&HOF_COLOUR
) {
1030 if (!rv
) setattr(h
, HA_UNSET
);
1031 else if (disp
== TVRD_FOUND
) setattr(h
, HA_FOUND
);
1032 else if (disp
== TVRD_EXPECT
) setattr(h
, HA_EXPECT
);
1034 if (!rv
) gprintf(&human_printops
, h
, "#unset");
1035 else rd
->ty
->dump(rv
, rd
, 0, &human_printops
, h
);
1036 setattr(h
, HA_PLAIN
); layout_char(&h
->lyt
, '\n');
1039 /* --- @human_etest@ --- *
1041 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1043 * @unsigned outcome@ = the test outcome
1047 * Use: Report that a test has finished.
1049 * The human driver reactivates the progress display, if
1050 * necessary, and adds a new character for the completed test.
1053 static void human_etest(struct tvec_output
*o
, unsigned outcome
)
1055 struct human_output
*h
= (struct human_output
*)o
;
1061 case TVOUT_WIN
: ch
= HSB_WIN
; break;
1062 case TVOUT_LOSE
: ch
= HSB_LOSE
; break;
1063 case TVOUT_XFAIL
: ch
= HSB_XFAIL
; break;
1064 case TVOUT_SKIP
: ch
= HSB_SKIP
; break;
1067 dstr_putc(&h
->scoreboard
, ch
);
1068 write_scoreboard_char(h
, ch
); fflush(h
->lyt
.fp
);
1072 /* --- @human_bbench@ --- *
1074 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1076 * @const char *ident@ = identifying register values
1077 * @unsigned unit@ = measurement unit (@TVBU_...@)
1081 * Use: Report that a benchmark has started.
1083 * The human driver just prints the start of the benchmark
1087 static void human_bbench(struct tvec_output
*o
,
1088 const char *ident
, unsigned unit
)
1090 struct human_output
*h
= (struct human_output
*)o
;
1091 struct tvec_state
*tv
= h
->tv
;
1094 fprintf(h
->lyt
.fp
, "%s: %s: ", tv
->test
->name
, ident
); fflush(h
->lyt
.fp
);
1097 /* --- @human_ebench@ --- *
1099 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1101 * @const char *ident@ = identifying register values
1102 * @unsigned unit@ = measurement unit (@TVBU_...@)
1103 * @const struct bench_timing *tm@ = measurement
1107 * Use: Report a benchmark's results
1109 * The human driver just delegates to the default benchmark
1110 * reporting, via the layout machinery.
1113 static void human_ebench(struct tvec_output
*o
,
1114 const char *ident
, unsigned unit
,
1115 const struct bench_timing
*tm
)
1117 struct human_output
*h
= (struct human_output
*)o
;
1119 tvec_benchreport(&human_printops
, h
->lyt
.fp
, unit
, tm
);
1120 fputc('\n', h
->lyt
.fp
);
1123 /* --- @human_report@ --- *
1125 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1127 * @unsigned level@ = message level (@TVLEV_...@)
1128 * @const char *msg@, @va_list *ap@ = format string and
1133 * Use: Report a message to the user.
1135 * The human driver arranges to show the message on @stderr@ as
1136 * well as the usual output, with a certain amount of
1137 * intelligence in case they're both actually the same device.
1140 static void human_report(struct tvec_output
*o
, unsigned level
,
1141 const char *msg
, va_list *ap
)
1143 struct human_output
*h
= (struct human_output
*)o
;
1144 struct tvec_state
*tv
= h
->tv
;
1147 dstr_vputf(&d
, msg
, ap
); dstr_putc(&d
, '\n');
1149 clear_progress(h
); fflush(h
->lyt
.fp
);
1150 fprintf(stderr
, "%s: ", QUIS
);
1151 report_location(h
, stderr
, tv
->infile
, tv
->lno
);
1152 fwrite(d
.buf
, 1, d
.len
, stderr
);
1154 if (h
->f
&HOF_DUPERR
) {
1155 report_location(h
, h
->lyt
.fp
, tv
->infile
, tv
->lno
);
1156 fwrite(d
.buf
, 1, d
.len
, h
->lyt
.fp
);
1161 /* --- @human_destroy@ --- *
1163 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1168 * Use: Release the resources held by the output driver.
1171 static void human_destroy(struct tvec_output
*o
)
1173 struct human_output
*h
= (struct human_output
*)o
;
1175 destroy_layout(&h
->lyt
,
1176 h
->lyt
.fp
== stdout
|| h
->lyt
.fp
== stderr ?
0 : DLF_CLOSE
);
1177 dstr_destroy(&h
->scoreboard
);
1178 xfree(h
->outbuf
); xfree(h
);
1181 static const struct tvec_outops human_ops
= {
1182 human_bsession
, human_esession
,
1183 human_bgroup
, human_skipgroup
, human_egroup
,
1184 human_btest
, human_skip
, human_fail
, human_dumpreg
, human_etest
,
1185 human_bbench
, human_ebench
,
1190 /* --- @tvec_humanoutput@ --- *
1192 * Arguments: @FILE *fp@ = output file to write on
1194 * Returns: An output formatter.
1196 * Use: Return an output formatter which writes on @fp@ with the
1197 * expectation that a human will be watching and interpreting
1198 * the output. If @fp@ denotes a terminal, the display shows a
1199 * `scoreboard' indicating the outcome of each test case
1200 * attempted, and may in addition use colour and other
1204 struct tvec_output
*tvec_humanoutput(FILE *fp
)
1206 struct human_output
*h
;
1209 h
= xmalloc(sizeof(*h
)); h
->_o
.ops
= &human_ops
;
1210 h
->f
= 0; h
->attr
= 0;
1212 init_layout(&h
->lyt
, fp
, 0);
1213 h
->outbuf
= 0; h
->outsz
= 0;
1215 switch (getenv_boolean("TVEC_TTY", -1)) {
1216 case 1: h
->f
|= HOF_TTY
; break;
1219 if (isatty(fileno(fp
))) h
->f
|= HOF_TTY
;
1222 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1223 case 1: h
->f
|= HOF_COLOUR
; break;
1228 if (p
&& STRCMP(p
, !=, "dumb")) h
->f
|= HOF_COLOUR
;
1233 if (fp
!= stderr
&& (fp
!= stdout
|| !(h
->f
&HOF_TTY
))) h
->f
|= HOF_DUPERR
;
1234 dstr_create(&h
->scoreboard
);
1238 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1241 struct tvec_output _o
; /* output base class */
1242 struct tvec_state
*tv
; /* stashed testing state */
1243 struct layout lyt
; /* output layout */
1244 char *outbuf
; size_t outsz
; /* buffer for formatted output */
1245 unsigned grpix
, testix
; /* group and test indices */
1246 unsigned previx
; /* previously reported test index */
1247 int maxlen
; /* longest register name */
1250 /* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
1252 * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
1253 * @int ch@ = character to write
1254 * @const char *@p@, @size_t sz@ = string (with explicit length)
1256 * @const char *p, ...@ = format control string and arguments to
1261 * Use: Write characters, strings, or formatted strings to the
1262 * output, applying appropriate layout.
1264 * For the TAP output driver, the layout machinery prefixes each
1265 * line with ` ## ' and strips trailing spaces.
1268 static int tap_writech(void *go
, int ch
)
1269 { struct tap_output
*t
= go
; return (layout_char(&t
->lyt
, ch
)); }
1271 static int tap_writem(void *go
, const char *p
, size_t sz
)
1272 { struct tap_output
*t
= go
; return (layout_string(&t
->lyt
, p
, sz
)); }
1274 static int tap_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
1276 struct tap_output
*t
= go
;
1281 n
= gprintf_memputf(&t
->outbuf
, &t
->outsz
, maxsz
, p
, ap
);
1283 return (layout_string(&t
->lyt
, t
->outbuf
, n
));
1286 static const struct gprintf_ops tap_printops
=
1287 { tap_writech
, tap_writem
, tap_nwritef
};
1289 /* --- @tap_bsession@ --- *
1291 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1293 * @struct tvec_state *tv@ = the test state producing output
1297 * Use: Begin a test session.
1299 * The TAP driver records the test state for later reference,
1300 * initializes the group index counter, and prints the version
1304 static void tap_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
1306 struct tap_output
*t
= (struct tap_output
*)o
;
1308 t
->tv
= tv
; t
->grpix
= 0;
1309 fputs("TAP version 13\n", t
->lyt
.fp
); /* but secretly 14 really */
1312 /* --- @tap_esession@ --- *
1314 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1317 * Returns: Suggested exit status.
1319 * Use: End a test session.
1321 * The TAP driver prints a final summary of the rest results
1322 * and returns a suitable exit code.
1325 static int tap_esession(struct tvec_output
*o
)
1327 struct tap_output
*t
= (struct tap_output
*)o
;
1328 struct tvec_state
*tv
= t
->tv
;
1330 if (tv
->f
&TVSF_ERROR
) {
1332 "Errors found in input; tests may not have run correctly\n",
1337 fprintf(t
->lyt
.fp
, "1..%u\n", t
->grpix
);
1338 t
->tv
= 0; return (tv
->all
[TVOUT_LOSE
] ?
1 : 0);
1341 /* --- @tap_bgroup@ --- *
1343 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1348 * Use: Begin a test group.
1350 * The TAP driver determines the length of the longest
1351 * register name, resets the group progress scoreboard, and
1352 * activates the progress display.
1355 static void tap_bgroup(struct tvec_output
*o
)
1357 struct tap_output
*t
= (struct tap_output
*)o
;
1358 struct tvec_state
*tv
= t
->tv
;
1360 t
->grpix
++; t
->testix
= t
->previx
= 0;
1361 t
->maxlen
= register_maxnamelen(t
->tv
);
1362 fprintf(t
->lyt
.fp
, "# Subtest: %s\n", tv
->test
->name
);
1365 /* --- @tap_skipgroup@ --- *
1367 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1369 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1374 * Use: Report that a test group is being skipped.
1376 * The TAP driver just reports the situation to its output
1380 static void tap_skipgroup(struct tvec_output
*o
,
1381 const char *excuse
, va_list *ap
)
1383 struct tap_output
*t
= (struct tap_output
*)o
;
1385 fprintf(t
->lyt
.fp
, " 1..%u\n", t
->testix
);
1386 fprintf(t
->lyt
.fp
, "ok %u %s # SKIP", t
->grpix
, t
->tv
->test
->name
);
1387 if (excuse
) { fputc(' ', t
->lyt
.fp
); vfprintf(t
->lyt
.fp
, excuse
, *ap
); }
1388 fputc('\n', t
->lyt
.fp
);
1391 /* --- @tap_egroup@ --- *
1393 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1398 * Use: Report that a test group has finished.
1400 * The TAP driver reports a summary of the group's tests.
1403 static void tap_egroup(struct tvec_output
*o
)
1405 struct tap_output
*t
= (struct tap_output
*)o
;
1406 struct tvec_state
*tv
= t
->tv
;
1408 fprintf(t
->lyt
.fp
, " 1..%u\n", t
->testix
);
1409 fprintf(t
->lyt
.fp
, "%s %u - %s\n",
1410 tv
->curr
[TVOUT_LOSE
] ?
"not ok" : "ok",
1411 t
->grpix
, tv
->test
->name
);
1414 /* --- @tap_btest@ --- *
1416 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1421 * Use: Report that a test is starting.
1423 * The TAP driver advances its test counter. (We could do this
1424 * by adding up up the counters in @tv->curr@, and add on the
1425 * current test, but it's easier this way.)
1428 static void tap_btest(struct tvec_output
*o
)
1429 { struct tap_output
*t
= (struct tap_output
*)o
; t
->testix
++; }
1431 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
1433 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1435 * @unsigned attr@ = attribute to apply to the outcome
1436 * @const char *outcome@ = outcome string to report
1437 * @const char *detail@, @va_list *ap@ = a detail message
1438 * @const char *excuse@, @va_list *ap@ = reason for skipping the
1443 * Use: Report that a test has been skipped or failed.
1445 * The TAP driver reports the situation on its output stream.
1446 * TAP only allows us to report a single status for each
1447 * subtest, so we notice when we've already reported a status
1448 * for the current test and convert the second report as a
1449 * comment. This should only happen in the case of multiple
1453 static void tap_outcome(struct tvec_output
*o
,
1454 const char *head
, const char *tail
,
1455 const char *detail
, va_list *ap
)
1457 struct tap_output
*t
= (struct tap_output
*)o
;
1458 struct tvec_state
*tv
= t
->tv
;
1460 fprintf(t
->lyt
.fp
, " %s %u - %s:%u%s",
1461 t
->testix
== t
->previx ?
"##" : head
,
1462 t
->testix
, tv
->infile
, tv
->test_lno
, tail
);
1464 { fputc(' ', t
->lyt
.fp
); vfprintf(t
->lyt
.fp
, detail
, *ap
); }
1465 fputc('\n', t
->lyt
.fp
);
1466 t
->previx
= t
->testix
;
1469 static void tap_skip(struct tvec_output
*o
, const char *excuse
, va_list *ap
)
1470 { tap_outcome(o
, "ok", " # SKIP", excuse
, ap
); }
1471 static void tap_fail(struct tvec_output
*o
, const char *detail
, va_list *ap
)
1472 { tap_outcome(o
, "not ok", "", detail
, ap
); }
1474 /* --- @tap_dumpreg@ --- *
1476 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1478 * @unsigned disp@ = register disposition
1479 * @const union tvec_regval *rv@ = register value
1480 * @const struct tvec_regdef *rd@ = register definition
1484 * Use: Dump a register.
1486 * The TAP driver applies highlighting to mismatching output
1487 * registers, but otherwise delegates to the register type
1488 * handler and the layout machinery. The result is that the
1489 * register dump is marked as a comment and indented.
1492 static void tap_dumpreg(struct tvec_output
*o
,
1493 unsigned disp
, const union tvec_regval
*rv
,
1494 const struct tvec_regdef
*rd
)
1496 struct tap_output
*t
= (struct tap_output
*)o
;
1497 const char *ds
= regdisp(disp
); int n
= strlen(ds
) + strlen(rd
->name
);
1499 gprintf(&tap_printops
, t
, "%*s%s %s = ",
1500 10 + t
->maxlen
- n
, "", ds
, rd
->name
);
1501 if (!rv
) gprintf(&tap_printops
, t
, "#<unset>");
1502 else rd
->ty
->dump(rv
, rd
, 0, &tap_printops
, t
);
1503 layout_char(&t
->lyt
, '\n');
1506 /* --- @tap_etest@ --- *
1508 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1510 * @unsigned outcome@ = the test outcome
1514 * Use: Report that a test has finished.
1516 * The TAP driver reports the outcome of the test, if that's not
1520 static void tap_etest(struct tvec_output
*o
, unsigned outcome
)
1524 tap_outcome(o
, "ok", "", 0, 0);
1527 tap_outcome(o
, "not ok", " # TODO expected failure", 0, 0);
1532 /* --- @tap_bbench@ --- *
1534 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1536 * @const char *ident@ = identifying register values
1537 * @unsigned unit@ = measurement unit (@TVBU_...@)
1541 * Use: Report that a benchmark has started.
1543 * The TAP driver does nothing here. All of the reporting
1544 * happens in @tap_ebench@.
1547 static void tap_bbench(struct tvec_output
*o
,
1548 const char *ident
, unsigned unit
)
1551 /* --- @tap_ebench@ --- *
1553 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1555 * @const char *ident@ = identifying register values
1556 * @unsigned unit@ = measurement unit (@TVBU_...@)
1557 * @const struct bench_timing *tm@ = measurement
1561 * Use: Report a benchmark's results
1563 * The TAP driver just delegates to the default benchmark
1564 * reporting, via the layout machinery so that the result is
1565 * printed as a comment.
1568 static void tap_ebench(struct tvec_output
*o
,
1569 const char *ident
, unsigned unit
,
1570 const struct bench_timing
*tm
)
1572 struct tap_output
*t
= (struct tap_output
*)o
;
1573 struct tvec_state
*tv
= t
->tv
;
1575 gprintf(&tap_printops
, t
, "%s: %s: ", tv
->test
->name
, ident
);
1576 tvec_benchreport(&tap_printops
, t
, unit
, tm
);
1577 layout_char(&t
->lyt
, '\n');
1580 /* --- @tap_report@ --- *
1582 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1584 * @unsigned level@ = message level (@TVLEV_...@)
1585 * @const char *msg@, @va_list *ap@ = format string and
1590 * Use: Report a message to the user.
1592 * The TAP driver converts error reports into TAP `Bail out!'
1593 * errors. Less critical notices end up as comments.
1596 static void tap_report(struct tvec_output
*o
, unsigned level
,
1597 const char *msg
, va_list *ap
)
1599 struct tap_output
*t
= (struct tap_output
*)o
;
1600 struct tvec_state
*tv
= t
->tv
;
1601 const struct gprintf_ops
*gops
; void *go
;
1603 if (level
>= TVLEV_ERR
) {
1604 fputs("Bail out! ", t
->lyt
.fp
);
1605 gops
= &file_printops
; go
= t
->lyt
.fp
;
1607 gops
= &tap_printops
; go
= t
;
1609 if (tv
->infile
) gprintf(gops
, go
, "%s:%u: ", tv
->infile
, tv
->lno
);
1610 gprintf(gops
, go
, msg
, ap
); gops
->putch(go
, '\n');
1613 /* --- @tap_destroy@ --- *
1615 * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
1620 * Use: Release the resources held by the output driver.
1623 static void tap_destroy(struct tvec_output
*o
)
1625 struct tap_output
*t
= (struct tap_output
*)o
;
1627 destroy_layout(&t
->lyt
,
1628 t
->lyt
.fp
== stdout
|| t
->lyt
.fp
== stderr ?
0 : DLF_CLOSE
);
1629 xfree(t
->outbuf
); xfree(t
);
1632 static const struct tvec_outops tap_ops
= {
1633 tap_bsession
, tap_esession
,
1634 tap_bgroup
, tap_skipgroup
, tap_egroup
,
1635 tap_btest
, tap_skip
, tap_fail
, tap_dumpreg
, tap_etest
,
1636 tap_bbench
, tap_ebench
,
1641 /* --- @tvec_tapoutput@ --- *
1643 * Arguments: @FILE *fp@ = output file to write on
1645 * Returns: An output formatter.
1647 * Use: Return an output formatter which writes on @fp@ in `TAP'
1648 * (`Test Anything Protocol') format.
1650 * TAP comes from the Perl community, but has spread rather
1651 * further. This driver produces TAP version 14, but pretends
1652 * to be version 13. The driver produces a TAP `test point' --
1653 * i.e., a result reported as `ok' or `not ok' -- for each input
1654 * test group. Failure reports and register dumps are produced
1655 * as diagnostic messages before the final group result. (TAP
1656 * permits structuerd YAML data after the test-point result,
1657 * which could be used to report details, but (a) postponing the
1658 * details until after the report is inconvenient, and (b) there
1659 * is no standardization for the YAML anyway, so in practice
1660 * it's no more useful than the unstructured diagnostics.
1663 struct tvec_output
*tvec_tapoutput(FILE *fp
)
1665 struct tap_output
*t
;
1667 t
= xmalloc(sizeof(*t
)); t
->_o
.ops
= &tap_ops
;
1668 init_layout(&t
->lyt
, fp
, " ## ");
1669 t
->outbuf
= 0; t
->outsz
= 0;
1673 /*----- Default output ----------------------------------------------------*/
1675 /* --- @tvec_dfltoutput@ --- *
1677 * Arguments: @FILE *fp@ = output file to write on
1679 * Returns: An output formatter.
1681 * Use: Selects and instantiates an output formatter suitable for
1682 * writing on @fp@. The policy is subject to change, but
1683 * currently the `human' output format is selected if @fp@ is
1684 * interactive (i.e., if @isatty(fileno(fp))@ is true), and
1685 * otherwise the `tap' format is used.
1688 struct tvec_output
*tvec_dfltout(FILE *fp
)
1690 int ttyp
= getenv_boolean("TVEC_TTY", -1);
1692 if (ttyp
== -1) ttyp
= isatty(fileno(fp
));
1693 if (ttyp
) return (tvec_humanoutput(fp
));
1694 else return (tvec_tapoutput(fp
));
1697 /*----- That's all, folks -------------------------------------------------*/