X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/b1a20beee623c83315c3ce21abc7bcce103c6efb..98ff9295493ed2b990f30768e11b18b6bc65eaa4:/test/tvec-output.c diff --git a/test/tvec-output.c b/test/tvec-output.c index 4e2ea64..40ab769 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -36,6 +36,7 @@ #include #include +#include #include "alloc.h" #include "bench.h" @@ -43,6 +44,8 @@ #include "macros.h" #include "quis.h" #include "report.h" +#include "tty.h" +#include "ttycolour.h" #include "tvec.h" #include "tvec-bench.h" @@ -70,37 +73,51 @@ static const char *regdisp(unsigned disp) } } -/* --- @getenv_boolean@ --- * +/* --- @interpret_boolean@, @getenv_boolean@ --- * * - * Arguments: @const char *var@ = environment variable name + * Arguments: @const char *val@ = a string + * @const char *var@ = environment variable name * @int dflt@ = default value + * @const char *what, ...@ = format string describing where the + * setting came from * - * Returns: @0@ if the variable is set to something falseish, @1@ if it's - * set to something truish, or @dflt@ otherwise. + * Returns: @0@ if the string, or variable, is set to something + * falseish, @1@ if it's set to something truish, or @dflt@ + * otherwise. */ -static int getenv_boolean(const char *var, int dflt) +static PRINTF_LIKE(3, 4) + int interpret_boolean(const char *val, int dflt, const char *what, ...) { - const char *p; - - p = getenv(var); - if (!p) - return (dflt); - else if (STRCMP(p, ==, "y") || STRCMP(p, ==, "yes") || - STRCMP(p, ==, "t") || STRCMP(p, ==, "true") || - STRCMP(p, ==, "on") || STRCMP(p, ==, "force") || - STRCMP(p, ==, "1")) - return (1); - else if (STRCMP(p, ==, "n") || STRCMP(p, ==, "no") || - STRCMP(p, ==, "f") || STRCMP(p, ==, "false") || - STRCMP(p, ==, "nil") || STRCMP(p, ==, "off") || - STRCMP(p, ==, "0")) - return (0); + dstr d = DSTR_INIT; + va_list ap; + int rc; + + if (!val) + rc = dflt; + else if (STRCMP(val, ==, "y") || STRCMP(val, ==, "yes") || + STRCMP(val, ==, "t") || STRCMP(val, ==, "true") || + STRCMP(val, ==, "on") || STRCMP(val, ==, "force") || + STRCMP(val, ==, "1")) + rc = 1; + else if (STRCMP(val, ==, "n") || STRCMP(val, ==, "no") || + STRCMP(val, ==, "nil") || STRCMP(val, ==, "f") || + STRCMP(val, ==, "false") || + STRCMP(val, ==, "off") || STRCMP(val, ==, "inhibit") || + STRCMP(val, ==, "0")) + rc = 0; else { - moan("ignoring unexpected value `%s' for environment variable `%s'", - var, p); - return (dflt); + va_start(ap, what); dstr_vputf(&d, what, &ap); va_end(ap); + moan("ignoring unexpected value `%s' for %s", val, d.buf); + rc = dflt; } + dstr_destroy(&d); return (rc); +} + +static int getenv_boolean(const char *var, int dflt) +{ + return (interpret_boolean(getenv(var), dflt, + "environment variable `%s'", var)); } /* --- @register_maxnamelen@ --- * @@ -166,6 +183,7 @@ struct layout { FILE *fp; /* output file */ const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */ dstr w; /* trailing whitespace */ + dstr ctrl; /* control sequence for next word */ unsigned f; /* flags */ #define LYTF_NEWL 1u /* start of output line */ }; @@ -211,6 +229,13 @@ struct layout { if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \ } while (0) +#define PUT_CTRL do { \ + /* Output the accumulated control string. */ \ + \ + size_t _n = lyt->ctrl.len; \ + if (_n && fwrite(lyt->ctrl.buf, 1, _n, lyt->fp) < _n) return (-1); \ +} while (0) + #define PUT_PFXINB do { \ /* Output the initial nonblank portion of the prefix, if there is \ * one. Return immediately on error. \ @@ -266,7 +291,7 @@ static void init_layout(struct layout *lyt, FILE *fp, const char *prefix) { lyt->fp = fp; lyt->f = LYTF_NEWL; - dstr_create(&lyt->w); + dstr_create(&lyt->w); dstr_create(&lyt->ctrl); set_layout_prefix(lyt, prefix); } @@ -286,7 +311,7 @@ static void init_layout(struct layout *lyt, FILE *fp, const char *prefix) static void destroy_layout(struct layout *lyt, unsigned f) { if (f&DLF_CLOSE) fclose(lyt->fp); - dstr_destroy(&lyt->w); + dstr_destroy(&lyt->w); dstr_destroy(&lyt->ctrl); } /* --- @layout_char@ --- * @@ -400,7 +425,8 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz) if (r > p) { if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; } - PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w); + PUT_SAVED; PUT_CTRL; PUT_NONBLANK; + DRESET(&lyt->w); DRESET(&lyt->ctrl); } SAVE_TAIL; return (0); @@ -411,7 +437,8 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz) */ if (r > p) { if (lyt->f&LYTF_NEWL) PUT_PREFIX; - PUT_SAVED; PUT_NONBLANK; + PUT_SAVED; PUT_CTRL; PUT_NONBLANK; + DRESET(&lyt->ctrl); } else if (lyt->f&LYTF_NEWL) PUT_PFXINB; PUT_NEWLINE; DRESET(&lyt->w); @@ -422,7 +449,7 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz) * newline, so we write the initial prefix and drop the trailing blanks. */ while (q) { - if (r > p) { PUT_PREFIX; PUT_NONBLANK; } + if (r > p) { PUT_PREFIX; PUT_CTRL; PUT_NONBLANK; DRESET(&lyt->ctrl); } else PUT_PFXINB; PUT_NEWLINE; SPLIT_SEGMENT; @@ -455,157 +482,127 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz) /*----- Human-readable output ---------------------------------------------*/ -/* Attributes for colour output. This should be done better, but @terminfo@ - * is a disaster. - * - * An attribute byte holds a foreground colour in the low nibble, a - * background colour in the next nibble, and some flags in the next few - * bits. A colour is expressed in classic 1-bit-per-channel style, with red, - * green, and blue in bits 0, 1, and 2, and a `bright' flag in bit 3. - */ -#define HAF_FGMASK 0x0f /* foreground colour mask */ -#define HAF_FGSHIFT 0 /* foreground colour shift */ -#define HAF_BGMASK 0xf0 /* background colour mask */ -#define HAF_BGSHIFT 4 /* background colour shift */ -#define HAF_FG 256u /* set foreground? */ -#define HAF_BG 512u /* set background? */ -#define HAF_BOLD 1024u /* set bold? */ -#define HCOL_BLACK 0u /* colour codes... */ -#define HCOL_RED 1u -#define HCOL_GREEN 2u -#define HCOL_YELLOW 3u -#define HCOL_BLUE 4u -#define HCOL_MAGENTA 5u -#define HCOL_CYAN 6u -#define HCOL_WHITE 7u -#define HCF_BRIGHT 8u /* bright colour flag */ -#define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT) /* set foreground */ -#define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT) /* set background */ +#define GENATTR(want, attrs, fgspc, fgcol, bgspc, bgcol) \ + { (want), (want), \ + { ((fgspc) << TTAF_FGSPCSHIFT) | \ + ((bgspc) << TTAF_BGSPCSHIFT) | (attrs), \ + 0, (fgcol), (bgcol) } } + +#define FGBG(want, attrs, fgcol, bgcol) \ + GENATTR(want | TTACF_FG | TTACF_BG, attrs, \ + TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_1BPCBR, TTCOL_##bgcol) +#define FG(want, attrs, fgcol) \ + GENATTR(want | TTACF_FG, attrs, \ + TTCSPC_1BPCBR, TTCOL_##fgcol, TTCSPC_NONE, 0) +#define BG(want, attrs, bgcol) \ + GENATTR(want | TTACF_BG, attrs, \ + TTCSPC_NONE, 0, TTCSPC_1BPCBR, TTCOL_##bgcol) +#define ATTR(want, attrs) \ + GENATTR(want, attrs, TTCSPC_NONE, 0, TTCSPC_NONE, 0) + +#define BOLD (TTWT_BOLD << TTAF_WTSHIFT) + +static const struct tty_attrlist + loc_attrs[] = { FG(0, 0, CYN), TTY_ATTRLIST_CLEAR }, + locsep_attrs[] = { FG(0, 0, BRBLU), TTY_ATTRLIST_CLEAR }, + note_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR }, + err_attrs[] = { FG(0, BOLD, MGN), TTY_ATTRLIST_CLEAR }, + unklv_attrs[] = { FGBG(0, BOLD, BRWHT, BRRED), ATTR(0, TTAF_INVV) }, + vfound_attrs[] = { FG(0, 0, RED), TTY_ATTRLIST_CLEAR }, + vexpect_attrs[] = { FG(0, 0, GRN), TTY_ATTRLIST_CLEAR }, + vunset_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR }, + lose_attrs[] = { FG(0, BOLD, RED), ATTR(0, BOLD | TTAF_INVV) }, + skip_attrs[] = { FG(0, 0, YLW), TTY_ATTRLIST_CLEAR }, + xfail_attrs[] = { FG(0, BOLD, BLU), ATTR(0, BOLD) }, + win_attrs[] = { FG(0, 0, GRN), TTY_ATTRLIST_CLEAR }, + sblose_attrs[] = { FG(0, BOLD, RED), ATTR(0, BOLD) }; + +#undef GENATTR +#undef FGBG +#undef FG +#undef BG +#undef ATTR +#undef BOLD /* Predefined attributes. */ -#define HA_PLAIN 0 /* nothing special: terminal defaults */ -#define HA_LOC (HFG(CYAN)) /* filename or line number */ -#define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */ -#define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */ -#define HA_NOTE (HFG(YELLOW)) /* notices */ -#define HA_INFO 0 /* information */ -#define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */ -#define HA_UNSET (HFG(YELLOW)) /* register not set */ -#define HA_FOUND (HFG(RED)) /* incorrect output value */ -#define HA_EXPECT (HFG(GREEN)) /* what the value should have been */ -#define HA_WIN (HFG(GREEN)) /* reporting success */ -#define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */ -#define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */ -#define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */ +#define HIGHLIGHTS(_st, _) \ + _(_st, LOCFN, "lf", loc_attrs) /* location filename */ \ + _(_st, LOCLN, "ln", loc_attrs) /* location line number */ \ + _(_st, LOCSEP, "ls", locsep_attrs) /* location separator `:' */ \ + _(_st, INFO, "mi", 0) /* information */ \ + _(_st, NOTE, "mn", note_attrs) /* notices */ \ + _(_st, ERR, "me", err_attrs) /* error messages */ \ + _(_st, UNKLV, "mu", unklv_attrs) /* unknown-level messages */ \ + _(_st, DSINPUT, "di", 0) /* disposition for input value */ \ + _(_st, DSOUTPUT, "do", 0) /* ... unsolicited output */ \ + _(_st, DSMATCH, "dm", 0) /* ... matching output */ \ + _(_st, DSFOUND, "df", 0) /* ... incorrect output */ \ + _(_st, DSEXPECT, "dx", 0) /* ... reference output */ \ + _(_st, RNINPUT, "ri", 0) /* register name for input value */ \ + _(_st, RNOUTPUT, "ro", 0) /* ... unsolicited output */ \ + _(_st, RNMATCH, "rm", 0) /* ... matching output */ \ + _(_st, RNFOUND, "rf", 0) /* ... incorrect output */ \ + _(_st, RNEXPECT, "rx", 0) /* ... reference output */ \ + _(_st, VINPUT, "vi", 0) /* input value */ \ + _(_st, VOUTPUT, "vo", 0) /* unsolicited output value */ \ + _(_st, VMATCH, "vm", 0) /* matching output value */ \ + _(_st, VFOUND, "vf", vfound_attrs) /* incorrect output value */ \ + _(_st, VEXPECT, "vx", vexpect_attrs) /* reference output value */ \ + _(_st, VUNSET, "vu", vunset_attrs) /* register not set */ \ + _(_st, LOSE, "ol", lose_attrs) /* report failure */ \ + _(_st, SKIP, "os", skip_attrs) /* report a skipped test/group */ \ + _(_st, XFAIL, "ox", xfail_attrs) /* report expected fail */ \ + _(_st, WIN, "ow", win_attrs) /* report success */ \ + _(_st, SBLOSE, "sl", sblose_attrs) /* scoreboard failure */ \ + _(_st, SBSKIP, "ss", skip_attrs) /* scoreboard skipped test */ \ + _(_st, SBXFAIL, "sx", xfail_attrs) /* scoreboard xfail */ \ + _(_st, SBWIN, "sw", 0) /* scoreboard success */ + +TTYCOLOUR_DEFENUM(HIGHLIGHTS, HL_); +#define HL_PLAIN (-1) /* Scoreboard indicators. */ -#define HSB_WIN '.' /* test passed */ -#define HSB_LOSE 'x' /* test failed */ -#define HSB_XFAIL 'o' /* test failed expectedly */ -#define HSB_SKIP '_' /* test wasn't run */ +static const char scoreboard[] = { 'x', '_', 'o', '.' }; struct human_output { struct tvec_output _o; /* output base class */ struct tvec_state *tv; /* stashed testing state */ arena *a; /* arena for memory allocation */ + struct tty *tty; /* output terminal, or null */ struct layout lyt; /* output layout */ char *outbuf; size_t outsz; /* buffer for formatted output */ dstr scoreboard; /* history of test group results */ - unsigned attr; /* current terminal attributes */ + struct tty_attr attr[HL__LIMIT]; /* highlight attribute map */ int maxlen; /* longest register name */ unsigned f; /* flags */ -#define HOF_TTY 1u /* writing to terminal */ -#define HOF_DUPERR 2u /* duplicate errors to stderr */ -#define HOF_COLOUR 4u /* print in angry fruit salad */ -#define HOF_PROGRESS 8u /* progress display is active */ + /* bits 0--7 from @TVHF_...@ */ +#define HOF_DUPERR 0x0100u /* duplicate errors to stderr */ +#define HOF_PROGRESS 0x0200u /* progress display is active */ }; -/* --- @set_colour@ --- * - * - * Arguments: @FILE *fp@ = output stream to write on - * @int *sep_inout@ = where to maintain separator - * @const char *norm@ = prefix for normal colour - * @const char *bright@ = prefix for bright colour - * @unsigned colour@ = four bit colour code - * - * Returns: --- - * - * Use: Write to the output stream @fp@, the current character at - * @*sep_inout@, if that's not zero, followed by either @norm@ - * or @bright@, according to whether the @HCF_BRIGHT@ flag is - * set in @colour@, followed by the plain colour code from - * @colour@; finally, update @*sep_inout@ to be a `%|;|%'. - * - * This is an internal subroutine for @setattr@ below. - */ - -static void set_colour(FILE *fp, int *sep_inout, - const char *norm, const char *bright, - unsigned colour) -{ - if (*sep_inout) putc(*sep_inout, fp); - fprintf(fp, "%s%d", colour&HCF_BRIGHT ? bright : norm, colour&7); - *sep_inout = ';'; -} - -/* --- @setattr@ --- * +/* --- @setattr@, @setattr_layout@ --- * * * Arguments: @struct human_output *h@ = output state - * @unsigned attr@ = attribute code to set + * @int hi@ = highlight code to set * * Returns: --- * * Use: Send a control sequence to the output stream so that * subsequent text is printed with the given attributes. - * - * Some effort is taken to avoid unnecessary control sequences. - * In particular, if @attr@ matches the current terminal - * settings already, then nothing is written. */ -static void setattr(struct human_output *h, unsigned attr) +static void setattr_common(struct human_output *h, + const struct gprintf_ops *gops, void *go, int hl) { - unsigned diff = h->attr ^ attr; - int sep = 0; - - /* If there's nothing to do, we might as well stop now. */ - if (!diff || !(h->f&HOF_COLOUR)) return; - - /* Start on the control command. */ - fputs("\x1b[", h->lyt.fp); - - /* Change the boldness if necessary. */ - if (diff&HAF_BOLD) { - if (attr&HAF_BOLD) putc('1', h->lyt.fp); - else { putc('0', h->lyt.fp); diff = h->attr; } - sep = ';'; - } - - /* Change the foreground colour if necessary. */ - if (diff&(HAF_FG | HAF_FGMASK)) { - if (attr&HAF_FG) - set_colour(h->lyt.fp, &sep, "3", "9", - (attr&HAF_FGMASK) >> HAF_FGSHIFT); - else { - if (sep) putc(sep, h->lyt.fp); - fputs("39", h->lyt.fp); sep = ';'; - } - } + if (h->f&TVHF_COLOUR) + tty_setattrg(h->tty, gops, go, hl >= 0 ? &h->attr[hl] : 0); +} - /* Change the background colour if necessary. */ - if (diff&(HAF_BG | HAF_BGMASK)) { - if (attr&HAF_BG) - set_colour(h->lyt.fp, &sep, "4", "10", - (attr&HAF_BGMASK) >> HAF_BGSHIFT); - else { - if (sep) putc(sep, h->lyt.fp); - fputs("49", h->lyt.fp); sep = ';'; - } - } +static void setattr(struct human_output *h, int hl) + { setattr_common(h, &file_printops, h->lyt.fp, hl); } - /* Terminate the control command and save the new attributes. */ - putc('m', h->lyt.fp); h->attr = attr; -} +static void setattr_layout(struct human_output *h, int hl) + { setattr_common(h, &dstr_printops, &h->lyt.ctrl, hl); } /* --- @clear_progress@ --- * * @@ -642,13 +639,8 @@ static void clear_progress(struct human_output *h) static void write_scoreboard_char(struct human_output *h, int ch) { - switch (ch) { - case HSB_LOSE: setattr(h, HA_LOSE); break; - case HSB_SKIP: setattr(h, HA_SKIP); break; - case HSB_XFAIL: setattr(h, HA_XFAIL); break; - default: setattr(h, HA_PLAIN); break; - } - putc(ch, h->lyt.fp); setattr(h, HA_PLAIN); + assert(0 <= ch && ch < TVOUT_LIMIT); + setattr(h, HL_SBLOSE + ch); putc(scoreboard[ch], h->lyt.fp); } /* --- @show_progress@ --- * @@ -669,12 +661,11 @@ static void show_progress(struct human_output *h) struct tvec_state *tv = h->tv; const char *p, *l; - if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) { + if (tv->test && (h->f&TVHF_TTY) && !(h->f&HOF_PROGRESS)) { fprintf(h->lyt.fp, "%s: ", tv->test->name); - if (!(h->f&HOF_COLOUR)) - dstr_write(&h->scoreboard, h->lyt.fp); - else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++) + for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++) write_scoreboard_char(h, *p); + setattr(h, HL_PLAIN); fflush(h->lyt.fp); h->f |= HOF_PROGRESS; } } @@ -736,7 +727,7 @@ static const struct gprintf_ops human_printops = static void human_bsession(struct tvec_output *o, struct tvec_state *tv) { struct human_output *h = (struct human_output *)o; h->tv = tv; } -/* --- @report_unusual@ --- * +/* --- @human_report_unusual@ --- * * * Arguments: @struct human_output *h@ = output sink * @unsigned nxfail, nskip@ = number of expected failures and @@ -748,25 +739,24 @@ static void human_bsession(struct tvec_output *o, struct tvec_state *tv) * failures and/or skipped tests, if there were any. */ -static void report_unusual(struct human_output *h, - unsigned nxfail, unsigned nskip) +static void human_report_unusual(struct human_output *h, + unsigned nxfail, unsigned nskip) { - const char *sep = " ("; unsigned f = 0; #define f_any 1u if (nxfail) { - fprintf(h->lyt.fp, "%s%u ", sep, nxfail); - setattr(h, HA_XFAIL); + fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nxfail); + setattr(h, HL_XFAIL); fprintf(h->lyt.fp, "expected %s", nxfail == 1 ? "failure" : "failures"); - setattr(h, HA_PLAIN); - sep = ", "; f |= f_any; + setattr(h, HL_PLAIN); + f |= f_any; } if (nskip) { - fprintf(h->lyt.fp, "%s%u ", sep, nskip); - setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN); - sep = ", "; f |= f_any; + fprintf(h->lyt.fp, "%s%u ", f&f_any ? ", " : " (", nskip); + setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN); + f |= f_any; } if (f&f_any) fputc(')', h->lyt.fp); @@ -800,27 +790,27 @@ static int human_esession(struct tvec_output *o) grps_run = grps_win + grps_lose; if (!all_lose) { - setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN); + setattr(h, HL_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HL_PLAIN); fprintf(h->lyt.fp, " %s%u %s", !(all_skip || grps_skip) ? "all " : "", all_pass, all_pass == 1 ? "test" : "tests"); - report_unusual(h, all_xfail, all_skip); + human_report_unusual(h, all_xfail, all_skip); fprintf(h->lyt.fp, " in %u %s", grps_win, grps_win == 1 ? "group" : "groups"); - report_unusual(h, 0, grps_skip); + human_report_unusual(h, 0, grps_skip); } else { - setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN); + setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN); fprintf(h->lyt.fp, " %u out of %u %s", all_lose, all_run, all_run == 1 ? "test" : "tests"); - report_unusual(h, all_xfail, all_skip); + human_report_unusual(h, all_xfail, all_skip); fprintf(h->lyt.fp, " in %u out of %u %s", grps_lose, grps_run, grps_run == 1 ? "group" : "groups"); - report_unusual(h, 0, grps_skip); + human_report_unusual(h, 0, grps_skip); } fputc('\n', h->lyt.fp); if (tv->f&TVSF_ERROR) { - setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN); + setattr(h, HL_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HL_PLAIN); fputs(" found in input; tests may not have run correctly\n", h->lyt.fp); } @@ -869,13 +859,13 @@ static void human_skipgroup(struct tvec_output *o, { struct human_output *h = (struct human_output *)o; - if (!(h->f&HOF_TTY)) + if (!(h->f&TVHF_TTY)) fprintf(h->lyt.fp, "%s ", h->tv->test->name); else { show_progress(h); h->f &= ~HOF_PROGRESS; if (h->scoreboard.len) putc(' ', h->lyt.fp); } - setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN); + setattr(h, HL_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HL_PLAIN); if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); } fputc('\n', h->lyt.fp); } @@ -900,17 +890,17 @@ static void human_egroup(struct tvec_output *o) lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP], run = win + lose + xfail; - if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS; + if (h->f&TVHF_TTY) h->f &= ~HOF_PROGRESS; else fprintf(h->lyt.fp, "%s:", h->tv->test->name); if (lose) { fprintf(h->lyt.fp, " %u/%u ", lose, run); - setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN); - report_unusual(h, xfail, skip); + setattr(h, HL_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HL_PLAIN); + human_report_unusual(h, xfail, skip); } else { - fputc(' ', h->lyt.fp); setattr(h, HA_WIN); - fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN); - report_unusual(h, xfail, skip); + fputc(' ', h->lyt.fp); setattr(h, HL_WIN); + fputs("ok", h->lyt.fp); setattr(h, HL_PLAIN); + human_report_unusual(h, xfail, skip); } fputc('\n', h->lyt.fp); } @@ -948,40 +938,17 @@ static void human_btest(struct tvec_output *o) static void human_report_location(struct human_output *h, FILE *fp, const char *file, unsigned lno) { - unsigned f = 0; -#define f_flush 1u - - /* We emit highlighting if @fp@ is our usual output stream, or the - * duplicate-errors flag is clear indicating that (we assume) they're - * secretly going to the same place anyway. If they're different streams, - * though, we have to be careful to keep the highlighting and the actual - * text synchronized. - */ - if (!file) /* nothing to do */; - else if (fp != h->lyt.fp && (h->f&HOF_DUPERR)) + else if (fp != h->lyt.fp || !(h->f&TVHF_COLOUR)) fprintf(fp, "%s:%u: ", file, lno); else { - if (fp != h->lyt.fp) f |= f_flush; - -#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0) - - setattr(h, HA_LOC); FLUSH(h->lyt.fp); - fputs(file, fp); FLUSH(fp); - setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp); - fputc(':', fp); FLUSH(fp); - setattr(h, HA_LOC); FLUSH(h->lyt.fp); - fprintf(fp, "%u", lno); FLUSH(fp); - setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp); - fputc(':', fp); FLUSH(fp); - setattr(h, HA_PLAIN); FLUSH(h->lyt.fp); - fputc(' ', fp); - -#undef FLUSH + setattr(h, HL_LOCFN); fputs(file, fp); + setattr(h, HL_LOCSEP); fputc(':', fp); + setattr(h, HL_LOCLN); fprintf(fp, "%u", lno); + setattr(h, HL_LOCSEP); fputc(':', fp); + setattr(h, HL_PLAIN); fputc(' ', fp); } - -#undef f_flush } /* --- @human_outcome@, @human_skip@, @human_fail@ --- * @@ -1011,17 +978,17 @@ static void human_outcome(struct tvec_output *o, clear_progress(h); human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno); fprintf(h->lyt.fp, "`%s' ", tv->test->name); - setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN); + setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HL_PLAIN); if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); } fputc('\n', h->lyt.fp); } static void human_skip(struct tvec_output *o, const char *excuse, va_list *ap) - { human_outcome(o, HA_SKIP, "skipped", excuse, ap); } + { human_outcome(o, HL_SKIP, "skipped", excuse, ap); } static void human_fail(struct tvec_output *o, const char *detail, va_list *ap) - { human_outcome(o, HA_LOSE, "FAILED", detail, ap); } + { human_outcome(o, HL_LOSE, "FAILED", detail, ap); } /* --- @human_dumpreg@ --- * * @@ -1048,16 +1015,21 @@ static void human_dumpreg(struct tvec_output *o, const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); clear_progress(h); - gprintf(&human_printops, h, "%*s%s %s = ", - 10 + h->maxlen - n, "", ds, rd->name); - if (h->f&HOF_COLOUR) { - if (!rv) setattr(h, HA_UNSET); - else if (disp == TVRD_FOUND) setattr(h, HA_FOUND); - else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT); + gprintf(&human_printops, h, "%*s", 10 + h->maxlen - n, ""); + setattr_layout(h, HL_DSINPUT + disp); + gprintf(&human_printops, h, "%s", ds); + setattr(h, HL_PLAIN); layout_char(&h->lyt, ' '); + setattr_layout(h, HL_RNINPUT + disp); + gprintf(&human_printops, h, "%s", rd->name); + setattr(h, HL_PLAIN); gprintf(&human_printops, h, " = "); + if (!rv) { + setattr_layout(h, HL_VUNSET); + gprintf(&human_printops, h, "#unset"); + } else { + setattr_layout(h, HL_VINPUT + disp); + rd->ty->dump(rv, rd, 0, &human_printops, h); } - if (!rv) gprintf(&human_printops, h, "#unset"); - else rd->ty->dump(rv, rd, 0, &human_printops, h); - setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n'); + setattr(h, HL_PLAIN); layout_char(&h->lyt, '\n'); } /* --- @human_etest@ --- * @@ -1077,19 +1049,11 @@ static void human_dumpreg(struct tvec_output *o, static void human_etest(struct tvec_output *o, unsigned outcome) { struct human_output *h = (struct human_output *)o; - int ch; - if (h->f&HOF_TTY) { + if (h->f&TVHF_TTY) { show_progress(h); - switch (outcome) { - case TVOUT_WIN: ch = HSB_WIN; break; - case TVOUT_LOSE: ch = HSB_LOSE; break; - case TVOUT_XFAIL: ch = HSB_XFAIL; break; - case TVOUT_SKIP: ch = HSB_SKIP; break; - default: abort(); - } - dstr_putc(&h->scoreboard, ch); - write_scoreboard_char(h, ch); fflush(h->lyt.fp); + dstr_putc(&h->scoreboard, outcome); write_scoreboard_char(h, outcome); + setattr(h, HL_PLAIN); fflush(h->lyt.fp); } } @@ -1097,7 +1061,7 @@ static void human_etest(struct tvec_output *o, unsigned outcome) * * Arguments: @struct tvec_output *o@ = output sink, secretly a * @struct human_output@ - * @unsigned level@ = message level (@TVLEV_...@) + * @unsigned level@ = message level (@TVLV_...@) * @const char *msg@, @va_list *ap@ = format string and * arguments * @@ -1115,44 +1079,37 @@ static void human_report(struct tvec_output *o, unsigned level, { struct human_output *h = (struct human_output *)o; struct tvec_state *tv = h->tv; - const char *levstr; unsigned levattr; + const char *levstr; unsigned levhl; dstr d = DSTR_INIT; unsigned f = 0; -#define f_flush 1u -#define f_progress 2u +#define f_progress 1u dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); switch (level) { #define CASE(tag, name, val) \ - case TVLEV_##tag: levstr = name; levattr = HA_##tag; break; + case TVLV_##tag: levstr = name; levhl = HL_##tag; break; TVEC_LEVELS(CASE) - default: levstr = "??"; levattr = HA_UNKLEV; break; + default: levstr = "??"; levhl = HL_UNKLV; break; } - if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush; - -#define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0) - - if (h->f^HOF_PROGRESS) - { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; } - fprintf(stderr, "%s: ", QUIS); - human_report_location(h, stderr, tv->infile, tv->lno); - setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH; - fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr); - -#undef FLUSH + if (h->f&HOF_PROGRESS) { clear_progress(h); f |= f_progress; } if (h->f&HOF_DUPERR) { - human_report_location(h, h->lyt.fp, tv->infile, tv->lno); - fprintf(h->lyt.fp, "%s: ", levstr); - fwrite(d.buf, 1, d.len, h->lyt.fp); + fprintf(stderr, "%s: ", QUIS); + human_report_location(h, stderr, tv->infile, tv->lno); + fprintf(stderr, "%s: ", levstr); + fwrite(d.buf, 1, d.len, stderr); } - if (f&f_progress) show_progress(h); + human_report_location(h, h->lyt.fp, tv->infile, tv->lno); + setattr(h, levhl); fputs(levstr, h->lyt.fp); + setattr(h, HL_PLAIN); fputs(": ", h->lyt.fp); + fwrite(d.buf, 1, d.len, h->lyt.fp); + + if (f&f_progress) show_progress(h); dstr_destroy(&d); -#undef f_flush #undef f_progress } @@ -1182,7 +1139,7 @@ static void human_bbench(struct tvec_output *o, if (desc) gprintf(&human_printops, h, "%s", desc); else print_ident(tv, TVSF_COMPACT, &human_printops, h); gprintf(&human_printops, h, ": "); - if (h->f&HOF_TTY) fflush(h->lyt.fp); + if (h->f&TVHF_TTY) fflush(h->lyt.fp); } /* --- @human_ebench@ --- * @@ -1243,6 +1200,7 @@ static void human_destroy(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; + tty_close(h->tty); destroy_layout(&h->lyt, h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE); dstr_destroy(&h->scoreboard); @@ -1259,47 +1217,96 @@ static const struct tvec_outops human_ops = { /* --- @tvec_humanoutput@ --- * * * Arguments: @FILE *fp@ = output file to write on + * @unsigned f, m@ = flags and mask * * Returns: An output formatter. * * Use: Return an output formatter which writes on @fp@ with the - * expectation that a human will be watching and interpreting - * the output. If @fp@ denotes a terminal, the display shows a - * `scoreboard' indicating the outcome of each test case - * attempted, and may in addition use colour and other - * highlighting. + * expectation that a human will interpret the output. + * + * The flags @f@ and mask @m@ operate together. Flag bits not + * covered by the mask must be left clear, i.e., @f&~m$ must be + * zero; the semantics are that a set mask bit indicates that + * the corresponding bit of @f@ should control the indicated + * behaviour; a clear mask bit indicates that a suitable default + * should be chosen based on environmental conditions. + * + * If @TVHF_TTY@ is set, then the output shows a `scoreboard' + * indicating the outcome of each test case attempted, providing + * a visual indication of progress. If @TVHF_COLOUR@ is set, + * then the output uses control codes for colour and other + * highlighting. It is unusual to set @TVHF_COLOUR@ without + * @TVHF_TTY@, this is permitted anyway. + * + * The environment variables %|TVEC_TTY|% and %|TVEC_COLOUR|% + * provide default values for these settings. If they are not + * set, then @TVHF_TTY@ is set if @fp@ refers to a terminal, and + * @TVHF_COLOUR@ is set if @TVHF_TTY@ is set and, additionally, + * the %|TERM|% environment variable is set to a value other + * than %|dumb|%. */ -struct tvec_output *tvec_humanoutput(FILE *fp) +struct tvec_output *tvec_humanoutput(FILE *fp, unsigned f, unsigned m) { struct human_output *h; - const char *p; + struct stat st_out, st_err; + int rc_out, rc_err; - XNEW(h); h->a = arena_global; h->_o.ops = &human_ops; - h->f = 0; h->attr = 0; + static const struct ttycolour_style hltab[] = + TTYCOLOUR_INITTAB(HIGHLIGHTS); - init_layout(&h->lyt, fp, 0); - h->outbuf = 0; h->outsz = 0; + assert(!(f&~m)); - switch (getenv_boolean("TVEC_TTY", -1)) { - case 1: h->f |= HOF_TTY; break; - case 0: break; - default: - if (isatty(fileno(fp))) h->f |= HOF_TTY; - break; + if (!(m&TVHF_TTY)) + switch (getenv_boolean("MLIB_TVEC_TTY", -1)) { + case 1: f |= TVHF_TTY; break; + case 0: break; + default: + if (isatty(fileno(fp))) f |= TVHF_TTY; + break; + } + if (!(m&TVHF_COLOUR)) + switch (getenv_boolean("MLIB_TVEC_COLOUR", -1)) { + case 1: f |= TVHF_COLOUR; break; + case 0: break; + default: + if (ttycolour_enablep((f&TVHF_TTY ? TCEF_TTY : 0) | TCEF_DFLT)) + f |= TVHF_COLOUR; + break; + } + + /* Decide whether to write copies of reports to stderr. + * + * There's not much point if the output is already going to stderr. + * Otherwise, check to see whether stdout is the same underlying file. + */ + if (fp != stderr) { + rc_out = fstat(fileno(fp), &st_out); + rc_err = fstat(STDERR_FILENO, &st_err); + if (!rc_err && (rc_out || + st_out.st_dev != st_err.st_dev || + st_out.st_ino != st_err.st_ino)) + f |= HOF_DUPERR; } - switch (getenv_boolean("TVEC_COLOUR", -1)) { - case 1: h->f |= HOF_COLOUR; break; - case 0: break; - default: - if (h->f&HOF_TTY) { - p = getenv("TERM"); - if (p && STRCMP(p, !=, "dumb")) h->f |= HOF_COLOUR; - } - break; + + XNEW(h); h->a = arena_global; h->_o.ops = &human_ops; + h->f = f; + + /* Initialize the colour tables. */ + if (!(h->f&TVHF_COLOUR)) + h->tty = 0; + else { + h->tty = tty_open(fp, TTF_BORROW, 0); + if (!h->tty) + h->f &= ~TVHF_COLOUR; + else + ttycolour_config(h->attr, "MLIB_TVEC_COLOURS", + TCIF_GETENV | TCIF_REPORT, h->tty, hltab); } - if (fp != stderr && (fp != stdout || !(h->f&HOF_TTY))) h->f |= HOF_DUPERR; + init_layout(&h->lyt, fp, 0); + h->outbuf = 0; h->outsz = 0; + dstr_create(&h->scoreboard); return (&h->_o); } @@ -1314,7 +1321,7 @@ struct machine_output { char *outbuf; size_t outsz; /* buffer for formatted output */ unsigned grpix, testix; /* group and test indices */ unsigned f; /* flags */ -#define MF_BENCH 1u +#define MF_BENCH 1u /* current test is a benchmark */ }; /* --- @machine_writech@, @machine_write@, @machine_writef@ --- * @@ -1711,7 +1718,7 @@ static void machine_etest(struct tvec_output *o, unsigned outcome) * * Arguments: @struct tvec_output *o@ = output sink, secretly a * @struct machine_output@ - * @unsigned level@ = message level (@TVLEV_...@) + * @unsigned level@ = message level (@TVLV_...@) * @const char *msg@, @va_list *ap@ = format string and * arguments * @@ -2162,7 +2169,7 @@ static void tap_etest(struct tvec_output *o, unsigned outcome) * * Arguments: @struct tvec_output *o@ = output sink, secretly a * @struct tap_output@ - * @unsigned level@ = message level (@TVLEV_...@) + * @unsigned level@ = message level (@TVLV_...@) * @const char *msg@, @va_list *ap@ = format string and * arguments * @@ -2262,6 +2269,430 @@ struct tvec_output *tvec_tapoutput(FILE *fp) return (&t->_o); } +/*----- Automake support --------------------------------------------------*/ + +struct automake_output { + struct tvec_output _o; + arena *a; /* arena */ + struct tvec_state *tv; /* test-vector state */ + struct tvec_output *progress; /* real-time progress output */ + struct tvec_output *log; /* log file output */ + FILE *trs; /* test result stream */ +}; + +/* --- @am_bsession@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @struct tvec_state *tv@ = the test state producing output + * + * Returns: --- + * + * Use: Begin a test session. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_bsession(struct tvec_output *o, struct tvec_state *tv) +{ + struct automake_output *am = (struct automake_output *)o; + + am->tv = tv; + human_bsession(am->progress, tv); + machine_bsession(am->log, tv); +} + +/* --- @am_esession@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * + * Returns: Suggested exit code. + * + * Use: End a test session. + * + * The Automake driver completes the test-results file and + * passes the event on to its subordinates. + */ + +static void am_report_unusual(struct automake_output *am, + unsigned xfail, unsigned skip) +{ + unsigned f = 0; +#define f_any 1u + + if (xfail) { + fprintf(am->trs, "%s%u expected %s", f&f_any ? ", " : " (", + xfail, xfail == 1 ? "failure" : "failures"); + f |= f_any; + } + if (skip) { + fprintf(am->trs, "%s%u skipped", f&f_any ? ", " : " (", skip); + f |= f_any; + } + if (f&f_any) fputc(')', am->trs); + +#undef f_any +} + +static int am_esession(struct tvec_output *o) +{ + struct automake_output *am = (struct automake_output *)o; + struct tvec_state *tv = am->tv; + unsigned + all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN], + all_xfail = tv->all[TVOUT_XFAIL], + all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE], + all_skip = tv->all[TVOUT_SKIP], grps_skip = tv->grps[TVOUT_SKIP], + all_pass = all_win + all_xfail, all_run = all_pass + all_lose, + grps_run = grps_win + grps_lose; + + human_esession(am->progress); + machine_esession(am->log); + + fputs(":test-global-result: ", am->trs); + if (tv->f&TVSF_ERROR) fputs("ERRORS; ", am->trs); + if (!all_lose) { + fprintf(am->trs, "PASSED %s%u %s", + !(all_skip || grps_skip) ? "all " : "", + all_win, all_win == 1 ? "test" : "tests"); + am_report_unusual(am, all_xfail, all_skip); + fprintf(am->trs, " in %u %s", + grps_win, grps_win == 1 ? "group" : "groups"); + am_report_unusual(am, 0, grps_skip); + } else { + fprintf(am->trs, "FAILED %u out of %u %s", + all_lose, all_run, all_run == 1 ? "test" : "tests"); + am_report_unusual(am, all_xfail, all_skip); + fprintf(am->trs, " in %u out of %u %s", + grps_lose, grps_run, grps_run == 1 ? "group" : "groups"); + am_report_unusual(am, 0, grps_skip); + } + fputc('\n', am->trs); + + fprintf(am->trs, ":copy-in-global-log: %s\n", + !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes"); + fprintf(am->trs, ":recheck: %s\n", + !all_lose && !(tv->f&TVSF_ERROR) ? "no" : "yes"); + + return (0); +} + +/* --- @am_bgroup@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * + * Returns: --- + * + * Use: Begin a test group. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_bgroup(struct tvec_output *o) +{ + struct automake_output *am = (struct automake_output *)o; + + human_bgroup(am->progress); + machine_bgroup(am->log); +} + +/* --- @am_skipgroup@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @const char *excuse@, @va_list *ap@ = reason for skipping the + * group, or null + * + * Returns: --- + * + * Use: Report that a test group is being skipped. + * + * The Automake driver makes a note in the test-results file and + * passes the event on to its subordinates. + */ + +static void am_skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) +{ + struct automake_output *am = (struct automake_output *)o; + struct tvec_state *tv = am->tv; + + fprintf(am->trs, ":test-result: SKIP %s\n", tv->test->name); + human_skipgroup(am->progress, excuse, ap); + machine_skipgroup(am->log, excuse, ap); +} + +/* --- @am_egroup@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * + * Returns: --- + * + * Use: Report that a test group has finished. + * + * The Automake driver makes a note in the test-results file and + * passes the event on to its subordinates. + */ + +static void am_egroup(struct tvec_output *o) +{ + struct automake_output *am = (struct automake_output *)o; + struct tvec_state *tv = am->tv; + + fprintf(am->trs, ":test-result: %s %s\n", + tv->curr[TVOUT_LOSE] ? "FAIL" : "PASS", tv->test->name); + human_egroup(am->progress); + machine_egroup(am->log); +} + +/* --- @am_btest@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * + * Returns: --- + * + * Use: Report that a test is starting. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_btest(struct tvec_output *o) +{ + struct automake_output *am = (struct automake_output *)o; + + human_btest(am->progress); + machine_btest(am->log); +} + +/* --- @am_skip@, @am_fail@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @const char *head, *tail@ = outcome strings to report + * @const char *detail@, @va_list *ap@ = a detail message + * @const char *excuse@, @va_list *ap@ = reason for skipping the + * test + * + * Returns: --- + * + * Use: Report that a test has been skipped or failed. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_skip(struct tvec_output *o, const char *excuse, va_list *ap) +{ + struct automake_output *am = (struct automake_output *)o; + + human_skip(am->progress, excuse, ap); + machine_skip(am->log, excuse, ap); +} + +static void am_fail(struct tvec_output *o, const char *detail, va_list *ap) +{ + struct automake_output *am = (struct automake_output *)o; + + human_fail(am->progress, detail, ap); + machine_fail(am->log, detail, ap); +} + +/* --- @am_dumpreg@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @unsigned disp@ = register disposition + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Dump a register. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_dumpreg(struct tvec_output *o, + unsigned disp, const union tvec_regval *rv, + const struct tvec_regdef *rd) +{ + struct automake_output *am = (struct automake_output *)o; + + human_dumpreg(am->progress, disp, rv, rd); + machine_dumpreg(am->log, disp, rv, rd); +} + +/* --- @am_etest@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @unsigned outcome@ = the test outcome + * + * Returns: --- + * + * Use: Report that a test has finished. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_etest(struct tvec_output *o, unsigned outcome) +{ + struct automake_output *am = (struct automake_output *)o; + + human_etest(am->progress, outcome); + machine_etest(am->log, outcome); +} + +/* --- @am_report@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @unsigned level@ = message level (@TVLV_...@) + * @const char *msg@, @va_list *ap@ = format string and + * arguments + * + * Returns: --- + * + * Use: Report a message to the user. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_report(struct tvec_output *o, unsigned level, + const char *msg, va_list *ap) +{ + struct automake_output *am = (struct automake_output *)o; + + human_report(am->progress, level, msg, ap); + machine_report(am->log, level, msg, ap); +} + +/* --- @am_bbench@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @const char *desc@ = adhoc test description + * @unsigned unit@ = measurement unit (@BTU_...@) + * + * Returns: --- + * + * Use: Report that a benchmark has started. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_bbench(struct tvec_output *o, + const char *desc, unsigned unit) +{ + struct automake_output *am = (struct automake_output *)o; + + human_bbench(am->progress, desc, unit); + machine_bbench(am->progress, desc, unit); +} + +/* --- @am_ebench@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @const char *desc@ = adhoc test description + * @unsigned unit@ = measurement unit (@BTU_...@) + * @const struct bench_timing *t@ = measurement + * + * Returns: --- + * + * Use: Report a benchmark's results. + * + * The Automake driver passes the event on to its subordinates. + */ + +static void am_ebench(struct tvec_output *o, + const char *desc, unsigned unit, + const struct bench_timing *t) +{ + struct automake_output *am = (struct automake_output *)o; + + human_ebench(am->progress, desc, unit, t); + machine_ebench(am->progress, desc, unit, t); +} + +static const struct tvec_benchoutops am_benchops = + { am_bbench, am_ebench }; + +/* --- @am_extend@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * @const char *name@ = extension name + * + * Returns: A pointer to the extension implementation, or null. + */ + +static const void *am_extend(struct tvec_output *o, const char *name) +{ + if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&am_benchops); + else return (0); +} + +/* --- @am_destroy@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink, secretly a + * @struct automake_output@ + * + * Returns: --- + * + * Use: Release the resources held by the output driver. + */ + +static void am_destroy(struct tvec_output *o) +{ + struct automake_output *am = (struct automake_output *)o; + + human_destroy(am->progress); + machine_destroy(am->log); + fclose(am->trs); x_free(am->a, am); +} + +static const struct tvec_outops automake_ops = { + am_bsession, am_esession, + am_bgroup, am_skipgroup, am_egroup, + am_btest, am_skip, am_fail, am_dumpreg, am_etest, + am_report, am_extend, am_destroy +}; + +/* --- @tvec_amoutput@ --- * + * + * Arguments: @const struct tvec_amargs *a@ = arguments from Automake + * command-line protocol + * + * Returns: An output formatter. + * + * Use: Returns an output formatter which writes on standard output + * in human format, pretending that the output is to a terminal + * (in order to cope with %%\manpage{make}{1}%%'s output- + * buffering behaviour, writes to the log file @a->log@ in + * machine-readable format, and writes an Automake rST-format + * test result file to @a->trs@. The `test name' is currently + * ignored, because the framework has its own means of + * determining test names. + */ + +struct tvec_output *tvec_amoutput(const struct tvec_amargs *a) +{ + struct automake_output *am; + unsigned f; + + f = TVHF_TTY; + if (a->f&TVAF_COLOUR) f |= TVHF_COLOUR; + + XNEW(am); am->a = arena_global; am->_o.ops = &automake_ops; + am->progress = tvec_humanoutput(stdout, f, TVHF_TTY | TVHF_COLOUR); + am->log = tvec_machineoutput(a->log); am->trs = a->trs; + return (&am->_o); +} + /*----- Default output ----------------------------------------------------*/ /* --- @tvec_dfltoutput@ --- * @@ -2277,12 +2708,12 @@ struct tvec_output *tvec_tapoutput(FILE *fp) * otherwise the `machine' format is used. */ -struct tvec_output *tvec_dfltout(FILE *fp) +struct tvec_output *tvec_dfltoutput(FILE *fp) { - int ttyp = getenv_boolean("TVEC_TTY", -1); + int ttyp = getenv_boolean("MLIB_TVEC_TTY", -1); if (ttyp == -1) ttyp = isatty(fileno(fp)); - if (ttyp) return (tvec_humanoutput(fp)); + if (ttyp) return (tvec_humanoutput(fp, TVHF_TTY, TVHF_TTY)); else return (tvec_machineoutput(fp)); }