chiark / gitweb /
@@@ wip mostly xfail
authorMark Wooding <mdw@distorted.org.uk>
Wed, 21 Feb 2024 13:06:18 +0000 (13:06 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 21 Feb 2024 14:19:53 +0000 (14:19 +0000)
test/tvec-core.c
test/tvec-output.c
test/tvec-types.c
test/tvec.h
utils/t/versioncmp-test.c
utils/t/versioncmp.tests

index 51834f43f04bcd1bfaa45be7604693acd2a37fe6..36a54edd7c7ce6167aeabdf8dedc1daec390668e 100644 (file)
@@ -367,6 +367,14 @@ void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
     { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
 }
 
+static void open_test(struct tvec_state *tv)
+{
+  if (!(tv->f&TVSF_OPEN)) {
+    tv->test_lno = tv->lno;
+    tv->f |= TVSF_OPEN; tv->f &= ~TVSF_XFAIL;
+  }
+}
+
 static void begin_test(struct tvec_state *tv)
 {
   tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK;
@@ -377,8 +385,10 @@ void tvec_endtest(struct tvec_state *tv)
 {
   unsigned out;
 
-  if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN;
-  else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
+  if (!(tv->f&TVSF_ACTIVE)) /* nothing to do */;
+  else if (tv->f&TVSF_XFAIL) set_outcome(tv, TVOUT_XFAIL);
+  else set_outcome(tv, TVOUT_WIN);
+  out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
   assert(out < TVOUT_LIMIT); tv->curr[out]++;
   tv->output->ops->etest(tv->output, out);
   tv->f &= ~TVSF_OPEN;
@@ -464,6 +474,20 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g)
   tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
 }
 
+enum { WIN, XFAIL, NOUT };
+static const struct tvec_uassoc outcome_assoc[] = {
+  { "success",         WIN },
+  { "win",             WIN },
+  { "expected-failure",        XFAIL },
+  { "xfail",           XFAIL },
+  TVEC_ENDENUM
+};
+static const struct tvec_urange outcome_range = { 0, NOUT - 1 };
+static const struct tvec_uenuminfo outcome_enum =
+  { "test-outcome", outcome_assoc, &outcome_range };
+static const struct tvec_regdef outcome_regdef =
+  { "outcome", 0, &tvty_uenum, 0, { &outcome_enum } };
+
 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
 {
   dstr d = DSTR_INIT;
@@ -472,6 +496,7 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
   const struct tvec_regdef *rd;
   struct tvec_reg *r;
   struct groupstate g = GROUPSTATE_INIT;
+  union tvec_regval rv;
   int ch, ret, rc = 0;
 
   tv->infile = infile; tv->lno = 1; tv->fp = fp;
@@ -523,15 +548,21 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
            { tvec_error(tv, "no current test"); goto flush_line; }
          if (d.buf[0] == '@') {
            env = tv->test->env;
-           if (!env || !env->set) ret = 0;
+           if (STRCMP(d.buf, ==, "@outcome")) {
+             if (tvty_uenum.parse(&rv, &outcome_regdef, tv))
+               ret = -1;
+             else {
+               if (rv.u == XFAIL) tv->f |= TVSF_XFAIL;
+               ret = 1;
+             }
+           } else if (!env || !env->set) ret = 0;
            else ret = env->set(tv, d.buf, env, g.ctx);
            if (ret <= 0) {
              if (!ret)
                tvec_error(tv, "unknown special register `%s'", d.buf);
              goto flush_line;
            }
-           if (!(tv->f&TVSF_OPEN))
-             { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
+           open_test(tv);
          } else {
            for (rd = tv->test->regs; rd->name; rd++)
              if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
@@ -539,8 +570,7 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
                       d.buf, tv->test->name);
            goto flush_line;
          found_reg:
-           if (!(tv->f&TVSF_OPEN))
-             { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
+           open_test(tv);
            tvec_skipspc(tv);
            r = TVEC_REG(tv, in, rd->i);
            if (r->f&TVRF_LIVE) {
index e92ea695e1107b7b739730f3f7dd70e3f5930816..37d010df992a921a590fff7272f7fb7cc203c0fd 100644 (file)
@@ -117,24 +117,24 @@ static int register_maxnamelen(const struct tvec_state *tv)
   return (maxlen);
 }
 
-/*----- Output formatting -------------------------------------------------*/
+/*----- Output layout -----------------------------------------------------*/
 
-/* We have two main jobs in output formatting: trimming trailing blanks; and
+/* We have two main jobs in output layout: trimming trailing blanks; and
  * adding a prefix to each line.
  *
  * This is somehow much more complicated than it ought to be.
  */
 
-struct format {
+struct layout {
   FILE *fp;                             /* output file */
   const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
   dstr w;                              /* trailing whitespace */
   unsigned f;                          /* flags */
-#define FMTF_NEWL 1u                   /*   start of output line */
+#define LYTF_NEWL 1u                   /*   start of output line */
 };
 
-/* Support macros.  These assume `fmt' is defined as a pointer to the `struct
- * format' state.
+/* Support macros.  These assume `lyt' is defined as a pointer to the `struct
+ * layout' state.
  */
 
 #define SPLIT_RANGE(tail, base, limit) do {                            \
@@ -152,26 +152,26 @@ struct format {
    */                                                                  \
                                                                        \
   size_t n = limit - base;                                             \
-  if (fwrite(base, 1, n, fmt->fp) < n) return (-1);                    \
+  if (fwrite(base, 1, n, lyt->fp) < n) return (-1);                    \
 } while (0)
 
 #define PUT_CHAR(ch) do {                                              \
   /* Write CH to the output. Return immediately on error. */           \
                                                                        \
-  if (putc(ch, fmt->fp) == EOF) return (-1);                           \
+  if (putc(ch, lyt->fp) == EOF) return (-1);                           \
 } while (0)
 
 #define PUT_PREFIX do {                                                        \
   /* Output the prefix, if there is one.  Return immediately on error. */ \
                                                                        \
-  if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim);                        \
+  if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxlim);                        \
 } while (0)
 
 #define PUT_SAVED do {                                                 \
   /* Output the saved trailing blank material in the buffer. */                \
                                                                        \
-  size_t n = fmt->w.len;                                               \
-  if (n && fwrite(fmt->w.buf, 1, n, fmt->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 {                                                        \
@@ -179,68 +179,68 @@ struct format {
    * one.  Return immediately on error.                                        \
    */                                                                  \
                                                                        \
-  if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail);               \
+  if (lyt->prefix) PUT_RANGE(lyt->prefix, lyt->pfxtail);               \
 } while (0)
 
 #define SAVE_PFXTAIL do {                                              \
   /* Save the trailing blank portion of the prefix. */                 \
                                                                        \
-  if (fmt->prefix)                                                     \
-    DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail);          \
+  if (lyt->prefix)                                                     \
+    DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail);          \
 } while (0)
 
