@@@ fltfmt mess
[mLib] / test / tvec-output.c
index 46c3442..4e2ea64 100644 (file)
 #include "macros.h"
 #include "quis.h"
 #include "report.h"
+
 #include "tvec.h"
+#include "tvec-bench.h"
+#include "tvec-output.h"
 
 /*----- Common machinery --------------------------------------------------*/
 
@@ -117,6 +120,40 @@ static int register_maxnamelen(const struct tvec_state *tv)
   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
@@ -451,6 +488,7 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz)
 #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 */
@@ -469,6 +507,7 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz)
 struct human_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 */
   dstr scoreboard;                     /* history of test group results */
@@ -671,7 +710,7 @@ static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
   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);
   if (layout_string(&h->lyt, h->outbuf, n)) return (-1);
   return (n);
@@ -682,8 +721,8 @@ 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:    ---
@@ -737,8 +776,8 @@ static void report_unusual(struct human_output *h,
 
 /* --- @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 code.
  *
@@ -790,8 +829,8 @@ static int human_esession(struct tvec_output *o)
 
 /* --- @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:    ---
  *
@@ -812,8 +851,8 @@ static void human_bgroup(struct tvec_output *o)
 
 /* --- @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
  *
@@ -843,8 +882,8 @@ static void human_skipgroup(struct tvec_output *o,
 
 /* --- @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:    ---
  *
@@ -878,8 +917,8 @@ static void human_egroup(struct tvec_output *o)
 
 /* --- @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:    ---
  *
@@ -947,8 +986,8 @@ static void human_report_location(struct human_output *h, FILE *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
@@ -986,8 +1025,8 @@ static void human_fail(struct tvec_output *o,
 
 /* --- @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
@@ -1023,8 +1062,8 @@ static void human_dumpreg(struct tvec_output *o,
 
 /* --- @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:    ---
@@ -1054,62 +1093,10 @@ static void human_etest(struct tvec_output *o, unsigned outcome)
   }
 }
 
-/* --- @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_...@)
- *
- * Returns:    ---
- *
- * Use:                Report that a benchmark has started.
- *
- *             The human driver just prints the start of the benchmark
- *             report.
- */
-
-static void human_bbench(struct tvec_output *o,
-                        const char *ident, unsigned unit)
-{
-  struct human_output *h = (struct human_output *)o;
-  struct tvec_state *tv = h->tv;
-
-  clear_progress(h);
-  gprintf(&human_printops, h, "%s %s: ", tv->test->name, ident);
-  if (h->f&HOF_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
- *
- * Returns:    ---
- *
- * 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)
-{
-  struct human_output *h = (struct human_output *)o;
-
-  tvec_benchreport(&human_printops, h, unit, 0, tm);
-  layout_char(&h->lyt, '\n');
-}
-
 /* --- @human_report@ --- *
  *
- * 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 level@ = message level (@TVLEV_...@)
  *             @const char *msg@, @va_list *ap@ = format string and
  *                     arguments
@@ -1163,14 +1150,89 @@ static void human_report(struct tvec_output *o, unsigned level,
   }
   if (f&f_progress) show_progress(h);
 
+  dstr_destroy(&d);
+
 #undef f_flush
 #undef f_progress
 }
 
+/* --- @human_bbench@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct human_output@
+ *             @const char *desc@ = adhoc test description
+ *             @unsigned unit@ = measurement unit (@BTU_...@)
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a benchmark has started.
+ *
+ *             The human driver just prints the start of the benchmark
+ *             report.
+ */
+
+static void human_bbench(struct tvec_output *o,
+                        const char *desc, unsigned unit)
+{
+  struct human_output *h = (struct human_output *)o;
+  struct tvec_state *tv = h->tv;
+
+  clear_progress(h);
+  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&HOF_TTY) fflush(h->lyt.fp);
+}
+
+/* --- @human_ebench@ --- *
+ *
+ * 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.
+ *
+ *             The human driver just delegates to the default benchmark
+ *             reporting, via the layout machinery.
+ */
+
+static void human_ebench(struct tvec_output *o,
+                        const char *desc, unsigned unit,
+                        const struct bench_timing *t)
+{
+  struct human_output *h = (struct human_output *)o;
+
+  tvec_benchreport(&human_printops, h, unit, 0, t);
+  layout_char(&h->lyt, '\n');
+}
+
+static const struct tvec_benchoutops human_benchops =
+  { human_bbench, human_ebench };
+
+/* --- @human_extend@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct human_output@
+ *             @const char *name@ = extension name
+ *
+ * Returns:    A pointer to the extension implementation, or null.
+ */
+
+static const void *human_extend(struct tvec_output *o, const char *name)
+{
+  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:    ---
  *
@@ -1184,16 +1246,14 @@ static void human_destroy(struct tvec_output *o)
   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@ --- *
@@ -1215,7 +1275,7 @@ struct tvec_output *tvec_humanoutput(FILE *fp)
   struct human_output *h;
   const char *p;
 
-  h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
+  XNEW(h); h->a = arena_global; h->_o.ops = &human_ops;
   h->f = 0; h->attr = 0;
 
   init_layout(&h->lyt, fp, 0);
@@ -1249,6 +1309,7 @@ struct tvec_output *tvec_humanoutput(FILE *fp)
 struct machine_output {
   struct tvec_output _o;               /* output base class */
   struct tvec_state *tv;               /* stashed testing state */
+  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 */
@@ -1325,7 +1386,7 @@ static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
   va_list ap;
 
   va_start(ap, p);
-  n = gprintf_memputf(&m->outbuf, &m->outsz, maxsz, p, ap);
+  n = gprintf_memputf(m->a, &m->outbuf, &m->outsz, maxsz, p, ap);
   va_end(ap);
   return (machine_writem(m, m->outbuf, n));
 }
@@ -1348,7 +1409,7 @@ static void machine_maybe_quote(struct machine_output *m, const char *p)
   const char *q;
 
   for (q = p; *q; q++)
-    if (!ISPRINT(*q)) goto quote;
+    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);
@@ -1356,8 +1417,8 @@ quote:
 
 /* --- @machine_bsession@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
  *             @struct tvec_state *tv@ = the test state producing output
  *
  * Returns:    ---
@@ -1400,8 +1461,8 @@ static void machine_show_stats(struct machine_output *m,
 
 /* --- @machine_esession@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
  *
  * Returns:    Suggested exit code.
  *
@@ -1428,8 +1489,8 @@ static int machine_esession(struct tvec_output *o)
 
 /* --- @machine_bgroup@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
  *
  * Returns:    ---
  *
@@ -1453,8 +1514,8 @@ static void machine_bgroup(struct tvec_output *o)
 
 /* --- @machine_skipgroup@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * 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
  *
@@ -1472,7 +1533,7 @@ static void machine_skipgroup(struct tvec_output *o,
   struct machine_output *m = (struct machine_output *)o;
   struct tvec_state *tv = m->tv;
 
-  fputs("BGROUP ", m->fp);
+  fputs("SKIPGRP ", m->fp);
   machine_maybe_quote(m, tv->test->name);
   if (excuse) {
     fputs(" \"", m->fp);
@@ -1484,8 +1545,8 @@ static void machine_skipgroup(struct tvec_output *o,
 
 /* --- @machine_egroup@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
  *
  * Returns:    ---
  *
@@ -1499,15 +1560,17 @@ static void machine_egroup(struct tvec_output *o)
   struct machine_output *m = (struct machine_output *)o;
   struct tvec_state *tv = m->tv;
 
-  fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
-  putc(' ', m->fp); machine_show_stats(m, tv->curr);
-  putc('\n', m->fp);
+  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@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
  *
  * Returns:    ---
  *
@@ -1533,12 +1596,18 @@ static void machine_btest(struct tvec_output *o) { ; }
 
 static void machine_report_location(struct machine_output *m,
                                    const char *file, unsigned lno)
-  { machine_maybe_quote(m, file); fprintf(m->fp, ":%u", 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@
+ * 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
@@ -1555,7 +1624,7 @@ static void machine_outcome(struct tvec_output *o, const char *outcome,
   struct machine_output *m = (struct machine_output *)o;
   struct tvec_state *tv = m->tv;
 
-  fprintf(m->fp, "%s %u ", outcome, m->testix);
+  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); }
@@ -1571,8 +1640,8 @@ static void machine_fail(struct tvec_output *o,
 
 /* --- @machine_dumpreg@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * 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
@@ -1581,10 +1650,9 @@ static void machine_fail(struct tvec_output *o,
  *
  * Use:                Dump a register.
  *
- *             The TAP driver applies highlighting to mismatching output
+ *             The machine driver applies highlighting to mismatching output
  *             registers, but otherwise delegates to the register type
- *             handler and the layout machinery.  The result is that the
- *             register dump is marked as a comment and indented.
+ *             handler.
  */
 
 static void machine_dumpreg(struct tvec_output *o,
@@ -1606,7 +1674,8 @@ static void machine_dumpreg(struct tvec_output *o,
   }
 
   if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
-  rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
+  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
@@ -1615,16 +1684,16 @@ static void machine_dumpreg(struct tvec_output *o,
 
 /* --- @machine_etest@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
+ * 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 TAP driver reports the outcome of the test, if that's not
- *             already decided.
+ *             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)
@@ -1638,93 +1707,109 @@ static void machine_etest(struct tvec_output *o, unsigned outcome)
   m->testix++; m->f &= ~MF_BENCH;
 }
 
+/* --- @machine_report@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
+ *             @unsigned level@ = message level (@TVLEV_...@)
+ *             @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 *ident@ = identifying register values
- *             @unsigned unit@ = measurement unit (@TVBU_...@)
+ * 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 TAP driver does nothing here.  All of the reporting
+ *             The machine driver does nothing here.  All of the reporting
  *             happens in @machine_ebench@.
  */
 
 static void machine_bbench(struct tvec_output *o,
-                          const char *ident, unsigned unit)
+                          const char *desc, unsigned unit)
   { ; }
 
 /* --- @machine_ebench@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_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 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
+ * 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.
+ *             The machine driver prints the result as a `BENCH' output
+ *             line, in raw format.
  */
 
 static void machine_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 machine_output *m = (struct machine_output *)o;
   struct tvec_state *tv = m->tv;
 
-  fprintf(m->fp, "BENCH %u ", m->testix);
+  fprintf(m->fp, "BENCH %u", m->testix);
   machine_report_location(m, tv->infile, tv->test_lno);
-  fprintf(m->fp, " %s: ", ident);
-  tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, tm);
+  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;
 }
 
