@@@ wip tap 14
[mLib] / test / tvec-output.c
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
30 #include "config.h"
31
32 #include <assert.h>
33 #include <ctype.h>
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"
43 #include "macros.h"
44 #include "quis.h"
45 #include "report.h"
46 #include "tvec.h"
47
48 /*----- Common machinery --------------------------------------------------*/
49
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
58 static const char *regdisp(unsigned disp)
59 {
60 switch (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";
66 default: abort();
67 }
68 }
69
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
79 static 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") ||
92 STRCMP(p, ==, "f") || STRCMP(p, ==, "false") ||
93 STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") ||
94 STRCMP(p, ==, "0"))
95 return (0);
96 else {
97 moan("ignoring unexpected value `%s' for environment variable `%s'",
98 var, p);
99 return (dflt);
100 }
101 }
102
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
110 static int register_maxnamelen(const struct tvec_state *tv)
111 {
112 const struct tvec_regdef *rd;
113 int maxlen = 10, n;
114
115 for (rd = tv->test->regs; rd->name; rd++)
116 { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
117 return (maxlen);
118 }
119
120 /*----- Output layout -----------------------------------------------------*/
121
122 /* We have two main jobs in output layout: 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
128 struct layout {
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 */
134 };
135
136 /* Support macros. These assume `lyt' is defined as a pointer to the `struct
137 * layout' 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, lyt->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, lyt->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 (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->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 = lyt->w.len; \
174 if (n && fwrite(lyt->w.buf, 1, n, lyt->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 (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail); \
183 } while (0)
184
185 #define SAVE_PFXTAIL do { \
186 /* Save the trailing blank portion of the prefix. */ \
187 \
188 if (lyt->prefix) \
189 DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
190 } while (0)
191
192 /* --- @init_layout@ --- *
193 *
194 * Arguments: @struct layout *lyt@ = layout 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 layout state.
201 */
202
203 static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
204 {
205 const char *q, *l;
206
207 /* Basics. */
208 lyt->fp = fp;
209 lyt->f = LYTF_NEWL;
210 dstr_create(&lyt->w);
211
212 /* Prefix portions. */
213 if (!prefix || !*prefix)
214 lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
215 else {
216 lyt->prefix = prefix;
217 l = lyt->pfxlim = prefix + strlen(prefix);
218 SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
219 }
220 }
221
222 /* --- @destroy_layout@ --- *
223 *
224 * Arguments: @struct layout *lyt@ = layout state
225 * @unsigned f@ = flags (@DLF_...@)
226 *
227 * Returns: ---
228 *
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).
232 */
233
234 #define DLF_CLOSE 1u
235 static void destroy_layout(struct layout *lyt, unsigned f)
236 {
237 if (f&DLF_CLOSE) fclose(lyt->fp);
238 dstr_destroy(&lyt->w);
239 }
240
241 /* --- @layout_char@ --- *
242 *
243 * Arguments: @struct layout *lyt@ = layout state
244 * @int ch@ = character to write
245 *
246 * Returns: Zero on success, @-1@ on failure.
247 *
248 * Use: Write a single character to the output.
249 */
250
251 static int layout_char(struct layout *lyt, int ch)
252 {
253 if (ch == '\n') {
254 if (lyt->f&LYTF_NEWL) PUT_PFXINB;
255 PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
256 } else if (isspace(ch))
257 DPUTC(&lyt->w, ch);
258 else {
259 if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
260 PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
261 }
262 return (0);
263 }
264
265 /* --- @layout_string@ --- *
266 *
267 * Arguments: @struct layout *lyt@ = layout state
268 * @const char *p@ = string to write
269 * @size_t sz@ = length of string
270 *
271 * Returns: Zero on success, @-1@ on failure.
272 *
273 * Use: Write a string to the output.
274 */
275
276 static int layout_string(struct layout *lyt, const char *p, size_t sz)
277 {
278 const char *q, *r, *l = p + sz;
279
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.
284 *
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:
287 *
288 * * an initial portion, which is either empty or ends with a nonblank
289 * character;
290 *
291 * * a suffix which consists only of blanks; and
292 *
293 * * an optional newline.
294 *
295 * All segments except the last end with a newline.
296 */
297
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 \
304 * no newline. \
305 */ \
306 \
307 q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
308 } while (0)
309
310 #define PUT_NONBLANK do { \
311 /* Output the initial portion of the segment. */ \
312 \
313 PUT_RANGE(p, r); \
314 } while (0)
315
316 #define PUT_NEWLINE do { \
317 /* Write a newline, and advance to the next segment. */ \
318 \
319 PUT_CHAR('\n'); p = q + 1; \
320 } while (0)
321
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 \
325 * be omitted. \
326 */ \
327 \
328 DPUTM(&lyt->w, r, l - r); \
329 } while (0)
330
331 /* Determine the bounds of the first segment. Handling this is the most
332 * complicated part of this function.
333 */
334 SPLIT_SEGMENT;
335
336 if (!q) {
337 /* This is the only segment. We'll handle the whole thing here.
338 *
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.
344 *
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.
348 */
349
350 if (r > p) {
351 if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
352 PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
353 }
354 SAVE_TAIL;
355 return (0);
356 }
357
358 /* There is at least one more segment, so we know that there'll be a line
359 * to output.
360 */
361 if (r > p) {
362 if (lyt->f&LYTF_NEWL) PUT_PREFIX;
363 PUT_SAVED; PUT_NONBLANK;
364 } else if (lyt->f&LYTF_NEWL)
365 PUT_PFXINB;
366 PUT_NEWLINE; DRESET(&lyt->w);
367 SPLIT_SEGMENT;
368
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.
372 */
373 while (q) {
374 if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
375 else PUT_PFXINB;
376 PUT_NEWLINE;
377 SPLIT_SEGMENT;
378 }
379
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.
384 */
385 if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
386 else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
387 SAVE_TAIL;
388
389 #undef SPLIT_SEGMENT
390 #undef PUT_NONBLANK
391 #undef PUT_NEWLINE
392 #undef SAVE_TAIL
393
394 return (0);
395 }
396
397 #undef SPLIT_RANGE
398 #undef PUT_RANGE
399 #undef PUT_PREFIX
400 #undef PUT_PFXINB
401 #undef PUT_SAVED
402 #undef PUT_CHAR
403 #undef SAVE_PFXTAIL
404
405 /*----- Skeleton ----------------------------------------------------------*/
406 /*
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)
427
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,
434 ..._destroy
435 };
436 */
437 /*----- Human-readable output ---------------------------------------------*/
438
439 /* Attributes for colour output. This should be done better, but @terminfo@
440 * is a disaster.
441 *
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.
446 */
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... */
455 #define HCOL_RED 1u
456 #define HCOL_GREEN 2u
457 #define HCOL_YELLOW 3u
458 #define HCOL_BLUE 4u
459 #define HCOL_MAGENTA 5u
460 #define HCOL_CYAN 6u
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 */
465
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 */
478
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 */
484
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 */
498 };
499
500 /* --- @set_colour@ --- *
501 *
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
507 *
508 * Returns: ---
509 *
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 `%|;|%'.
515 *
516 * This is an internal subroutine for @setattr@ below.
517 */
518
519 static void set_colour(FILE *fp, int *sep_inout,
520 const char *norm, const char *bright,
521 unsigned colour)
522 {
523 if (*sep_inout) putc(*sep_inout, fp);
524 fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7);
525 *sep_inout = ';';
526 }
527
528 /* --- @setattr@ --- *
529 *
530 * Arguments: @struct human_output *h@ = output state
531 * @unsigned attr@ = attribute code to set
532 *
533 * Returns: ---
534 *
535 * Use: Send a control sequence to the output stream so that
536 * subsequent text is printed with the given attributes.
537 *
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.
541 */
542
543 static void setattr(struct human_output *h, unsigned attr)
544 {
545 unsigned diff = h->attr ^ attr;
546 int sep = 0;
547
548 /* If there's nothing to do, we might as well stop now. */
549 if (!diff || !(h->f&HOF_COLOUR)) return;
550
551 /* Start on the control command. */
552 fputs("\x1b[", h->lyt.fp);
553
554 /* Change the boldness if necessary. */
555 if (diff&HAF_BOLD) {
556 if (attr&HAF_BOLD) putc('1', h->lyt.fp);
557 else { putc('0', h->lyt.fp); diff = h->attr; }
558 sep = ';';
559 }
560
561 /* Change the foreground colour if necessary. */
562 if (diff&(HAF_FG | HAF_FGMASK)) {
563 if (attr&HAF_FG)
564 set_colour(h->lyt.fp, &sep, "3", "9",
565 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
566 else {
567 if (sep) putc(sep, h->lyt.fp);
568 fputs("39", h->lyt.fp); sep = ';';
569 }
570 }
571
572 /* Change the background colour if necessary. */
573 if (diff&(HAF_BG | HAF_BGMASK)) {
574 if (attr&HAF_BG)
575 set_colour(h->lyt.fp, &sep, "4", "10",
576 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
577 else {
578 if (sep) putc(sep, h->lyt.fp);
579 fputs("49", h->lyt.fp); sep = ';';
580 }
581 }
582
583 /* Terminate the control command and save the new attributes. */
584 putc('m', h->lyt.fp); h->attr = attr;
585 }
586
587 /* --- @clear_progress@ --- *
588 *
589 * Arguments: @struct human_output *h@ = output state
590 *
591 * Returns: ---
592 *
593 * Use: Remove the progress display from the terminal.
594 *
595 * If the progress display isn't active then do nothing.
596 */
597
598 static void clear_progress(struct human_output *h)
599 {
600 size_t i, n;
601
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;
606 }
607 }
608
609 /* --- @write_scoreboard_char@ --- *
610 *
611 * Arguments: @struct human_output *h@ = output state
612 * @int ch@ = scoreboard character to print
613 *
614 * Returns: ---
615 *
616 * Use: Write a scoreboard character, indicating the outcome of a
617 * test, to the output stream, with appropriate highlighting.
618 */
619
620 static void write_scoreboard_char(struct human_output *h, int ch)
621 {
622 switch (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;
627 }
628 putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
629 }
630
631 /* --- @show_progress@ --- *
632 *
633 * Arguments: @struct human_output *h@ = output state
634 *
635 * Returns: ---
636 *
637 * Use: Show the progress display, with the record of outcomes for
638 * the current test group.
639 *
640 * If the progress display is already active, or the output
641 * stream is not interactive, then nothing happens.
642 */
643
644 static void show_progress(struct human_output *h)
645 {
646 struct tvec_state *tv = h->tv;
647 const char *p, *l;
648
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;
656 }
657 }
658
659 /* --- @report_location@ --- *
660 *
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
665 *
666 * Returns: ---
667 *
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
671 * nothing.
672 */
673
674 static void report_location(struct human_output *h, FILE *fp,
675 const char *file, unsigned lno)
676 {
677 unsigned f = 0;
678 #define f_flush 1u
679
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
684 * text synchronized.
685 */
686
687 if (!file)
688 /* nothing to do */;
689 else if (fp != h->lyt.fp && (h->f&HOF_DUPERR))
690 fprintf(fp, "%s:%u: ", file, lno);
691 else {
692 if (fp != h->lyt.fp) f |= f_flush;
693
694 #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
695
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);
705 fputc(' ', fp);
706
707 #undef FLUSH
708 }
709
710 #undef f_flush
711 }
712
713 /* Output layout. Pass everything along to the layout machinery above. */
714
715 static int human_writech(void *go, int ch)
716 { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
717
718 static int human_writem(void *go, const char *p, size_t sz)
719 { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
720
721 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
722 {
723 struct human_output *h = go;
724 size_t n;
725 va_list ap;
726
727 va_start(ap, p);
728 n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
729 va_end(ap);
730 return (layout_string(&h->lyt, h->outbuf, n));
731 }
732
733 static const struct gprintf_ops human_printops =
734 { human_writech, human_writem, human_nwritef };
735
736 /* Output methods. */
737
738 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
739 { struct human_output *h = (struct human_output *)o; h->tv = tv; }
740
741 static void human_report_unusual(struct human_output *h,
742 unsigned nxfail, unsigned nskip)
743 {
744 const char *sep = " (";
745 unsigned f = 0;
746 #define f_any 1u
747
748 if (nxfail) {
749 fprintf(h->lyt.fp, "%s%u ", sep, nxfail);
750 setattr(h, HA_XFAIL);
751 fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures");
752 setattr(h, HA_PLAIN);
753 sep = ", "; f |= f_any;
754 }
755
756 if (nskip) {
757 fprintf(h->lyt.fp, "%s%u ", sep, nskip);
758 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
759 sep = ", "; f |= f_any;
760 }
761
762 if (f&f_any) fputc(')', h->lyt.fp);
763
764 #undef f_any
765 }
766
767 static int human_esession(struct tvec_output *o)
768 {
769 struct human_output *h = (struct human_output *)o;
770 struct tvec_state *tv = h->tv;
771 unsigned
772 all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN],
773 all_xfail = tv->all[TVOUT_XFAIL],
774 all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE],
775 all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP],
776 all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
777 grps_run = grps_win + grps_lose;
778
779 if (!all_lose) {
780 setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
781 fprintf(h->lyt.fp, " %s%u %s",
782 !(all_skip || grps_skip) ? "all " : "",
783 all_pass, all_pass == 1 ? "test" : "tests");
784 human_report_unusual(h, all_xfail, all_skip);
785 fprintf(h->lyt.fp, " in %u %s",
786 grps_win, grps_win == 1 ? "group" : "groups");
787 human_report_unusual(h, 0, grps_skip);
788 } else {
789 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
790 fprintf(h->lyt.fp, " %u out of %u %s",
791 all_lose, all_run, all_run == 1 ? "test" : "tests");
792 human_report_unusual(h, all_xfail, all_skip);
793 fprintf(h->lyt.fp, " in %u out of %u %s",
794 grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
795 human_report_unusual(h, 0, grps_skip);
796 }
797 fputc('\n', h->lyt.fp);
798
799 if (tv->f&TVSF_ERROR) {
800 setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
801 fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
802 }
803
804 h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
805 }
806
807 static void human_bgroup(struct tvec_output *o)
808 {
809 struct human_output *h = (struct human_output *)o;
810
811 h->maxlen = register_maxnamelen(h->tv);
812 dstr_reset(&h->scoreboard); show_progress(h);
813 }
814
815 static void human_skipgroup(struct tvec_output *o,
816 const char *excuse, va_list *ap)
817 {
818 struct human_output *h = (struct human_output *)o;
819
820 if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
821 h->f &= ~HOF_PROGRESS;
822 putc(' ', h->lyt.fp);
823 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
824 } else {
825 fprintf(h->lyt.fp, "%s: ", h->tv->test->name);
826 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
827 }
828 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
829 fputc('\n', h->lyt.fp);
830 }
831
832 static void human_egroup(struct tvec_output *o)
833 {
834 struct human_output *h = (struct human_output *)o;
835 struct tvec_state *tv = h->tv;
836 unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
837 lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
838 run = win + lose + xfail;
839
840 if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
841 else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
842
843 if (lose) {
844 fprintf(h->lyt.fp, " %u/%u ", lose, run);
845 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
846 human_report_unusual(h, xfail, skip);
847 } else {
848 fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
849 fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
850 human_report_unusual(h, xfail, skip);
851 }
852 fputc('\n', h->lyt.fp);
853 }
854
855 static void human_btest(struct tvec_output *o)
856 { struct human_output *h = (struct human_output *)o; show_progress(h); }
857
858 static void human_skip(struct tvec_output *o,
859 const char *excuse, va_list *ap)
860 {
861 struct human_output *h = (struct human_output *)o;
862 struct tvec_state *tv = h->tv;
863
864 clear_progress(h);
865 report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
866 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
867 setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
868 if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
869 fputc('\n', h->lyt.fp);
870 }
871
872 static void human_fail(struct tvec_output *o,
873 const char *detail, va_list *ap)
874 {
875 struct human_output *h = (struct human_output *)o;
876 struct tvec_state *tv = h->tv;
877
878 clear_progress(h);
879 report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
880 fprintf(h->lyt.fp, "`%s' ", tv->test->name);
881 setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
882 if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
883 fputc('\n', h->lyt.fp);
884 }
885
886 static void human_dumpreg(struct tvec_output *o,
887 unsigned disp, const union tvec_regval *rv,
888 const struct tvec_regdef *rd)
889 {
890 struct human_output *h = (struct human_output *)o;
891 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
892
893 clear_progress(h);
894 gprintf(&human_printops, h, "%*s%s %s = ",
895 10 + h->maxlen - n, "", ds, rd->name);
896 if (h->f&HOF_COLOUR) {
897 if (!rv) setattr(h, HA_UNSET);
898 else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
899 else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
900 }
901 if (!rv) gprintf(&human_printops, h, "#unset");
902 else rd->ty->dump(rv, rd, 0, &human_printops, h);
903 setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
904 }
905
906 static void human_etest(struct tvec_output *o, unsigned outcome)
907 {
908 struct human_output *h = (struct human_output *)o;
909 int ch;
910
911 if (h->f&HOF_TTY) {
912 show_progress(h);
913 switch (outcome) {
914 case TVOUT_WIN: ch = HSB_WIN; break;
915 case TVOUT_LOSE: ch = HSB_LOSE; break;
916 case TVOUT_XFAIL: ch = HSB_XFAIL; break;
917 case TVOUT_SKIP: ch = HSB_SKIP; break;
918 default: abort();
919 }
920 dstr_putc(&h->scoreboard, ch);
921 write_scoreboard_char(h, ch); fflush(h->lyt.fp);
922 }
923 }
924
925 static void human_bbench(struct tvec_output *o,
926 const char *ident, unsigned unit)
927 {
928 struct human_output *h = (struct human_output *)o;
929 struct tvec_state *tv = h->tv;
930
931 clear_progress(h);
932 fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
933 }
934
935 static void human_ebench(struct tvec_output *o,
936 const char *ident, unsigned unit,
937 const struct bench_timing *tm)
938 {
939 struct human_output *h = (struct human_output *)o;
940
941 tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
942 fputc('\n', h->lyt.fp);
943 }
944
945 static void human_report(struct tvec_output *o, unsigned level,
946 const char *msg, va_list *ap)
947 {
948 struct human_output *h = (struct human_output *)o;
949 struct tvec_state *tv = h->tv;
950 dstr d = DSTR_INIT;
951
952 dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
953
954 clear_progress(h); fflush(h->lyt.fp);
955 fprintf(stderr, "%s: ", QUIS);
956 report_location(h, stderr, tv->infile, tv->lno);
957 fwrite(d.buf, 1, d.len, stderr);
958
959 if (h->f&HOF_DUPERR) {
960 report_location(h, h->lyt.fp, tv->infile, tv->lno);
961 fwrite(d.buf, 1, d.len, h->lyt.fp);
962 }
963 show_progress(h);
964 }
965
966 static void human_destroy(struct tvec_output *o)
967 {
968 struct human_output *h = (struct human_output *)o;
969
970 destroy_layout(&h->lyt,
971 h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
972 dstr_destroy(&h->scoreboard);
973 xfree(h->outbuf); xfree(h);
974 }
975
976 static const struct tvec_outops human_ops = {
977 human_bsession, human_esession,
978 human_bgroup, human_skipgroup, human_egroup,
979 human_btest, human_skip, human_fail, human_dumpreg, human_etest,
980 human_bbench, human_ebench,
981 human_report,
982 human_destroy
983 };
984
985 struct tvec_output *tvec_humanoutput(FILE *fp)
986 {
987 struct human_output *h;
988 const char *p;
989
990 h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
991 h->f = 0; h->attr = 0;
992
993 init_layout(&h->lyt, fp, 0);
994 h->outbuf = 0; h->outsz = 0;
995
996 switch (getenv_boolean("TVEC_TTY", -1)) {
997 case 1: h->f |= HOF_TTY; break;
998 case 0: break;
999 default:
1000 if (isatty(fileno(fp))) h->f |= HOF_TTY;
1001 break;
1002 }
1003 switch (getenv_boolean("TVEC_COLOUR", -1)) {
1004 case 1: h->f |= HOF_COLOUR; break;
1005 case 0: break;
1006 default:
1007 if (h->f&HOF_TTY) {
1008 p = getenv("TERM");
1009 if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR;
1010 }
1011 break;
1012 }
1013
1014 if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR;
1015 dstr_create(&h->scoreboard);
1016 return (&h->_o);
1017 }
1018
1019 /*----- Perl's `Test Anything Protocol' -----------------------------------*/
1020
1021 struct tap_output {
1022 struct tvec_output _o;
1023 struct tvec_state *tv;
1024 struct layout lyt;
1025 unsigned last;
1026 char *outbuf; size_t outsz;
1027 int maxlen;
1028 };
1029
1030 static int tap_writech(void *go, int ch)
1031 { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
1032
1033 static int tap_writem(void *go, const char *p, size_t sz)
1034 { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
1035
1036 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
1037 {
1038 struct tap_output *t = go;
1039 size_t n;
1040 va_list ap;
1041
1042 va_start(ap, p);
1043 n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
1044 va_end(ap);
1045 return (layout_string(&t->lyt, t->outbuf, n));
1046 }
1047
1048 static const struct gprintf_ops tap_printops =
1049 { tap_writech, tap_writem, tap_nwritef };
1050
1051 static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
1052 {
1053 struct tap_output *t = (struct tap_output *)o;
1054
1055 t->tv = tv; t->last = 0;
1056 fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
1057 }
1058
1059 static unsigned tap_grpix(struct tap_output *t)
1060 {
1061 struct tvec_state *tv = t->tv;
1062 unsigned i, n;
1063
1064 for (n = 0, i = 0; i < TVOUT_LIMIT; i++) n += tv->grps[i];
1065 return (n);
1066 }
1067
1068 static unsigned tap_testix(struct tap_output *t)
1069 {
1070 struct tvec_state *tv = t->tv;
1071 unsigned i, n;
1072
1073 for (n = 0, i = 0; i < TVOUT_LIMIT; i++) n += tv->curr[i];
1074 if (tv->f&TVSF_OPEN) n++;
1075 return (n);
1076 }
1077
1078 static int tap_esession(struct tvec_output *o)
1079 {
1080 struct tap_output *t = (struct tap_output *)o;
1081 struct tvec_state *tv = t->tv;
1082
1083 if (tv->f&TVSF_ERROR) {
1084 fputs("Bail out! "
1085 "Errors found in input; tests may not have run correctly\n",
1086 t->lyt.fp);
1087 return (2);
1088 }
1089
1090 fprintf(t->lyt.fp, "1..%u\n", tap_grpix(t));
1091 t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
1092 }
1093
1094 static void tap_bgroup(struct tvec_output *o)
1095 {
1096 struct tap_output *t = (struct tap_output *)o;
1097 struct tvec_state *tv = t->tv;
1098
1099 t->maxlen = register_maxnamelen(t->tv);
1100 fprintf(t->lyt.fp, "# Subtest: %s\n", tv->test->name);
1101 }
1102
1103 static void tap_skipgroup(struct tvec_output *o,
1104 const char *excuse, va_list *ap)
1105 {
1106 struct tap_output *t = (struct tap_output *)o;
1107
1108 fprintf(t->lyt.fp, " 1..%u\n", tap_testix(t));
1109 fprintf(t->lyt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
1110 if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
1111 fputc('\n', t->lyt.fp);
1112 }
1113
1114 static void tap_egroup(struct tvec_output *o)
1115 {
1116 struct tap_output *t = (struct tap_output *)o;
1117 struct tvec_state *tv = t->tv;
1118
1119 fprintf(t->lyt.fp, " 1..%u\n", tap_testix(t));
1120 fprintf(t->lyt.fp, "%s %u - %s\n",
1121 tv->curr[TVOUT_LOSE] ? "not ok" : "ok",
1122 tap_grpix(t), tv->test->name);
1123 }
1124
1125 static void tap_btest(struct tvec_output *o) { ; }
1126
1127 static void tap_outcome(struct tvec_output *o,
1128 const char *head, const char *tail,
1129 const char *detail, va_list *ap)
1130 {
1131 struct tap_output *t = (struct tap_output *)o;
1132 struct tvec_state *tv = t->tv;
1133 unsigned ix = tap_testix(t);
1134
1135 fprintf(t->lyt.fp, " %s %u - %s:%u%s",
1136 ix == t->last ? "##" : head, ix, tv->infile, tv->test_lno, tail);
1137 if (detail)
1138 { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, detail, *ap); }
1139 fputc('\n', t->lyt.fp);
1140 t->last = ix;
1141 }
1142
1143 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
1144 { tap_outcome(o, "ok", " # SKIP", excuse, ap); }
1145 static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
1146 { tap_outcome(o, "not ok", "", detail, ap); }
1147
1148 static void tap_dumpreg(struct tvec_output *o,
1149 unsigned disp, const union tvec_regval *rv,
1150 const struct tvec_regdef *rd)
1151 {
1152 struct tap_output *t = (struct tap_output *)o;
1153 const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
1154
1155 gprintf(&tap_printops, t, "%*s%s %s = ",
1156 10 + t->maxlen - n, "", ds, rd->name);
1157 if (!rv) gprintf(&tap_printops, t, "#<unset>");
1158 else rd->ty->dump(rv, rd, 0, &tap_printops, t);
1159 layout_char(&t->lyt, '\n');
1160 }
1161
1162 static void tap_etest(struct tvec_output *o, unsigned outcome)
1163 {
1164 switch (outcome) {
1165 case TVOUT_WIN:
1166 tap_outcome(o, "ok", "", 0, 0);
1167 break;
1168 case TVOUT_XFAIL:
1169 tap_outcome(o, "not ok", " # TODO expected failure", 0, 0);
1170 break;
1171 }
1172 }
1173
1174 static void tap_bbench(struct tvec_output *o,
1175 const char *ident, unsigned unit)
1176 { ; }
1177
1178 static void tap_ebench(struct tvec_output *o,
1179 const char *ident, unsigned unit,
1180 const struct bench_timing *tm)
1181 {
1182 struct tap_output *t = (struct tap_output *)o;
1183 struct tvec_state *tv = t->tv;
1184
1185 gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
1186 tvec_benchreport(&tap_printops, t, unit, tm);
1187 layout_char(&t->lyt, '\n');
1188 }
1189
1190 static void tap_report(struct tvec_output *o, unsigned level,
1191 const char *msg, va_list *ap)
1192 {
1193 struct tap_output *t = (struct tap_output *)o;
1194 struct tvec_state *tv = t->tv;
1195 const struct gprintf_ops *gops; void *go;
1196
1197 if (level >= TVLEV_ERR) {
1198 fputs("Bail out! ", t->lyt.fp);
1199 gops = &file_printops; go = t->lyt.fp;
1200 } else {
1201 gops = &tap_printops; go = t;
1202 }
1203 if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
1204 gprintf(gops, go, msg, ap); gops->putch(go, '\n');
1205 }
1206
1207 static void tap_destroy(struct tvec_output *o)
1208 {
1209 struct tap_output *t = (struct tap_output *)o;
1210
1211 destroy_layout(&t->lyt,
1212 t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
1213 xfree(t->outbuf); xfree(t);
1214 }
1215
1216 static const struct tvec_outops tap_ops = {
1217 tap_bsession, tap_esession,
1218 tap_bgroup, tap_skipgroup, tap_egroup,
1219 tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
1220 tap_bbench, tap_ebench,
1221 tap_report,
1222 tap_destroy
1223 };
1224
1225 struct tvec_output *tvec_tapoutput(FILE *fp)
1226 {
1227 struct tap_output *t;
1228
1229 t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
1230 init_layout(&t->lyt, fp, " ## ");
1231 t->outbuf = 0; t->outsz = 0;
1232 return (&t->_o);
1233 }
1234
1235 /*----- Default output ----------------------------------------------------*/
1236
1237 struct tvec_output *tvec_dfltout(FILE *fp)
1238 {
1239 int ttyp = getenv_boolean("TVEC_TTY", -1);
1240
1241 if (ttyp == -1) ttyp = isatty(fileno(fp));
1242 if (ttyp) return (tvec_humanoutput(fp));
1243 else return (tvec_tapoutput(fp));
1244 }
1245
1246 /*----- That's all, folks -------------------------------------------------*/