-/* --- @init_fmt@ --- *
+/* --- @init_layout@ --- *
  *
- * Arguments:  @struct format *fmt@ = formatting state to initialize
+ * 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 formatting state.
+ * Use:                Initialize a layout state.
  */
 
-static void init_fmt(struct format *fmt, FILE *fp, const char *prefix)
+static void init_layout(struct layout *lyt, FILE *fp, const char *prefix)
 {
   const char *q, *l;
 
   /* Basics. */
-  fmt->fp = fp;
-  fmt->f = FMTF_NEWL;
-  dstr_create(&fmt->w);
+  lyt->fp = fp;
+  lyt->f = LYTF_NEWL;
+  dstr_create(&lyt->w);
 
   /* Prefix portions. */
   if (!prefix || !*prefix)
-    fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0;
+    lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0;
   else {
-    fmt->prefix = prefix;
-    l = fmt->pfxlim = prefix + strlen(prefix);
-    SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q;
+    lyt->prefix = prefix;
+    l = lyt->pfxlim = prefix + strlen(prefix);
+    SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q;
   }
 }
 
-/* --- @destroy_fmt@ --- *
+/* --- @destroy_layout@ --- *
  *
- * Arguments:  @struct format *fmt@ = formatting state
- *             @unsigned f@ = flags (@DFF_...@)
+ * Arguments:  @struct layout *lyt@ = layout state
+ *             @unsigned f@ = flags (@DLF_...@)
  *
  * Returns:    ---
  *
- * Use:                Releases a formatting state and the resources it holds.
- *             Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave
+ * Use:                Releases a layout state and the resources it holds.
+ *             Close the file if @DLF_CLOSE@ is set in @f@; otherwise leave
  *             it open (in case it's @stderr@ or something).
  */
 
-#define DFF_CLOSE 1u
-static void destroy_fmt(struct format *fmt, unsigned f)
+#define DLF_CLOSE 1u
+static void destroy_layout(struct layout *lyt, unsigned f)
 {
-  if (f&DFF_CLOSE) fclose(fmt->fp);
-  dstr_destroy(&fmt->w);
+  if (f&DLF_CLOSE) fclose(lyt->fp);
+  dstr_destroy(&lyt->w);
 }
 
-/* --- @format_char@ --- *
+/* --- @layout_char@ --- *
  *
- * Arguments:  @struct format *fmt@ = formatting state
+ * Arguments:  @struct layout *lyt@ = layout state
  *             @int ch@ = character to write
  *
  * Returns:    Zero on success, @-1@ on failure.
@@ -248,23 +248,23 @@ static void destroy_fmt(struct format *fmt, unsigned f)
  * Use:                Write a single character to the output.
  */
 
-static int format_char(struct format *fmt, int ch)
+static int layout_char(struct layout *lyt, int ch)
 {
   if (ch == '\n') {
-    if (fmt->f&FMTF_NEWL) PUT_PFXINB;
-    PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w);
+    if (lyt->f&LYTF_NEWL) PUT_PFXINB;
+    PUT_CHAR('\n'); lyt->f |= LYTF_NEWL; DRESET(&lyt->w);
   } else if (isspace(ch))
-    DPUTC(&fmt->w, ch);
+    DPUTC(&lyt->w, ch);
   else {
-    if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
-    PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w);
+    if (lyt->f&LYTF_NEWL) { PUT_PFXINB; lyt->f &= ~LYTF_NEWL; }
+    PUT_SAVED; PUT_CHAR(ch); DRESET(&lyt->w);
   }
   return (0);
 }
 
-/* --- @format_string@ --- *
+/* --- @layout_string@ --- *
  *
- * Arguments:  @struct format *fmt@ = formatting state
+ * Arguments:  @struct layout *lyt@ = layout state
  *             @const char *p@ = string to write
  *             @size_t sz@ = length of string
  *
@@ -273,7 +273,7 @@ static int format_char(struct format *fmt, int ch)
  * Use:                Write a string to the output.
  */
 