-/* --- @machine_report@ --- *
- *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     machine_output@
- *             @unsigned level@ = message level (@TVLEV_...@)
- *             @const char *msg@, @va_list *ap@ = format string and
- *                     arguments
- *
- * Returns:    ---
+static const struct tvec_benchoutops machine_benchops =
+  { machine_bbench, machine_ebench };
+
+/* --- @machine_extend@ --- *
  *
- * Use:                Report a message to the user.
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
+ *             @const char *name@ = extension name
  *
- *             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.
+ * Returns:    A pointer to the extension implementation, or null.
  */
 
-static void machine_report(struct tvec_output *o, unsigned level,
-                          const char *msg, va_list *ap)
+static const void *machine_extend(struct tvec_output *o, const char *name)
 {
-  struct machine_output *m = (struct machine_output *)o;
-  const char *p;
-
-  for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
-  putc(' ', m->fp);
-  putc('"', m->fp);
-  vgprintf(&machine_printops, m, msg, ap);
-  putc('"', m->fp);
-  putc('\n', m->fp);
+  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@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct machine_output@
  *
  * Returns:    ---
  *
@@ -1736,16 +1821,14 @@ 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);
-  xfree(m->outbuf); xfree(m);
+  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_bbench, machine_ebench,
-  machine_report,
-  machine_destroy
+  machine_report, machine_extend, machine_destroy
 };
 
 /* --- @tvec_machineoutput@ --- *
@@ -1762,8 +1845,8 @@ struct tvec_output *tvec_machineoutput(FILE *fp)
 {
   struct machine_output *m;
 
-  m = xmalloc(sizeof(*m)); m->_o.ops = &machine_ops;
-  m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
+  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);
 }
 
@@ -1772,6 +1855,7 @@ struct tvec_output *tvec_machineoutput(FILE *fp)
 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 */
