#include <string.h>
#include <unistd.h>
+#include <sys/stat.h>
#include "alloc.h"
#include "bench.h"
#include "macros.h"
#include "quis.h"
#include "report.h"
+#include "tty.h"
+#include "ttycolour.h"
+
#include "tvec.h"
+#include "tvec-bench.h"
+#include "tvec-output.h"
/*----- Common machinery --------------------------------------------------*/
case TVRD_INPUT: return "input";
case TVRD_OUTPUT: return "output";
case TVRD_MATCH: return "matched";
- case TVRD_EXPECT: return "expected";
case TVRD_FOUND: return "found";
+ case TVRD_EXPECT: return "expected";
default: abort();
}
}
-/* --- @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@ --- *
return (maxlen);
}
+/* --- @print_ident@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned style@ = style to use for register dumps
+ * @const struct gprintf_ops *gops@ = output operations
+ * @void *go@ = output state
+ *
+ * Returns: ---
+ *
+ * Use: Write a benchmark identification to the output.
+ */
+
+static void print_ident(struct tvec_state *tv, unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ const struct tvec_regdef *rd;
+ unsigned f = 0;
+
+#define f_any 1u
+
+ for (rd = tv->test->regs; rd->name; rd++)
+ if (rd->f&TVRF_ID) {
+ if (!(f&f_any)) f |= f_any;
+ else if (style&TVSF_RAW) gops->putch(go, ' ');
+ else gprintf(gops, go, ", ");
+ gprintf(gops, go, "%s", rd->name);
+ if (style&TVSF_RAW) gops->putch(go, '=');
+ else gprintf(gops, go, " = ");
+ rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, style, gops, go);
+ }
+
+#undef f_any
+}
+
/*----- Output layout -----------------------------------------------------*/
/* We have two main jobs in output layout: trimming trailing blanks; and
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 */
};
* file. Return immediately on error. \
*/ \
\
- size_t n = limit - base; \
- if (fwrite(base, 1, n, lyt->fp) < n) return (-1); \
+ size_t _n = limit - base; \
+ if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \
} while (0)
#define PUT_CHAR(ch) do { \
#define PUT_SAVED do { \
/* Output the saved trailing blank material in the buffer. */ \
\
- size_t n = lyt->w.len; \
- if (n && fwrite(lyt->w.buf, 1, n, lyt->fp) < n) return (-1); \
+ size_t _n = lyt->w.len; \
+ 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 { \
DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
} while (0)
-/* --- @init_layout@ --- *
+/* --- @set_layout_prefix@ --- *
*
- * Arguments: @struct layout *lyt@ = layout state to initialize
- * @FILE *fp@ = output file
- * @const char *prefix@ = prefix string (or null if empty)
+ * Arguments: @struct layout *lyt@ = layout state
+ * @const char *prefix@ = new prefix string or null
*
* Returns: ---
*
- * Use: Initialize a layout state.
+ * Use: Change the configured prefix string. The change takes effect
+ * at the start of the next line (or the current line if it's
+ * empty or only whitespace so far).
*/
-static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
+static void set_layout_prefix(struct layout *lyt, const char *prefix)
{
const char *q, *l;
- /* Basics. */
- lyt->fp = fp;
- lyt->f = LYTF_NEWL;
- dstr_create(&lyt->w);
-
- /* Prefix portions. */
- if (!prefix || !*prefix)
+ if (!prefix || !*prefix)
lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
else {
lyt->prefix = prefix;
}
}
+/* --- @init_layout@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state to initialize
+ * @FILE *fp@ = output file
+ * @const char *prefix@ = prefix string (or null if empty)
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a layout state.
+ */
+
+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->ctrl);
+ set_layout_prefix(lyt, prefix);
+}
+
/* --- @destroy_layout@ --- *
*
* Arguments: @struct layout *lyt@ = layout state
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@ --- *
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);
*/
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);
* 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;
#undef PUT_CHAR
#undef SAVE_PFXTAIL
-/*----- Skeleton ----------------------------------------------------------*/
-/*
-static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
-static int ..._esession(struct tvec_output *o)
-static void ..._bgroup(struct tvec_output *o)
-static void ..._skipgroup(struct tvec_output *o,
- const char *excuse, va_list *ap)
-static void ..._egroup(struct tvec_output *o)
-static void ..._btest(struct tvec_output *o)
-static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap)
-static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap)
-static void ..._dumpreg(struct tvec_output *o, unsigned disp,
- union tvec_regval *rv, const struct tvec_regdef *rd)
-static void ..._etest(struct tvec_output *o, unsigned outcome)
-static void ..._bbench(struct tvec_output *o,
- const char *ident, unsigned unit)
-static void ..._ebench(struct tvec_output *o,
- const char *ident, unsigned unit,
- const struct tvec_timing *t)
-static void ..._error(struct tvec_output *o, const char *msg, va_list *ap)
-static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap)
-static void ..._destroy(struct tvec_output *o)
-
-static const struct tvec_outops ..._ops = {
- ..._bsession, ..._esession,
- ..._bgroup, ..._egroup, ..._skip,
- ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest,
- ..._bbench, ..._ebench,
- ..._error, ..._notice,
- ..._destroy
-};
-*/
/*----- 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_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 HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* reporting an error */
+#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@ --- *
*
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@ --- *
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;
}
}
-/* --- @report_location@ --- *
- *
- * Arguments: @struct human_output *h@ = output state
- * @FILE *fp@ = stream to write the location on
- * @const char *file@ = filename
- * @unsigned lno@ = line number
- *
- * Returns: ---
- *
- * Use: Print the filename and line number to the output stream @fp@.
- * Also, if appropriate, print interleaved highlighting control
- * codes to our usual output stream. If @file@ is null then do
- * nothing.
- */
-
-static void 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))
- 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
- }
-
-#undef f_flush
-}
-
/* --- @human_writech@, @human_write@, @human_writef@ --- *
*
* Arguments: @void *go@ = output sink, secretly a @struct human_output@
va_list ap;
va_start(ap, p);
- n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
+ n = gprintf_memputf(h->a, &h->outbuf, &h->outsz, maxsz, p, ap);
va_end(ap);
- return (layout_string(&h->lyt, h->outbuf, n));
+ if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
+ return (n);
}
static const struct gprintf_ops human_printops =
/* --- @human_bsession@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
* @struct tvec_state *tv@ = the test state producing output
*
* Returns: ---
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
* 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);
/* --- @human_esession@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
*
- * Returns: Suggested exit status.
+ * Returns: Suggested exit code.
*
* Use: End a test session.
*
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);
}
/* --- @human_bgroup@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
*
* Returns: ---
*
/* --- @human_skipgroup@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
* @const char *excuse@, @va_list *ap@ = reason for skipping the
* group, or null
*
{
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);
}
/* --- @human_egroup@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
*
* Returns: ---
*
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);
}
/* --- @human_btest@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
*
* Returns: ---
*
static void human_btest(struct tvec_output *o)
{ struct human_output *h = (struct human_output *)o; show_progress(h); }
+/* --- @human_report_location@ --- *
+ *
+ * Arguments: @struct human_output *h@ = output state
+ * @FILE *fp@ = stream to write the location on
+ * @const char *file@ = filename
+ * @unsigned lno@ = line number
+ *
+ * Returns: ---
+ *
+ * Use: Print the filename and line number to the output stream @fp@.
+ * Also, if appropriate, print interleaved highlighting control
+ * codes to our usual output stream. If @file@ is null then do
+ * nothing.
+ */
+
+static void human_report_location(struct human_output *h, FILE *fp,
+ const char *file, unsigned lno)
+{
+ if (!file)
+ /* nothing to do */;
+ else if (fp != h->lyt.fp || !(h->f&TVHF_COLOUR))
+ fprintf(fp, "%s:%u: ", file, lno);
+ else {
+ 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);
+ }
+}
+
/* --- @human_outcome@, @human_skip@, @human_fail@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
* @unsigned attr@ = attribute to apply to the outcome
* @const char *outcome@ = outcome string to report
* @const char *detail@, @va_list *ap@ = a detail message
struct tvec_state *tv = h->tv;
clear_progress(h);
- report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
+ 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@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
* @unsigned disp@ = register disposition
* @const union tvec_regval *rv@ = register value
* @const struct tvec_regdef *rd@ = register definition
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@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
* @unsigned outcome@ = the test outcome
*
* Returns: ---
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);
+ }
+}
+
+/* --- @human_report@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_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 human driver arranges to show the message on @stderr@ as
+ * well as the usual output, with a certain amount of
+ * intelligence in case they're both actually the same device.
+ */
+
+static void human_report(struct tvec_output *o, unsigned level,
+ const char *msg, va_list *ap)
+{
+ struct human_output *h = (struct human_output *)o;
+ struct tvec_state *tv = h->tv;
+ const char *levstr; unsigned levhl;
+ dstr d = DSTR_INIT;
+ unsigned f = 0;
+#define f_progress 1u
+
+ dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
+
+ switch (level) {
+#define CASE(tag, name, val) \
+ case TVLV_##tag: levstr = name; levhl = HL_##tag; break;
+ TVEC_LEVELS(CASE)
+ default: levstr = "??"; levhl = HL_UNKLV; break;
+ }
+
+ if (h->f&HOF_PROGRESS) { clear_progress(h); f |= f_progress; }
+
+ if (h->f&HOF_DUPERR) {
+ fprintf(stderr, "%s: ", QUIS);
+ human_report_location(h, stderr, tv->infile, tv->lno);
+ fprintf(stderr, "%s: ", levstr);
+ fwrite(d.buf, 1, d.len, stderr);
}
+
+ 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_progress
}
/* --- @human_bbench@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
- * @const char *ident@ = identifying register values
- * @unsigned unit@ = measurement unit (@TVBU_...@)
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
+ * @const char *desc@ = adhoc test description
+ * @unsigned unit@ = measurement unit (@BTU_...@)
*
* Returns: ---
*
*/
static void human_bbench(struct tvec_output *o,
- const char *ident, unsigned unit)
+ const char *desc, unsigned unit)
{
struct human_output *h = (struct human_output *)o;
struct tvec_state *tv = h->tv;
clear_progress(h);
- fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
+ gprintf(&human_printops, h, "%s ", tv->test->name);
+ if (desc) gprintf(&human_printops, h, "%s", desc);
+ else print_ident(tv, TVSF_COMPACT, &human_printops, h);
+ gprintf(&human_printops, h, ": ");
+ if (h->f&TVHF_TTY) fflush(h->lyt.fp);
}
/* --- @human_ebench@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
- * @const char *ident@ = identifying register values
- * @unsigned unit@ = measurement unit (@TVBU_...@)
- * @const struct bench_timing *tm@ = measurement
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_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
+ * Use: Report a benchmark's results.
*
* The human driver just delegates to the default benchmark
* reporting, via the layout machinery.
*/
static void human_ebench(struct tvec_output *o,
- const char *ident, unsigned unit,
- const struct bench_timing *tm)
+ const char *desc, unsigned unit,
+ const struct bench_timing *t)
{
struct human_output *h = (struct human_output *)o;
- tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
- fputc('\n', h->lyt.fp);
+ tvec_benchreport(&human_printops, h, unit, 0, t);
+ layout_char(&h->lyt, '\n');
}
-/* --- @human_report@ --- *
- *
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
- * @unsigned level@ = message level (@TVLEV_...@)
- * @const char *msg@, @va_list *ap@ = format string and
- * arguments
- *
- * Returns: ---
+static const struct tvec_benchoutops human_benchops =
+ { human_bbench, human_ebench };
+
+/* --- @human_extend@ --- *
*
- * Use: Report a message to the user.
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
+ * @const char *name@ = extension name
*
- * The human driver arranges to show the message on @stderr@ as
- * well as the usual output, with a certain amount of
- * intelligence in case they're both actually the same device.
+ * Returns: A pointer to the extension implementation, or null.
*/
-static void human_report(struct tvec_output *o, unsigned level,
- const char *msg, va_list *ap)
+static const void *human_extend(struct tvec_output *o, const char *name)
{
- struct human_output *h = (struct human_output *)o;
- struct tvec_state *tv = h->tv;
- dstr d = DSTR_INIT;
-
- dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
-
- clear_progress(h); fflush(h->lyt.fp);
- fprintf(stderr, "%s: ", QUIS);
- report_location(h, stderr, tv->infile, tv->lno);
- fwrite(d.buf, 1, d.len, stderr);
-
- if (h->f&HOF_DUPERR) {
- report_location(h, h->lyt.fp, tv->infile, tv->lno);
- fwrite(d.buf, 1, d.len, h->lyt.fp);
- }
- show_progress(h);
+ if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&human_benchops);
+ else return (0);
}
/* --- @human_destroy@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * human_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct human_output@
*
* Returns: ---
*
{
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);
- xfree(h->outbuf); xfree(h);
+ x_free(h->a, h->outbuf); x_free(h->a, h);
}
static const struct tvec_outops human_ops = {
human_bsession, human_esession,
human_bgroup, human_skipgroup, human_egroup,
human_btest, human_skip, human_fail, human_dumpreg, human_etest,
- human_bbench, human_ebench,
- human_report,
- human_destroy
+ human_report, human_extend, human_destroy
};
/* --- @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;
- h = xmalloc(sizeof(*h)); 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);
}
-/*----- Perl's `Test Anything Protocol' -----------------------------------*/
+/*----- Machine-readable output -------------------------------------------*/
-struct tap_output {
+struct machine_output {
struct tvec_output _o; /* output base class */
struct tvec_state *tv; /* stashed testing state */
- struct layout lyt; /* output layout */
+ arena *a; /* arena for memory allocation */
+ FILE *fp; /* output stream */
char *outbuf; size_t outsz; /* buffer for formatted output */
unsigned grpix, testix; /* group and test indices */
- unsigned previx; /* previously reported test index */
- int maxlen; /* longest register name */
+ unsigned f; /* flags */
+#define MF_BENCH 1u /* current test is a benchmark */
};
-/* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
+/* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
*
- * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
+ * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
* @int ch@ = character to write
* @const char *@p@, @size_t sz@ = string (with explicit length)
* to write
* Returns: ---
*
* Use: Write characters, strings, or formatted strings to the
- * output, applying appropriate layout.
- *
- * For the TAP output driver, the layout machinery prefixes each
- * line with ` ## ' and strips trailing spaces.
+ * output, applying appropriate quoting.
*/
-static int tap_writech(void *go, int ch)
- { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
+static void machine_escape(struct machine_output *m, int ch)
+{
+ switch (ch) {
+ case 0: fputs("\\0", m->fp); break;
+ case '\a': fputs("\\a", m->fp); break;
+ case '\b': fputs("\\b", m->fp); break;
+ case '\x1b': fputs("\\e", m->fp); break;
+ case '\f': fputs("\\f", m->fp); break;
+ case '\n': fputs("\\n", m->fp); break;
+ case '\r': fputs("\\r", m->fp); break;
+ case '\t': fputs("\\t", m->fp); break;
+ case '\v': fputs("\\v", m->fp); break;
+ case '"': fputs("\\\"", m->fp); break;
+ case '\\': fputs("\\\\", m->fp); break;
+ default: fprintf(m->fp, "\\x{%02x}", ch);
+ }
+}
-static int tap_writem(void *go, const char *p, size_t sz)
- { struct tap_output *t = go; return (layout_string(&t->lyt, p, sz)); }
+static int machine_writech(void *go, int ch)
+{
+ struct machine_output *m = go;
-static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
+ if (ISPRINT(ch)) putc(ch, m->fp);
+ else machine_escape(m, ch);
+ return (1);
+}
+
+static int machine_writem(void *go, const char *p, size_t sz)
{
- struct tap_output *t = go;
- size_t n;
+ struct machine_output *m = go;
+ const char *q, *l = p + sz;
+
+ for (;;) {
+ q = p;
+ for (;;) {
+ if (q >= l) goto final;
+ if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
+ q++;
+ }
+ if (p < q) fwrite(p, 1, q - p, m->fp);
+ p = q; machine_escape(m, (unsigned char)*p++);
+ }
+final:
+ if (p < l) fwrite(p, 1, l - p, m->fp);
+ return (sz);
+}
+
+static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
+{
+ struct machine_output *m = go;
+ int n;
va_list ap;
va_start(ap, p);
- n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
+ n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
va_end(ap);
- return (layout_string(&t->lyt, t->outbuf, n));
+ return (machine_writem(m, m->outbuf, n));
}
-static const struct gprintf_ops tap_printops =
- { tap_writech, tap_writem, tap_nwritef };
+static const struct gprintf_ops machine_printops =
+ { machine_writech, machine_writem, machine_nwritef };
-/* --- @tap_bsession@ --- *
+/* --- @machine_maybe_quote@ --- *
+ *
+ * Arguments: @struct machine_output *m@ = output sink
+ * @const char *p@ = pointer to string
+ *
+ * Returns: ---
+ *
+ * Use: Print the string @p@, quoting it if necessary.
+ */
+
+static void machine_maybe_quote(struct machine_output *m, const char *p)
+{
+ const char *q;
+
+ for (q = p; *q; q++)
+ if (!ISPRINT(*q) || ISSPACE(*q)) goto quote;
+ fputs(p, m->fp); return;
+quote:
+ putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
+}
+
+/* --- @machine_bsession@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
* @struct tvec_state *tv@ = the test state producing output
*
* Returns: ---
* number.
*/
-static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
+static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
{
- struct tap_output *t = (struct tap_output *)o;
+ struct machine_output *m = (struct machine_output *)o;
- t->tv = tv; t->grpix = 0;
+ m->tv = tv; m->grpix = 0;
+}
+
+/* --- @machine_show_stats@ --- *
+ *
+ * Arguments: @struct machine_output *m@ = output sink
+ * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
+ *
+ * Returns: ---
+ *
+ * Use: Print a machine readable outcome statistics table
+ */
+
+static void machine_show_stats(struct machine_output *m,
+ unsigned out[TVOUT_LIMIT])
+{
+ static const char *outtab[] = { "lose", "skip", "xfail", "win" };
+ unsigned i;
+
+ for (i = 0; i < TVOUT_LIMIT; i++) {
+ if (i) putc(' ', m->fp);
+ fprintf(m->fp, "%s=%u", outtab[i], out[i]);
+ }
+}
+
+/* --- @machine_esession@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ *
+ * Returns: Suggested exit code.
+ *
+ * Use: End a test session.
+ *
+ * The TAP driver prints a final summary of the rest results
+ * and returns a suitable exit code. If errors occurred, it
+ * instead prints a `Bail out!' line forcing the reader to
+ * report a failure.
+ */
+
+static int machine_esession(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("END groups: ", m->fp);
+ machine_show_stats(m, tv->grps);
+ fputs("; tests: ", m->fp);
+ machine_show_stats(m, tv->all);
+ putc('\n', m->fp);
+ m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
+}
+
+/* --- @machine_bgroup@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Begin a test group.
+ *
+ * The TAP driver determines the length of the longest
+ * register name, resets the group progress scoreboard, and
+ * activates the progress display.
+ */
+
+static void machine_bgroup(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("BGROUP ", m->fp);
+ machine_maybe_quote(m, tv->test->name);
+ putc('\n', m->fp);
+ m->grpix++; m->testix = 0;
+}
+
+/* --- @machine_skipgroup@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_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 TAP driver just reports the situation to its output
+ * stream.
+ */
+
+static void machine_skipgroup(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("SKIPGRP ", m->fp);
+ machine_maybe_quote(m, tv->test->name);
+ if (excuse) {
+ fputs(" \"", m->fp);
+ vgprintf(&machine_printops, m, excuse, ap);
+ putc('\"', m->fp);
+ }
+ putc('\n', m->fp);
+}
+
+/* --- @machine_egroup@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test group has finished.
+ *
+ * The TAP driver reports a summary of the group's tests.
+ */
+
+static void machine_egroup(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ if (!(tv->f&TVSF_SKIP)) {
+ fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
+ putc(' ', m->fp); machine_show_stats(m, tv->curr);
+ putc('\n', m->fp);
+ }
+}
+
+/* --- @machine_btest@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test is starting.
+ *
+ * The TAP driver advances its test counter. (We could do this
+ * by adding up up the counters in @tv->curr@, and add on the
+ * current test, but it's easier this way.)
+ */
+
+static void machine_btest(struct tvec_output *o) { ; }
+
+/* --- @machine_report_location@ --- *
+ *
+ * Arguments: @struct human_output *h@ = output state
+ * @const char *file@ = filename
+ * @unsigned lno@ = line number
+ *
+ * Returns: ---
+ *
+ * Use: Print the filename and line number to the output stream.
+ */
+
+static void machine_report_location(struct machine_output *m,
+ const char *file, unsigned lno)
+{
+ if (file) {
+ putc(' ', m->fp);
+ machine_maybe_quote(m, file);
+ fprintf(m->fp, ":%u", lno);
+ }
+}
+
+/* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ * @const char *outcome@ = outcome string 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.
+ */
+
+static void machine_outcome(struct tvec_output *o, const char *outcome,
+ const char *detail, va_list *ap)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fprintf(m->fp, "%s %u", outcome, m->testix);
+ machine_report_location(m, tv->infile, tv->test_lno);
+ if (detail)
+ { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
+ putc('\n', m->fp);
+}
+
+static void machine_skip(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+ { machine_outcome(o, "SKIP", excuse, ap); }
+static void machine_fail(struct tvec_output *o,
+ const char *detail, va_list *ap)
+ { machine_outcome(o, "FAIL", detail, ap); }
+
+/* --- @machine_dumpreg@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_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 machine driver applies highlighting to mismatching output
+ * registers, but otherwise delegates to the register type
+ * handler.
+ */
+
+static void machine_dumpreg(struct tvec_output *o,
+ unsigned disp, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ unsigned f = 0;
+#define f_reg 1u
+#define f_nl 2u
+
+ switch (disp) {
+ case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
+ case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
+ case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
+ case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
+ case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
+ default: abort();
+ }
+
+ if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
+ if (!rv) fputs("#unset", m->fp);
+ else rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
+ if (f&f_nl) putc('\n', m->fp);
+
+#undef f_reg
+#undef f_newline
+}
+
+/* --- @machine_etest@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ * @unsigned outcome@ = the test outcome
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test has finished.
+ *
+ * The machine driver reports the outcome of the test, if that's
+ * not already decided.
+ */
+
+static void machine_etest(struct tvec_output *o, unsigned outcome)
+{
+ struct machine_output *m = (struct machine_output *)o;
+
+ if (!(m->f&MF_BENCH)) switch (outcome) {
+ case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
+ case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
+ }
+ m->testix++; m->f &= ~MF_BENCH;
+}
+
+/* --- @machine_report@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ * @unsigned level@ = message level (@TVLV_...@)
+ * @const char *msg@, @va_list *ap@ = format string and
+ * arguments
+ *
+ * Returns: ---
+ *
+ * Use: Report a message to the user.
+ *
+ * Each report level has its own output tag
+ */
+
+static void machine_report(struct tvec_output *o, unsigned level,
+ const char *msg, va_list *ap)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+ const char *p;
+
+ for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
+ machine_report_location(m, tv->infile, tv->lno);
+ fputs(" \"", m->fp); vgprintf(&machine_printops, m, msg, ap);
+ putc('"', m->fp);
+ putc('\n', m->fp);
+}
+
+/* --- @machine_bbench@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ * @const char *desc@ = adhoc test description
+ * @unsigned unit@ = measurement unit (@BTU_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Report that a benchmark has started.
+ *
+ * The machine driver does nothing here. All of the reporting
+ * happens in @machine_ebench@.
+ */
+
+static void machine_bbench(struct tvec_output *o,
+ const char *desc, unsigned unit)
+ { ; }
+
+/* --- @machine_ebench@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_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 machine driver prints the result as a `BENCH' output
+ * line, in raw format.
+ */
+
+static void machine_ebench(struct tvec_output *o,
+ const char *desc, unsigned unit,
+ const struct bench_timing *t)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fprintf(m->fp, "BENCH %u", m->testix);
+ machine_report_location(m, tv->infile, tv->test_lno);
+ putc(' ', m->fp);
+ if (desc) machine_maybe_quote(m, desc);
+ else print_ident(tv, TVSF_RAW, &file_printops, m->fp);
+ fputs(": ", m->fp);
+ tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, t);
+ putc('\n', m->fp); m->f |= MF_BENCH;
+}
+
+static const struct tvec_benchoutops machine_benchops =
+ { machine_bbench, machine_ebench };
+
+/* --- @machine_extend@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ * @const char *name@ = extension name
+ *
+ * Returns: A pointer to the extension implementation, or null.
+ */
+
+static const void *machine_extend(struct tvec_output *o, const char *name)
+{
+ if (STRCMP(name, ==, TVEC_BENCHOUTEXT)) return (&machine_benchops);
+ else return (0);
+}
+
+/* --- @machine_destroy@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Release the resources held by the output driver.
+ */
+
+static void machine_destroy(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+
+ if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
+ x_free(m->a, m->outbuf); x_free(m->a, m);
+}
+
+static const struct tvec_outops machine_ops = {
+ machine_bsession, machine_esession,
+ machine_bgroup, machine_skipgroup, machine_egroup,
+ machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
+ machine_report, machine_extend, machine_destroy
+};
+
+/* --- @tvec_machineoutput@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file to write on
+ *
+ * Returns: An output formatter.
+ *
+ * Use: Return an output formatter which writes on @fp@ in a
+ * moderately simple machine-readable format.
+ */
+
+struct tvec_output *tvec_machineoutput(FILE *fp)
+{
+ struct machine_output *m;
+
+ XNEW(m); m->a = arena_global; m->_o.ops = &machine_ops;
+ m->f = 0; m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
+ return (&m->_o);
+}
+
+/*----- Perl's `Test Anything Protocol' -----------------------------------*/
+
+struct tap_output {
+ struct tvec_output _o; /* output base class */
+ struct tvec_state *tv; /* stashed testing state */
+ arena *a; /* arena for memory allocation */
+ struct layout lyt; /* output layout */
+ char *outbuf; size_t outsz; /* buffer for formatted output */
+ unsigned grpix, testix; /* group and test indices */
+ unsigned previx; /* previously reported test index */
+ int maxlen; /* longest register name */
+};
+
+/* --- @tap_writech@, @tap_write@, @tap_writef@ --- *
+ *
+ * Arguments: @void *go@ = output sink, secretly a @struct tap_output@
+ * @int ch@ = character to write
+ * @const char *@p@, @size_t sz@ = string (with explicit length)
+ * to write
+ * @const char *p, ...@ = format control string and arguments to
+ * write
+ *
+ * Returns: ---
+ *
+ * Use: Write characters, strings, or formatted strings to the
+ * output, applying appropriate layout.
+ *
+ * For the TAP output driver, the layout machinery prefixes each
+ * line with ` ## ' and strips trailing spaces.
+ */
+
+static int tap_writech(void *go, int ch)
+{
+ struct tap_output *t = go;
+
+ if (layout_char(&t->lyt, ch)) return (-1);
+ else return (1);
+}
+
+static int tap_writem(void *go, const char *p, size_t sz)
+{
+ struct tap_output *t = go;
+
+ if (layout_string(&t->lyt, p, sz)) return (-1);
+ else return (sz);
+}
+
+static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
+{
+ struct tap_output *t = go;
+ size_t n;
+ va_list ap;
+
+ va_start(ap, p);
+ n = gprintf_memputf(t->a, &t->outbuf, &t->outsz, maxsz, p, ap);
+ va_end(ap);
+ if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
+ return (n);
+}
+
+static const struct gprintf_ops tap_printops =
+ { tap_writech, tap_writem, tap_nwritef };
+
+/* --- @tap_bsession@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
+ * @struct tvec_state *tv@ = the test state producing output
+ *
+ * Returns: ---
+ *
+ * Use: Begin a test session.
+ *
+ * The TAP driver records the test state for later reference,
+ * initializes the group index counter, and prints the version
+ * number.
+ */
+
+static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
+{
+ struct tap_output *t = (struct tap_output *)o;
+
+ t->tv = tv; t->grpix = 0;
fputs("TAP version 13\n", t->lyt.fp); /* but secretly 14 really */
}
/* --- @tap_esession@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
*
- * Returns: Suggested exit status.
+ * Returns: Suggested exit code.
*
* Use: End a test session.
*
* The TAP driver prints a final summary of the rest results
- * and returns a suitable exit code.
+ * and returns a suitable exit code. If errors occurred, it
+ * instead prints a `Bail out!' line forcing the reader to
+ * report a failure.
*/
static int tap_esession(struct tvec_output *o)
/* --- @tap_bgroup@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
*
* Returns: ---
*
/* --- @tap_skipgroup@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
* @const char *excuse@, @va_list *ap@ = reason for skipping the
* group, or null
*
/* --- @tap_egroup@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
*
* Returns: ---
*
/* --- @tap_btest@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
*
* Returns: ---
*
/* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
- * @unsigned attr@ = attribute to apply to the outcome
- * @const char *outcome@ = outcome string to report
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_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
/* --- @tap_dumpreg@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
* @unsigned disp@ = register disposition
* @const union tvec_regval *rv@ = register value
* @const struct tvec_regdef *rd@ = register definition
struct tap_output *t = (struct tap_output *)o;
const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
+ set_layout_prefix(&t->lyt, " ## ");
gprintf(&tap_printops, t, "%*s%s %s = ",
10 + t->maxlen - n, "", ds, rd->name);
if (!rv) gprintf(&tap_printops, t, "#<unset>");
/* --- @tap_etest@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
* @unsigned outcome@ = the test outcome
*
* Returns: ---
}
}
-/* --- @tap_bbench@ --- *
- *
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
- * @const char *ident@ = identifying register values
- * @unsigned unit@ = measurement unit (@TVBU_...@)
- *
- * Returns: ---
- *
- * Use: Report that a benchmark has started.
- *
- * The TAP driver does nothing here. All of the reporting
- * happens in @tap_ebench@.
- */
-
-static void tap_bbench(struct tvec_output *o,
- const char *ident, unsigned unit)
- { ; }
-
-/* --- @tap_ebench@ --- *
- *
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
- * @const char *ident@ = identifying register values
- * @unsigned unit@ = measurement unit (@TVBU_...@)
- * @const struct bench_timing *tm@ = measurement
- *
- * Returns: ---
- *
- * Use: Report a benchmark's results
- *
- * The TAP driver just delegates to the default benchmark
- * reporting, via the layout machinery so that the result is
- * printed as a comment.
- */
-
-static void tap_ebench(struct tvec_output *o,
- const char *ident, unsigned unit,
- const struct bench_timing *tm)
-{
- struct tap_output *t = (struct tap_output *)o;
- struct tvec_state *tv = t->tv;
-
- gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
- tvec_benchreport(&tap_printops, t, unit, tm);
- layout_char(&t->lyt, '\n');
-}
-
/* --- @tap_report@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
- * @unsigned level@ = message level (@TVLEV_...@)
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
+ * @unsigned level@ = message level (@TVLV_...@)
* @const char *msg@, @va_list *ap@ = format string and
* arguments
*
*
* Use: Report a message to the user.
*
- * The TAP driver converts error reports into TAP `Bail out!'
- * errors. Less critical notices end up as comments.
+ * Messages are reported as comments, so that they can be
+ * accumulated by the reader. An error will cause a later
+ * bailout or, if we crash before then, a missing plan line,
+ * either of which will cause the reader to report a serious
+ * problem.
*/
static void tap_report(struct tvec_output *o, unsigned level,
{
struct tap_output *t = (struct tap_output *)o;
struct tvec_state *tv = t->tv;
- const struct gprintf_ops *gops; void *go;
- if (level >= TVLEV_ERR) {
- fputs("Bail out! ", t->lyt.fp);
- gops = &file_printops; go = t->lyt.fp;
- } else {
- gops = &tap_printops; go = t;
- }
- if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
- gprintf(gops, go, msg, ap); gops->putch(go, '\n');
+ if (tv->test) set_layout_prefix(&t->lyt, " ## ");
+ else set_layout_prefix(&t->lyt, "## ");
+
+ if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno);
+ gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level));
+ vgprintf(&tap_printops, t, msg, ap);
+ layout_char(&t->lyt, '\n');
}
+/* --- @tap_extend@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
+ * @const char *name@ = extension name
+ *
+ * Returns: A pointer to the extension implementation, or null.
+ */
+
+static const void *tap_extend(struct tvec_output *o, const char *name)
+ { return (0); }
+
/* --- @tap_destroy@ --- *
*
- * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
- * tap_output@
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct tap_output@
*
* Returns: ---
*
destroy_layout(&t->lyt,
t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
- xfree(t->outbuf); xfree(t);
+ x_free(t->a, t->outbuf); x_free(t->a, t);
}
static const struct tvec_outops tap_ops = {
tap_bsession, tap_esession,
tap_bgroup, tap_skipgroup, tap_egroup,
tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
- tap_bbench, tap_ebench,
- tap_report,
- tap_destroy
+ tap_report, tap_extend, tap_destroy
};
/* --- @tvec_tapoutput@ --- *
{
struct tap_output *t;
- t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
- init_layout(&t->lyt, fp, " ## ");
+ XNEW(t); t->a = arena_global; t->_o.ops = &tap_ops;
+ init_layout(&t->lyt, fp, 0);
t->outbuf = 0; t->outsz = 0;
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@ --- *
* writing on @fp@. The policy is subject to change, but
* currently the `human' output format is selected if @fp@ is
* interactive (i.e., if @isatty(fileno(fp))@ is true), and
- * otherwise the `tap' format is used.
+ * 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));
- else return (tvec_tapoutput(fp));
+ if (ttyp) return (tvec_humanoutput(fp, TVHF_TTY, TVHF_TTY));
+ else return (tvec_machineoutput(fp));
}
/*----- That's all, folks -------------------------------------------------*/