-static int format_string(struct format *fmt, const char *p, size_t sz)
+static int layout_string(struct layout *lyt, const char *p, size_t sz)
 {
   const char *q, *r, *l = p + sz;
 
@@ -325,7 +325,7 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
    * be omitted.                                                       \
    */                                                                  \
                                                                        \
-  DPUTM(&fmt->w, r, l - r);                                            \
+  DPUTM(&lyt->w, r, l - r);                                            \
 } while (0)
 
   /* Determine the bounds of the first segment.  Handling this is the most
@@ -342,12 +342,14 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
      * need to write that.  Otherwise, there's only blank stuff, which we
      * accumulate in the buffer.
      *
-     * If we're at the start of a line here, then    
+     * If we're at the start of a line here, then put the prefix followed by
+     * any saved whitespace, and then our initial nonblank portion.  Then
+     * save our new trailing space.
      */
 
     if (r > p) {
-      if (fmt->f&FMTF_NEWL) { PUT_PREFIX; fmt->f &= ~FMTF_NEWL; }
-      PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w);
+      if (lyt->f&LYTF_NEWL) { PUT_PREFIX; lyt->f &= ~LYTF_NEWL; }
+      PUT_SAVED; PUT_NONBLANK; DRESET(&lyt->w);
     }
     SAVE_TAIL;
     return (0);
@@ -357,11 +359,11 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
    * to output.
    */
   if (r > p) {
-    if (fmt->f&FMTF_NEWL) PUT_PREFIX;
+    if (lyt->f&LYTF_NEWL) PUT_PREFIX;
     PUT_SAVED; PUT_NONBLANK;
-  } else if (fmt->f&FMTF_NEWL)
+  } else if (lyt->f&LYTF_NEWL)
     PUT_PFXINB;
-  PUT_NEWLINE; DRESET(&fmt->w);
+  PUT_NEWLINE; DRESET(&lyt->w);
   SPLIT_SEGMENT;
 
   /* Main loop over whole segments with trailing newlines.  For each one, we
@@ -380,8 +382,8 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
    * the blank stuff (including the trailing blanks of the prefix) and leave
    * the newline flag set.
    */
-  if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; }
-  else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; }
+  if (r > p) { PUT_PREFIX; PUT_NONBLANK; lyt->f &= ~LYTF_NEWL; }
+  else { lyt->f |= LYTF_NEWL; SAVE_PFXTAIL; }
   SAVE_TAIL;
 
 #undef SPLIT_SEGMENT
@@ -434,14 +436,22 @@ static const struct tvec_outops ..._ops = {
 */
 /*----- Human-readable output ---------------------------------------------*/
 
-#define HAF_FGMASK 0x0f
-#define HAF_FGSHIFT 0
-#define HAF_BGMASK 0xf0
-#define HAF_BGSHIFT 4
-#define HAF_FG 256u
-#define HAF_BG 512u
-#define HAF_BOLD 1024u
-#define HCOL_BLACK 0u
+/* 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
@@ -449,30 +459,63 @@ static const struct tvec_outops ..._ops = {
 #define HCOL_MAGENTA 5u
 #define HCOL_CYAN 6u
 #define HCOL_WHITE 7u
-#define HCF_BRIGHT 8u
-#define HFG(col) (HAF_FG | (HCOL_##col) << HAF_FGSHIFT)
-#define HBG(col) (HAF_BG | (HCOL_##col) << HAF_BGSHIFT)
-
-#define HA_WIN (HFG(GREEN))
-#define HA_LOSE (HFG(RED) | HAF_BOLD)
-#define HA_SKIP (HFG(YELLOW))
-#define HA_ERR (HFG(MAGENTA) | HAF_BOLD)
+#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 */
+
+/* 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 */
+
+/* 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 */
 
 struct human_output {
-  struct tvec_output _o;
-  struct tvec_state *tv;
-  struct format fmt;
-  char *outbuf; size_t outsz;
-  dstr scoreboard;
-  unsigned attr;
-  int maxlen;
-  unsigned f;
-#define HOF_TTY 1u
-#define HOF_DUPERR 2u
-#define HOF_COLOUR 4u
-#define HOF_PROGRESS 8u
+  struct tvec_output _o;               /* output base class */
+  struct tvec_state *tv;               /* stashed testing state */
+  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 */
+  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 */
 };
 
+/* --- @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)
@@ -482,107 +525,198 @@ static void set_colour(FILE *fp, int *sep_inout,
   *sep_inout = ';';
 }
 
+/* --- @setattr@ --- *
+ *
+ * Arguments:  @struct human_output *h@ = output state
+ *             @unsigned attr@ = attribute 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)
 {
   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;
-  fputs("\x1b[", h->fmt.fp);
 
+  /* 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->fmt.fp);
-    else { putc('0', h->fmt.fp); diff = h->attr; }
+    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->fmt.fp, &sep, "3", "9",
+      set_colour(h->lyt.fp, &sep, "3", "9",
                 (attr&HAF_FGMASK) >> HAF_FGSHIFT);
-    else
-      { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; }
+    else {
+      if (sep) putc(sep, h->lyt.fp);
+      fputs("39", h->lyt.fp); sep = ';';
+    }
   }
+
+  /* Change the background colour if necessary. */
   if (diff&(HAF_BG | HAF_BGMASK)) {
     if (attr&HAF_BG)
-      set_colour(h->fmt.fp, &sep, "4", "10",
+      set_colour(h->lyt.fp, &sep, "4", "10",
                 (attr&HAF_BGMASK) >> HAF_BGSHIFT);
-    else
-      { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; }
+    else {
+      if (sep) putc(sep, h->lyt.fp);
+      fputs("49", h->lyt.fp); sep = ';';
+    }
   }
 
-  putc('m', h->fmt.fp); h->attr = attr;
-
-#undef f_any
+  /* Terminate the control command and save the new attributes. */
+  putc('m', h->lyt.fp); h->attr = attr;
 }
 
+/* --- @clear_progress@ --- *
+ *
+ * Arguments:  @struct human_output *h@ = output state
+ *
+ * Returns:    ---
+ *
+ * Use:                Remove the progress display from the terminal.
+ *
+ *             If the progress display isn't active then do nothing.
+ */
+
 static void clear_progress(struct human_output *h)
 {
   size_t i, n;
 
   if (h->f&HOF_PROGRESS) {
     n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
-    for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp);
+    for (i = 0; i < n; i++) fputs("\b \b", h->lyt.fp);
     h->f &= ~HOF_PROGRESS;
   }
 }
 
