@@@ wip
[mLib] / test / tvec-output.c
CommitLineData
b64eb60f
MW
1/* -*-c-*-
2 *
3 * Test vector output management
4 *
5 * (c) 2023 Straylight/Edgeware
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the mLib utilities library.
11 *
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
16 *
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 * USA.
26 */
27
28/*----- Header files ------------------------------------------------------*/
29
e63124bc
MW
30#include "config.h"
31
b64eb60f 32#include <assert.h>
67b5031e 33#include <ctype.h>
b64eb60f
MW
34#include <stdarg.h>
35#include <stdio.h>
36#include <string.h>
37
38#include <unistd.h>
39
40#include "alloc.h"
41#include "bench.h"
42#include "dstr.h"
67b5031e 43#include "macros.h"
b64eb60f
MW
44#include "quis.h"
45#include "report.h"
46#include "tvec.h"
47
48/*----- Common machinery --------------------------------------------------*/
49
67b5031e
MW
50/* --- @regdisp@ --- *
51 *
52 * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
53 *
54 * Returns: A human-readable adjective describing the register
55 * disposition.
56 */
57
b64eb60f
MW
58static const char *regdisp(unsigned disp)
59{
60 switch (disp) {
e63124bc
MW
61 case TVRD_INPUT: return "input";
62 case TVRD_OUTPUT: return "output";
63 case TVRD_MATCH: return "matched";
64 case TVRD_EXPECT: return "expected";
65 case TVRD_FOUND: return "found";
b64eb60f
MW
66 default: abort();
67 }
68}
69
67b5031e
MW
70/* --- @getenv_boolean@ --- *
71 *
72 * Arguments: @const char *var@ = environment variable name
73 * @int dflt@ = default value
74 *
75 * Returns: @0@ if the variable is set to something falseish, @1@ if it's
76 * set to something truish, or @dflt@ otherwise.
77 */
78
b64eb60f
MW
79static int getenv_boolean(const char *var, int dflt)
80{
81 const char *p;
82
83 p = getenv(var);
84 if (!p)
85 return (dflt);
86 else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") ||
87 STRCMP(p, ==, "t") || STRCMP(p, ==, "true") ||
88 STRCMP(p, ==, "on") || STRCMP(p, ==, "force") ||
89 STRCMP(p, ==, "1"))
90 return (1);
91 else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") ||
e63124bc 92 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
b64eb60f
MW
93 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
94 STRCMP(p, ==, "0"))
95 return (0);
96 else {
67b5031e 97 moan("ignoring unexpected value `%s' for environment variable `%s'",
b64eb60f
MW
98 var, p);
99 return (dflt);
100 }
101}
102
67b5031e
MW
103/* --- @register_maxnamelen@ --- *
104 *
105 * Arguments: @const struct tvec_state *tv@ = test vector state
106 *
107 * Returns: The maximum length of a register name in the current test.
108 */
109
e63124bc 110static int register_maxnamelen(const struct tvec_state *tv)
b64eb60f
MW
111{
112 const struct tvec_regdef *rd;
e63124bc 113 int maxlen = 6, n;
b64eb60f
MW
114
115 for (rd = tv->test->regs; rd->name; rd++)
e63124bc
MW
116 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
117 return (maxlen);
b64eb60f
MW
118}
119
67b5031e
MW
120/*----- Output formatting -------------------------------------------------*/
121
122/* We have two main jobs in output formatting: trimming trailing blanks; and
123 * adding a prefix to each line.
124 *
125 * This is somehow much more complicated than it ought to be.
126 */
127
128struct format {
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 */
134};
135
136/* Support macros. These assume `fmt' is defined as a pointer to the `struct
137 * format' state.
138 */
139
140#define SPLIT_RANGE(tail, base, limit) do { \
141 /* Set TAIL to point just after the last nonspace character between \
142 * BASE and LIMIT. If there are no nonspace characters, then set \
143 * TAIL to equal BASE. \
144 */ \
145 \
146 for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
147} while (0)
148
149#define PUT_RANGE(base, limit) do { \
150 /* Write the range of characters between BASE and LIMIT to the output \
151 * file. Return immediately on error. \
152 */ \
153 \
154 size_t n = limit - base; \
155 if (fwrite(base, 1, n, fmt->fp) < n) return (-1); \
156} while (0)
157
158#define PUT_CHAR(ch) do { \
159 /* Write CH to the output. Return immediately on error. */ \
160 \
161 if (putc(ch, fmt->fp) == EOF) return (-1); \
162} while (0)
163
164#define PUT_PREFIX do { \
165 /* Output the prefix, if there is one. Return immediately on error. */ \
166 \
167 if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim); \
168} while (0)
169
170#define PUT_SAVED do { \
171 /* Output the saved trailing blank material in the buffer. */ \
172 \
173 size_t n = fmt->w.len; \
174 if (n && fwrite(fmt->w.buf, 1, n, fmt->fp) < n) return (-1); \
175} while (0)
176
177#define PUT_PFXINB do { \
178 /* Output the initial nonblank portion of the prefix, if there is \
179 * one. Return immediately on error. \
180 */ \
181 \
182 if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail); \
183} while (0)
184
185#define SAVE_PFXTAIL do { \
186 /* Save the trailing blank portion of the prefix. */ \
187 \
188 if (fmt->prefix) \
189 DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail); \
190} while (0)
191
192/* --- @init_fmt@ --- *
193 *
194 * Arguments: @struct format *fmt@ = formatting state to initialize
195 * @FILE *fp@ = output file
196 * @const char *prefix@ = prefix string (or null if empty)
197 *
198 * Returns: ---
199 *
200 * Use: Initialize a formatting state.
201 */
202
203static void init_fmt(struct format *fmt, FILE *fp, const char *prefix)
204{
205 const char *q, *l;
206
207 /* Basics. */
208 fmt->fp = fp;
209 fmt->f = FMTF_NEWL;
210 dstr_create(&fmt->w);
211
212 /* Prefix portions. */
213 if (!prefix || !*prefix)
214 fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0;
215 else {
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);
220 }
221}
222
223/* --- @destroy_fmt@ --- *
224 *
225 * Arguments: @struct format *fmt@ = formatting state
226 * @unsigned f@ = flags (@DFF_...@)
227 *
228 * Returns: ---
229 *
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).
233 */
234
235#define DFF_CLOSE 1u
236static void destroy_fmt(struct format *fmt, unsigned f)
237{
238 if (f&DFF_CLOSE) fclose(fmt->fp);
239 dstr_destroy(&fmt->w);
240}
241
242/* --- @format_char@ --- *
243 *
244 * Arguments: @struct format *fmt@ = formatting state
245 * @int ch@ = character to write
246 *
247 * Returns: Zero on success, @-1@ on failure.
248 *
249 * Use: Write a single character to the output.
250 */
251
252static int format_char(struct format *fmt, int ch)
253{
254 if (ch == '\n') {
255 if (fmt->f&FMTF_NEWL) PUT_PFXINB;
256 PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w);
257 } else if (isspace(ch))
258 DPUTC(&fmt->w, ch);
259 else {
260 if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
261 PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w);
262 }
263 return (0);
264}
265
266/* --- @format_string@ --- *
267 *
268 * Arguments: @struct format *fmt@ = formatting state
269 * @const char *p@ = string to write
270 * @size_t sz@ = length of string
271 *
272 * Returns: Zero on success, @-1@ on failure.
273 *
274 * Use: Write a string to the output.
275 */
276
277static int format_string(struct format *fmt, const char *p, size_t sz)
278{
279 const char *q, *r, *l = p + sz;
280
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.
285 *
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:
288 *
289 * * an initial portion, which is either empty or ends with a nonblank
290 * character;
291 *
292 * * a suffix which consists only of blanks; and
293 *
294 * * an optional newline.
295 *
296 * All segments except the last end with a newline.
297 */
298
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 \
305 * no newline. \
306 */ \
307 \
308 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
309} while (0)
310
311#define PUT_NONBLANK do { \
312 /* Output the initial portion of the segment. */ \
313 \
314 PUT_RANGE(p, r); \
315} while (0)
316
317#define PUT_NEWLINE do { \
318 /* Write a newline, and advance to the next segment. */ \
319 \
320 PUT_CHAR('\n'); p = q + 1; \
321} while (0)
322
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 \
326 * be omitted. \
327 */ \
328 \
329 DPUTM(&fmt->w, r, l - r); \
330} while (0)
331
332 /* Determine the bounds of the first segment. Handling this is the most
333 * complicated part of this function.
334 */
335 SPLIT_SEGMENT;
336
337 if (!q) {
338 /* This is the only segment. We'll handle the whole thing here.
339 *
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.
345 *
346 * If we're at the start of a line here, then
347 */
348
349 if (r > p) {
350 if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
351 PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w);
352 }
353 SAVE_TAIL;
354 return (0);
355 }
356
357 /* There is at least one more segment, so we know that there'll be a line
358 * to output.
359 */
360 if (fmt->f&FMTF_NEWL) PUT_PFXINB;
361 if (r > p) { PUT_SAVED; PUT_NONBLANK; }
362 PUT_NEWLINE; DRESET(&fmt->w);
363 SPLIT_SEGMENT;
364
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.
368 */
369 while (q) {
370 PUT_PREFIX; PUT_NONBLANK; PUT_NEWLINE;
371 SPLIT_SEGMENT;
372 }
373
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.
378 */
379 if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; }
380 else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; }
381 SAVE_TAIL;
382
383#undef SPLIT_SEGMENT
384#undef PUT_NONBLANK
385#undef PUT_NEWLINE
386#undef SAVE_TAIL
387
388 return (0);
389}
390
391#undef SPLIT_RANGE
392#undef PUT_RANGE
393#undef PUT_PREFIX
394#undef PUT_PFXINB
395#undef PUT_SAVED
396#undef PUT_CHAR
397#undef SAVE_PFXTAIL
398
b64eb60f
MW
399/*----- Skeleton ----------------------------------------------------------*/
400/*
3efcfd2d 401static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
b64eb60f
MW
402static int ..._esession(struct tvec_output *o)
403static void ..._bgroup(struct tvec_output *o)
b64eb60f
MW
404static void ..._skipgroup(struct tvec_output *o,
405 const char *excuse, va_list *ap)
3efcfd2d 406static void ..._egroup(struct tvec_output *o)
b64eb60f 407static void ..._btest(struct tvec_output *o)
e63124bc 408static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
b64eb60f 409static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
e63124bc
MW
410static void ..._dumpreg(struct tvec_output *o, unsigned disp,
411 union tvec_regval *rv, const struct tvec_regdef *rd)
b64eb60f 412static void ..._etest(struct tvec_output *o, unsigned outcome)
e63124bc
MW
413static void ..._bbench(struct tvec_output *o,
414 const char *ident, unsigned unit)
415static void ..._ebench(struct tvec_output *o,
416 const char *ident, unsigned unit,
417 const struct tvec_timing *t)
3efcfd2d
MW
418static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
419static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
b64eb60f
MW
420static void ..._destroy(struct tvec_output *o)
421
422static const struct tvec_outops ..._ops = {
b64eb60f
MW
423 ..._bsession, ..._esession,
424 ..._bgroup, ..._egroup, ..._skip,
e63124bc 425 ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
b64eb60f 426 ..._bbench, ..._ebench,
3efcfd2d 427 ..._error, ..._notice,
b64eb60f
MW
428 ..._destroy
429};
430*/
431/*----- Human-readable output ---------------------------------------------*/
432
433#define HAF_FGMASK 0x0f
434#define HAF_FGSHIFT 0
435#define HAF_BGMASK 0xf0
436#define HAF_BGSHIFT 4
437#define HAF_FG 256u
438#define HAF_BG 512u
439#define HAF_BOLD 1024u
440#define HCOL_BLACK 0u
441#define HCOL_RED 1u
442#define HCOL_GREEN 2u
443#define HCOL_YELLOW 3u
444#define HCOL_BLUE 4u
445#define HCOL_MAGENTA 5u
446#define HCOL_CYAN 6u
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)
451
452#define HA_WIN (HFG(GREEN))
453#define HA_LOSE (HFG(RED) | HAF_BOLD)
454#define HA_SKIP (HFG(YELLOW))
882a39c1 455#define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
b64eb60f
MW
456
457struct human_output {
458 struct tvec_output _o;
3efcfd2d 459 struct tvec_state *tv;
67b5031e
MW
460 struct format fmt;
461 char *outbuf; size_t outsz;
b64eb60f
MW
462 dstr scoreboard;
463 unsigned attr;
e63124bc 464 int maxlen;
b64eb60f
MW
465 unsigned f;
466#define HOF_TTY 1u
467#define HOF_DUPERR 2u
468#define HOF_COLOUR 4u
469#define HOF_PROGRESS 8u
470};
471
472static void set_colour(FILE *fp, int *sep_inout,
473 const char *norm, const char *bright,
474 unsigned colour)
475{
476 if (*sep_inout) putc(*sep_inout, fp);
477 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
478 *sep_inout = ';';
479}
480
481static void setattr(struct human_output *h, unsigned attr)
482{
483 unsigned diff = h->attr ^ attr;
484 int sep = 0;
485
486 if (!diff || !(h->f&HOF_COLOUR)) return;
67b5031e 487 fputs("\x1b[", h->fmt.fp);
b64eb60f
MW
488
489 if (diff&HAF_BOLD) {
67b5031e
MW
490 if (attr&HAF_BOLD) putc('1', h->fmt.fp);
491 else { putc('0', h->fmt.fp); diff = h->attr; }
b64eb60f
MW
492 sep = ';';
493 }
494 if (diff&(HAF_FG | HAF_FGMASK)) {
495 if (attr&HAF_FG)
67b5031e
MW
496 set_colour(h->fmt.fp, &sep, "3", "9",
497 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
b64eb60f 498 else
67b5031e 499 { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; }
b64eb60f
MW
500 }
501 if (diff&(HAF_BG | HAF_BGMASK)) {
502 if (attr&HAF_BG)
67b5031e
MW
503 set_colour(h->fmt.fp, &sep, "4", "10",
504 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
b64eb60f 505 else
67b5031e 506 { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; }
b64eb60f
MW
507 }
508
67b5031e 509 putc('m', h->fmt.fp); h->attr = attr;
b64eb60f
MW
510
511#undef f_any
512}
513
514static void clear_progress(struct human_output *h)
515{
516 size_t i, n;
517
518 if (h->f&HOF_PROGRESS) {
3efcfd2d 519 n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
67b5031e 520 for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp);
b64eb60f
MW
521 h->f &= ~HOF_PROGRESS;
522 }
523}
524
525static void write_scoreboard_char(struct human_output *h, int ch)
526{
527 switch (ch) {
528 case 'x': setattr(h, HA_LOSE); break;
529 case '_': setattr(h, HA_SKIP); break;
530 default: setattr(h, 0); break;
531 }
67b5031e 532 putc(ch, h->fmt.fp); setattr(h, 0);
b64eb60f
MW
533}
534
535static void show_progress(struct human_output *h)
536{
3efcfd2d 537 struct tvec_state *tv = h->tv;
b64eb60f
MW
538 const char *p, *l;
539
882a39c1 540 if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
67b5031e 541 fprintf(h->fmt.fp, "%s: ", tv->test->name);
b64eb60f 542 if (!(h->f&HOF_COLOUR))
67b5031e 543 dstr_write(&h->scoreboard, h->fmt.fp);
b64eb60f
MW
544 else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
545 write_scoreboard_char(h, *p);
67b5031e 546 fflush(h->fmt.fp); h->f |= HOF_PROGRESS;
b64eb60f
MW
547 }
548}
549
550static void report_location(struct human_output *h, FILE *fp,
551 const char *file, unsigned lno)
552{
553 unsigned f = 0;
554#define f_flush 1u
555
556#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
557
67b5031e 558 if (fp != h->fmt.fp) f |= f_flush;
b64eb60f
MW
559
560 if (file) {
67b5031e
MW
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);
570 fputc(' ', fp);
b64eb60f
MW
571 }
572
573#undef f_flush
574#undef FLUSH
575}
576
67b5031e
MW
577static int human_writech(void *go, int ch)
578 { struct human_output *h = go; return (format_char(&h->fmt, ch)); }
579
580static int human_writem(void *go, const char *p, size_t sz)
581 { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); }
582
583static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
584{
585 struct human_output *h = go;
586 size_t n;
587 va_list ap;
588
589 va_start(ap, p);
590 n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
591 va_end(ap);
592 return (format_string(&h->fmt, h->outbuf, n));
593}
594
595static const struct gprintf_ops human_printops =
596 { human_writech, human_writem, human_nwritef };
597
3efcfd2d
MW
598static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
599 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
b64eb60f
MW
600
601static void report_skipped(struct human_output *h, unsigned n)
602{
603 if (n) {
67b5031e
MW
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);
b64eb60f
MW
607 }
608}
609
610static int human_esession(struct tvec_output *o)
611{
612 struct human_output *h = (struct human_output *)o;
3efcfd2d 613 struct tvec_state *tv = h->tv;
b64eb60f
MW
614 unsigned
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;
619
620 if (!all_lose) {
67b5031e
MW
621 setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0);
622 fprintf(h->fmt.fp, " %s%u %s",
b64eb60f
MW
623 !(all_skip || grps_skip) ? "all " : "",
624 all_win, all_win == 1 ? "test" : "tests");
625 report_skipped(h, all_skip);
67b5031e 626 fprintf(h->fmt.fp, " in %u %s",
b64eb60f
MW
627 grps_win, grps_win == 1 ? "group" : "groups");
628 report_skipped(h, grps_skip);
629 } else {
67b5031e
MW
630 setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
631 fprintf(h->fmt.fp, " %u out of %u %s",
b64eb60f
MW
632 all_lose, all_run, all_run == 1 ? "test" : "tests");
633 report_skipped(h, all_skip);
67b5031e 634 fprintf(h->fmt.fp, " in %u out of %u %s",
b64eb60f
MW
635 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
636 report_skipped(h, grps_skip);
637 }
67b5031e 638 fputc('\n', h->fmt.fp);
b64eb60f 639
882a39c1 640 if (tv->f&TVSF_ERROR) {
67b5031e
MW
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);
882a39c1
MW
643 }
644
3efcfd2d 645 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
646}
647
648static void human_bgroup(struct tvec_output *o)
649{
650 struct human_output *h = (struct human_output *)o;
e63124bc 651
3efcfd2d 652 h->maxlen = register_maxnamelen(h->tv);
b64eb60f
MW
653 dstr_reset(&h->scoreboard); show_progress(h);
654}
655
3efcfd2d
MW
656static void human_skipgroup(struct tvec_output *o,
657 const char *excuse, va_list *ap)
b64eb60f 658{
3efcfd2d 659 struct human_output *h = (struct human_output *)o;
b64eb60f 660
3efcfd2d
MW
661 if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
662 h->f &= ~HOF_PROGRESS;
67b5031e 663 setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
b64eb60f 664 } else {
67b5031e
MW
665 fprintf(h->fmt.fp, "%s: ", h->tv->test->name);
666 setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
b64eb60f 667 }
67b5031e
MW
668 if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
669 fputc('\n', h->fmt.fp);
b64eb60f
MW
670}
671
3efcfd2d 672static void human_egroup(struct tvec_output *o)
b64eb60f
MW
673{
674 struct human_output *h = (struct human_output *)o;
3efcfd2d
MW
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;
b64eb60f
MW
678
679 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
67b5031e 680 else fprintf(h->fmt.fp, "%s:", h->tv->test->name);
b64eb60f 681
3efcfd2d 682 if (lose) {
67b5031e
MW
683 fprintf(h->fmt.fp, " %u/%u ", lose, run);
684 setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
3efcfd2d 685 report_skipped(h, skip);
b64eb60f 686 } else {
67b5031e
MW
687 fputc(' ', h->fmt.fp); setattr(h, HA_WIN);
688 fputs("ok", h->fmt.fp); setattr(h, 0);
3efcfd2d 689 report_skipped(h, skip);
b64eb60f 690 }
67b5031e 691 fputc('\n', h->fmt.fp);
b64eb60f
MW
692}
693
694static void human_btest(struct tvec_output *o)
695 { struct human_output *h = (struct human_output *)o; show_progress(h); }
696
697static void human_skip(struct tvec_output *o,
698 const char *excuse, va_list *ap)
699{
700 struct human_output *h = (struct human_output *)o;
3efcfd2d 701 struct tvec_state *tv = h->tv;
b64eb60f
MW
702
703 clear_progress(h);
67b5031e
MW
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);
b64eb60f
MW
709}
710
711static void human_fail(struct tvec_output *o,
712 const char *detail, va_list *ap)
713{
714 struct human_output *h = (struct human_output *)o;
3efcfd2d 715 struct tvec_state *tv = h->tv;
b64eb60f
MW
716
717 clear_progress(h);
67b5031e
MW
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);
b64eb60f
MW
723}
724
e63124bc
MW
725static void human_dumpreg(struct tvec_output *o,
726 unsigned disp, const union tvec_regval *rv,
727 const struct tvec_regdef *rd)
b64eb60f
MW
728{
729 struct human_output *h = (struct human_output *)o;
e63124bc 730 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
b64eb60f 731
67b5031e
MW
732 clear_progress(h);
733 gprintf(&human_printops, h, "%*s%s %s = ",
734 10 + h->maxlen - n, "", ds, rd->name);
e63124bc
MW
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));
739 }
67b5031e
MW
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');
b64eb60f
MW
743}
744
745static void human_etest(struct tvec_output *o, unsigned outcome)
746{
747 struct human_output *h = (struct human_output *)o;
748 int ch;
749
750 if (h->f&HOF_TTY) {
751 show_progress(h);
752 switch (outcome) {
753 case TVOUT_WIN: ch = '.'; break;
754 case TVOUT_LOSE: ch = 'x'; break;
755 case TVOUT_SKIP: ch = '_'; break;
756 default: abort();
757 }
758 dstr_putc(&h->scoreboard, ch);
67b5031e 759 write_scoreboard_char(h, ch); fflush(h->fmt.fp);
b64eb60f
MW
760 }
761}
762
e63124bc
MW
763static void human_bbench(struct tvec_output *o,
764 const char *ident, unsigned unit)
b64eb60f
MW
765{
766 struct human_output *h = (struct human_output *)o;
3efcfd2d 767 struct tvec_state *tv = h->tv;
b64eb60f
MW
768
769 clear_progress(h);
67b5031e 770 fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp);
b64eb60f
MW
771}
772
773static void human_ebench(struct tvec_output *o,
e63124bc 774 const char *ident, unsigned unit,
b64eb60f
MW
775 const struct bench_timing *tm)
776{
777 struct human_output *h = (struct human_output *)o;
67b5031e
MW
778
779 tvec_benchreport(&human_printops, h->fmt.fp, unit, tm);
780 fputc('\n', h->fmt.fp);
b64eb60f
MW
781}
782
3efcfd2d
MW
783static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
784{
785 struct human_output *h = (struct human_output *)o;
786 struct tvec_state *tv = h->tv;
787 dstr d = DSTR_INIT;
788
789 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
790
67b5031e 791 clear_progress(h); fflush(h->fmt.fp);
3efcfd2d
MW
792 fprintf(stderr, "%s: ", QUIS);
793 report_location(h, stderr, tv->infile, tv->lno);
794 fwrite(d.buf, 1, d.len, stderr);
795
796 if (h->f&HOF_DUPERR) {
67b5031e
MW
797 report_location(h, h->fmt.fp, tv->infile, tv->lno);
798 fwrite(d.buf, 1, d.len, h->fmt.fp);
3efcfd2d
MW
799 }
800 show_progress(h);
801}
802
b64eb60f
MW
803static void human_destroy(struct tvec_output *o)
804{
805 struct human_output *h = (struct human_output *)o;
806
67b5031e 807 destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0);
b64eb60f 808 dstr_destroy(&h->scoreboard);
67b5031e 809 xfree(h->outbuf); xfree(h);
b64eb60f
MW
810}
811
812static const struct tvec_outops human_ops = {
b64eb60f 813 human_bsession, human_esession,
3efcfd2d 814 human_bgroup, human_skipgroup, human_egroup,
e63124bc 815 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
b64eb60f 816 human_bbench, human_ebench,
3efcfd2d 817 human_report, human_report,
b64eb60f
MW
818 human_destroy
819};
820
821struct tvec_output *tvec_humanoutput(FILE *fp)
822{
823 struct human_output *h;
824 const char *p;
825
826 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
827 h->f = 0; h->attr = 0;
828
67b5031e
MW
829 init_fmt(&h->fmt, fp, 0);
830 h->outbuf = 0; h->outsz = 0;
b64eb60f
MW
831
832 switch (getenv_boolean("TVEC_TTY", -1)) {
833 case 1: h->f |= HOF_TTY; break;
834 case 0: break;
835 default:
836 if (isatty(fileno(fp))) h->f |= HOF_TTY;
837 break;
838 }
839 switch (getenv_boolean("TVEC_COLOUR", -1)) {
840 case 1: h->f |= HOF_COLOUR; break;
841 case 0: break;
842 default:
843 if (h->f&HOF_TTY) {
844 p = getenv("TERM");
845 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
846 }
847 break;
848 }
849
e63124bc 850 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
b64eb60f
MW
851 dstr_create(&h->scoreboard);
852 return (&h->_o);
853}
854
855/*----- Perl's `Test Anything Protocol' -----------------------------------*/
856
857struct tap_output {
858 struct tvec_output _o;
3efcfd2d 859 struct tvec_state *tv;
67b5031e
MW
860 struct format fmt;
861 char *outbuf; size_t outsz;
e63124bc 862 int maxlen;
b64eb60f
MW
863};
864
e63124bc 865static int tap_writech(void *go, int ch)
67b5031e 866 { struct tap_output *t = go; return (format_char(&t->fmt, ch)); }
e63124bc
MW
867
868static int tap_writem(void *go, const char *p, size_t sz)
67b5031e 869 { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); }
e63124bc
MW
870
871static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
872{
67b5031e
MW
873 struct human_output *t = go;
874 size_t n;
e63124bc 875 va_list ap;
67b5031e
MW
876
877 va_start(ap, p);
878 n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
e63124bc 879 va_end(ap);
67b5031e 880 return (format_string(&t->fmt, t->outbuf, n));
b64eb60f
MW
881}
882
e63124bc
MW
883static const struct gprintf_ops tap_printops =
884 { tap_writech, tap_writem, tap_nwritef };
885
3efcfd2d
MW
886static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
887{
888 struct tap_output *t = (struct tap_output *)o;
889
890 t->tv = tv;
67b5031e 891 fputs("TAP version 13\n", t->fmt.fp);
3efcfd2d 892}
b64eb60f
MW
893
894static unsigned tap_grpix(struct tap_output *t)
895{
3efcfd2d 896 struct tvec_state *tv = t->tv;
b64eb60f
MW
897
898 return (tv->grps[TVOUT_WIN] +
899 tv->grps[TVOUT_LOSE] +
900 tv->grps[TVOUT_SKIP]);
901}
902
903static int tap_esession(struct tvec_output *o)
904{
905 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 906 struct tvec_state *tv = t->tv;
882a39c1
MW
907
908 if (tv->f&TVSF_ERROR) {
909 fputs("Bail out! "
910 "Errors found in input; tests may not have run correctly\n",
67b5031e 911 t->fmt.fp);
882a39c1
MW
912 return (2);
913 }
b64eb60f 914
67b5031e 915 fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t));
3efcfd2d 916 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
b64eb60f
MW
917}
918
e63124bc
MW
919static void tap_bgroup(struct tvec_output *o)
920{
921 struct tap_output *t = (struct tap_output *)o;
3efcfd2d
MW
922 t->maxlen = register_maxnamelen(t->tv);
923}
924
925static void tap_skipgroup(struct tvec_output *o,
926 const char *excuse, va_list *ap)
927{
928 struct tap_output *t = (struct tap_output *)o;
929
67b5031e
MW
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);
e63124bc 933}
b64eb60f 934
3efcfd2d 935static void tap_egroup(struct tvec_output *o)
b64eb60f
MW
936{
937 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 938 struct tvec_state *tv = t->tv;
b64eb60f
MW
939 unsigned
940 grpix = tap_grpix(t),
941 win = tv->curr[TVOUT_WIN],
942 lose = tv->curr[TVOUT_LOSE],
943 skip = tv->curr[TVOUT_SKIP];
944
945 if (lose) {
67b5031e 946 fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u",
b64eb60f 947 grpix, tv->test->name, lose, win + lose);
67b5031e 948 if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
b64eb60f 949 } else {
67b5031e
MW
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);
b64eb60f 952 }
67b5031e 953 fputc('\n', t->fmt.fp);
b64eb60f
MW
954}
955
b64eb60f
MW
956static void tap_btest(struct tvec_output *o) { ; }
957
67b5031e
MW
958static void tap_outcome(struct tvec_output *o, const char *outcome,
959 const char *detail, va_list *ap)
b64eb60f
MW
960{
961 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 962 struct tvec_state *tv = t->tv;
b64eb60f 963
67b5031e
MW
964 gprintf(&tap_printops, t, "%s:%u: `%s' %s",
965 tv->infile, tv->test_lno, tv->test->name, outcome);
966 if (detail) {
967 format_string(&t->fmt, ": ", 2);
968 vgprintf(&tap_printops, t, detail, ap);
969 }
970 format_char(&t->fmt, '\n');
b64eb60f
MW
971}
972
67b5031e
MW
973static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
974 { tap_outcome(o, "skipped", excuse, ap); }
b64eb60f 975static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
67b5031e 976 { tap_outcome(o, "FAILED", detail, ap); }
b64eb60f 977
e63124bc
MW
978static void tap_dumpreg(struct tvec_output *o,
979 unsigned disp, const union tvec_regval *rv,
980 const struct tvec_regdef *rd)
981{
982 struct tap_output *t = (struct tap_output *)o;
983 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
984
67b5031e
MW
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');
e63124bc 990}
b64eb60f
MW
991
992static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
993
e63124bc
MW
994static void tap_bbench(struct tvec_output *o,
995 const char *ident, unsigned unit)
996 { ; }
b64eb60f
MW
997
998static void tap_ebench(struct tvec_output *o,
e63124bc 999 const char *ident, unsigned unit,
b64eb60f
MW
1000 const struct bench_timing *tm)
1001{
1002 struct tap_output *t = (struct tap_output *)o;
3efcfd2d 1003 struct tvec_state *tv = t->tv;
b64eb60f 1004
67b5031e
MW
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');
b64eb60f
MW
1008}
1009
67b5031e
MW
1010static void tap_report(struct tap_output *t,
1011 const struct gprintf_ops *gops, void *go,
1012 const char *msg, va_list *ap)
3efcfd2d
MW
1013{
1014 struct tvec_state *tv = t->tv;
1015
67b5031e
MW
1016 if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
1017 gprintf(gops, go, msg, ap); gops->putch(go, '\n');
3efcfd2d
MW
1018}
1019
1020static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
1021{
1022 struct tap_output *t = (struct tap_output *)o;
67b5031e
MW
1023
1024 fputs("Bail out! ", t->fmt.fp);
1025 tap_report(t, &file_printops, t->fmt.fp, msg, ap);
3efcfd2d
MW
1026}
1027
1028static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
1029{
1030 struct tap_output *t = (struct tap_output *)o;
67b5031e
MW
1031
1032 tap_report(t, &tap_printops, t, msg, ap);
3efcfd2d
MW
1033}
1034
b64eb60f
MW
1035static void tap_destroy(struct tvec_output *o)
1036{
1037 struct tap_output *t = (struct tap_output *)o;
1038
67b5031e
MW
1039 destroy_fmt(&t->fmt,
1040 t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE);
1041 xfree(t->outbuf); xfree(t);
b64eb60f
MW
1042}
1043
1044static const struct tvec_outops tap_ops = {
b64eb60f 1045 tap_bsession, tap_esession,
3efcfd2d 1046 tap_bgroup, tap_skipgroup, tap_egroup,
e63124bc 1047 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
b64eb60f 1048 tap_bbench, tap_ebench,
3efcfd2d 1049 tap_error, tap_notice,
b64eb60f
MW
1050 tap_destroy
1051};
1052
1053struct tvec_output *tvec_tapoutput(FILE *fp)
1054{
1055 struct tap_output *t;
1056
1057 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
67b5031e
MW
1058 init_fmt(&t->fmt, fp, "## ");
1059 t->outbuf = 0; t->outsz = 0;
b64eb60f
MW
1060 return (&t->_o);
1061}
1062
1063/*----- Default output ----------------------------------------------------*/
1064
1065struct tvec_output *tvec_dfltout(FILE *fp)
1066{
1067 int ttyp = getenv_boolean("TVEC_TTY", -1);
1068
1069 if (ttyp == -1) ttyp = isatty(fileno(fp));
1070 if (ttyp) return (tvec_humanoutput(fp));
1071 else return (tvec_tapoutput(fp));
1072}
1073
1074/*----- That's all, folks -------------------------------------------------*/