* 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_PFXINB do { \
DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \
} while (0)
+/* --- @set_layout_prefix@ --- *
+ *
+ * Arguments: @struct layout *lyt@ = layout state
+ * @const char *prefix@ = new prefix string or null
+ *
+ * Returns: ---
+ *
+ * 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 set_layout_prefix(struct layout *lyt, const char *prefix)
+{
+ const char *q, *l;
+
+ if (!prefix || !*prefix)
+ lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
+ else {
+ lyt->prefix = prefix;
+ l = lyt->pfxlim = prefix + strlen(prefix);
+ SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
+ }
+}
+
/* --- @init_layout@ --- *
*
* Arguments: @struct layout *lyt@ = layout state to initialize
static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
{
- const char *q, *l;
-
- /* Basics. */
lyt->fp = fp;
lyt->f = LYTF_NEWL;
dstr_create(&lyt->w);
-
- /* Prefix portions. */
- if (!prefix || !*prefix)
- lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
- else {
- lyt->prefix = prefix;
- l = lyt->pfxlim = prefix + strlen(prefix);
- SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
- }
+ set_layout_prefix(lyt, prefix);
}
/* --- @destroy_layout@ --- *
#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@
#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_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_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 */
/* Scoreboard indicators. */
#define HSB_WIN '.' /* test passed */
}
}
-/* --- @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_start(ap, p);
n = gprintf_memputf(&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 =
* 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.
*
static void human_btest(struct tvec_output *o)
{ struct human_output *h = (struct human_output *)o; show_progress(h); }
+/* --- @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_outcome@, @human_skip@, @human_fail@ --- *
*
* Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
{
struct human_output *h = (struct human_output *)o;
- tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
+ tvec_benchreport(&human_printops, h, unit, tm);
fputc('\n', h->lyt.fp);
}
{
struct human_output *h = (struct human_output *)o;
struct tvec_state *tv = h->tv;
+ const char *levstr; unsigned levattr;
dstr d = DSTR_INIT;
+ unsigned f = 0;
+#define f_flush 1u
+#define f_progress 2u
dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
- clear_progress(h); fflush(h->lyt.fp);
+ switch (level) {
+#define CASE(tag, name, val) \
+ case TVLEV_##tag: levstr = name; levattr = HA_##tag; break;
+ TVEC_LEVELS(CASE)
+ default: levstr = "??"; levattr = HA_UNKLEV; 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);
report_location(h, stderr, tv->infile, tv->lno);
- fwrite(d.buf, 1, d.len, stderr);
+ 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_DUPERR) {
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);
}
- show_progress(h);
+ if (f&f_progress) show_progress(h);
+
+#undef f_flush
+#undef f_progress
}
/* --- @human_destroy@ --- *
*/
static int tap_writech(void *go, int ch)
- { struct tap_output *t = go; return (layout_char(&t->lyt, 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; return (layout_string(&t->lyt, p, 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, ...)
{
va_start(ap, p);
n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
va_end(ap);
- return (layout_string(&t->lyt, t->outbuf, n));
+ if (layout_string(&t->lyt, t->outbuf, n)) return (-1);
+ return (n);
}
static const struct gprintf_ops tap_printops =
* 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)
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>");
struct tap_output *t = (struct tap_output *)o;
struct tvec_state *tv = t->tv;
+ set_layout_prefix(&t->lyt, " ## ");
gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
tvec_benchreport(&tap_printops, t, unit, tm);
layout_char(&t->lyt, '\n');
*
* 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_destroy@ --- *
struct tap_output *t;
t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
- init_layout(&t->lyt, fp, " ## ");
+ init_layout(&t->lyt, fp, 0);
t->outbuf = 0; t->outsz = 0;
return (&t->_o);
}