+/* --- @write_scoreboard_char@ --- *
+ *
+ * Arguments:  @struct human_output *h@ = output state
+ *             @int ch@ = scoreboard character to print
+ *
+ * Returns:    ---
+ *
+ * Use:                Write a scoreboard character, indicating the outcome of a
+ *             test, to the output stream, with appropriate highlighting.
+ */
+
 static void write_scoreboard_char(struct human_output *h, int ch)
 {
   switch (ch) {
-    case 'x': setattr(h, HA_LOSE); break;
-    case '_': setattr(h, HA_SKIP); break;
-    default: setattr(h, 0); break;
+    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->fmt.fp); setattr(h, 0);
+  putc(ch, h->lyt.fp); setattr(h, HA_PLAIN);
 }
 
+/* --- @show_progress@ --- *
+ *
+ * Arguments:  @struct human_output *h@ = output state
+ *
+ * Returns:    ---
+ *
+ * Use:                Show the progress display, with the record of outcomes for
+ *             the current test group.
+ *
+ *             If the progress display is already active, or the output
+ *             stream is not interactive, then nothing happens.
+ */
+
 static void show_progress(struct human_output *h)
 {
   struct tvec_state *tv = h->tv;
   const char *p, *l;
 
   if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
-    fprintf(h->fmt.fp, "%s: ", tv->test->name);
+    fprintf(h->lyt.fp, "%s: ", tv->test->name);
     if (!(h->f&HOF_COLOUR))
-      dstr_write(&h->scoreboard, h->fmt.fp);
+      dstr_write(&h->scoreboard, h->lyt.fp);
     else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
       write_scoreboard_char(h, *p);
-    fflush(h->fmt.fp); h->f |= HOF_PROGRESS;
+    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
 
-#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
+  /* 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 (fp != h->fmt.fp) f |= f_flush;
+  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)
 
-  if (file) {
-    setattr(h, HFG(CYAN));     FLUSH(h->fmt.fp);
+    setattr(h, HA_LOC);                FLUSH(h->lyt.fp);
     fputs(file, fp);           FLUSH(fp);
-    setattr(h, HFG(BLUE));     FLUSH(h->fmt.fp);
+    setattr(h, HA_LOCSEP);     FLUSH(h->lyt.fp);
     fputc(':', fp);            FLUSH(fp);
-    setattr(h, HFG(CYAN));     FLUSH(h->fmt.fp);
+    setattr(h, HA_LOC);                FLUSH(h->lyt.fp);
     fprintf(fp, "%u", lno);    FLUSH(fp);
-    setattr(h, HFG(BLUE));     FLUSH(h->fmt.fp);
+    setattr(h, HA_LOCSEP);     FLUSH(h->lyt.fp);
     fputc(':', fp);            FLUSH(fp);
-    setattr(h, 0);             FLUSH(h->fmt.fp);
+    setattr(h, HA_PLAIN);      FLUSH(h->lyt.fp);
     fputc(' ', fp);
+
+#undef FLUSH
   }
 
 #undef f_flush
-#undef FLUSH
 }
 
+/* Output layout.  Pass everything along to the layout machinery above. */
+
 static int human_writech(void *go, int ch)
-  { struct human_output *h = go; return (format_char(&h->fmt, ch)); }
+  { struct human_output *h = go; return (layout_char(&h->lyt, ch)); }
 
 static int human_writem(void *go, const char *p, size_t sz)
-  { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); }
+  { struct human_output *h = go; return (layout_string(&h->lyt, p, sz)); }
 
 static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
 {
@@ -593,22 +727,41 @@ static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
   va_start(ap, p);
   n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
   va_end(ap);
-  return (format_string(&h->fmt, h->outbuf, n));
+  return (layout_string(&h->lyt, h->outbuf, n));
 }
 
 static const struct gprintf_ops human_printops =
   { human_writech, human_writem, human_nwritef };
 
+/* Output methods. */
+
 static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
   { struct human_output *h = (struct human_output *)o; h->tv = tv; }
 
-static void report_skipped(struct human_output *h, unsigned n)
+static void human_report_unusual(struct human_output *h,
+                                unsigned nxfail, unsigned nskip)
 {
-  if (n) {
-    fprintf(h->fmt.fp, " (%u ", n);
-    setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
-    fputc(')', h->fmt.fp);
+  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, "expected %s", nxfail == 1 ? "failure" : "failures");
+    setattr(h, HA_PLAIN);
+    sep = ", "; 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;
   }
+
+  if (f&f_any) fputc(')', h->lyt.fp);
+
+#undef f_any
 }
 
 static int human_esession(struct tvec_output *o)
@@ -617,36 +770,38 @@ static int human_esession(struct tvec_output *o)
   struct tvec_state *tv = h->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_run = all_win + all_lose, grps_run = grps_win + grps_lose;
