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 formatting -------------------------------------------------*/
122 /* We have two main jobs in output formatting: 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 FMTF_NEWL 1u /* start of output line */
136 /* Support macros. These assume `fmt' 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, fmt->fp) < n) return (-1); \
158 #define PUT_CHAR(ch) do { \
159 /* Write CH to the output. Return immediately on error. */ \
161 if (putc(ch, fmt->fp) == EOF) return (-1); \
164 #define PUT_PREFIX do { \
165 /* Output the prefix, if there is one. Return immediately on error. */ \
167 if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim); \
170 #define PUT_SAVED do { \
171 /* Output the saved trailing blank material in the buffer. */ \
173 size_t n = fmt->w.len; \
174 if (n && fwrite(fmt->w.buf, 1, n, fmt->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 (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail); \
185 #define SAVE_PFXTAIL do { \
186 /* Save the trailing blank portion of the prefix. */ \
189 DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail); \
192 /* --- @init_fmt@ --- *
194 * Arguments: @struct format *fmt@ = formatting state to initialize
195 * @FILE *fp@ = output file
196 * @const char *prefix@ = prefix string (or null if empty)
200 * Use: Initialize a formatting state.
203 static void init_fmt(struct format
*fmt
, FILE *fp
, const char *prefix
)
210 dstr_create(&fmt
->w
);
212 /* Prefix portions. */
213 if (!prefix
|| !*prefix
)
214 fmt
->prefix
= fmt
->pfxtail
= fmt
->pfxlim
= 0;
216 fmt
->prefix
= prefix
;
217 l
= fmt
->pfxlim
= prefix
+ strlen(prefix
);
218 SPLIT_RANGE(q
, prefix
, l
); fmt
->pfxtail
= q
;
219 DPUTM(&fmt
->w
, q
, l
- q
);
223 /* --- @destroy_fmt@ --- *
225 * Arguments: @struct format *fmt@ = formatting state
226 * @unsigned f@ = flags (@DFF_...@)
230 * Use: Releases a formatting state and the resources it holds.
231 * Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave
232 * it open (in case it's @stderr@ or something).
236 static void destroy_fmt(struct format
*fmt
, unsigned f
)
238 if (f
&DFF_CLOSE
) fclose(fmt
->fp
);
239 dstr_destroy(&fmt
->w
);
242 /* --- @format_char@ --- *
244 * Arguments: @struct format *fmt@ = formatting state
245 * @int ch@ = character to write
247 * Returns: Zero on success, @-1@ on failure.
249 * Use: Write a single character to the output.
252 static int format_char(struct format
*fmt
, int ch
)
255 if (fmt
->f
&FMTF_NEWL
) PUT_PFXINB
;
256 PUT_CHAR('\n'); fmt
->f
|= FMTF_NEWL
; DRESET(&fmt
->w
);
257 } else if (isspace(ch
))
260 if (fmt
->f
&FMTF_NEWL
) { PUT_PFXINB
; fmt
->f
&= ~FMTF_NEWL
; }
261 PUT_SAVED
; PUT_CHAR(ch
); DRESET(&fmt
->w
);
266 /* --- @format_string@ --- *
268 * Arguments: @struct format *fmt@ = formatting state
269 * @const char *p@ = string to write
270 * @size_t sz@ = length of string
272 * Returns: Zero on success, @-1@ on failure.
274 * Use: Write a string to the output.
277 static int format_string(struct format
*fmt
, const char *p
, size_t sz
)
279 const char *q
, *r
, *l
= p
+ sz
;
281 /* This is rather vexing. There are a small number of jobs to do, but the
282 * logic for deciding which to do when gets rather hairy if, as I've tried
283 * here, one aims to minimize the number of decisions being checked, so
284 * it's worth canning them into macros.
286 * Here, a `blank' is a whitespace character other than newline. The input
287 * buffer consists of one or more `segments', each of which consists of:
289 * * an initial portion, which is either empty or ends with a nonblank
292 * * a suffix which consists only of blanks; and
294 * * an optional newline.
296 * All segments except the last end with a newline.
299 #define SPLIT_SEGMENT do { \
300 /* Determine the bounds of the current segment. If there is a final \
301 * newline, then q is non-null and points to this newline; otherwise, \
302 * q is null. The initial portion of the segment lies between p .. r \
303 * and the blank suffix lies between r .. q (or r .. l if q is null). \
304 * This sounds awkward, but the suffix is only relevant if there is \
308 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
311 #define PUT_NONBLANK do { \
312 /* Output the initial portion of the segment. */ \
317 #define PUT_NEWLINE do { \
318 /* Write a newline, and advance to the next segment. */ \
320 PUT_CHAR('\n'); p = q + 1; \
323 #define SAVE_TAIL do { \
324 /* Save the trailing blank portion of the segment in the buffer. \
325 * Assumes that there is no newline, since otherwise the suffix would \
329 DPUTM(&fmt->w, r, l - r); \
332 /* Determine the bounds of the first segment. Handling this is the most
333 * complicated part of this function.
338 /* This is the only segment. We'll handle the whole thing here.
340 * If there's an initial nonblank portion, then we need to write that
341 * out. Furthermore, if we're at the start of the line then we'll need
342 * to write the prefix, and if there's saved blank material then we'll
343 * need to write that. Otherwise, there's only blank stuff, which we
344 * accumulate in the buffer.
346 * If we're at the start of a line here, then
350 if (fmt
->f
&FMTF_NEWL
) { PUT_PFXINB
; fmt
->f
&= ~FMTF_NEWL
; }
351 PUT_SAVED
; PUT_NONBLANK
; DRESET(&fmt
->w
);
357 /* There is at least one more segment, so we know that there'll be a line
360 if (fmt
->f
&FMTF_NEWL
) PUT_PFXINB
;
361 if (r
> p
) { PUT_SAVED
; PUT_NONBLANK
; }
362 PUT_NEWLINE
; DRESET(&fmt
->w
);
365 /* Main loop over whole segments with trailing newlines. For each one, we
366 * know that we're starting at the beginning of a line and there's a final
367 * newline, so we write the initial prefix and drop the trailing blanks.
370 PUT_PREFIX
; PUT_NONBLANK
; PUT_NEWLINE
;
374 /* At the end, there's no final newline. If there's nonblank material,
375 * then we can write the prefix and the nonblank stuff. Otherwise, stash
376 * the blank stuff (including the trailing blanks of the prefix) and leave
377 * the newline flag set.
379 if (r
> p
) { PUT_PREFIX
; PUT_NONBLANK
; fmt
->f
&= ~FMTF_NEWL
; }
380 else { fmt
->f
|= FMTF_NEWL
; SAVE_PFXTAIL
; }
399 /*----- Skeleton ----------------------------------------------------------*/
401 static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
402 static int ..._esession(struct tvec_output *o)
403 static void ..._bgroup(struct tvec_output *o)
404 static void ..._skipgroup(struct tvec_output *o,
405 const char *excuse, va_list *ap)
406 static void ..._egroup(struct tvec_output *o)
407 static void ..._btest(struct tvec_output *o)
408 static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
409 static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
410 static void ..._dumpreg(struct tvec_output *o, unsigned disp,
411 union tvec_regval *rv, const struct tvec_regdef *rd)
412 static void ..._etest(struct tvec_output *o, unsigned outcome)
413 static void ..._bbench(struct tvec_output *o,
414 const char *ident, unsigned unit)
415 static void ..._ebench(struct tvec_output *o,
416 const char *ident, unsigned unit,
417 const struct tvec_timing *t)
418 static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
419 static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
420 static void ..._destroy(struct tvec_output *o)
422 static const struct tvec_outops ..._ops = {
423 ..._bsession, ..._esession,
424 ..._bgroup, ..._egroup, ..._skip,
425 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
426 ..._bbench, ..._ebench,
427 ..._error, ..._notice,
431 /*----- Human-readable output ---------------------------------------------*/
433 #define HAF_FGMASK 0x0f
434 #define HAF_FGSHIFT 0
435 #define HAF_BGMASK 0xf0
436 #define HAF_BGSHIFT 4
439 #define HAF_BOLD 1024u
440 #define HCOL_BLACK 0u
442 #define HCOL_GREEN 2u
443 #define HCOL_YELLOW 3u
445 #define HCOL_MAGENTA 5u
447 #define HCOL_WHITE 7u
448 #define HCF_BRIGHT 8u
449 #define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
450 #define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
452 #define HA_WIN (HFG(GREEN))
453 #define HA_LOSE (HFG(RED) | HAF_BOLD)
454 #define HA_SKIP (HFG(YELLOW))
455 #define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
457 struct human_output
{
458 struct tvec_output _o
;
459 struct tvec_state
*tv
;
461 char *outbuf
; size_t outsz
;
467 #define HOF_DUPERR 2u
468 #define HOF_COLOUR 4u
469 #define HOF_PROGRESS 8u
472 static void set_colour(FILE *fp
, int *sep_inout
,
473 const char *norm
, const char *bright
,
476 if (*sep_inout
) putc(*sep_inout
, fp
);
477 fprintf(fp
, "%s%d", colour
&HCF_BRIGHT ? bright
: norm
, colour
&7);
481 static void setattr(struct human_output
*h
, unsigned attr
)
483 unsigned diff
= h
->attr
^ attr
;
486 if (!diff
|| !(h
->f
&HOF_COLOUR
)) return;
487 fputs("\x1b[", h
->fmt
.fp
);
490 if (attr
&HAF_BOLD
) putc('1', h
->fmt
.fp
);
491 else { putc('0', h
->fmt
.fp
); diff
= h
->attr
; }
494 if (diff
&(HAF_FG
| HAF_FGMASK
)) {
496 set_colour(h
->fmt
.fp
, &sep
, "3", "9",
497 (attr
&HAF_FGMASK
) >> HAF_FGSHIFT
);
499 { if (sep
) putc(sep
, h
->fmt
.fp
); fputs("39", h
->fmt
.fp
); sep
= ';'; }
501 if (diff
&(HAF_BG
| HAF_BGMASK
)) {
503 set_colour(h
->fmt
.fp
, &sep
, "4", "10",
504 (attr
&HAF_BGMASK
) >> HAF_BGSHIFT
);
506 { if (sep
) putc(sep
, h
->fmt
.fp
); fputs("49", h
->fmt
.fp
); sep
= ';'; }
509 putc('m', h
->fmt
.fp
); h
->attr
= attr
;
514 static void clear_progress(struct human_output
*h
)
518 if (h
->f
&HOF_PROGRESS
) {
519 n
= strlen(h
->tv
->test
->name
) + 2 + h
->scoreboard
.len
;
520 for (i
= 0; i
< n
; i
++) fputs("\b \b", h
->fmt
.fp
);
521 h
->f
&= ~HOF_PROGRESS
;
525 static void write_scoreboard_char(struct human_output
*h
, int ch
)
528 case 'x': setattr(h
, HA_LOSE
); break;
529 case '_': setattr(h
, HA_SKIP
); break;
530 default: setattr(h
, 0); break;
532 putc(ch
, h
->fmt
.fp
); setattr(h
, 0);
535 static void show_progress(struct human_output
*h
)
537 struct tvec_state
*tv
= h
->tv
;
540 if (tv
->test
&& (h
->f
&HOF_TTY
) && !(h
->f
&HOF_PROGRESS
)) {
541 fprintf(h
->fmt
.fp
, "%s: ", tv
->test
->name
);
542 if (!(h
->f
&HOF_COLOUR
))
543 dstr_write(&h
->scoreboard
, h
->fmt
.fp
);
544 else for (p
= h
->scoreboard
.buf
, l
= p
+ h
->scoreboard
.len
; p
< l
; p
++)
545 write_scoreboard_char(h
, *p
);
546 fflush(h
->fmt
.fp
); h
->f
|= HOF_PROGRESS
;
550 static void report_location(struct human_output
*h
, FILE *fp
,
551 const char *file
, unsigned lno
)
556 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
558 if (fp
!= h
->fmt
.fp
) f
|= f_flush
;
561 setattr(h
, HFG(CYAN
)); FLUSH(h
->fmt
.fp
);
562 fputs(file
, fp
); FLUSH(fp
);
563 setattr(h
, HFG(BLUE
)); FLUSH(h
->fmt
.fp
);
564 fputc(':', fp
); FLUSH(fp
);
565 setattr(h
, HFG(CYAN
)); FLUSH(h
->fmt
.fp
);
566 fprintf(fp
, "%u", lno
); FLUSH(fp
);
567 setattr(h
, HFG(BLUE
)); FLUSH(h
->fmt
.fp
);
568 fputc(':', fp
); FLUSH(fp
);
569 setattr(h
, 0); FLUSH(h
->fmt
.fp
);
577 static int human_writech(void *go
, int ch
)
578 { struct human_output
*h
= go
; return (format_char(&h
->fmt
, ch
)); }
580 static int human_writem(void *go
, const char *p
, size_t sz
)
581 { struct human_output
*h
= go
; return (format_string(&h
->fmt
, p
, sz
)); }
583 static int human_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
585 struct human_output
*h
= go
;
590 n
= gprintf_memputf(&h
->outbuf
, &h
->outsz
, maxsz
, p
, ap
);
592 return (format_string(&h
->fmt
, h
->outbuf
, n
));
595 static const struct gprintf_ops human_printops
=
596 { human_writech
, human_writem
, human_nwritef
};
598 static void human_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
599 { struct human_output
*h
= (struct human_output
*)o
; h
->tv
= tv
; }
601 static void report_skipped(struct human_output
*h
, unsigned n
)
604 fprintf(h
->fmt
.fp
, " (%u ", n
);
605 setattr(h
, HA_SKIP
); fputs("skipped", h
->fmt
.fp
); setattr(h
, 0);
606 fputc(')', h
->fmt
.fp
);
610 static int human_esession(struct tvec_output
*o
)
612 struct human_output
*h
= (struct human_output
*)o
;
613 struct tvec_state
*tv
= h
->tv
;
615 all_win
= tv
->all
[TVOUT_WIN
], grps_win
= tv
->grps
[TVOUT_WIN
],
616 all_lose
= tv
->all
[TVOUT_LOSE
], grps_lose
= tv
->grps
[TVOUT_LOSE
],
617 all_skip
= tv
->all
[TVOUT_SKIP
], grps_skip
= tv
->grps
[TVOUT_SKIP
],
618 all_run
= all_win
+ all_lose
, grps_run
= grps_win
+ grps_lose
;
621 setattr(h
, HA_WIN
); fputs("PASSED", h
->fmt
.fp
); setattr(h
, 0);
622 fprintf(h
->fmt
.fp
, " %s%u %s",
623 !(all_skip
|| grps_skip
) ?
"all " : "",
624 all_win
, all_win
== 1 ?
"test" : "tests");
625 report_skipped(h
, all_skip
);
626 fprintf(h
->fmt
.fp
, " in %u %s",
627 grps_win
, grps_win
== 1 ?
"group" : "groups");
628 report_skipped(h
, grps_skip
);
630 setattr(h
, HA_LOSE
); fputs("FAILED", h
->fmt
.fp
); setattr(h
, 0);
631 fprintf(h
->fmt
.fp
, " %u out of %u %s",
632 all_lose
, all_run
, all_run
== 1 ?
"test" : "tests");
633 report_skipped(h
, all_skip
);
634 fprintf(h
->fmt
.fp
, " in %u out of %u %s",
635 grps_lose
, grps_run
, grps_run
== 1 ?
"group" : "groups");
636 report_skipped(h
, grps_skip
);
638 fputc('\n', h
->fmt
.fp
);
640 if (tv
->f
&TVSF_ERROR
) {
641 setattr(h
, HA_ERR
); fputs("ERRORS", h
->fmt
.fp
); setattr(h
, 0);
642 fputs(" found in input; tests may not have run correctly\n", h
->fmt
.fp
);
645 h
->tv
= 0; return (tv
->f
&TVSF_ERROR ?
2 : tv
->all
[TVOUT_LOSE
] ?
1 : 0);
648 static void human_bgroup(struct tvec_output
*o
)
650 struct human_output
*h
= (struct human_output
*)o
;
652 h
->maxlen
= register_maxnamelen(h
->tv
);
653 dstr_reset(&h
->scoreboard
); show_progress(h
);
656 static void human_skipgroup(struct tvec_output
*o
,
657 const char *excuse
, va_list *ap
)
659 struct human_output
*h
= (struct human_output
*)o
;
661 if (!(~h
->f
&(HOF_TTY
| HOF_PROGRESS
))) {
662 h
->f
&= ~HOF_PROGRESS
;
663 setattr(h
, HA_SKIP
); fputs("skipped", h
->fmt
.fp
); setattr(h
, 0);
665 fprintf(h
->fmt
.fp
, "%s: ", h
->tv
->test
->name
);
666 setattr(h
, HA_SKIP
); fputs("skipped", h
->fmt
.fp
); setattr(h
, 0);
668 if (excuse
) { fputs(": ", h
->fmt
.fp
); vfprintf(h
->fmt
.fp
, excuse
, *ap
); }
669 fputc('\n', h
->fmt
.fp
);
672 static void human_egroup(struct tvec_output
*o
)
674 struct human_output
*h
= (struct human_output
*)o
;
675 struct tvec_state
*tv
= h
->tv
;
676 unsigned win
= tv
->curr
[TVOUT_WIN
], lose
= tv
->curr
[TVOUT_LOSE
],
677 skip
= tv
->curr
[TVOUT_SKIP
], run
= win
+ lose
;
679 if (h
->f
&HOF_TTY
) h
->f
&= ~HOF_PROGRESS
;
680 else fprintf(h
->fmt
.fp
, "%s:", h
->tv
->test
->name
);
683 fprintf(h
->fmt
.fp
, " %u/%u ", lose
, run
);
684 setattr(h
, HA_LOSE
); fputs("FAILED", h
->fmt
.fp
); setattr(h
, 0);
685 report_skipped(h
, skip
);
687 fputc(' ', h
->fmt
.fp
); setattr(h
, HA_WIN
);
688 fputs("ok", h
->fmt
.fp
); setattr(h
, 0);
689 report_skipped(h
, skip
);
691 fputc('\n', h
->fmt
.fp
);
694 static void human_btest(struct tvec_output
*o
)
695 { struct human_output
*h
= (struct human_output
*)o
; show_progress(h
); }
697 static void human_skip(struct tvec_output
*o
,
698 const char *excuse
, va_list *ap
)
700 struct human_output
*h
= (struct human_output
*)o
;
701 struct tvec_state
*tv
= h
->tv
;
704 report_location(h
, h
->fmt
.fp
, tv
->infile
, tv
->test_lno
);
705 fprintf(h
->fmt
.fp
, "`%s' ", tv
->test
->name
);
706 setattr(h
, HA_SKIP
); fputs("skipped", h
->fmt
.fp
); setattr(h
, 0);
707 if (excuse
) { fputs(": ", h
->fmt
.fp
); vfprintf(h
->fmt
.fp
, excuse
, *ap
); }
708 fputc('\n', h
->fmt
.fp
);
711 static void human_fail(struct tvec_output
*o
,
712 const char *detail
, va_list *ap
)
714 struct human_output
*h
= (struct human_output
*)o
;
715 struct tvec_state
*tv
= h
->tv
;
718 report_location(h
, h
->fmt
.fp
, tv
->infile
, tv
->test_lno
);
719 fprintf(h
->fmt
.fp
, "`%s' ", tv
->test
->name
);
720 setattr(h
, HA_LOSE
); fputs("FAILED", h
->fmt
.fp
); setattr(h
, 0);
721 if (detail
) { fputs(": ", h
->fmt
.fp
); vfprintf(h
->fmt
.fp
, detail
, *ap
); }
722 fputc('\n', h
->fmt
.fp
);
725 static void human_dumpreg(struct tvec_output
*o
,
726 unsigned disp
, const union tvec_regval
*rv
,
727 const struct tvec_regdef
*rd
)
729 struct human_output
*h
= (struct human_output
*)o
;
730 const char *ds
= regdisp(disp
); int n
= strlen(ds
) + strlen(rd
->name
);
733 gprintf(&human_printops
, h
, "%*s%s %s = ",
734 10 + h
->maxlen
- n
, "", ds
, rd
->name
);
735 if (h
->f
&HOF_COLOUR
) {
736 if (!rv
) setattr(h
, HFG(YELLOW
));
737 else if (disp
== TVRD_FOUND
) setattr(h
, HFG(RED
));
738 else if (disp
== TVRD_EXPECT
) setattr(h
, HFG(GREEN
));
740 if (!rv
) gprintf(&human_printops
, h
, "#unset");
741 else rd
->ty
->dump(rv
, rd
, 0, &human_printops
, h
);
742 setattr(h
, 0); format_char(&h
->fmt
, '\n');
745 static void human_etest(struct tvec_output
*o
, unsigned outcome
)
747 struct human_output
*h
= (struct human_output
*)o
;
753 case TVOUT_WIN
: ch
= '.'; break;
754 case TVOUT_LOSE
: ch
= 'x'; break;
755 case TVOUT_SKIP
: ch
= '_'; break;
758 dstr_putc(&h
->scoreboard
, ch
);
759 write_scoreboard_char(h
, ch
); fflush(h
->fmt
.fp
);
763 static void human_bbench(struct tvec_output
*o
,
764 const char *ident
, unsigned unit
)
766 struct human_output
*h
= (struct human_output
*)o
;
767 struct tvec_state
*tv
= h
->tv
;
770 fprintf(h
->fmt
.fp
, "%s: %s: ", tv
->test
->name
, ident
); fflush(h
->fmt
.fp
);
773 static void human_ebench(struct tvec_output
*o
,
774 const char *ident
, unsigned unit
,
775 const struct bench_timing
*tm
)
777 struct human_output
*h
= (struct human_output
*)o
;
779 tvec_benchreport(&human_printops
, h
->fmt
.fp
, unit
, tm
);
780 fputc('\n', h
->fmt
.fp
);
783 static void human_report(struct tvec_output
*o
, const char *msg
, va_list *ap
)
785 struct human_output
*h
= (struct human_output
*)o
;
786 struct tvec_state
*tv
= h
->tv
;
789 dstr_vputf(&d
, msg
, ap
); dstr_putc(&d
, '\n');
791 clear_progress(h
); fflush(h
->fmt
.fp
);
792 fprintf(stderr
, "%s: ", QUIS
);
793 report_location(h
, stderr
, tv
->infile
, tv
->lno
);
794 fwrite(d
.buf
, 1, d
.len
, stderr
);
796 if (h
->f
&HOF_DUPERR
) {
797 report_location(h
, h
->fmt
.fp
, tv
->infile
, tv
->lno
);
798 fwrite(d
.buf
, 1, d
.len
, h
->fmt
.fp
);
803 static void human_destroy(struct tvec_output
*o
)
805 struct human_output
*h
= (struct human_output
*)o
;
807 destroy_fmt(&h
->fmt
, h
->f
&HOF_DUPERR ? DFF_CLOSE
: 0);
808 dstr_destroy(&h
->scoreboard
);
809 xfree(h
->outbuf
); xfree(h
);
812 static const struct tvec_outops human_ops
= {
813 human_bsession
, human_esession
,
814 human_bgroup
, human_skipgroup
, human_egroup
,
815 human_btest
, human_skip
, human_fail
, human_dumpreg
, human_etest
,
816 human_bbench
, human_ebench
,
817 human_report
, human_report
,
821 struct tvec_output
*tvec_humanoutput(FILE *fp
)
823 struct human_output
*h
;
826 h
= xmalloc(sizeof(*h
)); h
->_o
.ops
= &human_ops
;
827 h
->f
= 0; h
->attr
= 0;
829 init_fmt(&h
->fmt
, fp
, 0);
830 h
->outbuf
= 0; h
->outsz
= 0;
832 switch (getenv_boolean("TVEC_TTY", -1)) {
833 case 1: h
->f
|= HOF_TTY
; break;
836 if (isatty(fileno(fp
))) h
->f
|= HOF_TTY
;
839 switch (getenv_boolean("TVEC_COLOUR", -1)) {
840 case 1: h
->f
|= HOF_COLOUR
; break;
845 if (p
&& STRCMP(p
, !=, "dumb")) h
->f
|= HOF_COLOUR
;
850 if (fp
!= stderr
&& (fp
!= stdout
|| !(h
->f
&HOF_TTY
))) h
->f
|= HOF_DUPERR
;
851 dstr_create(&h
->scoreboard
);
855 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
858 struct tvec_output _o
;
859 struct tvec_state
*tv
;
861 char *outbuf
; size_t outsz
;
865 static int tap_writech(void *go
, int ch
)
866 { struct tap_output
*t
= go
; return (format_char(&t
->fmt
, ch
)); }
868 static int tap_writem(void *go
, const char *p
, size_t sz
)
869 { struct human_output
*t
= go
; return (format_string(&t
->fmt
, p
, sz
)); }
871 static int tap_nwritef(void *go
, size_t maxsz
, const char *p
, ...)
873 struct human_output
*t
= go
;
878 n
= gprintf_memputf(&t
->outbuf
, &t
->outsz
, maxsz
, p
, ap
);
880 return (format_string(&t
->fmt
, t
->outbuf
, n
));
883 static const struct gprintf_ops tap_printops
=
884 { tap_writech
, tap_writem
, tap_nwritef
};
886 static void tap_bsession(struct tvec_output
*o
, struct tvec_state
*tv
)
888 struct tap_output
*t
= (struct tap_output
*)o
;
891 fputs("TAP version 13\n", t
->fmt
.fp
);
894 static unsigned tap_grpix(struct tap_output
*t
)
896 struct tvec_state
*tv
= t
->tv
;
898 return (tv
->grps
[TVOUT_WIN
] +
899 tv
->grps
[TVOUT_LOSE
] +
900 tv
->grps
[TVOUT_SKIP
]);
903 static int tap_esession(struct tvec_output
*o
)
905 struct tap_output
*t
= (struct tap_output
*)o
;
906 struct tvec_state
*tv
= t
->tv
;
908 if (tv
->f
&TVSF_ERROR
) {
910 "Errors found in input; tests may not have run correctly\n",
915 fprintf(t
->fmt
.fp
, "1..%u\n", tap_grpix(t
));
916 t
->tv
= 0; return (tv
->all
[TVOUT_LOSE
] ?
1 : 0);
919 static void tap_bgroup(struct tvec_output
*o
)
921 struct tap_output
*t
= (struct tap_output
*)o
;
922 t
->maxlen
= register_maxnamelen(t
->tv
);
925 static void tap_skipgroup(struct tvec_output
*o
,
926 const char *excuse
, va_list *ap
)
928 struct tap_output
*t
= (struct tap_output
*)o
;
930 fprintf(t
->fmt
.fp
, "ok %u %s # SKIP", tap_grpix(t
), t
->tv
->test
->name
);
931 if (excuse
) { fputc(' ', t
->fmt
.fp
); vfprintf(t
->fmt
.fp
, excuse
, *ap
); }
932 fputc('\n', t
->fmt
.fp
);
935 static void tap_egroup(struct tvec_output
*o
)
937 struct tap_output
*t
= (struct tap_output
*)o
;
938 struct tvec_state
*tv
= t
->tv
;
940 grpix
= tap_grpix(t
),
941 win
= tv
->curr
[TVOUT_WIN
],
942 lose
= tv
->curr
[TVOUT_LOSE
],
943 skip
= tv
->curr
[TVOUT_SKIP
];
946 fprintf(t
->fmt
.fp
, "not ok %u - %s: FAILED %u/%u",
947 grpix
, tv
->test
->name
, lose
, win
+ lose
);
948 if (skip
) fprintf(t
->fmt
.fp
, " (skipped %u)", skip
);
950 fprintf(t
->fmt
.fp
, "ok %u - %s: passed %u", grpix
, tv
->test
->name
, win
);
951 if (skip
) fprintf(t
->fmt
.fp
, " (skipped %u)", skip
);
953 fputc('\n', t
->fmt
.fp
);
956 static void tap_btest(struct tvec_output
*o
) { ; }
958 static void tap_outcome(struct tvec_output
*o
, const char *outcome
,
959 const char *detail
, va_list *ap
)
961 struct tap_output
*t
= (struct tap_output
*)o
;
962 struct tvec_state
*tv
= t
->tv
;
964 gprintf(&tap_printops
, t
, "%s:%u: `%s' %s",
965 tv
->infile
, tv
->test_lno
, tv
->test
->name
, outcome
);
967 format_string(&t
->fmt
, ": ", 2);
968 vgprintf(&tap_printops
, t
, detail
, ap
);
970 format_char(&t
->fmt
, '\n');
973 static void tap_skip(struct tvec_output
*o
, const char *excuse
, va_list *ap
)
974 { tap_outcome(o
, "skipped", excuse
, ap
); }
975 static void tap_fail(struct tvec_output
*o
, const char *detail
, va_list *ap
)
976 { tap_outcome(o
, "FAILED", detail
, ap
); }
978 static void tap_dumpreg(struct tvec_output
*o
,
979 unsigned disp
, const union tvec_regval
*rv
,
980 const struct tvec_regdef
*rd
)
982 struct tap_output
*t
= (struct tap_output
*)o
;
983 const char *ds
= regdisp(disp
); int n
= strlen(ds
) + strlen(rd
->name
);
985 gprintf(&tap_printops
, t
, "%*s%s %s = ",
986 10 + t
->maxlen
- n
, "", ds
, rd
->name
);
987 if (!rv
) gprintf(&tap_printops
, t
, "#<unset>");
988 else rd
->ty
->dump(rv
, rd
, 0, &tap_printops
, t
);
989 format_char(&t
->fmt
, '\n');
992 static void tap_etest(struct tvec_output
*o
, unsigned outcome
) { ; }
994 static void tap_bbench(struct tvec_output
*o
,
995 const char *ident
, unsigned unit
)
998 static void tap_ebench(struct tvec_output
*o
,
999 const char *ident
, unsigned unit
,
1000 const struct bench_timing
*tm
)
1002 struct tap_output
*t
= (struct tap_output
*)o
;
1003 struct tvec_state
*tv
= t
->tv
;
1005 gprintf(&tap_printops
, t
, "%s: %s: ", tv
->test
->name
, ident
);
1006 tvec_benchreport(&tap_printops
, t
, unit
, tm
);
1007 format_char(&t
->fmt
, '\n');
1010 static void tap_report(struct tap_output
*t
,
1011 const struct gprintf_ops
*gops
, void *go
,
1012 const char *msg
, va_list *ap
)
1014 struct tvec_state
*tv
= t
->tv
;
1016 if (tv
->infile
) gprintf(gops
, go
, "%s:%u: ", tv
->infile
, tv
->lno
);
1017 gprintf(gops
, go
, msg
, ap
); gops
->putch(go
, '\n');
1020 static void tap_error(struct tvec_output
*o
, const char *msg
, va_list *ap
)
1022 struct tap_output
*t
= (struct tap_output
*)o
;
1024 fputs("Bail out! ", t
->fmt
.fp
);
1025 tap_report(t
, &file_printops
, t
->fmt
.fp
, msg
, ap
);
1028 static void tap_notice(struct tvec_output
*o
, const char *msg
, va_list *ap
)
1030 struct tap_output
*t
= (struct tap_output
*)o
;
1032 tap_report(t
, &tap_printops
, t
, msg
, ap
);
1035 static void tap_destroy(struct tvec_output
*o
)
1037 struct tap_output
*t
= (struct tap_output
*)o
;
1039 destroy_fmt(&t
->fmt
,
1040 t
->fmt
.fp
== stdout
|| t
->fmt
.fp
== stderr ?
0 : DFF_CLOSE
);
1041 xfree(t
->outbuf
); xfree(t
);
1044 static const struct tvec_outops tap_ops
= {
1045 tap_bsession
, tap_esession
,
1046 tap_bgroup
, tap_skipgroup
, tap_egroup
,
1047 tap_btest
, tap_skip
, tap_fail
, tap_dumpreg
, tap_etest
,
1048 tap_bbench
, tap_ebench
,
1049 tap_error
, tap_notice
,
1053 struct tvec_output
*tvec_tapoutput(FILE *fp
)
1055 struct tap_output
*t
;
1057 t
= xmalloc(sizeof(*t
)); t
->_o
.ops
= &tap_ops
;
1058 init_fmt(&t
->fmt
, fp
, "## ");
1059 t
->outbuf
= 0; t
->outsz
= 0;
1063 /*----- Default output ----------------------------------------------------*/
1065 struct tvec_output
*tvec_dfltout(FILE *fp
)
1067 int ttyp
= getenv_boolean("TVEC_TTY", -1);
1069 if (ttyp
== -1) ttyp
= isatty(fileno(fp
));
1070 if (ttyp
) return (tvec_humanoutput(fp
));
1071 else return (tvec_tapoutput(fp
));
1074 /*----- That's all, folks -------------------------------------------------*/