@@ -1820,7 +1904,7 @@ static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
   va_list ap;
 
   va_start(ap, p);
-  n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
+  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);
@@ -1831,8 +1915,8 @@ static const struct gprintf_ops tap_printops =
 
 /* --- @tap_bsession@ --- *
  *
- * Arguments:  @struct tvec_output *o@ = output sink, secretly a @struct
- *                     tap_output@
+ * Arguments:  @struct tvec_output *o@ = output sink, secretly a
+ *                      @struct tap_output@
  *             @struct tvec_state *tv@ = the test state producing output
  *
  * Returns:    ---
@@ -1854,8 +1938,8 @@ static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
 
 /* --- @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 code.
  *
@@ -1885,8 +1969,8 @@ 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:    ---
  *
@@ -1909,8 +1993,8 @@ static void tap_bgroup(struct tvec_output *o)
 
 /* --- @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
  *
@@ -1935,8 +2019,8 @@ static void tap_skipgroup(struct tvec_output *o,
 
 /* --- @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:    ---
  *
@@ -1958,8 +2042,8 @@ static void tap_egroup(struct tvec_output *o)
 
 /* --- @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:    ---
  *
@@ -1975,8 +2059,8 @@ static void tap_btest(struct tvec_output *o)
 
 /* --- @tap_outcome@, @tap_skip@, @tap_fail@ --- *
  *
- * 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 *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
@@ -2017,8 +2101,8 @@ static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
 
 /* --- @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
@@ -2050,8 +2134,8 @@ static void tap_dumpreg(struct tvec_output *o,
 
 /* --- @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:    ---
@@ -2074,59 +2158,10 @@ static void tap_etest(struct tvec_output *o, unsigned outcome)
   }
 }
 
-/* --- @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;
-
-  set_layout_prefix(&t->lyt, "    ## ");
-  gprintf(&tap_printops, t, "%s %s: ", tv->test->name, ident);
-  tvec_benchreport(&tap_printops, t, unit, 0, tm);
-  layout_char(&t->lyt, '\n');
-}
-
 /* --- @tap_report@ --- *
  *
- * 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 level@ = message level (@TVLEV_...@)
  *             @const char *msg@, @va_list *ap@ = format string and
  *                     arguments
@@ -2157,10 +2192,22 @@ static void tap_report(struct tvec_output *o, unsigned level,
   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:    ---
  *
@@ -2173,16 +2220,14 @@ static void tap_destroy(struct tvec_output *o)
 
   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@ --- *
@@ -2211,7 +2256,7 @@ struct tvec_output *tvec_tapoutput(FILE *fp)
 {
   struct tap_output *t;
 
-  t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
+  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);
@@ -2229,7 +2274,7 @@ struct tvec_output *tvec_tapoutput(FILE *fp)
  *             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)
@@ -2238,7 +2283,7 @@ struct tvec_output *tvec_dfltout(FILE *fp)
 
   if (ttyp == -1) ttyp = isatty(fileno(fp));
   if (ttyp) return (tvec_humanoutput(fp));
-  else return (tvec_tapoutput(fp));
+  else return (tvec_machineoutput(fp));
 }
 
 /*----- That's all, folks -------------------------------------------------*/