+    all_pass = all_win + all_xfail, all_run = all_pass + all_lose,
+    grps_run = grps_win + grps_lose;
 
   if (!all_lose) {
-    setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0);
-    fprintf(h->fmt.fp, " %s%u %s",
+    setattr(h, HA_WIN); fputs("PASSED", h->lyt.fp); setattr(h, HA_PLAIN);
+    fprintf(h->lyt.fp, " %s%u %s",
            !(all_skip || grps_skip) ? "all " : "",
-           all_win, all_win == 1 ? "test" : "tests");
-    report_skipped(h, all_skip);
-    fprintf(h->fmt.fp, " in %u %s",
+           all_pass, all_pass == 1 ? "test" : "tests");
+    human_report_unusual(h, all_xfail, all_skip);
+    fprintf(h->lyt.fp, " in %u %s",
            grps_win, grps_win == 1 ? "group" : "groups");
-    report_skipped(h, grps_skip);
+    human_report_unusual(h, 0, grps_skip);
   } else {
-    setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
-    fprintf(h->fmt.fp, " %u out of %u %s",
+    setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
+    fprintf(h->lyt.fp, " %u out of %u %s",
            all_lose, all_run, all_run == 1 ? "test" : "tests");
-    report_skipped(h, all_skip);
-    fprintf(h->fmt.fp, " in %u out of %u %s",
+    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_skipped(h, grps_skip);
+    human_report_unusual(h, 0, grps_skip);
   }
-  fputc('\n', h->fmt.fp);
+  fputc('\n', h->lyt.fp);
 
   if (tv->f&TVSF_ERROR) {
-    setattr(h, HA_ERR); fputs("ERRORS", h->fmt.fp); setattr(h, 0);
-    fputs(" found in input; tests may not have run correctly\n", h->fmt.fp);
+    setattr(h, HA_ERR); fputs("ERRORS", h->lyt.fp); setattr(h, HA_PLAIN);
+    fputs(" found in input; tests may not have run correctly\n", h->lyt.fp);
   }
 
-  h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
+  h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : all_lose ? 1 : 0);
 }
 
 static void human_bgroup(struct tvec_output *o)
@@ -664,36 +819,37 @@ static void human_skipgroup(struct tvec_output *o,
 
   if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
     h->f &= ~HOF_PROGRESS;
-    putc(' ', h->fmt.fp);
-    setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
+    putc(' ', h->lyt.fp);
+    setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
   } else {
-    fprintf(h->fmt.fp, "%s: ", h->tv->test->name);
-    setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
+    fprintf(h->lyt.fp, "%s: ", h->tv->test->name);
+    setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
   }
-  if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
-  fputc('\n', h->fmt.fp);
+  if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
+  fputc('\n', h->lyt.fp);
 }
 
 static void human_egroup(struct tvec_output *o)
 {
   struct human_output *h = (struct human_output *)o;
   struct tvec_state *tv = h->tv;
-  unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE],
-    skip = tv->curr[TVOUT_SKIP], run = win + lose;
+  unsigned win = tv->curr[TVOUT_WIN], xfail = tv->curr[TVOUT_XFAIL],
+    lose = tv->curr[TVOUT_LOSE], skip = tv->curr[TVOUT_SKIP],
+    run = win + lose + xfail;
 
   if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
-  else fprintf(h->fmt.fp, "%s:", h->tv->test->name);
+  else fprintf(h->lyt.fp, "%s:", h->tv->test->name);
 
   if (lose) {
-    fprintf(h->fmt.fp, " %u/%u ", lose, run);
-    setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
-    report_skipped(h, skip);
+    fprintf(h->lyt.fp, " %u/%u ", lose, run);
+    setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
+    human_report_unusual(h, xfail, skip);
   } else {
-    fputc(' ', h->fmt.fp); setattr(h, HA_WIN);
-    fputs("ok", h->fmt.fp); setattr(h, 0);
-    report_skipped(h, skip);
+    fputc(' ', h->lyt.fp); setattr(h, HA_WIN);
+    fputs("ok", h->lyt.fp); setattr(h, HA_PLAIN);
+    human_report_unusual(h, xfail, skip);
   }
-  fputc('\n', h->fmt.fp);
+  fputc('\n', h->lyt.fp);
 }
 
 static void human_btest(struct tvec_output *o)
@@ -706,11 +862,11 @@ static void human_skip(struct tvec_output *o,
   struct tvec_state *tv = h->tv;
 
   clear_progress(h);
-  report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
-  fprintf(h->fmt.fp, "`%s' ", tv->test->name);
-  setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
-  if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
-  fputc('\n', h->fmt.fp);
+  report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
+  fprintf(h->lyt.fp, "`%s' ", tv->test->name);
+  setattr(h, HA_SKIP); fputs("skipped", h->lyt.fp); setattr(h, HA_PLAIN);
+  if (excuse) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, excuse, *ap); }
+  fputc('\n', h->lyt.fp);
 }
 
 static void human_fail(struct tvec_output *o,
@@ -720,11 +876,11 @@ static void human_fail(struct tvec_output *o,
   struct tvec_state *tv = h->tv;
 
   clear_progress(h);
-  report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
-  fprintf(h->fmt.fp, "`%s' ", tv->test->name);
-  setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
-  if (detail) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, detail, *ap); }
-  fputc('\n', h->fmt.fp);
+  report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
+  fprintf(h->lyt.fp, "`%s' ", tv->test->name);
+  setattr(h, HA_LOSE); fputs("FAILED", h->lyt.fp); setattr(h, HA_PLAIN);
+  if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
+  fputc('\n', h->lyt.fp);
 }
 
 static void human_dumpreg(struct tvec_output *o,
@@ -738,13 +894,13 @@ static void human_dumpreg(struct tvec_output *o,
   gprintf(&human_printops, h, "%*s%s %s = ",
          10 + h->maxlen - n, "", ds, rd->name);
   if (h->f&HOF_COLOUR) {
-    if (!rv) setattr(h, HFG(YELLOW));
-    else if (disp == TVRD_FOUND) setattr(h, HFG(RED));
-    else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN));
+    if (!rv) setattr(h, HA_UNSET);
+    else if (disp == TVRD_FOUND) setattr(h, HA_FOUND);
+    else if (disp == TVRD_EXPECT) setattr(h, HA_EXPECT);
   }
   if (!rv) gprintf(&human_printops, h, "#unset");
   else rd->ty->dump(rv, rd, 0, &human_printops, h);
-  setattr(h, 0); format_char(&h->fmt, '\n');
+  setattr(h, HA_PLAIN); layout_char(&h->lyt, '\n');
 }
 
 static void human_etest(struct tvec_output *o, unsigned outcome)
@@ -755,13 +911,14 @@ static void human_etest(struct tvec_output *o, unsigned outcome)
   if (h->f&HOF_TTY) {
     show_progress(h);
     switch (outcome) {
-      case TVOUT_WIN: ch = '.'; break;
-      case TVOUT_LOSE: ch = 'x'; break;
-      case TVOUT_SKIP: ch = '_'; break;
+      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->fmt.fp);
+    write_scoreboard_char(h, ch); fflush(h->lyt.fp);
   }
 }
 
@@ -772,7 +929,7 @@ static void human_bbench(struct tvec_output *o,
   struct tvec_state *tv = h->tv;
 
   clear_progress(h);
-  fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp);
+  fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
 }
 
 static void human_ebench(struct tvec_output *o,
@@ -781,8 +938,8 @@ static void human_ebench(struct tvec_output *o,
 {
   struct human_output *h = (struct human_output *)o;
 
-  tvec_benchreport(&human_printops, h->fmt.fp, unit, tm);
-  fputc('\n', h->fmt.fp);
+  tvec_benchreport(&human_printops, h->lyt.fp, unit, tm);
+  fputc('\n', h->lyt.fp);
 }
 
 static void human_report(struct tvec_output *o, unsigned level,
@@ -794,14 +951,14 @@ static void human_report(struct tvec_output *o, unsigned level,
 
   dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
 
-  clear_progress(h); fflush(h->fmt.fp);
+  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->fmt.fp, tv->infile, tv->lno);
-    fwrite(d.buf, 1, d.len, h->fmt.fp);
+    report_location(h, h->lyt.fp, tv->infile, tv->lno);
+    fwrite(d.buf, 1, d.len, h->lyt.fp);
   }
   show_progress(h);
 }
@@ -810,7 +967,8 @@ static void human_destroy(struct tvec_output *o)
 {
   struct human_output *h = (struct human_output *)o;
 
-  destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0);
+  destroy_layout(&h->lyt,
+                h->lyt.fp == stdout || h->lyt.fp == stderr ? 0 : DLF_CLOSE);
   dstr_destroy(&h->scoreboard);
   xfree(h->outbuf); xfree(h);
 }
@@ -832,7 +990,7 @@ struct tvec_output *tvec_humanoutput(FILE *fp)
   h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
   h->f = 0; h->attr = 0;
 
-  init_fmt(&h->fmt, fp, 0);
+  init_layout(&h->lyt, fp, 0);
   h->outbuf = 0; h->outsz = 0;
 
   switch (getenv_boolean("TVEC_TTY", -1)) {
@@ -863,16 +1021,16 @@ struct tvec_output *tvec_humanoutput(FILE *fp)
 struct tap_output {
   struct tvec_output _o;
   struct tvec_state *tv;
-  struct format fmt;
+  struct layout lyt;
   char *outbuf; size_t outsz;
   int maxlen;
 };
 
 static int tap_writech(void *go, int ch)
-  { struct tap_output *t = go; return (format_char(&t->fmt, ch)); }
+  { struct tap_output *t = go; return (layout_char(&t->lyt, ch)); }
 
 static int tap_writem(void *go, const char *p, size_t sz)
-  { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); }
+  { struct human_output *t = go; return (layout_string(&t->lyt, p, sz)); }
 
 static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
 {
@@ -883,7 +1041,7 @@ 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 (format_string(&t->fmt, t->outbuf, n));
+  return (layout_string(&t->lyt, t->outbuf, n));
 }
 
 static const struct gprintf_ops tap_printops =
@@ -894,16 +1052,16 @@ static void tap_bsession(struct tvec_output *o, struct tvec_state *tv)
   struct tap_output *t = (struct tap_output *)o;
 
   t->tv = tv;
-  fputs("TAP version 13\n", t->fmt.fp);
+  fputs("TAP version 13\n", t->lyt.fp);
 }
 
 static unsigned tap_grpix(struct tap_output *t)
 {
   struct tvec_state *tv = t->tv;
+  unsigned i, n;
 
-  return (tv->grps[TVOUT_WIN] +
-         tv->grps[TVOUT_LOSE] +
-         tv->grps[TVOUT_SKIP]);
+  for (n = 0, i = 0; i < TVOUT_LIMIT; i++) n += tv->grps[i];
+  return (n);
 }
 
 static int tap_esession(struct tvec_output *o)
@@ -914,11 +1072,11 @@ static int tap_esession(struct tvec_output *o)
   if (tv->f&TVSF_ERROR) {
     fputs("Bail out!  "
          "Errors found in input; tests may not have run correctly\n",
-         t->fmt.fp);
+         t->lyt.fp);
     return (2);
   }
 
-  fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t));
+  fprintf(t->lyt.fp, "1..%u\n", tap_grpix(t));
   t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
 }
 
@@ -933,9 +1091,32 @@ static void tap_skipgroup(struct tvec_output *o,
 {
   struct tap_output *t = (struct tap_output *)o;
 
-  fprintf(t->fmt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
-  if (excuse) { fputc(' ', t->fmt.fp); vfprintf(t->fmt.fp, excuse, *ap); }
-  fputc('\n', t->fmt.fp);
+  fprintf(t->lyt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
+  if (excuse) { fputc(' ', t->lyt.fp); vfprintf(t->lyt.fp, excuse, *ap); }
+  fputc('\n', t->lyt.fp);
+}
+
+static void tap_report_unusual(struct tap_output *t,
+                              unsigned nxfail, unsigned nskip)
+{
+  const char *sep = " (";
+  unsigned f = 0;
+#define f_any 1u
+
+  if (nxfail) {
+    fprintf(t->lyt.fp, "%s%u expected %s",
+           sep, nxfail, nxfail == 1 ? "failure" : "failures");
+    sep = ", "; f |= f_any;
+  }
+
+  if (nskip) {
+    fprintf(t->lyt.fp, "%s%u skipped", sep, nskip);
+    sep = ", "; f |= f_any;
+  }
+
+  if (f&f_any) fputc(')', t->lyt.fp);
+
+#undef f_any
 }
 
 static void tap_egroup(struct tvec_output *o)
@@ -945,18 +1126,18 @@ static void tap_egroup(struct tvec_output *o)
   unsigned
     grpix = tap_grpix(t),
     win = tv->curr[TVOUT_WIN],
+    xfail = tv->curr[TVOUT_XFAIL],
     lose = tv->curr[TVOUT_LOSE],
-    skip = tv->curr[TVOUT_SKIP];
-
-  if (lose) {
-    fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u",
-           grpix, tv->test->name, lose, win + lose);
-    if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
-  } else {
-    fprintf(t->fmt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
-    if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
-  }
-  fputc('\n', t->fmt.fp);
+    skip = tv->curr[TVOUT_SKIP],
+    pass = win + xfail;
+
+  if (lose)
+    fprintf(t->lyt.fp, "not ok %u - %s: FAILED %u/%u",
+           grpix, tv->test->name, lose, pass + lose);
+  else
+    fprintf(t->lyt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, pass);
+  tap_report_unusual(t, xfail, skip);
+  fputc('\n', t->lyt.fp);
 }
 
 static void tap_btest(struct tvec_output *o) { ; }
@@ -970,10 +1151,10 @@ static void tap_outcome(struct tvec_output *o, const char *outcome,
   gprintf(&tap_printops, t, "%s:%u: `%s' %s",
          tv->infile, tv->test_lno, tv->test->name, outcome);
   if (detail) {
-    format_string(&t->fmt, ": ", 2);
+    layout_string(&t->lyt, ": ", 2);
     vgprintf(&tap_printops, t, detail, ap);
   }
-  format_char(&t->fmt, '\n');
+  layout_char(&t->lyt, '\n');
 }
 
 static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
@@ -992,10 +1173,11 @@ static void tap_dumpreg(struct tvec_output *o,
          10 + t->maxlen - n, "", ds, rd->name);
   if (!rv) gprintf(&tap_printops, t, "#<unset>");
   else rd->ty->dump(rv, rd, 0, &tap_printops, t);
-  format_char(&t->fmt, '\n');
+  layout_char(&t->lyt, '\n');
 }
 
-static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
+static void tap_etest(struct tvec_output *o, unsigned outcome)
+  { if (outcome == TVOUT_XFAIL) tap_outcome(o, "EXPECTED failure", 0, 0); }
 
 static void tap_bbench(struct tvec_output *o,
                       const char *ident, unsigned unit)
@@ -1010,7 +1192,7 @@ static void tap_ebench(struct tvec_output *o,
 
   gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
   tvec_benchreport(&tap_printops, t, unit, tm);
-  format_char(&t->fmt, '\n');
+  layout_char(&t->lyt, '\n');
 }
 
 static void tap_report(struct tvec_output *o, unsigned level,
@@ -1021,8 +1203,8 @@ static void tap_report(struct tvec_output *o, unsigned level,
   const struct gprintf_ops *gops; void *go;
 
   if (level >= TVLEV_ERR) {
-    fputs("Bail out!  ", t->fmt.fp);
-    gops = &file_printops; go = t->fmt.fp;
+    fputs("Bail out!  ", t->lyt.fp);
+    gops = &file_printops; go = t->lyt.fp;
   } else {
     gops = &tap_printops; go = t;
   }
@@ -1034,8 +1216,8 @@ static void tap_destroy(struct tvec_output *o)
 {
   struct tap_output *t = (struct tap_output *)o;
 
-  destroy_fmt(&t->fmt,
-             t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE);
+  destroy_layout(&t->lyt,
+                t->lyt.fp == stdout || t->lyt.fp == stderr ? 0 : DLF_CLOSE);
   xfree(t->outbuf); xfree(t);
 }
 
@@ -1053,7 +1235,7 @@ struct tvec_output *tvec_tapoutput(FILE *fp)
   struct tap_output *t;
 
   t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
-  init_fmt(&t->fmt, fp, "## ");
+  init_layout(&t->lyt, fp, "## ");
   t->outbuf = 0; t->outsz = 0;
   return (&t->_o);
 }
index 695d66dd9c965f598e64554c31cb5f5ce2d848bf..3afa8086b0eab1b116585877aa534a2d83845e68 100644 (file)
@@ -1764,12 +1764,31 @@ static const struct tvec_iassoc bool_assoc[] = {
   { "y",               1 },
   { "on",              1 },
 
-  { 0,                 0 }
+  TVEC_ENDENUM
 };
 
 const struct tvec_ienuminfo tvenum_bool =
   { "bool", bool_assoc, &tvrange_int };
 
+static const struct tvec_iassoc cmp_assoc[] = {
+  { "<",               -1 },
+  { "less",            -1 },
+  { "lt",              -1 },
+
+  { "=",                0 },
+  { "equal",            0 },
+  { "eq",               0 },
+
+  { ">",               +1 },
+  { "greater",         +1 },
+  { "gt",              +1 },
+
+  TVEC_ENDENUM
+};
+
+const struct tvec_ienuminfo tvenum_cmp =
+  { "cmp", cmp_assoc, &tvrange_int };
+
 /* --- @tvec_claimeq_tenum@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
index d18411ca8077c3cad01ae41f5e99da48c721676d..e9e785af91b78983544b3ddffe20dfc5666ed066 100644 (file)
@@ -432,6 +432,7 @@ enum {
   TVOUT_LOSE,                          /* test failed */
   TVOUT_SKIP,                          /* test skipped */
   TVOUT_WIN,                           /* test passed */
+  TVOUT_XFAIL,                         /* test passed, but shouldn't have */
   TVOUT_LIMIT                          /* (number of possible outcomes) */
 };
 
@@ -439,12 +440,13 @@ struct tvec_state {
   /* The primary state structure for the test vector machinery. */
 
   unsigned f;                          /* flags */
-#define TVSF_SKIP 1u                   /*   skip this test group */
-#define TVSF_OPEN 2u                   /*   test is open */
-#define TVSF_ACTIVE 4u                 /*   test is active */
-#define TVSF_ERROR 8u                  /*   an error occurred */
-#define TVSF_OUTMASK 0xf0              /*   test outcome (@TVOUT_...@) */
+#define TVSF_SKIP 0x0001u              /*   skip this test group */
+#define TVSF_OPEN 0x0002u              /*   test is open */
+#define TVSF_ACTIVE 0x0004u            /*   test is active */
+#define TVSF_ERROR 0x0008u             /*   an error occurred */
+#define TVSF_OUTMASK 0x00f0u           /*   test outcome (@TVOUT_...@) */
 #define TVSF_OUTSHIFT 4                        /*   shift applied to outcome */
+#define TVSF_XFAIL 0x0100u             /*   test expected to fail */
 
   /* Registers.  Available to execution environments. */
   unsigned nrout, nreg;                       /* number of output/total registers */
@@ -652,9 +654,9 @@ extern int tvec_read(struct tvec_state */*tv*/,
 /*----- Command-line interface --------------------------------------------*/
 
 extern const struct tvec_config tvec_adhocconfig;
-/* A special @struct tvec_config@ to use for programs which perform ad-hoc
- * testing.
- */
+  /* A special @struct tvec_config@ to use for programs which perform ad-hoc
  * testing.
  */
 
 /* --- @tvec_parseargs@ --- *
  *
@@ -1815,7 +1817,8 @@ TVEC_MISCSLOTS(DEFINFO)
 #undef DEFINFO
 
 /* Standard enumerations. */
-const struct tvec_ienuminfo tvenum_bool;
+extern const struct tvec_ienuminfo tvenum_bool;
+extern const struct tvec_ienuminfo tvenum_cmp;
 
 /* --- @tvec_claimeq_tenum@, @TVEC_CLAIMEQ_TENUM@ --- *
  *
index 5e13e155e5f0f7685fb7ae544b7e784e74cba4e3..396325ac1d504127eabf49b3d1f843603a7ce148 100644 (file)
@@ -49,11 +49,10 @@ static void swap_test(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 }
 static const struct tvec_env swap_testenv = { 0, 0, 0, 0, swap_test, 0, 0 };
 
-static const struct tvec_irange cmp_range = { -1, +1 };
 static const struct tvec_regdef versioncmp_regs[] = {
   { "v0", RV0, &tvty_string, 0 },
   { "v1", RV1, &tvty_string, 0 },
-  { "rc", RRC, &tvty_int, 0, { &cmp_range } },
+  { "rc", RRC, &tvty_ienum, 0, { &tvenum_cmp } },
   TVEC_ENDREGS
 };
 
index f8924176afea5718dda53491287e820173abd049..75d03ea37771423be5925bc258f578169659084b 100644 (file)
@@ -7,48 +7,48 @@
 
 v0 = 1.2
 v1 = 1.2
-rc = 0
+rc = =
 
 v0 = 1.1
 v1 = 1.2
-rc = -1
+rc = <
 
 v0 = 1.1
 v1 = 1.0
-rc = +1
+rc = >
 
 ;; compare numeric to alphabetic
 
 v0 = 1.0
 v1 = 1.a
-rc = -1
+rc = <
 
 ;; compare epochs
 
 v0 = 1:2.0
 v1 = 2:0.4
-rc = -1
+rc = <
 
 ;; exercise `~'
 
 v0 = 1.2
 v1 = 1.2~pre0
-rc = +1
+rc = >
 
 v0 = 1~~
 v1 = 1~~a
-rc = -1
+rc = <
 
 v0 = 1~~a
 v1 = 1~
-rc = -1
+rc = <
 
 v0 = 1~
 v1 = 1
-rc = -1
+rc = <
 
 ;; juxtaposed alphabetics
 
 v0 = 1
 v1 = 1a
-rc = -1
+rc = <