@@@ tvec doc wip
authorMark Wooding <mdw@distorted.org.uk>
Fri, 8 Mar 2024 00:53:40 +0000 (00:53 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 8 Mar 2024 02:39:12 +0000 (02:39 +0000)
24 files changed:
hash/Makefile.am
hash/t/unihash-testgen [moved from hash/t/unihash-testgen.py with 100% similarity]
hash/tests.at
struct/Makefile.am
struct/t/da-gtest [moved from struct/t/da-gtest.py with 100% similarity]
struct/t/dstr-putf-test.c
struct/t/sym-gtest [moved from struct/t/sym-gtest.py with 100% similarity]
struct/tests.at
t/template-canonify
test/bench.c
test/t/tvec-test.c
test/tests.at
test/tvec-bench.c
test/tvec-core.c
test/tvec-output.c
test/tvec-remote.c
test/tvec-timeout.c
test/tvec-types.c
test/tvec.h
utils/Makefile.am
utils/t/bits-testgen [moved from utils/t/bits-testgen.py with 100% similarity]
utils/t/control-test.c
utils/t/versioncmp-test.c
utils/tests.at

index 564be16..df6537a 100644 (file)
@@ -80,7 +80,7 @@ $(precomp)/unihash-global.c:
                -o$@.new && mv $@.new $@
 endif
 
-EXTRA_DIST             += t/unihash-testgen.py
+EXTRA_DIST             += t/unihash-testgen
 
 ## Test program.
 check_PROGRAMS         += t/hash.t
index 45d4c13..5a2f1f7 100644 (file)
@@ -39,7 +39,7 @@ AT_CLEANUP
 AT_SETUP([hash: unihash])
 AT_KEYWORDS([hash unihash])
 
-$PYTHON SRCDIR/t/unihash-testgen.py >unihash.tests
+$PYTHON SRCDIR/t/unihash-testgen >unihash.tests
 AT_CHECK([BUILDDIR/t/hash.t unihash.tests], [0], [ignore], [ignore])
 
 AT_CLEANUP
index 7996231..124b76e 100644 (file)
@@ -56,7 +56,7 @@ t_darray_t_SOURCES     = t/da-test.c
 t_darray_t_CPPFLAGS     = $(TEST_CPPFLAGS)
 t_darray_t_LDFLAGS      = -static
 
-EXTRA_DIST             += t/da-gtest.py
+EXTRA_DIST             += t/da-gtest
 
 ## Hash tables.
 pkginclude_HEADERS     += hash.h
@@ -73,7 +73,7 @@ t_sym_t_SOURCES                = t/sym-test.c
 t_sym_t_CPPFLAGS        = $(TEST_CPPFLAGS)
 t_sym_t_LDFLAGS                 = -static
 
-EXTRA_DIST             += t/sym-gtest.py
+EXTRA_DIST             += t/sym-gtest
 
 ## Atoms.
 pkginclude_HEADERS     += atom.h
similarity index 100%
rename from struct/t/da-gtest.py
rename to struct/t/da-gtest
index 13fe155..eb67e60 100644 (file)
@@ -1,3 +1,32 @@
+/* -*-c-*-
+ *
+ * Test driver for universal hashing
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
 #include "config.h"
 
 #include <assert.h>
 #include "report.h"
 #include "tvec.h"
 
+/*----- Static variables --------------------------------------------------*/
+
 static struct tvec_state tvstate;
 static dstr d = DSTR_INIT;
 static char strbuf[1024];
 
+/*----- Utilities ---------------------------------------------------------*/
+
 #define TESTGROUP(name) TVEC_TESTGROUP_TAG(grp, &tvstate, name)
 
 static PRINTF_LIKE(1, 2) int format(const char *fmt, ...)
@@ -48,8 +81,8 @@ static PRINTF_LIKE(1, 2) void prepare(const char *fmt, ...)
   int n; n = format fmtargs;                                           \
   tvec_claimeq_int(&tvstate, n, strlen(want),                          \
                   __FILE__, __LINE__, "format " #fmtargs);             \
-  tvec_claimeq_string(&tvstate, d.buf, d.len, want, strlen(want),      \
-                     __FILE__, __LINE__, "format " #fmtargs);          \
+  tvec_claimeq_text(&tvstate, d.buf, d.len, want, strlen(want),        \
+                   __FILE__, __LINE__, "format " #fmtargs);    \
 } while (0)
 
 #define TEST_REF(fmtargs) do {                                         \
@@ -57,13 +90,15 @@ static PRINTF_LIKE(1, 2) void prepare(const char *fmt, ...)
   prepare fmtargs;                                                     \
   tvec_claimeq_int(&tvstate, n, strlen(strbuf),                                \
                   __FILE__, __LINE__, "format " #fmtargs);             \
-  tvec_claimeq_string(&tvstate, d.buf, d.len, strbuf, strlen(strbuf),  \
-                     __FILE__, __LINE__, "format " #fmtargs);          \
+  tvec_claimeq_text(&tvstate, d.buf, d.len, strbuf, strlen(strbuf),    \
+                   __FILE__, __LINE__, "format " #fmtargs);            \
 } while (0)
 
-#define LENGTHY \
+#define LENGTHY                                                                \
   "This is a rather longer string than the code is expecting: will it fit?"
 
+/*----- Main program ------------------------------------------------------*/
+
 int main(int argc, char *argv[])
 {
   struct tvec_test test;
@@ -115,3 +150,5 @@ int main(int argc, char *argv[])
 
   return (tvec_end(&tvstate));
 }
+
+/*----- That's all, folks -------------------------------------------------*/
similarity index 100%
rename from struct/t/sym-gtest.py
rename to struct/t/sym-gtest
index 63d5fc4..6646355 100644 (file)
@@ -32,7 +32,7 @@ AT_SETUP([struct: assoc])
 AT_KEYWORDS([struct assoc])
 AT_SKIP_IF([test "$PYTHON" = :])
 for seed in 0x58677213 0xdfcc2ff4 ""; do
-  $PYTHON SRCDIR/t/sym-gtest.py $seed
+  $PYTHON SRCDIR/t/sym-gtest $seed
   AT_CHECK([BUILDDIR/t/assoc.t <sym.script], [0], [expout])
 done
 AT_CLEANUP
@@ -42,7 +42,7 @@ AT_SETUP([struct: darray])
 AT_KEYWORDS([struct darray])
 AT_SKIP_IF([test "$PYTHON" = :])
 for seed in 0x0394946c 0xe8991664 ""; do
-  $PYTHON SRCDIR/t/da-gtest.py $seed
+  $PYTHON SRCDIR/t/da-gtest $seed
   AT_CHECK([BUILDDIR/t/darray.t <da.script], [0], [expout])
 done
 AT_CLEANUP
@@ -58,7 +58,7 @@ AT_SETUP([struct: sym])
 AT_KEYWORDS([struct sym])
 AT_SKIP_IF([test "$PYTHON" = :])
 for seed in 0xdc0f64a3 0xd0b9fad0 ""; do
-  $PYTHON SRCDIR/t/sym-gtest.py $seed
+  $PYTHON SRCDIR/t/sym-gtest $seed
   AT_CHECK([BUILDDIR/t/sym.t <sym.script], [0], [expout])
 done
 AT_CLEANUP
index a82acde..a8f2737 100755 (executable)
@@ -58,7 +58,7 @@ while True:
     skelsio.write("%s={%s}" % (lit[i], tag[i]))
     xctsio.write(q + pat[i])
     gensio.write(q + "(.*)")
-  q = lit[n]
+  q = RX.escape(lit[n])
   skelsio.write(lit[n]); skel = skelsio.getvalue()
   xctsio.write(q); xctsio.write("$"); xct = xctsio.getvalue()
   gensio.write(q); gensio.write("$"); gen = gensio.getvalue()
index 345944b..1643e55 100644 (file)
@@ -648,12 +648,23 @@ int bench_measure(struct bench_timing *t_out, struct bench_state *b,
 {
   struct bench_timer *tm = b->tm;
   struct bench_time t0, t1;
-  unsigned long n;
+  unsigned long n, nn;
 
   /* Make sure the state is calibrated. */
   if (bench_calibrate(b)) return (-1);
 
-  /* Main adaptive measurement loop. */
+  /* Main adaptive measurement loop.
+   *
+   * Suppose the timer loop %$n$% iterations in %$t$% seconds.  Our ideal
+   * time is %$T$% seconds.  If %$t \ge T/\sqrt{2}$%, we're happy.
+   * Otherwise, we need to scale up the iteration count.  The obvious next
+   * choice is %$n' = n T/t$%.  Alas, rounding is a problem: if
+   * %$T/t < 1 + 1/n$% then %$\floor{n T/t} = n$% and we will make no
+   * progress.  We know that %$T/t > \sqrt{2}%, so this can only happen when
+   * %$1 + 1/n > \sqrt{2}$%, i.e., when %$n < \sqrt{2} + 1$%.  On the other
+   * hand, if %$T/t < 1 + 1/n$% then %$t (n + 1)/n > T$%, so just trying
+   * again with %$n' = n + 1$% iterations will very likely work.
+   */
   debug("measuring..."); n = 1;
   for (;;) {
     tm->ops->now(tm, &t0); fn(n, ctx); tm->ops->now(tm, &t1);
@@ -661,8 +672,10 @@ int bench_measure(struct bench_timing *t_out, struct bench_state *b,
     if (!(t_out->f&BTF_TIMEOK)) return (-1);
     if (!(t_out->f&BTF_CYOK)) debug("  n = %10lu; t = %12g", n, t_out->t);
     else debug("  n = %10lu; t = %12g, cy = %10.0f", n, t_out->t, t_out->cy);
-    if (t_out->t >= 0.72*b->target_s) break;
-    n *= 1.44*b->target_s/t_out->t;
+    if (t_out->t >= 0.707*b->target_s) break;
+    nn = n*b->target_s/t_out->t;
+    if (nn > n) n = nn;
+    else n++;
   }
 
   /* Adjust according to the calibration. */
index ac316b6..aaa4465 100644 (file)
@@ -131,7 +131,7 @@ static const struct tvec_urange range_32 = { 0, 31 };
   _(fenum,     RFE,    fenum,                  p, &fenum_info)         \
   _(penum,     RPE,    penum,                  p, &penum_info)         \
   _(flags,     RF,     flags,                  p, &attr_info)          \
-  _(string,    RSTR,   string,                 p, &range_32)           \
+  _(text,      RTXT,   text,                   p, &range_32)           \
   _(bytes,     RBY,    bytes,                  p, &tvrange_byte)       \
   _(buffer,    RBUF,   buffer,                 p, 0)
 
@@ -221,18 +221,18 @@ static void test_copy_simple
   (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
   { out->v = in->v; }
 
-static void test_copy_string
+static void test_copy_text
   (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
 {
-  tvec_allocstring(&out->v, in->v.str.sz);
-  memcpy(out->v.str.p, in->v.str.p, in->v.str.sz);
+  tvec_alloctext(&out->v, in->v.text.sz);
+  memcpy(out->v.text.p, in->v.text.p, in->v.text.sz);
 }
 
 static void test_copy_bytes
   (const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
 {
-  tvec_allocstring(&out->v, in->v.str.sz);
-  memcpy(out->v.str.p, in->v.str.p, in->v.str.sz);
+  tvec_allocbytes(&out->v, in->v.bytes.sz);
+  memcpy(out->v.bytes.p, in->v.bytes.p, in->v.bytes.sz);
 }
 
 #define test_copy_int test_copy_simple
@@ -394,8 +394,8 @@ static void test_multi_serialize
        ? 0
        : (/*unconst*/ void *)
        ((const struct tvec_penuminfo *)rd->arg.p)->av[0].p;
-    else if (rd->ty == &tvty_string)
-      { if (rv->str.sz) rv->str.p[0] ^= 1; }
+    else if (rd->ty == &tvty_text)
+      { if (rv->text.sz) rv->text.p[0] ^= 1; }
     else if (rd->ty == &tvty_bytes)
       { if (rv->bytes.sz) rv->bytes.p[0] ^= 1; }
   }
index fbb62bb..4bea513 100644 (file)
@@ -233,6 +233,7 @@ test_parse([char], [';'], [';' ; = 59 = 0x3b])
 test_parse([char], [';';?], [';' ; = 59 = 0x3b])
 test_parse([char], [';' ;?], [';' ; = 59 = 0x3b])
 test_parse([char], [\"], ['"' ; = 34 = 0x22]) # "
+test_parse([char], [@%:@], ['@%:@' ; = 35 = 0x23])
 test_parse([char], ['@%:@'], ['@%:@' ; = 35 = 0x23])
 
 test_parse([char], [\n], [@%:@newline ; = '\n' = 10 = 0x0a])
@@ -262,8 +263,6 @@ test_parserr([char], ['''],
        [3], [syntax error: expected character but found `''])
 test_parserr([char], [;],
        [3], [syntax error: expected character but found `;'])
-test_parserr([char], [@%:@],
-       [3], [unknown character name `@%:@'])
 test_parserr([char], [';],
        [3], [syntax error: expected `'' but found @%:@eol])
 test_parserr([char], [\],
@@ -282,41 +281,41 @@ test_parserr([char], [\{012],
 AT_CLEANUP
 
 ###--------------------------------------------------------------------------
-AT_SETUP([tvec type-string])
-
-test_parse([string], [foo], [foo])
-test_parse([string], [foo bar], ["foo bar"])
-test_parse([string], [foo  bar], ["foo  bar"])
-test_parse([string], [foo  ""  bar], [foobar])
-test_parse([string], [ foo  bar ], ["foo  bar"])
-test_parse([string], [ foo @&t@
+AT_SETUP([tvec type-text])
+
+test_parse([text], [foo], [foo])
+test_parse([text], [foo bar], ["foo bar"])
+test_parse([text], [foo  bar], ["foo  bar"])
+test_parse([text], [foo  ""  bar], [foobar])
+test_parse([text], [ foo  bar ], ["foo  bar"])
+test_parse([text], [ foo @&t@
        bar ], ["foo  bar"])
 
-test_parse([string], [foo @%:@nul bar], ["foo\{0}bar"])
+test_parse([text], [foo @%:@nul bar], ["foo\{0}bar"])
 
-test_parse([string], ["f" !repeat 2 { o } "bar"], [foobar])
-test_parse([string], ["{"!repeat 5{"abc"}"}"], ["{abcabcabcabcabc}"])
+test_parse([text], ["f" !repeat 2 { o } "bar"], [foobar])
+test_parse([text], ["{"!repeat 5{"abc"}"}"], ["{abcabcabcabcabc}"])
 
-test_parse([string], [!hex "f" 6f "o"], [foo])
+test_parse([text], [!hex "f" 6f "o"], [foo])
 
-test_parse([string], ["foo\n"], ["foo\n"])
+test_parse([text], ["foo\n"], ["foo\n"])
 
-test_parse([string], [foo\
+test_parse([text], [foo\
 bar], [
        "foo\n"
        "bar"])
-test_parse([string], ["foo\
+test_parse([text], ["foo\
 bar"], [foobar])
-test_parse([string], ["foo" @%:@newline "bar" @%:@newline], [
+test_parse([text], ["foo" @%:@newline "bar" @%:@newline], [
        "foo\n"
        "bar\n"])
 
-test_parserr([string], [],
+test_parserr([text], [],
        [4], [syntax error: expected string but found @%:@eof])
-test_parse([string], [""], [""])
-test_parse([string], [''], [""])
+test_parse([text], [""], [""])
+test_parse([text], [''], [""])
 
-test_parse([string], ["f\x{6f}o"], [foo])
+test_parse([text], ["f\x{6f}o"], [foo])
 
 AT_CLEANUP
 
@@ -348,6 +347,8 @@ test_parse([bytes], [!base64 AAAA], [000000 ; ...])
 test_parse([bytes], [!base64 AA], [00 ; .])
 test_parse([bytes], [!base64 AAA], [0000 ; ..])
 
+test_parserr([text], [],
+       [4], [syntax error: expected string but found @%:@eof])
 test_parserr([bytes], [0],
        [4], [invalid hex sequence end: Excess or nonzero padding bits])
 test_parserr([bytes], [!base64 A],
@@ -420,7 +421,30 @@ z = 1
 check_template([BUILDDIR/t/tvec.t -fh tv], [0],
 [tv:11: `crash' skipped: no connection
 crash: ok (1 skipped)
-PASSED 3 tests (1 skipped) in 1 group
+PASSED ={N:\d+} tests (1 skipped) in 1 group
+])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+AT_SETUP([tvec timeout])
+
+AT_DATA([tv],
+[;;; -*-conf-*-
+
+@<:@sleep@:>@
+
+time = 0.5
+z = 0
+@progress = %RUN
+@exit = killed | SIGALRM
+
+time = 0.125
+z = 1
+])
+check_template([BUILDDIR/t/tvec.t -fh tv], [0],
+[sleep: ok
+PASSED all ={N:\d+} tests in 1 group
 ])
 
 AT_CLEANUP
@@ -441,7 +465,7 @@ uenum = banana
 fenum = tau
 penum = alice
 flags = red-fg | white-bg | bright
-string = "Hello, world!"
+text = "Hello, world!"
 bytes =
        2923be84 e16cd6ae 529049f1 f1bbe9eb
        b3a6db3c 870c3e99 245e0d1c 06b747de
index 7086e14..c1d8cc0 100644 (file)
@@ -81,6 +81,110 @@ static void normalize(double *x_inout, const char **unit_out, double scale)
   *x_inout = x; *unit_out = *u;
 }
 
+/* --- @benchloop_...@ --- *
+ *
+ * Arguments:  @unsigned long n@ = iteration count
+ *             @void *ctx@ = benchmark running context
+ *
+ * Returns:    ---
+ *
+ * Use:                Run various kinds of benchmarking loops.
+ *
+ *               * The @..._outer_...@ functions call the underlying
+ *                 function @n@ times in a loop; by contrast, the
+ *                 @..._inner_...@ functions set a register value to the
+ *                 chosen iteration count and expect the underlying function
+ *                 to perform the loop itself.
+ *
+ *               * The @..._direct@ functions just call the underlying test
+ *                 function directly (though still through an `indirect
+ *                 jump' instruction); by contrast, the @..._indirect@
+ *                 functions invoke a subsidiary environment's @run@
+ *                 function, which adds additional overhead.
+ */
+
+static void benchloop_outer_direct(unsigned long n, void *ctx)
+{
+  struct benchrun *r = ctx;
+  tvec_testfn *fn = r->fn; void *tctx = r->ctx;
+  const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out;
+
+  while (n--) fn(in, out, tctx);
+}
+
+static void benchloop_inner_direct(unsigned long n, void *ctx)
+  { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); }
+
+static void benchloop_outer_indirect(unsigned long n, void *ctx)
+{
+  struct benchrun *r = ctx;
+  struct tvec_state *tv = r->tv;
+  void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run;
+  tvec_testfn *fn = r->fn; void *tctx = r->ctx;
+
+  while (n--) run(tv, fn, tctx);
+}
+
+static void benchloop_inner_indirect(unsigned long n, void *ctx)
+  { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
+
+/*----- Output utilities --------------------------------------------------*/
+
+/* --- @tvec_benchreport@ --- *
+ *
+ * Arguments:  @const struct gprintf_ops *gops@ = print operations
+ *             @void *go@ = print destination
+ *             @unsigned unit@ = the unit being measured (~TVBU_...@)
+ *             @const struct bench_timing *tm@ = the benchmark result
+ *
+ * Returns:    ---
+ *
+ * Use:                Formats a report about the benchmark performance.  This
+ *             function is intended to be called on by an output
+ *             @ebench@ function.
+ */
+
+void tvec_benchreport(const struct gprintf_ops *gops, void *go,
+                     unsigned unit, const struct bench_timing *tm)
+{
+  double scale, x, n = tm->n;
+  const char *u, *what, *whats;
+
+  if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; }
+
+  assert(tm->f&BTF_TIMEOK);
+
+  switch (unit) {
+    case TVBU_OP:
+      gprintf(gops, go, "%.0f iterations ", n);
+      what = "op"; whats = "ops"; scale = 1000;
+      break;
+    case TVBU_BYTE:
+      x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u);
+      what = whats = "B"; scale = 1024;
+      break;
+    default:
+      abort();
+  }
+
+  x = tm->t; normalize(&x, &u, 1000);
+  gprintf(gops, go, "in %.3f %ss", x, u);
+  if (tm->f&BTF_CYOK) {
+    x = tm->cy; normalize(&x, &u, 1000);
+    gprintf(gops, go, " (%.3f %scy)", x, u);
+  }
+  gprintf(gops, go, ": ");
+
+  x = n/tm->t; normalize(&x, &u, scale);
+  gprintf(gops, go, "%.3f %s%s/s", x, u, whats);
+  x = tm->t/n; normalize(&x, &u, 1000);
+  gprintf(gops, go, ", %.3f %ss/%s", x, u, what);
+  if (tm->f&BTF_CYOK) {
+    x = tm->cy/n; normalize(&x, &u, 1000);
+    gprintf(gops, go, " (%.3f %scy/%s)", x, u, what);
+  }
+}
+
 /*----- Benchmark environment scaffolding ---------------------------------*/
 
 /* --- @tvec_benchsetup@ --- *
@@ -145,7 +249,8 @@ fail_timer:
  *             @const char *var@ = variable name to set
  *             @void *ctx@ = context pointer
  *
- * Returns:    Zero on success, @-1@ on failure.
+ * Returns:    %$+1$% on success, %$0$% if the variable name was not
+ *             recognized, or %$-1$% on any other error.
  *
  * Use:                Set a special variable.  The following special variables are
  *             supported.
@@ -198,109 +303,6 @@ void tvec_benchbefore(struct tvec_state *tv, void *ctx)
   if (subenv && subenv->before) subenv->before(tv, bc->subctx);
 }
 
-/* --- @tvec_benchafter@ --- *
- *
- * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @void *ctx@ = context pointer
- *
- * Returns:    ---
- *
- * Use:                Invoke the subordinate environment's @after@ function to
- *             clean up after the benchmark.
- */
-
-void tvec_benchafter(struct tvec_state *tv, void *ctx)
-{
-  struct tvec_benchctx *bc = ctx;
-  const struct tvec_benchenv *be = bc->be;
-  const struct tvec_env *subenv = be->env;
-
-  /* Restore the benchmark state's old target. */
-  bc->bst->target_s = bc->dflt_target;
-  bc->f &= ~TVBF_SETTRG;
-
-  /* Pass the call on to the subsidiary environment. */
-  if (subenv && subenv->after) subenv->after(tv, bc->subctx);
-}
-
-/* --- @tvec_benchteardown@ --- *
- *
- * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @void *ctx@ = context pointer
- *
- * Returns:    ---
- *
- * Use:                Tear down the benchmark environment.
- */
-
-void tvec_benchteardown(struct tvec_state *tv, void *ctx)
-{
-  struct tvec_benchctx *bc = ctx;
-  const struct tvec_benchenv *be;
-  const struct tvec_env *subenv;
-
-  be = bc->be; subenv = be->env;
-
-  /* Tear down any subsidiary environment. */
-  if (subenv && subenv->teardown)
-    subenv->teardown(tv, bc->subctx);
-
-  /* If the benchmark state was temporary, then dispose of it. */
-  if (bc->bst) {
-    if (be->bst) bc->bst->target_s = bc->dflt_target;
-    else { bench_destroy(bc->bst); xfree(bc->bst); }
-  }
-}
-
-/*----- Measurement machinery ---------------------------------------------*/
-
-/* --- @benchloop_...@ --- *
- *
- * Arguments:  @unsigned long n@ = iteration count
- *             @void *ctx@ = benchmark running context
- *
- * Returns:    ---
- *
- * Use:                Run various kinds of benchmarking loops.
- *
- *               * The @..._outer_...@ functions call the underlying
- *                 function @n@ times in a loop; by contrast, the
- *                 @..._inner_...@ functions set a register value to the
- *                 chosen iteration count and expect the underlying function
- *                 to perform the loop itself.
- *
- *               * The @..._direct@ functions just call the underlying test
- *                 function directly (though still through an `indirect
- *                 jump' instruction); by contrast, the @..._indirect@
- *                 functions invoke a subsidiary environment's @run@
- *                 function, which adds additional overhead.
- */
-
-static void benchloop_outer_direct(unsigned long n, void *ctx)
-{
-  struct benchrun *r = ctx;
-  tvec_testfn *fn = r->fn; void *tctx = r->ctx;
-  const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out;
-
-  while (n--) fn(in, out, tctx);
-}
-
-static void benchloop_inner_direct(unsigned long n, void *ctx)
-  { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); }
-
-static void benchloop_outer_indirect(unsigned long n, void *ctx)
-{
-  struct benchrun *r = ctx;
-  struct tvec_state *tv = r->tv;
-  void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run;
-  tvec_testfn *fn = r->fn; void *tctx = r->ctx;
-
-  while (n--) run(tv, fn, tctx);
-}
-
-static void benchloop_inner_indirect(unsigned long n, void *ctx)
-  { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
-
 /* --- @tvec_benchrun@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
@@ -370,60 +372,57 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 #undef f_any
 }
 
-/*----- Output utilities --------------------------------------------------*/
-
-/* --- @tvec_benchreport@ --- *
+/* --- @tvec_benchafter@ --- *
  *
- * Arguments:  @const struct gprintf_ops *gops@ = print operations
- *             @void *go@ = print destination
- *             @unsigned unit@ = the unit being measured (~TVBU_...@)
- *             @const struct bench_timing *tm@ = the benchmark result
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
  *
  * Returns:    ---
  *
- * Use:                Formats a report about the benchmark performance.  This
- *             function is intended to be called on by an output
- *             @ebench@ function.
+ * Use:                Invoke the subordinate environment's @after@ function to
+ *             clean up after the benchmark.
  */
 
-void tvec_benchreport(const struct gprintf_ops *gops, void *go,
-                     unsigned unit, const struct bench_timing *tm)
+void tvec_benchafter(struct tvec_state *tv, void *ctx)
 {
-  double scale, x, n = tm->n;
-  const char *u, *what, *whats;
+  struct tvec_benchctx *bc = ctx;
+  const struct tvec_benchenv *be = bc->be;
+  const struct tvec_env *subenv = be->env;
 
-  if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; }
+  /* Restore the benchmark state's old target. */
+  bc->bst->target_s = bc->dflt_target;
+  bc->f &= ~TVBF_SETTRG;
 
-  assert(tm->f&BTF_TIMEOK);
+  /* Pass the call on to the subsidiary environment. */
+  if (subenv && subenv->after) subenv->after(tv, bc->subctx);
+}
 
-  switch (unit) {
-    case TVBU_OP:
-      gprintf(gops, go, "%.0f iterations ", n);
-      what = "op"; whats = "ops"; scale = 1000;
-      break;
-    case TVBU_BYTE:
-      x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u);
-      what = whats = "B"; scale = 1024;
-      break;
-    default:
-      abort();
-  }
+/* --- @tvec_benchteardown@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Tear down the benchmark environment.
+ */
 
-  x = tm->t; normalize(&x, &u, 1000);
-  gprintf(gops, go, "in %.3f %ss", x, u);
-  if (tm->f&BTF_CYOK) {
-    x = tm->cy; normalize(&x, &u, 1000);
-    gprintf(gops, go, " (%.3f %scy)", x, u);
-  }
-  gprintf(gops, go, ": ");
+void tvec_benchteardown(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_benchctx *bc = ctx;
+  const struct tvec_benchenv *be;
+  const struct tvec_env *subenv;
 
-  x = n/tm->t; normalize(&x, &u, scale);
-  gprintf(gops, go, "%.3f %s%s/s", x, u, whats);
-  x = tm->t/n; normalize(&x, &u, 1000);
-  gprintf(gops, go, ", %.3f %ss/%s", x, u, what);
-  if (tm->f&BTF_CYOK) {
-    x = tm->cy/n; normalize(&x, &u, 1000);
-    gprintf(gops, go, " (%.3f %scy/%s)", x, u, what);
+  be = bc->be; subenv = be->env;
+
+  /* Tear down any subsidiary environment. */
+  if (subenv && subenv->teardown)
+    subenv->teardown(tv, bc->subctx);
+
+  /* If the benchmark state was temporary, then dispose of it. */
+  if (bc->bst) {
+    if (be->bst) bc->bst->target_s = bc->dflt_target;
+    else { bench_destroy(bc->bst); xfree(bc->bst); }
   }
 }
 
index f6d7918..65c74cf 100644 (file)
@@ -744,7 +744,7 @@ void tvec_xfail(struct tvec_state *tv)
 static void check(struct tvec_state *tv, struct groupstate *g)
 {
   const struct tvec_test *t = tv->test;
-  const struct tvec_env *env;
+  const struct tvec_env *env = t->env;
   const struct tvec_regdef *rd;
 
   if (!(tv->f&TVSF_OPEN)) return;
@@ -761,7 +761,6 @@ static void check(struct tvec_state *tv, struct groupstate *g)
 
   if (!(tv->f&TVSF_SKIP)) {
     begin_test(tv);
-    env = t->env;
     if (env && env->before) env->before(tv, g->ctx);
     if (!(tv->f&TVSF_ACTIVE))
       /* setup forced a skip */;
index 5210d11..8c9c214 100644 (file)
@@ -416,38 +416,6 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz)
 #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@
index 62dd150..052ce13 100644 (file)
@@ -99,8 +99,13 @@ static void init_comms(struct tvec_remotecomms *rc)
 
 static void close_comms(struct tvec_remotecomms *rc)
 {
-  if (rc->infd >= 0) { close(rc->infd); rc->infd = -1; }
-  if (rc->outfd >= 0) { close(rc->outfd); rc->outfd = -1; }
+  if (rc->infd >= 0) {
+    if (rc->infd != rc->outfd) close(rc->infd);
+    rc->infd = -1;
+  }
+  if (rc->outfd >= 0)
+    { close(rc->outfd); rc->outfd = -1; }
+  rc->f |= TVRF_BROKEN;
 }
 
 /* --- @release_comms@ --- *
@@ -203,7 +208,7 @@ end:
  *             @size_t min@ = minimum acceptable size to read
  *             @size_t *n_out@ = size read
  *
- * Returns:    An @RECV_...@ code.
+ * Returns:    A @RECV_...@ code.
  *
  * Use:                Receive data on the communication state's input descriptor to
  *             read at least @min@ bytes into the input buffer, even if it
@@ -239,7 +244,7 @@ static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
       p += n; sz -= n; tot += n;
       if (tot >= min) break;
     } else if (!n && !tot && (f&RCVF_ALLOWEOF))
-      return (RECV_EOF);
+      { rc->f |= TVRF_BROKEN; return (RECV_EOF); }
     else
       return (ioerr(tv, rc, "failed to receive: %s",
                    n ? strerror(errno) : "unexpected end-of-file"));
@@ -326,7 +331,7 @@ end:
  *             @unsigned f@ = flags (@RCVF_...@)
  *             @size_t want@ = data block size required
  *
- * Returns:    An @RECV_...@ code.
+ * Returns:    A @RECV_...@ code.
  *
  * Use:                Reads a block of data from the input descriptor into the
  *             input buffer.
@@ -403,7 +408,7 @@ static int receive_buffered(struct tvec_state *tv,
  *             @unsigned f@ = flags (@RCVF_...@)
  *             @buf *b_out@ = buffer to establish around the packet contents
  *
- * Returns:    An @RECV_...@ code.
+ * Returns:    A @RECV_...@ code.
  *
  * Use:                Receive a packet into the input buffer @rc->bin@ and
  *             establish @*b_out@ to read from it.
@@ -780,6 +785,63 @@ bad:
 
 /*----- Server output driver ----------------------------------------------*/
 
+/* --- @remote_bsession@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @struct tvec_state *tv@ = the test state producing output
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin a test session.
+ *
+ *             The remote driver does nothing at all.
+ */
+
+static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
+  { ; }
+
+/* --- @remote_esession@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *
+ * Returns:    Suggested exit code.
+ *
+ * Use:                End a test session.
+ *
+ *             The remote driver returns a suitable exit code without
+ *             printing anything.
+ */
+
+static int remote_esession(struct tvec_output *o)
+  { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
+
+/* --- @remote_bgroup@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin a test group.
+ *
+ *             This is a stub which should never be called.
+ */
+
+static void remote_bgroup(struct tvec_output *o)
+  { assert(!"remote_bgroup"); }
+
+/* --- @remote_skipgroup@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @const char *excuse@, @va_list *ap@ = reason for skipping the
+ *                     group, or null
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a test group is being skipped.
+ *
+ *             The remote driver sends a @TVPK_SKIP@ packet to its client.
+ */
+
 static void remote_skipgroup(struct tvec_output *o,
                             const char *excuse, va_list *ap)
 {
@@ -787,6 +849,51 @@ static void remote_skipgroup(struct tvec_output *o,
     dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
 }
 
+/* --- @remote_egroup@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a test group has finished.
+ *
+ *             This is a stub which should never be called.
+ */
+
+static void remote_egroup(struct tvec_output *o)
+  { assert(!"remote_egroup"); }
+
+/* --- @remote_btest@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a test is starting.
+ *
+ *             This is a stub which should never be called.
+ */
+
+static void remote_btest(struct tvec_output *o)
+  { assert(!"remote_btest"); }
+
+/* --- @remote_skip@, @remote_fail@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @unsigned attr@ = attribute to apply to the outcome
+ *             @const char *outcome@ = outcome string to report
+ *             @const char *detail@, @va_list *ap@ = a detail message
+ *             @const char *excuse@, @va_list *ap@ = reason for skipping the
+ *                     test
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a test has been skipped or failed.
+ *
+ *             The remote driver sends a @TVPK_SKIP@ or @TVPK_FAIL@ packet
+ *             to its client as appropriate.
+ */
+
 static void remote_skip(struct tvec_output *o,
                        const char *excuse, va_list *ap)
 {
@@ -806,6 +913,22 @@ static void remote_fail(struct tvec_output *o,
     }
 }
 
+/* --- @remote_dumpreg@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @unsigned disp@ = register disposition
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register.
+ *
+ *             The remote driver sends a @TVPK_DUMPREG@ packet to its
+ *             client.  This will only work if the register definition is
+ *             one of those listed in the current test definition.
+ */
+
 static void remote_dumpreg(struct tvec_output *o,
                           unsigned disp, const union tvec_regval *rv,
                           const struct tvec_regdef *rd)
@@ -831,6 +954,34 @@ found:
   }
 }
 
+/* --- @remote_etest@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @unsigned outcome@ = the test outcome
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a test has finished.
+ *
+ *             The remote driver does nothing at all.
+ */
+
+static void remote_etest(struct tvec_output *o, unsigned outcome)
+  { ; }
+
+/* --- @remote_bbench@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @const char *ident@ = identifying register values
+ *             @unsigned unit@ = measurement unit (@TVBU_...@)
+ *
+ * Returns:    ---
+ *
+ * Use:                Report that a benchmark has started.
+ *
+ *             The remote driver sends a @TVPK_BBENCH@ packet to its client.
+ */
+
 static void remote_bbench(struct tvec_output *o,
                          const char *ident, unsigned unit)
 {
@@ -840,6 +991,20 @@ static void remote_bbench(struct tvec_output *o,
   }
 }
 
+/* --- @remote_ebench@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @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 remote driver sends a @TVPK_EBENCH@ packet to its client.
+ */
+
 static void remote_ebench(struct tvec_output *o,
                          const char *ident, unsigned unit,
                          const struct bench_timing *t)
@@ -858,6 +1023,23 @@ static void remote_ebench(struct tvec_output *o,
   }
 }
 
+/* --- @remote_report@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *             @unsigned level@ = message level (@TVLEV_...@)
+ *             @const char *msg@, @va_list *ap@ = format string and
+ *                     arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Report a message to the user.
+ *
+ *             The remote driver sends a @TVPK_REPORT@ packet to its
+ *             client.  If its attempt to transmit the packet fails, then
+ *             the message is written to the standard error stream instead,
+ *             in the hope that this will help it be noticed.
+ */
+
 static void remote_report(struct tvec_output *o, unsigned level,
                          const char *msg, va_list *ap)
 {
@@ -871,21 +1053,19 @@ static void remote_report(struct tvec_output *o, unsigned level,
   }
 }
 
-static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
-  { ; }
-static int remote_esession(struct tvec_output *o)
-  { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
+/* --- @remote_destroy@ --- *
+ *
+ * Arguments:  @struct tvec_output *o@ = output sink (ignored)
+ *
+ * Returns:    ---
+ *
+ * Use:                Release the resources held by the output driver.
+ *
+ *             The remote driver does nothing at all.
+ */
+
 static void remote_destroy(struct tvec_output *o)
   { ; }
-static void remote_etest(struct tvec_output *o, unsigned outcome)
-  { ; }
-
-static void remote_bgroup(struct tvec_output *o)
-  { assert(!"remote_bgroup"); }
-static void remote_egroup(struct tvec_output *o)
-  { assert(!"remote_egroup"); }
-static void remote_btest(struct tvec_output *o)
-  { assert(!"remote_btest"); }
 
 static const struct tvec_outops remote_ops = {
   remote_bsession, remote_esession,
@@ -896,21 +1076,20 @@ static const struct tvec_outops remote_ops = {
   remote_destroy
 };
 
-/*----- Client ------------------------------------------------------------*/
-
-#define TVXF_VALMASK 0x0fffu
-#define TVXF_SIG 0x1000u
-#define TVXF_CAUSEMASK 0xe000u
-#define TVXST_RUN 0x0000u
-#define TVXST_EXIT 0x2000u
-#define TVXST_KILL 0x4000u
-#define TVXST_CONT 0x6000u
-#define TVXST_STOP 0x8000u
-#define TVXST_DISCONN 0xa000u
-#define TVXST_UNK 0xc000u
-#define TVXST_ERR 0xe000u
+/*----- Pseudoregister definitions ----------------------------------------*/
 
 static const struct tvec_flag exit_flags[] = {
+
+  /* Cause codes. */
+  { "running",         TVXF_CAUSEMASK,         TVXST_RUN },
+  { "exited",          TVXF_CAUSEMASK,         TVXST_EXIT },
+  { "killed",          TVXF_CAUSEMASK,         TVXST_KILL },
+  { "stopped",         TVXF_CAUSEMASK,         TVXST_STOP },
+  { "continued",       TVXF_CAUSEMASK,         TVXST_CONT },
+  { "disconnected",    TVXF_CAUSEMASK,         TVXST_DISCONN },
+  { "unknown",         TVXF_CAUSEMASK,         TVXST_UNK },
+  { "error",           TVXF_CAUSEMASK,         TVXST_ERR },
+
   /*
     ;;; The signal name table is very boring to type.  To make life less
     ;;; awful, put the signal names in this list and evaluate the code to
@@ -1060,17 +1239,9 @@ static const struct tvec_flag exit_flags[] = {
 #endif
   /***END***/
 
+  /* This should be folded into the signal entries above. */
   { "signal",          TVXF_SIG,               TVXF_SIG },
 
-  { "running",         TVXF_CAUSEMASK,         TVXST_RUN },
-  { "exited",          TVXF_CAUSEMASK,         TVXST_EXIT },
-  { "killed",          TVXF_CAUSEMASK,         TVXST_KILL },
-  { "stopped",         TVXF_CAUSEMASK,         TVXST_STOP },
-  { "continued",       TVXF_CAUSEMASK,         TVXST_CONT },
-  { "disconnected",    TVXF_CAUSEMASK,         TVXST_DISCONN },
-  { "unknown",         TVXF_CAUSEMASK,         TVXST_UNK },
-  { "error",           TVXF_CAUSEMASK,         TVXST_ERR },
-
   TVEC_ENDFLAGS
 };
 
@@ -1079,8 +1250,12 @@ static const struct tvec_flaginfo exit_flaginfo =
 static const struct tvec_regdef exit_regdef =
   { "@exit", 0, &tvty_flags, 0, { &exit_flaginfo } };
 
+/* Progress. */
+
 static const struct tvec_regdef progress_regdef =
-  { "@progress", 0, &tvty_string, 0 };
+  { "@progress", 0, &tvty_text, 0 };
+
+/* Reconnection. */
 
 static const struct tvec_uassoc reconn_assocs[] = {
   { "on-demand",       TVRCN_DEMAND },
@@ -1089,6 +1264,14 @@ static const struct tvec_uassoc reconn_assocs[] = {
   TVEC_ENDENUM
 };
 
+static const struct tvec_uenuminfo reconn_enuminfo =
+  { "remote-reconnection", reconn_assocs, &tvrange_uint };
+static const struct tvec_regdef reconn_regdef =
+  { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } };
+
+/*----- Client ------------------------------------------------------------*/
+
+/* Connection state. */
 enum {
   CONN_BROKEN = -2,                    /* previously broken */
   CONN_FAILED = -1,                    /* attempt freshly failed */
@@ -1096,10 +1279,35 @@ enum {
   CONN_FRESH = 1                       /* freshly connected */
 };
 
-static const struct tvec_uenuminfo reconn_enuminfo =
-  { "remote-reconnection", reconn_assocs, &tvrange_uint };
-static const struct tvec_regdef reconn_regdef =
-  { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } };
+/* --- @handle_packets@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *             @unsigned f@ = receive flags (@RCVF_...@)
+ *             @uint16 end@ = expected end packet type
+ *             @buf *b_out@ = buffer in which to return end packet payload
+ *
+ * Returns:    A @RECV_...@ code.
+ *
+ * Use:                Handles notification packets from the server until a final
+ *             termination packet is received.
+ *
+ *             The client/server protocol consists of a number of flows,
+ *             beginning with a request from the client, followed by a
+ *             number of notifications from the server, and terminated by an
+ *             acknowledgement to the original request indicating that the
+ *             server has completed acting on the original request.
+ *
+ *             This function handles the notifications issued by the server,
+ *             returning when one of the following occurs: (a) a packet of
+ *             type @end@ is received, in which case the function returns
+ *             @RECV_OK@ and the remainder of the packet payload is left in
+ *             @b_out@; (b) the flag @RCVF_ALLOWEOF@ was set in @f@ on entry
+ *             and end-of-file is received at a packet boundary, in which
+ *             case the function returns @RECV_EOF@; or (c) an I/O error
+ *             occurs, in which case @ioerr@ is called and the function
+ *             returns @RECV_FAIL@.
+ */
 
 static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
                          unsigned f, uint16 end, buf *b_out)
@@ -1116,13 +1324,21 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
   int rc;
 
   for (;;) {
-    rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end;
+
+    /* Read the next packet.  If we didn't receive one then end the loop.
+     * Otherwise, retrieve the packet type and check it against @end@: quit
+     * the loop if we get a match.
+     */
+    rc = remote_recv(tv, &r->rc, f, b); if (rc) break;
     if (buf_getu16l(b, &pk)) goto bad;
-    if (pk == end) break;
+    if (pk == end) { rc = 0; break; }
 
+    /* Dispatch based on the packet type. */
     switch (pk) {
 
       case TVPK_PROGRESS:
+       /* A progress report.  Update the saved progress. */
+
        p = buf_getmem16l(b, &n); if (!p) goto bad;
        if (BLEFT(b)) goto bad;
 
@@ -1130,6 +1346,8 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       case TVPK_REPORT:
+       /* A report.  Recover the message and pass it along. */
+
        if (buf_getu16l(b, &u)) goto bad;
        p = buf_getmem16l(b, &n); if (!p) goto bad;
        if (BLEFT(b)) goto bad;
@@ -1139,14 +1357,22 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       case TVPK_SKIPGRP:
+       /* A request to skip the group.  Recover the excuse message and pass
+        * it along.
+        */
+
        p = buf_getmem16l(b, &n); if (!p) goto bad;
-       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
        if (BLEFT(b)) goto bad;
 
+       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
        tvec_skipgroup(tv, "%s", d.buf);
        break;
 
       case TVPK_SKIP:
+       /* A request to skip the test.  Recover the excuse message and pass
+        * it along, if it's not unreasonable.
+        */
+
        if (!(tv->f&TVSF_ACTIVE)) {
          rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name);
          goto end;
@@ -1160,6 +1386,10 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       case TVPK_FAIL:
+       /* A report that the test failed.  Recover the detail message, if
+        * any, and pass it along, if it's not unreasonable.
+        */
+
        if (!(tv->f&TVSF_ACTIVE) &&
            ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) {
          rc = ioerr(tv, &r->rc, "test `%s' not active or failing",
@@ -1181,6 +1411,9 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       case TVPK_DUMPREG:
+       /* A request to dump a register. */
+
+       /* Find the register definition. */
        if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
        for (rd = tv->test->regs, i = 0; rd->name; rd++, i++)
          if (i == u) goto found_reg;
@@ -1194,6 +1427,9 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
          goto end;
        }
 
+       /* Read the flag.  If there's no register value, then `dump' its
+        * absence.  Otherwise retrieve the register value and dump it.
+        */
        rc = buf_getbyte(b); if (rc < 0) goto bad;
        if (!rc)
          tvec_dumpreg(tv, v, 0, rd);
@@ -1209,6 +1445,8 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       case TVPK_BBENCH:
+       /* A report that we're starting a benchmark.  Pass this along. */
+
        p = buf_getmem32l(b, &n); if (!p) goto bad;
        if (buf_getu16l(b, &u)) goto bad;
        if (BLEFT(b)) goto bad;
@@ -1222,6 +1460,8 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       case TVPK_EBENCH:
+       /* A report that a benchmark completed.  Pass this along. */
+
        p = buf_getmem32l(b, &n); if (!p) goto bad;
        if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
        if (u >= TVBU_LIMIT) {
@@ -1238,12 +1478,13 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
        break;
 
       default:
+       /* Something else.  This is unexpected. */
+
        rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
        goto end;
     }
   }
 
-  rc = RECV_OK;
 end:
   DDESTROY(&d);
   xfree(reg);
@@ -1252,11 +1493,36 @@ bad:
   rc = malformed(tv, &r->rc); goto end;
 }
 
+/* --- @reap_kid@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns:    ---
+ *
+ * Use:                Determine the exit status of a broken connection, setting
+ *             @r->exit@ appropriately.
+ *
+ *             If @r->kid@ is negative, the exit status has already been
+ *             set, and nothing further happens; this is not an error.
+ *
+ *             If @r->kid@ is zero, then there is no real child process
+ *             (e.g., because the remote connection is a network connection
+ *             or similar), so @r->exit@ is set equal to @RVXST_DISCONN@.
+ *
+ *             If @r->kid@ is positive, then it holds a child process id;
+ *             the function waits for it to end and collects its exit status
+ *
+ *             It is an error to call this function if the connection is not
+ *             broken.
+ */
+
 static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
 {
   pid_t kid;
   int st;
 
+  assert(r->rc.f&TVRF_BROKEN);
   if (!r->kid)
     { r->exit = TVXST_DISCONN; r->kid = -1; }
   else if (r->kid > 0) {
@@ -1285,6 +1551,20 @@ static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
   }
 }
 
+/* --- @report_errline@ --- *
+ *
+ * Arguments:  @char *p@ = pointer to the line
+ *             @size_t n@ = length in characters
+ *             @void *ctx@ = context, secretly a @struct tvec_remotectx@
+ *
+ * Returns:    ---
+ *
+ * Use:                Print a line of stderr output from the child.  If
+ *             @TVRF_MUFFLE@ is set, then discard the line silently.
+ *
+ *             This is an @lbuf_func@, invoked via @drain_errfd@.
+ */
+
 static void report_errline(char *p, size_t n, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
@@ -1294,6 +1574,29 @@ static void report_errline(char *p, size_t n, void *ctx)
     tvec_notice(tv, "child process stderr: %s", p);
 }
 
+/* --- @drain_errfd@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *             @unsigned f@ = receive flags (@ERF_...@)
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Collect material written by the child to its stderr stream
+ *             and report it.
+ *
+ *             If @f@ has @ERF_SILENT@ set, then discard the stderr material
+ *             without reporting it.  Otherwise it is reported as
+ *             @TVLEV_NOTE@.
+ *
+ *             if @f@ has @ERF_CLOSE@ set, then continue reading until
+ *             end-of-file is received; also, report any final partial line,
+ *             and close @r->errfd@.
+ *
+ *             If @r->errfd@ is already closed, or never established, then
+ *             do nothing and return successfully.
+ */
+
 #define ERF_SILENT 0x0001u
 #define ERF_CLOSE 0x0002u
 static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
@@ -1303,6 +1606,10 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
   ssize_t n;
   int rc;
 
+  /* Preliminaries.  Bail if there is no error stream to fetch.  Arrange
+   * (rather clumsily) to muffle the output if we're supposed to be client.
+   * And set the nonblocking state on @errfd@ appropriately.
+   */
   if (r->errfd < 0) { rc = 0; goto end; }
   if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
   else r->rc.f &= ~TVRF_MUFFLE;
@@ -1312,6 +1619,7 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
     goto end;
   }
 
+  /* Read pieces of error output and feed them into the line buffer. */
   for (;;) {
     sz = lbuf_free(&r->errbuf, &p);
     n = read(r->errfd, p, sz);
@@ -1326,6 +1634,8 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
       }
     lbuf_flush(&r->errbuf, p, n);
   }
+
+  /* Done. */
   rc = 0;
 end:
   if (f&ERF_CLOSE) {
@@ -1335,6 +1645,24 @@ end:
   return (rc);
 }
 
+/* --- @disconnect_remote@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *             @unsigned f@ = receive flags (@DCF_...@)
+ *
+ * Returns:    ---
+ *
+ * Use:                Disconnect and shut down all of the remote client state.
+ *
+ *             If @f@ has @DCF_KILL@ set then send the child process (if
+ *             any) @SIGTERM@ to make sure it shuts down in a timely manner.
+ *
+ *             In detail: this function closes the @infd@ and @outfd@
+ *             descriptors, drains and closes @errfd@, and collects the exit
+ *             status (if any).
+ */
+
 #define DCF_KILL 0x0100u
 static void disconnect_remote(struct tvec_state *tv,
                              struct tvec_remotectx *r, unsigned f)
@@ -1344,6 +1672,16 @@ static void disconnect_remote(struct tvec_state *tv,
   drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
 }
 
+/* --- @connect_remote@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Connect to the test server.
+ */
+
 static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
 {
   const struct tvec_remoteenv *re = r->re;
@@ -1352,19 +1690,26 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
   uint16 v;
   int infd = -1, outfd = -1, errfd = -1, rc;
 
-  DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
+  /* If we're already connected, then there's nothing to do. */
   if (r->kid >= 0) { rc = 0; goto end; }
+
+  /* Set the preliminary progress indication. */
+  DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
+
+  /* Call the connection function to establish descriptors. */
   if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
     { rc = -1; goto end; }
+
+  /* Establish communications state. */
   setup_comms(&r->rc, infd, outfd); r->kid = kid; r->errfd = errfd;
   lbuf_init(&r->errbuf, report_errline, r);
   r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
 
+  /* Do version negotiation. */
   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) {
     dbuf_putu16l(&r->rc.bout, 0);
     dbuf_putu16l(&r->rc.bout, 0);
   } else { rc = -1; goto end; }
-
   if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b))
     { rc = -1; goto end; }
   if (buf_getu16l(&b, &v)) goto bad;
@@ -1373,14 +1718,18 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
     rc = ioerr(tv, &r->rc, "protocol version %u not supported", v);
     goto end;
   }
+  r->ver = v;
 
+  /* Begin the test group at the server. */
   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP)
     dbuf_putstr16l(&r->rc.bout, tv->test->name);
   else { rc = -1; goto end; }
   if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b))
     { rc = -1; goto end; }
   if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
-  r->ver = v; rc = 0;
+
+  /* Done. */
+  rc = 0;
 end:
   if (rc) disconnect_remote(tv, r, DCF_KILL);
   return (rc);
@@ -1388,6 +1737,19 @@ bad:
   rc = malformed(tv, &r->rc); goto end;
 }
 
+/* --- @check_comms@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns:    A @CONN_...@ code reflecting the current communication
+ *             state.
+ *
+ * Use:                Determine the current connection state.  If the connection
+ *             has recently broken (i.e., @TVRF_BROKEN@ is set in @r->rc.f@)
+ *             since the last time we checked then disconnect.
+ */
+
 static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
 {
   if (r->kid < 0)
@@ -1398,6 +1760,17 @@ static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
     return (CONN_ESTABLISHED);
 }
 
+/* --- @try_reconnect@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns:    A @CONN_...@ code reflecting the new communication state.
+ *
+ * Use:                Reconnects to the server according to the configured
+ *             @TVRCN_...@ policy.
+ */
+
 static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
 {
   int rc;
@@ -1425,6 +1798,17 @@ static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
   return (rc);
 }
 
+/*----- Remote environment ------------------------------------------------*/
+
+/* --- @reset_vars@ --- *
+ *
+ * Arguments:  @struct tvec_remotectx *r@ = remote client context
+ *
+ * Returns:    ---
+ *
+ * Use:                Reset the pseudoregisters set through @tvec_remoteset@.
+ */
+
 static void reset_vars(struct tvec_remotectx *r)
 {
   r->exwant = TVXST_RUN;
@@ -1432,6 +1816,22 @@ static void reset_vars(struct tvec_remotectx *r)
   DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
 }
 
+/* --- @tvec_remotesetup@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @const struct tvec_env *env@ = environment description
+ *             @void *pctx@ = parent context (ignored)
+ *             @void *ctx@ = context pointer to initialize
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a timeout environment context.
+ *
+ *             The environment description should be a @struct
+ *             tvec_remoteenv@ subclass suitable for use by the @connect@
+ *             function.
+ */
+
 void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
                      void *pctx, void *ctx)
 {
@@ -1449,6 +1849,29 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
   reset_vars(r);
 }
 
+/* --- @tvec_remoteset@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @const char *var@ = variable name to set
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    %$+1$% on success, %$0$% if the variable name was not
+ *             recognized, or %$-1$% on any other error.
+ *
+ * Use:                Set a special variable.  The following special variables are
+ *             supported.
+ *
+ *               * %|@exit|% is the expected exit status; see @TVXF_...@ and
+ *                 @TVXST_...@.
+ *
+ *               * %|progress|% is the expected progress token when the test
+ *                 completes.  On successful completion, this will be
+ *                 %|%DONE|%; it's %|%RUN|% on entry to the test function,
+ *                 but that can call @tvec_setprogress@ to change it.
+ *
+ *               * %|reconnect|% is a reconnection policy; see @TVRCN_...@.
+ */
+
 int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
@@ -1461,13 +1884,13 @@ int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx)
     r->exwant = rv.u; r->rc.f |= TVRF_SETEXIT; rc = 1;
   } else if (STRCMP(var, ==, "@progress")) {
     if (r->rc.f&TVRF_SETPRG) { rc = tvec_dupreg(tv, var); goto end; }
-    tvty_string.init(&rv, &progress_regdef);
-    rc = tvty_string.parse(&rv, &progress_regdef, tv);
+    tvty_text.init(&rv, &progress_regdef);
+    rc = tvty_text.parse(&rv, &progress_regdef, tv);
     if (!rc) {
-      DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz);
+      DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.text.p, rv.text.sz);
       r->rc.f |= TVRF_SETPRG;
     }
-    tvty_string.release(&rv, &progress_regdef);
+    tvty_text.release(&rv, &progress_regdef);
     if (rc) { rc = -1; goto end; }
     rc = 1;
   } else if (STRCMP(var, ==, "@reconnect")) {
@@ -1482,12 +1905,16 @@ end:
   return (rc);
 }
 
-void tvec_remoteafter(struct tvec_state *tv, void *ctx)
-{
-  struct tvec_remotectx *r = ctx;
-
-  reset_vars(r);
-}
+/* --- @tvec_remoterun@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @tvec_testfn *fn@ = test function to run
+ *             @void *ctx@ = context pointer for the test function
+ *
+ * Returns:    ---
+ *
+ * Use:                Run a test on a remote server.
+ */
 
 void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 {
@@ -1500,6 +1927,7 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
   buf b;
   int rc;
 
+  /* Reconnect to the server according to policy. */
   switch (try_reconnect(tv, r)) {
     case CONN_FAILED:
       tvec_skip(tv, "failed to connect to test backend"); return;
@@ -1507,29 +1935,61 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
       tvec_skip(tv, "no connection"); return;
   }
 
+  /* Set initial progress state. */
   DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
+
+  /* Send the command to the server and handle output. */
   QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST)
     tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout),
                   tv->test->regs, tv->nreg, tv->regsz);
-  else { rc = -1; goto end; }
+  else { goto fail; }
   rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
+
+  /* Deal with the outcome. */
   switch (rc) {
+
     case RECV_FAIL:
-      goto end;
+      /* Some kind of error.  Abandon ship. */
+
+    fail:
+      tvec_skip(tv, "remote test runner communications failed");
+      disconnect_remote(tv, r, 0);
+      break;
+
     case RECV_EOF:
+      /* End-of-file at a packet boundary.  The server crashed trying to run
+       * our test.  Collect the exit status and continue.
+       */
       reap_kid(tv, r);
       /* fall through */
+
     case RECV_OK:
+      /* Successful completion (or EOF). */
+
+      /* Notice if the exit status isn't right. */
       if (r->exit != r->exwant) f |= f_exit;
+
+      /* Notice if the progress token isn't right. */
       if (r->progress.len != r->prgwant.len ||
          MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
        f |= f_progress;
+
+      /* If we found something wrong but the test is passing so far, then
+       * report the failure and dump the input registers.
+       */
       if (f && (tv->f&TVSF_ACTIVE))
        { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
+
+      /* If the test failed, then report the exit and progress states
+       * relative to their expectations.
+       */
       if (!(tv->f&TVSF_ACTIVE) &&
          (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
+
+       /* Note here that the test failed. */
        f |= f_fail;
 
+       /* Report exit status. */
        rv.u = r->exit;
        tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
                     &rv, &exit_regdef);
@@ -1538,32 +1998,56 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
          tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef);
        }
 
-       rv.str.p = r->progress.buf; rv.str.sz = r->progress.len;
+       /* Report progress token. */
+       rv.text.p = r->progress.buf; rv.text.sz = r->progress.len;
        tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
                     &rv, &progress_regdef);
        if (f&f_progress) {
-         rv.str.p = r->prgwant.buf; rv.str.sz = r->prgwant.len;
+         rv.text.p = r->prgwant.buf; rv.text.sz = r->prgwant.len;
          tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef);
        }
       }
 
+      /* If we received end-of-file, then close the connection.  Suppress
+       * error output if the test passed: it was presumably expected.
+       */
       if (rc == RECV_EOF)
        disconnect_remote(tv, r, f ? 0 : ERF_SILENT);
       break;
   }
 
-end:
-  if (rc) {
-    if ((tv->f&TVSF_ACTIVE) && f)
-      tvec_skip(tv, "remote test runner communications failed");
-    disconnect_remote(tv, r, 0);
-  }
-
 #undef f_exit
 #undef f_progress
 #undef f_fail
 }
 
+/* --- @tvec_remoteafter@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Reset variables to their default values.
+ */
+
+void tvec_remoteafter(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+
+  reset_vars(r);
+}
+
+/* --- @tvec_remoteteardown@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Tear down the remote environment.
+ */
+
 void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
@@ -1580,6 +2064,27 @@ void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
 
 /*----- Connectors --------------------------------------------------------*/
 
+/* --- @fork_common@ --- *
+ *
+ * Arguments:  @pid_t *kid_out@ = where to put child process-id
+ *             @int *infd_out, *outfd_out, *errfd_out@ = where to put file
+ *                     descriptors
+ *             @struct tvec_state *tv@ = test vector state
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Common @fork@ machinery for the connectors.  Create a
+ *             subprocess.  On successful return, in the subprocess,
+ *             @*kid_out@ is zero, and the error descriptor replaces the
+ *             standard-error descriptor; in the parent, @*kid_out@ is the
+ *             child process-id, and @*errfd_out@ is a descriptor on which
+ *             the child's standard-error output can be read; in both
+ *             @*infd_out@ and @*outfd_out@ are descriptors for input and
+ *             output respectively -- they're opposite ends of pipes, but
+ *             obviously they're crossed over so that the parent's output
+ *             matches the child's input and %%\emph{vice versa}%%.
+ */
+
 static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
                       int *errfd_out, struct tvec_state *tv)
 {
@@ -1587,6 +2092,7 @@ static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
   pid_t kid = -1;
   int rc;
 
+  /* Try to create the pipes. */
   if (pipe(p0) || pipe(p1) || pipe(pe) ||
       fdflags(p0[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
       fdflags(p1[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC) ||
@@ -1595,8 +2101,12 @@ static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
     rc = -1; goto end;
   }
 
+  /* Flush all of the stream buffers so that we don't get duplicated
+   * output.
+   */
   fflush(0);
 
+  /* Try to set up the child process. */
   kid = fork();
   if (kid < 0) {
     tvec_error(tv, "fork failed: %s", strerror(errno));
@@ -1604,23 +2114,30 @@ static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
   }
 
   if (!kid) {
+    /* Child process. */
+
     *kid_out = 0;
     *infd_out = p0[0]; p0[0] = -1;
     *outfd_out = p1[1]; p1[1] = -1;
     if (pe[1] != STDERR_FILENO && dup2(pe[1], STDERR_FILENO) < 0) {
       fprintf(stderr, "failed to establish child stderr: %s",
              strerror(errno));
-      exit(127);
+      _exit(127);
     }
   } else {
+    /* Parent process. */
+
     *kid_out = kid; kid = -1;
     *infd_out = p1[0]; p1[0] = -1;
     *outfd_out = p0[1]; p0[1] = -1;
     *errfd_out = pe[0]; pe[0] = -1;
   }
 
+  /* All done. */
   rc = 0;
+
 end:
+  /* Clean up.  So much of this... */
   if (p0[0] >= 0) close(p0[0]);
   if (p0[1] >= 0) close(p0[1]);
   if (p1[0] >= 0) close(p1[0]);
@@ -1630,6 +2147,21 @@ end:
   return (rc);
 }
 
+/* --- @tvec_fork@ --- *
+ *
+ * Arguments:  @pid_t *kid_out@ = where to put child process-id
+ *             @int *infd_out, *outfd_out, *errfd_out@ = where to put file
+ *                     descriptors
+ *             @struct tvec_state *tv@ = test vector state
+ *             @const struct tvec_remoteenv@ = the remote environment
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Starts a remote server running in a fork of the main
+ *             process.  This is useful for testing functions which might --
+ *             or are even intended to -- crash.
+ */
+
 int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
              struct tvec_state *tv, const struct tvec_remoteenv *env)
 {
@@ -1654,6 +2186,22 @@ end:
   return (rc);
 }
 
+/* --- @tvec_exec@ --- *
+ *
+ * Arguments:  @pid_t *kid_out@ = where to put child process-id
+ *             @int *infd_out, *outfd_out, *errfd_out@ = where to put file
+ *                     descriptors
+ *             @struct tvec_state *tv@ = test vector state
+ *             @const struct tvec_remoteenv@ = the remote environment
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Starts a remote server by running some program.  The command
+ *             given in the environment description will probably some hairy
+ *             shell rune allowing for configuration via files or
+ *             environment variables.
+ */
+
 int tvec_exec(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out,
              struct tvec_state *tv, const struct tvec_remoteenv *env)
 {
index ca86b5c..d9a614b 100644 (file)
@@ -83,7 +83,8 @@ void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env,
  *             @const char *var@ = variable name to set
  *             @void *ctx@ = context pointer
  *
- * Returns:    Zero on success, @-1@ on failure.
+ * Returns:    %$+1$% on success, %$0$% if the variable name was not
+ *             recognized, or %$-1$% on any other error.
  *
  * Use:                Set a special variable.  The following special variables are
  *             supported.
@@ -112,12 +113,22 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
 #define f_all (f_time | f_timer)
 
   if (STRCMP(var, ==, "@timeout")) {
+    /* Parse a timeout specification. */
+
+    /* If we've already set one then report an error. */
     if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
 
     for (;;) {
+      /* Continue until we run out of things. */
+
+      /* Read the next word. */
       DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
     timeout_primed:
+
+      /* Start parsing the word. */
       p = d.buf;
+
+      /* Check for timer tokens. */
       if (!(f&f_timer) && STRCMP(p, ==, "REAL"))
        { tmr = ITIMER_REAL; f |= f_timer;  }
       else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL"))
@@ -125,26 +136,42 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
       else if (!(f&f_timer) && STRCMP(p, ==, "PROF"))
        { tmr = ITIMER_PROF; f |= f_timer; }
 
+      /* Otherwise, check for durations. */
       else if (!(f&f_time)) {
+
+       /* Skip leading stuff that isn't digits.  This is a hedge against
+        * @strtod@ interpreting something unhelpful like a NaN.
+        */
        if (*p == '+' || *p == '-') p++;
        if (*p == '.') p++;
        if (!ISDIGIT(*p)) {
          tvec_syntax(tv, *d.buf, "floating-point number");
          rc = -1; goto end;
        }
+
+       /* Parse the number and check that it's reasonable. */
        errno = 0; t = strtod(p, &q); f |= f_time;
        if (errno) {
          tvec_error(tv, "invalid floating-point number `%s': %s",
                     d.buf, strerror(errno));
          rc = -1; goto end;
        }
+       if (t < 0) {
+         tvec_error(tv, "invalid duration `%s': %s",
+                    d.buf, strerror(errno));
+         rc = -1; goto end;
+       }
 
+       /* We're now on the lookout for units.  If there's nothing here then
+        * fetch the next word.
+        */
        if (!*q) {
          tvec_skipspc(tv); pos = d.len;
          if (!tvec_readword(tv, &d, ";", 0)) pos++;
          q = d.buf + pos;
        }
 
+       /* Match various units. */
        if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
          /* nothing to do */;
 
@@ -175,32 +202,45 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
        else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400;
        else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600;
 
+       /* Not a unit specification after all.  If we've already selected a
+        * timer, then this is just junk so report the error.  Otherwise, we
+        * snarfed the next token too early, so move it to the start of the
+        * buffer and go round again.
+        */
        else {
          if (f&f_timer)
            { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; }
          pos = q - d.buf; d.len -= pos;
-         memmove(d.buf, q, d.len);
+         memmove(d.buf, q, d.len + 1);
          goto timeout_primed;
        }
       }
 
+      /* If we've read all that we need to, then stop. */
       if (!(~f&f_all)) break;
     }
 
+    /* If we didn't get anything, that's a problem. */
     if (!f) {
       rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
       goto end;
     }
+
+    /* Make sure there's nothing else on the line. */
     rc = tvec_flushtoeol(tv, 0); if (rc) goto end;
     if (f&f_time) tc->t = t;
     if (f&f_timer) tc->timer = tmr;
     tc->f |= TVTF_SETTMO;
+    rc = 1;
 
   } else if (subenv && subenv->set)
+    /* Not one of ours: pass it on to the sub-environment. */
     rc = subenv->set(tv, var, tc->subctx);
   else
+    /* No subenvironment.  Report the error. */
     rc = 0;
 
+  /* Done. */
 end:
   dstr_destroy(&d);
   return (rc);
@@ -231,83 +271,83 @@ void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
   if (subenv && subenv->before) subenv->before(tv, tc->subctx);
 }
 
-/* --- @tvec_timeoutafter@ --- *
+/* --- @tvec_timeoutrun@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @void *ctx@ = context pointer
+ *             @tvec_testfn *fn@ = test function to run
+ *             @void *ctx@ = context pointer for the test function
  *
  * Returns:    ---
  *
- * Use:                Invoke the subordinate environment's @after@ function to
- *             clean up after the test.
+ * Use:                Runs a test with a timeout attached.
  */
 
-void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
+void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 {
   struct tvec_timeoutctx *tc = ctx;
   const struct tvec_timeoutenv *te = tc->te;
   const struct tvec_env *subenv = te->env;
+  struct itimerval itv;
 
-  /* Reset variables. */
-  reset(tc);
+  itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
+  itv.it_value.tv_sec = tc->t;
+  itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6;
 
-  /* Pass the call on to the subsidiary environment. */
-  if (subenv && subenv->after) subenv->after(tv, tc->subctx);
+  if (setitimer(tc->timer, &itv, 0))
+    tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
+  else {
+    if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
+    else fn(tv->in, tv->out, tc->subctx);
+
+    itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
+    itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0;
+    if (setitimer(tc->timer, &itv, 0))
+      tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno));
+  }
 }
 
-/* --- @tvec_timeoutteardown@ --- *
+/* --- @tvec_timeoutafter@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @void *ctx@ = context pointer
  *
  * Returns:    ---
  *
- * Use:                Tear down the timeoutmark environment.
+ * Use:                Invoke the subordinate environment's @after@ function to
+ *             clean up after the test.
  */
 
-void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
+void tvec_timeoutafter(struct tvec_state *tv, void *ctx)
 {
   struct tvec_timeoutctx *tc = ctx;
   const struct tvec_timeoutenv *te = tc->te;
   const struct tvec_env *subenv = te->env;
 
-  /* Just call the subsidiary environment. */
-  if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
+  /* Reset variables. */
+  reset(tc);
+
+  /* Pass the call on to the subsidiary environment. */
+  if (subenv && subenv->after) subenv->after(tv, tc->subctx);
 }
 
-/* --- @tvec_timeoutrun@ --- *
+/* --- @tvec_timeoutteardown@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @tvec_testfn *fn@ = test function to run
- *             @void *ctx@ = context pointer for the test function
+ *             @void *ctx@ = context pointer
  *
  * Returns:    ---
  *
- * Use:                Runs a test with a timeout attached.
+ * Use:                Tear down the timeoutmark environment.
  */
 
-void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
+void tvec_timeoutteardown(struct tvec_state *tv, void *ctx)
 {
   struct tvec_timeoutctx *tc = ctx;
   const struct tvec_timeoutenv *te = tc->te;
   const struct tvec_env *subenv = te->env;
-  struct itimerval itv;
-
-  itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
-  itv.it_value.tv_sec = tc->t;
-  itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6;
-
-  if (setitimer(tc->timer, &itv, 0))
-    tvec_skip(tv, "failed to set interval timer: %s", strerror(errno));
-  else {
-    if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx);
-    else fn(tv->in, tv->out, tc->subctx);
 
-    itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0;
-    itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0;
-    if (setitimer(tc->timer, &itv, 0))
-      tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno));
-  }
+  /* Just call the subsidiary environment. */
+  if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx);
 }
 
 /*----- That's all, folks -------------------------------------------------*/
index 681409d..acb848c 100644 (file)
@@ -1241,36 +1241,36 @@ end:
   return (rc);
 }
 
-/*----- Skeleton ----------------------------------------------------------*/
-/*
-static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd)
-static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd)
-static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1,
-                 const struct tvec_regdef *rd)
-static int tobuf_...(buf *b, const union tvec_regval *rv,
-                    const struct tvec_regdef *rd)
-static int frombuf_...(buf *b, union tvec_regval *rv,
-                      const struct tvec_regdef *rd)
-static int parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
-                    struct tvec_state *tv)
-static void dump_...(const union tvec_regval *rv,
-                    const struct tvec_regdef *rd,
-                    struct tvec_state *tv, unsigned style)
-
-const struct tvec_regty tvty_... = {
-  init_..., release_..., eq_...,
-  tobuf_..., frombuf_...,
-  parse_..., dump_...
-};
-*/
 /*----- Signed and unsigned integer types ---------------------------------*/
 
+/* --- @init_int@, @init_uint@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a register value.
+ *
+ *             Integer values are initialized to zero.
+ */
+
 static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd)
   { rv->i = 0; }
 
 static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd)
   { rv->u = 0; }
 
+/* --- @eq_int@, @eq_uint@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv0, *rv1@ = register values
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Nonzero if the values are equal, zero if unequal
+ *
+ * Use:                Compare register values for equality.
+ */
+
 static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1,
                  const struct tvec_regdef *rd)
   { return (rv0->i == rv1->i); }
@@ -1280,6 +1280,20 @@ static int eq_uint(const union tvec_regval *rv0,
                   const struct tvec_regdef *rd)
   { return (rv0->u == rv1->u); }
 
+/* --- @tobuf_int@, @tobuf_uint@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Serialize a register value to a buffer.
+ *
+ *             Integer values are serialized as little-endian 64-bit signed
+ *             or unsigned integers.
+ */
+
 static int tobuf_int(buf *b, const union tvec_regval *rv,
                     const struct tvec_regdef *rd)
   { return (signed_to_buf(b, rv->i)); }
@@ -1288,6 +1302,20 @@ static int tobuf_uint(buf *b, const union tvec_regval *rv,
                       const struct tvec_regdef *rd)
   { return (unsigned_to_buf(b, rv->u)); }
 
+/* --- @frombuf_int@, @frombuf_uint@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Deserialize a register value from a buffer.
+ *
+ *             Integer values are serialized as 64-bit signed or unsigned
+ *             integers.
+ */
+
 static int frombuf_int(buf *b, union tvec_regval *rv,
                       const struct tvec_regdef *rd)
   { return (signed_from_buf(b, &rv->i)); }
@@ -1296,18 +1324,51 @@ static int frombuf_uint(buf *b, union tvec_regval *rv,
                        const struct tvec_regdef *rd)
   { return (unsigned_from_buf(b, &rv->u)); }
 
+/* --- @parse_int@, @parse_uint@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             Integers may be input in decimal, hex, binary, or octal,
+ *             following approximately usual conventions.
+ *
+ *               * Signed integers may be preceded with a `+' or `-' sign.
+ *
+ *               * Decimal integers are just a sequence of decimal digits
+ *                 `0' ... `9'.
+ *
+ *               * Octal integers are a sequence of digits `0' ... `7',
+ *                 preceded by `0o' or `0O'.
+ *
+ *               * Hexadecimal integers are a sequence of digits `0'
+ *                 ... `9', `a' ... `f', or `A' ... `F', preceded by `0x' or
+ *                 `0X'.
+ *
+ *               * Radix-B integers are a sequence of digits `0' ... `9',
+ *                 `a' ... `f', or `A' ... `F', each with value less than B,
+ *                 preceded by `Br' or `BR', where 0 < B < 36 is expressed
+ *                 in decimal without any leading `0' or internal
+ *                 underscores `_'.
+ *
+ *               * A digit sequence may contain internal underscore `_'
+ *                 separators, but not before or after all of the digits;
+ *                 and two consecutive `_' characters are not permitted.
+ */
+
 static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
                     struct tvec_state *tv)
 {
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "signed integer"))
-    { rc = -1; goto end; }
-  if (parse_signed(&rv->i, d.buf, rd->arg.p, tv))
-    { rc = -1; goto end; }
-  if (tvec_flushtoeol(tv, 0))
-    { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; }
+  if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
+  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
 end:
   dstr_destroy(&d);
@@ -1320,18 +1381,31 @@ static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "unsigned integer"))
-    { rc = -1; goto end; }
-  if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv))
-    { rc = -1; goto end; }
-  if (tvec_flushtoeol(tv, 0))
-    { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; }
+  if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
+  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
 end:
   dstr_destroy(&d);
   return (rc);
 }
 
+/* --- @dump_int@, @dump_uint@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Integer values are dumped in decimal and, unless compact
+ *             output is requested, hex, and maybe a character, as a
+ *             comment.
+ */
+
 static void dump_int(const union tvec_regval *rv,
                     const struct tvec_regdef *rd,
                     unsigned style,
@@ -1359,12 +1433,19 @@ static void dump_uint(const union tvec_regval *rv,
   }
 }
 
+/* Integer type definitions. */
 const struct tvec_regty tvty_int = {
   init_int, trivial_release, eq_int,
   tobuf_int, frombuf_int,
   parse_int, dump_int
 };
+const struct tvec_regty tvty_uint = {
+  init_uint, trivial_release, eq_uint,
+  tobuf_uint, frombuf_uint,
+  parse_uint, dump_uint
+};
 
+/* Predefined integer ranges. */
 const struct tvec_irange
   tvrange_schar = { SCHAR_MIN, SCHAR_MAX },
   tvrange_short = { SHRT_MIN, SHRT_MAX },
@@ -1373,13 +1454,6 @@ const struct tvec_irange
   tvrange_sbyte = { -128, 127 },
   tvrange_i16 = { -32768, +32767 },
   tvrange_i32 = { -2147483648, 2147483647 };
-
-const struct tvec_regty tvty_uint = {
-  init_uint, trivial_release, eq_uint,
-  tobuf_uint, frombuf_uint,
-  parse_uint, dump_uint
-};
-
 const struct tvec_urange
   tvrange_uchar = { 0, UCHAR_MAX },
   tvrange_ushort = { 0, USHRT_MAX },
@@ -1441,21 +1515,93 @@ int tvec_claimeq_uint(struct tvec_state *tv,
 
 /*----- Floating-point type -----------------------------------------------*/
 
+/* --- @float_int@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a register value.
+ *
+ *             Floating-point values are initialized to zero.
+ */
+
 static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd)
   { rv->f = 0.0; }
 
+/* --- @eq_float@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv0, *rv1@ = register values
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Nonzero if the values are equal, zero if unequal
+ *
+ * Use:                Compare register values for equality.
+ *
+ *             Floating-point values may be considered equal if their
+ *             absolute or relative difference is sufficiently small, as
+ *             described in the register definition.
+ */
+
 static int eq_float(const union tvec_regval *rv0,
                    const union tvec_regval *rv1,
                    const struct tvec_regdef *rd)
   { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); }
 
+/* --- @tobuf_float@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Serialize a register value to a buffer.
+ *
+ *             Floating-point values are serialized as little-endian
+ *             IEEE 754 Binary64.
+ */
+
 static int tobuf_float(buf *b, const union tvec_regval *rv,
                     const struct tvec_regdef *rd)
   { return (buf_putf64l(b, rv->f)); }
+
+/* --- @frombuf_float@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Deserialize a register value from a buffer.
+ *
+ *             Floating-point values are serialized as little-endian
+ *             IEEE 754 Binary64.
+ */
+
 static int frombuf_float(buf *b, union tvec_regval *rv,
                       const struct tvec_regdef *rd)
   { return (buf_getf64l(b, &rv->f)); }
 
+/* --- @parse_float@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             Floating-point values are either NaN (%|#nan|%, if supported
+ *             by the platform); positive or negative infinity (%|#inf|%,
+ *             %|+#inf|%, or %|#+inf|% (preferring the last), and %|-#inf|%
+ *             or %|#-inf|% (preferring the latter), if supported by the
+ *             platform); or a number in strtod(3) syntax.
+ */
+
 static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
                       struct tvec_state *tv)
 {
@@ -1464,28 +1610,49 @@ static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
 
   if (tvec_readword(tv, &d, ";", "floating-point number"))
     { rc = -1; goto end; }
-  if (parse_floating(&rv->f, d.buf, rd->arg.p, tv))
-    { rc = -1; goto end; }
-  if (tvec_flushtoeol(tv, 0))
-    { rc = -1; goto end; }
+  if (parse_floating(&rv->f, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
+  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
 end:
   dstr_destroy(&d);
   return (rc);
 }
 
+/* --- @dump_float@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Floating-point values are dumped in decimal or as a special
+ *             token beginning with `%|#|%'.  Some effort is taken to ensure
+ *             that the output is sufficient to uniquely identify the
+ *             original value, but, honestly, C makes this really hard.
+ */
+
 static void dump_float(const union tvec_regval *rv,
                       const struct tvec_regdef *rd,
                       unsigned style,
                       const struct gprintf_ops *gops, void *go)
   { format_floating(gops, go, rv->f); }
 
+/* Floating-point type definition. */
 const struct tvec_regty tvty_float = {
   init_float, trivial_release, eq_float,
   tobuf_float, frombuf_float,
   parse_float, dump_float
 };
 
+/* Predefined floating-point ranges. */
+const struct tvec_floatinfo
+  tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 },
+  tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 };
+
 /* --- @tvec_claimeqish_float@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -1562,20 +1729,48 @@ int tvec_claimeq_float(struct tvec_state *tv,
                                file, lno, expr));
 }
 
-const struct tvec_floatinfo
-  tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 },
-  tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 };
-
 /*----- Enumerations ------------------------------------------------------*/
 
+/* --- @init_tenum@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a register value.
+ *
+ *             Integer and floating-point enumeration values are initialized
+ *             as their underlying representations.  Pointer enumerations
+ *             are initialized to %|#nil|%.
+ */
+
 #define init_ienum init_int
 #define init_uenum init_uint
 #define init_fenum init_float
+
 static void init_penum(union tvec_regval *rv, const struct tvec_regdef *rd)
   { rv->p = 0; }
 
+/* --- @eq_tenum@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv0, *rv1@ = register values
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Nonzero if the values are equal, zero if unequal
+ *
+ * Use:                Compare register values for equality.
+ *
+ *             Integer and floating-point enumeration values are compared as
+ *             their underlying representations; in particular, floating-
+ *             point enumerations may compare equal if their absolute or
+ *             relative difference is sufficiently small.  Pointer
+ *             enumerations are compared as pointers.
+ */
+
 #define eq_ienum eq_int
 #define eq_uenum eq_uint
+
 static int eq_fenum(const union tvec_regval *rv0,
                    const union tvec_regval *rv1,
                    const struct tvec_regdef *rd)
@@ -1583,14 +1778,33 @@ static int eq_fenum(const union tvec_regval *rv0,
   const struct tvec_fenuminfo *ei = rd->arg.p;
   return (eqish_floating_p(rv0->f, rv1->f, ei->fi));
 }
+
 static int eq_penum(const union tvec_regval *rv0,
                    const union tvec_regval *rv1,
                    const struct tvec_regdef *rd)
   { return (rv0->p == rv1->p); }
 
+/* --- @tobuf_tenum@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Serialize a register value to a buffer.
+ *
+ *             Integer and floating-point enumeration values are serialized
+ *             as their underlying representations.  Pointer enumerations
+ *             are serialized as the signed integer index into the
+ *             association table; %|#nil|% serializes as %$-1$%, and
+ *             unrecognized pointers cause failure.
+ */
+
 #define tobuf_ienum tobuf_int
 #define tobuf_uenum tobuf_uint
 #define tobuf_fenum tobuf_float
+
 static int tobuf_penum(buf *b, const union tvec_regval *rv,
                       const struct tvec_regdef *rd)
 {
@@ -1606,6 +1820,23 @@ found:
   return (signed_to_buf(b, i));
 }
 
+/* --- @frombuf_tenum@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Deserialize a register value from a buffer.
+ *
+ *             Integer and floating-point enumeration values are serialized
+ *             as their underlying representations.  Pointer enumerations
+ *             are serialized as the signed integer index into the
+ *             association table; %|#nil|% serializes as %$-1$%; out-of-
+ *             range indices cause failure.
+ */
+
 #define frombuf_ienum frombuf_int
 #define frombuf_uenum frombuf_uint
 #define frombuf_fenum frombuf_float
@@ -1624,6 +1855,23 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
   return (0);
 }
 
+/* --- @parse_tenum@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             An enumerated value may be given by name or as a literal
+ *             value.  For enumerations based on numeric types, the literal
+ *             values can be written in the same syntax as the underlying
+ *             values.  For enumerations based on pointers, the only
+ *             permitted literal is %|#nil|%, which denotes a null pointer.
+ */
+
 #define DEFPARSE_ENUM(tag_, ty, slot)                                  \
   static int parse_##slot##enum(union tvec_regval *rv,                 \
                                const struct tvec_regdef *rd,           \
@@ -1693,6 +1941,27 @@ TVEC_MISCSLOTS(DEFPARSE_ENUM)
 
 #undef DEFPARSE_ENUM
 
+/* --- @dump_tenum@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Enumeration values are dumped as their symbolic names, if
+ *             possible, with the underlying values provided as a comment
+ *             unless compact output is requested, as for the underlying
+ *             representation.  A null pointer is printed as %|#nil|%;
+ *             non-null pointers are printed as %|#<TYPE PTR>|%, with the
+ *             enumeration TYPE and the raw pointer PTR printed with the
+ *             system's %|%p|% format specifier.
+ */
+
+
 #define DEFDUMP_ENUM(tag_, ty, slot)                                   \
   static void dump_##slot##enum(const union tvec_regval *rv,           \
                                const struct tvec_regdef *rd,           \
@@ -1713,7 +1982,7 @@ TVEC_MISCSLOTS(DEFPARSE_ENUM)
   }
 
 #define MAYBE_PRINT_EXTRA                                              \
-       if (style&TVSF_COMPACT) ;                                       \
+       if (style&TVSF_COMPACT) /* nothing to do */;                    \
        else if (!a->tag) { gprintf(gops, go, " ; = "); goto _extra; }  \
        else if (1) { gprintf(gops, go, " = "); goto _extra; }          \
        else _extra:
@@ -1745,6 +2014,7 @@ TVEC_MISCSLOTS(DEFDUMP_ENUM)
 #undef MAYBE_PRINT_EXTRA
 #undef DEFDUMP_ENUM
 
+/* Enumeration type definitions. */
 #define DEFTY_ENUM(tag, ty, slot)                                      \
   const struct tvec_regty tvty_##slot##enum = {                                \
     init_##slot##enum, trivial_release, eq_##slot##enum,               \
@@ -1754,6 +2024,7 @@ TVEC_MISCSLOTS(DEFDUMP_ENUM)
 TVEC_MISCSLOTS(DEFTY_ENUM)
 #undef DEFTY_ENUM
 
+/* Predefined enumeration types. */
 static const struct tvec_iassoc bool_assoc[] = {
   { "nil",             0 },
   { "false",           0 },
@@ -1838,6 +2109,27 @@ TVEC_MISCSLOTS(DEFCLAIM)
 
 /*----- Flag types --------------------------------------------------------*/
 
+/* Flag types are initialized, compared, and serialized as unsigned
+ * integers.
+ */
+
+/* --- @parse_flags@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             The input syntax is a sequence of items separated by `|'
+ *             signs.  Each item may be the symbolic name of a field value,
+ *             or a literal unsigned integer.  The masks associated with the
+ *             given symbolic names must be disjoint.  The resulting
+ *             numerical value is simply the bitwise OR of the given values.
+ */
+
 static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
                       struct tvec_state *tv)
 {
@@ -1848,10 +2140,13 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
   int ch, rc;
 
   for (;;) {
+
+    /* Read the next item. */
     DRESET(&d);
     if (tvec_readword(tv, &d, "|;", "flag name or integer"))
       { rc = -1; goto end; }
 
+    /* Try to find a matching entry in the table. */
     for (f = fi->fv; f->tag; f++)
       if (STRCMP(f->tag, ==, d.buf)) {
        if (m&f->m)
@@ -1860,23 +2155,54 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
          { m |= f->m; v |= f->v; goto next; }
       }
 
+    /* Otherwise, try to parse it as a raw integer. */
     if (parse_unsigned(&t, d.buf, fi->range, tv))
       { rc = -1; goto end; }
     v |= t;
+
   next:
+    /* Advance to the next token.  If it's a separator then consume it, and
+     * go round again.  Otherwise we stop here.
+     */
     if (tvec_nexttoken(tv)) break;
     ch = getc(tv->fp);
       if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; }
-    if (tvec_nexttoken(tv))
+      if (tvec_nexttoken(tv))
       { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; }
   }
-  rv->u = v;
-  rc = 0;
+
+  /* Done. */
+  rv->u = v; rc = 0;
 end:
   dstr_destroy(&d);
   return (rc);
 }
 
+/* --- @dump_flags@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             The table of symbolic names and their associated values and
+ *             masks is repeatedly scanned, in order, to find disjoint
+ *             matches -- i.e., entries whose value matches the target value
+ *             in the bit positions indicated by the mask, and whose mask
+ *             doesn't overlap with any previously found matches; the names
+ *             are then output, separated by `|'.  Any remaining nonzero
+ *             bits not covered by any of the matching masks are output as a
+ *             single literal integer, in hex.
+ *
+ *             Unless compact output is requested, or no symbolic names were
+ *             found, the raw numeric value is also printed in hex, as a
+ *             comment.
+ */
+
 static void dump_flags(const union tvec_regval *rv,
                       const struct tvec_regdef *rd,
                       unsigned style,
@@ -1884,7 +2210,7 @@ static void dump_flags(const union tvec_regval *rv,
 {
   const struct tvec_flaginfo *fi = rd->arg.p;
   const struct tvec_flag *f;
-  unsigned long m = ~(unsigned long)0, v = rv->u;
+  unsigned long m = ~0ul, v = rv->u;
   const char *sep;
 
   for (f = fi->fv, sep = ""; f->tag; f++)
@@ -1895,10 +2221,11 @@ static void dump_flags(const union tvec_regval *rv,
 
   if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m);
 
-  if (!(style&TVSF_COMPACT))
+  if (m != ~0ul && !(style&TVSF_COMPACT))
     gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
 }
 
+/* Flags type definition. */
 const struct tvec_regty tvty_flags = {
   init_uint, trivial_release, eq_uint,
   tobuf_uint, frombuf_uint,
@@ -1936,16 +2263,47 @@ int tvec_claimeq_flags(struct tvec_state *tv,
 
 /*----- Characters --------------------------------------------------------*/
 
+/* Character values are initialized and compared as signed integers. */
+
+/* --- @tobuf_char@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Serialize a register value to a buffer.
+ *
+ *             Character values are serialized as little-endian 32-bit
+ *             unsigned integers, with %|EOF|% serialized as all-bits-set.
+ */
+
 static int tobuf_char(buf *b, const union tvec_regval *rv,
                      const struct tvec_regdef *rd)
 {
   uint32 u;
+
   if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
   else if (rv->i == EOF) u = MASK32;
   else return (-1);
   return (buf_putu32l(b, u));
 }
 
+/* --- @frombuf_char@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Deserialize a register value from a buffer.
+ *
+ *             Character values are serialized as little-endian 32-bit
+ *             unsigned integers, with %|EOF|% serialized as all-bits-set.
+ */
+
 static int frombuf_char(buf *b, union tvec_regval *rv,
                        const struct tvec_regdef *rd)
 {
@@ -1958,6 +2316,75 @@ static int frombuf_char(buf *b, union tvec_regval *rv,
   return (0);
 }
 
+/* --- @parse_char@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             A character value can be given by symbolic name, with a
+ *             leading `%|#|%'; or a character or `%|\|%'-escape sequence,
+ *             optionally in single quotes.
+ *
+ *             The following escape sequences and character names are
+ *             recognized.
+ *
+ *             * `%|#eof|%' is the special end-of-file marker.
+ *
+ *             * `%|#nul|%' is the NUL character, sometimes used to
+ *               terminate strings.
+ *
+ *             * `%|bell|%', `%|bel|%', `%|ding|%', or `%|\a|%' is the BEL
+ *               character used to ring the terminal bell (or do some other
+ *               thing to attract the user's attention).
+ *
+ *             * %|#backspace|%, %|#bs|%, or %|\b|% is the backspace
+ *               character, used to move the cursor backwords by one cell.
+ *
+ *             * %|#escape|% %|#esc|%, or%|\e|% is the escape character,
+ *               used to introduce special terminal commands.
+ *
+ *             * %|#formfeed|%, %|#ff|%, or %|\f|% is the formfeed
+ *               character, used to separate pages of text.
+ *
+ *             * %|#newline|%, %|#linefeed|%, %|#lf|%, %|#nl|%, or %|\n|% is
+ *               the newline character, used to terminate lines of text or
+ *               advance the cursor to the next line (perhaps without
+ *               returning it to the start of the line).
+ *
+ *             * %|#return|%, %|#carriage-return|%, %|#cr|%, or %|\r|% is
+ *               the carriage-return character, used to return the cursor to
+ *               the start of the line.
+ *
+ *             * %|#tab|%, %|#horizontal-tab|%, %|#ht|%, or %|\t|% is the
+ *               tab character, used to advance the cursor to the next tab
+ *               stop on the current line.
+ *
+ *             * %|#vertical-tab|%, %|#vt|%, %|\v|% is the vertical tab
+ *               character.
+ *
+ *             * %|#space|%, %|#spc|% is the space character.
+ *
+ *             * %|#delete|%, %|#del|% is the delete character, used to
+ *               erase the most recent character.
+ *
+ *             * %|\'|% is the single-quote character.
+ *
+ *             * %|\\|% is the backslash character.
+ *
+ *             * %|\"|% is the double-quote character.
+ *
+ *             * %|\NNN|% or %|\{NNN}|% is the character with code NNN in
+ *               octal.  The NNN may be up to three digits long.
+ *
+ *             * %|\xNN|% or %|\x{NN}|% is the character with code NNN in
+ *               hexadecimal.
+ */
+
 static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
                      struct tvec_state *tv)
 {
@@ -1966,40 +2393,77 @@ static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
   unsigned f = 0;
 #define f_quote 1u
 
+  /* Inspect the character to see what we're up against. */
   ch = getc(tv->fp);
+
   if (ch == '#') {
+    /* It looks like a special token.  Push the `%|#|%' back and fetch the
+     * whole word.  If there's just the `%|#|%' after all, then treat it as
+     * literal.
+     */
+
     ungetc(ch, tv->fp);
     if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
-    if (read_charname(&ch, d.buf, RCF_EOFOK)) {
-      rc = tvec_error(tv, "unknown character name `%s'", d.buf);
-      goto end;
+    if (STRCMP(d.buf, !=, "#")) {
+      if (read_charname(&ch, d.buf, RCF_EOFOK)) {
+       rc = tvec_error(tv, "unknown character name `%s'", d.buf);
+       goto end;
+      }
+      if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+      rv->i = ch; rc = 0; goto end;
     }
-    if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
-    rv->i = ch; rc = 0; goto end;
   }
 
+  /* If this is a single quote then we expect to see a matching one later,
+   * and we should process backslash escapes.  Get the next character and see
+   * what happens.
+   */
   if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); }
+
+  /* Main character dispatch. */
   switch (ch) {
+
     case ';':
+      /* Unquoted, semicolon begins a comment. */
       if (!(f&f_quote)) { rc = tvec_syntax(tv, ch, "character"); goto end; }
-      goto plain;
+      else goto plain;
+
     case '\n':
-      if (f&f_quote)
-       { f &= ~f_quote; ungetc(ch, tv->fp); ch = '\''; goto plain; }
+      /* A newline.  If we saw a single quote, then treat that as literal.
+       * Otherwise this is an error.
+       */
+      if (!(f&f_quote)) goto nochar;
+      else { f &= ~f_quote; ungetc(ch, tv->fp); ch = '\''; goto plain; }
+
     case EOF:
-      if (f&f_quote) { f &= ~f_quote; ch = '\''; goto plain; }
-      /* fall through */
-    case '\'':
+      /* End-of-file.  Similar to newline, but with slightly different
+       * effects on the parse state.
+       */
+      if (!(f&f_quote)) goto nochar;
+      else { f &= ~f_quote; ch = '\''; goto plain; }
+
+    case '\'': nochar:
+      /* A single quote.  This must be the second of a pair, and there should
+       * have been a character or escape sequence between them.
+       */
       rc = tvec_syntax(tv, ch, "character"); goto end;
+
     case '\\':
+      /* A backslash.  Read a character escape. */
       if (read_charesc(&ch, tv)) return (-1);
+
     default: plain:
+      /* Anything else.  Treat as literal. */
       rv->i = ch; break;
   }
+
+  /* If we saw an opening quote, then expect the closing quote. */
   if (f&f_quote) {
     ch = getc(tv->fp);
     if (ch != '\'') { rc = tvec_syntax(tv, ch, "`''"); goto end; }
   }
+
+  /* Done. */
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
 end:
@@ -2009,6 +2473,25 @@ end:
 #undef f_quote
 }
 
+/* --- @dump_char@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Character values are dumped as their symbolic names, if any,
+ *             or as a character or escape sequence within single quotes
+ *             (which may be omitted in compact style).  If compact output
+ *             is not requested, then the single-quoted representation (for
+ *             characters dumped as symbolic names) and integer code in
+ *             decimal and hex are printed as a comment.
+ */
+
 static void dump_char(const union tvec_regval *rv,
                      const struct tvec_regdef *rd,
                      unsigned style,
@@ -2018,6 +2501,7 @@ static void dump_char(const union tvec_regval *rv,
   unsigned f = 0;
 #define f_semi 1u
 
+  /* Print a character name if we can find one. */
   p = find_charname(rv->i, (style&TVSF_COMPACT) ? CTF_SHORT : CTF_PREFER);
   if (p) {
     gprintf(gops, go, "%s", p);
@@ -2025,6 +2509,9 @@ static void dump_char(const union tvec_regval *rv,
     else { gprintf(gops, go, " ;"); f |= f_semi; }
   }
 
+  /* If the character isn't @EOF@ then print it as a single-quoted thing.
+   * In compact style, see if we can omit the quotes.
+   */
   if (rv->i >= 0) {
     if (f&f_semi) gprintf(gops, go, " = ");
     switch (rv->i) {
@@ -2038,6 +2525,7 @@ static void dump_char(const union tvec_regval *rv,
     }
   }
 
+  /* And the character code as an integer. */
   if (!(style&TVSF_COMPACT)) {
     if (!(f&f_semi)) gprintf(gops, go, " ;");
     gprintf(gops, go, " = %ld = ", rv->i);
@@ -2047,6 +2535,7 @@ static void dump_char(const union tvec_regval *rv,
 #undef f_semi
 }
 
+/* Character type definition. */
 const struct tvec_regty tvty_char = {
   init_int, trivial_release, eq_int,
   tobuf_char, frombuf_char,
@@ -2079,27 +2568,62 @@ int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1,
 
 /*----- Text and byte strings ---------------------------------------------*/
 
-static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd)
-  { rv->str.p = 0; rv->str.sz = 0; }
+/* --- @init_text@, @init_bytes@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a register value.
+ *
+ *             Text and binary string values are initialized with a null
+ *             pointer and zero length.
+ */
+
+static void init_text(union tvec_regval *rv, const struct tvec_regdef *rd)
+  { rv->text.p = 0; rv->text.sz = 0; }
 
 static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd)
   { rv->bytes.p = 0; rv->bytes.sz = 0; }
 
-static void release_string(union tvec_regval *rv,
-                         const struct tvec_regdef *rd)
-  { xfree(rv->str.p); }
+/* --- @release_string@, @release_bytes@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Release resources held by a register value.
+ *
+ *             Text and binary string buffers are freed.
+ */
+
+static void release_text(union tvec_regval *rv,
+                        const struct tvec_regdef *rd)
+  { xfree(rv->text.p); }
 
 static void release_bytes(union tvec_regval *rv,
                          const struct tvec_regdef *rd)
   { xfree(rv->bytes.p); }
 
-static int eq_string(const union tvec_regval *rv0,
-                    const union tvec_regval *rv1,
-                    const struct tvec_regdef *rd)
+/* --- @eq_text@, @eq_bytes@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv0, *rv1@ = register values
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Nonzero if the values are equal, zero if unequal
+ *
+ * Use:                Compare register values for equality.
+ */
+
+static int eq_text(const union tvec_regval *rv0,
+                  const union tvec_regval *rv1,
+                  const struct tvec_regdef *rd)
 {
-  return (rv0->str.sz == rv1->str.sz &&
-         (!rv0->bytes.sz ||
-          MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz)));
+  return (rv0->text.sz == rv1->text.sz &&
+         (!rv0->text.sz ||
+          MEMCMP(rv0->text.p, ==, rv1->text.p, rv1->text.sz)));
 }
 
 static int eq_bytes(const union tvec_regval *rv0,
@@ -2111,22 +2635,52 @@ static int eq_bytes(const union tvec_regval *rv0,
           MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz)));
 }
 
-static int tobuf_string(buf *b, const union tvec_regval *rv,
-                       const struct tvec_regdef *rd)
-  { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); }
+/* --- @tobuf_text@, @tobuf_bytes@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Serialize a register value to a buffer.
+ *
+ *             Text and binary string values are serialized as a little-
+ *             endian 64-bit length %$n$% in bytes followed by %$n$% bytes
+ *             of string data.
+ */
+
+static int tobuf_text(buf *b, const union tvec_regval *rv,
+                     const struct tvec_regdef *rd)
+  { return (buf_putmem64l(b, rv->text.p, rv->text.sz)); }
 
 static int tobuf_bytes(buf *b, const union tvec_regval *rv,
                       const struct tvec_regdef *rd)
-  { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); }
+  { return (buf_putmem64l(b, rv->bytes.p, rv->bytes.sz)); }
 
-static int frombuf_string(buf *b, union tvec_regval *rv,
-                         const struct tvec_regdef *rd)
+/* --- @frombuf_text@, @frombuf_bytes@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Deserialize a register value from a buffer.
+ *
+ *             Text and binary string values are serialized as a little-
+ *             endian 64-bit length %$n$% in bytes followed by %$n$% bytes
+ *             of string data.
+ */
+
+static int frombuf_text(buf *b, union tvec_regval *rv,
+                       const struct tvec_regdef *rd)
 {
   const void *p;
   size_t sz;
 
-  p = buf_getmem32l(b, &sz); if (!p) return (-1);
-  tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz); rv->str.p[sz] = 0;
+  p = buf_getmem64l(b, &sz); if (!p) return (-1);
+  tvec_alloctext(rv, sz); memcpy(rv->text.p, p, sz); rv->text.p[sz] = 0;
   return (0);
 }
 
@@ -2136,11 +2690,23 @@ static int frombuf_bytes(buf *b, union tvec_regval *rv,
   const void *p;
   size_t sz;
 
-  p = buf_getmem32l(b, &sz); if (!p) return (-1);
+  p = buf_getmem64l(b, &sz); if (!p) return (-1);
   tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz);
   return (0);
 }
 
+/* --- @check_string_length@ --- *
+ *
+ * Arguments:  @size_t sz@ = found string length
+ *             @const struct tvec_urange *ur@ = acceptable range
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Checks that @sz@ is within the bounds described by @ur@,
+ *             reporting an error if not.
+ */
+
 static int check_string_length(size_t sz, const struct tvec_urange *ur,
                               struct tvec_state *tv)
 {
@@ -2151,15 +2717,58 @@ static int check_string_length(size_t sz, const struct tvec_urange *ur,
   return (0);
 }
 
-static int parse_string(union tvec_regval *rv, const struct tvec_regdef *rd,
-                       struct tvec_state *tv)
+/* --- @parse_text@, @parse_bytes@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             The input format for both kinds of strings is basically the
+ *             same: a `compound string', consisting of
+ *
+ *               * single-quoted strings, which are interpreted entirely
+ *                 literally, but can't contain single quotes or newlines;
+ *
+ *               * double-quoted strings, in which `%|\|%'-escapes are
+ *                 interpreted as for characters;
+ *
+ *               * character names, marked by an initial `%|#|%' sign;
+ *
+ *               * special tokens marked by an initial `%|!|%' sign; or
+ *
+ *               * barewords interpreted according to the current coding
+ *                 scheme.
+ *
+ *             The special tokens are
+ *
+ *               * `%|!bare|%', which causes subsequent sequences of
+ *                 barewords to be treated as plain text;
+ *
+ *               * `%|!hex|%', `%|!base32|%', `%|!base64|%', which cause
+ *                 subsequent barewords to be decoded in the requested
+ *                 manner.
+ *
+ *               * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%',
+ *                 which includes %$n$% copies of the (compound) string.
+ *
+ *             The only difference between text and binary strings is that
+ *             the initial coding scheme is %|bare|% for text strings and
+ *             %|hex|% for binary strings.
+ */
+
+static int parse_text(union tvec_regval *rv, const struct tvec_regdef *rd,
+                     struct tvec_state *tv)
 {
-  void *p = rv->str.p;
+  void *p = rv->text.p;
 
-  if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, 0, tv))
+  if (read_compound_string(&p, &rv->text.sz, TVCODE_BARE, 0, tv))
     return (-1);
-  rv->str.p = p;
-  if (check_string_length(rv->str.sz, rd->arg.p, tv)) return (-1);
+  rv->text.p = p;
+  if (check_string_length(rv->text.sz, rd->arg.p, tv)) return (-1);
   return (0);
 }
 
@@ -2175,19 +2784,45 @@ static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
   return (0);
 }
 
-static void dump_string(const union tvec_regval *rv,
-                       const struct tvec_regdef *rd,
-                       unsigned style,
-                       const struct gprintf_ops *gops, void *go)
+/* --- @dump_text@, @dump_bytes@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Text string values are dumped as plain text, in double quotes
+ *             if necessary, and using backslash escape sequences for
+ *             nonprintable characters.  Unless compact output is requested,
+ *             strings consisting of multiple lines are dumped with each
+ *             line of the string on a separate output line.
+ *
+ *             Binary string values are dumped in hexadecimal.  In compact
+ *             style, the output simply consists of a single block of hex
+ *             digits.  Otherwise, the dump is a display consisting of
+ *             groups of hex digits, with comments showing the offset (if
+ *             the string is long enough) and the corresponding plain text.
+ *
+ *             Empty strings are dumped as %|""|%.
+ */
+
+static void dump_text(const union tvec_regval *rv,
+                     const struct tvec_regdef *rd,
+                     unsigned style,
+                     const struct gprintf_ops *gops, void *go)
 {
   const unsigned char *p, *q, *l;
   unsigned f = 0;
 #define f_nonword 1u
 #define f_newline 2u
 
-  if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; }
+  if (!rv->text.sz) { gprintf(gops, go, "\"\""); return; }
 
-  p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
+  p = (const unsigned char *)rv->text.p; l = p + rv->text.sz;
   switch (*p) {
     case '!': case '#': case ';': case '"': case '\'':
     case '(': case '{': case '[': case ']': case '}': case ')':
@@ -2199,7 +2834,7 @@ static void dump_string(const union tvec_regval *rv,
   if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; }
   else if (f&f_nonword) goto quote;
 
-  gops->putm(go, (const char *)p, rv->str.sz);
+  gops->putm(go, (const char *)p, rv->text.sz);
   return;
 
 quote:
@@ -2263,19 +2898,19 @@ static void dump_bytes(const union tvec_regval *rv,
   }
 }
 
-const struct tvec_regty tvty_string = {
-  init_string, release_string, eq_string,
-  tobuf_string, frombuf_string,
-  parse_string, dump_string
+/* Text and byte string type definitions. */
+const struct tvec_regty tvty_text = {
+  init_text, release_text, eq_text,
+  tobuf_text, frombuf_text,
+  parse_text, dump_text
 };
-
 const struct tvec_regty tvty_bytes = {
   init_bytes, release_bytes, eq_bytes,
   tobuf_bytes, frombuf_bytes,
   parse_bytes, dump_bytes
 };
 
-/* --- @tvec_claimeq_string@ --- *
+/* --- @tvec_claimeq_text@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *p0@, @size_t sz0@ = first string with length
@@ -2294,17 +2929,17 @@ const struct tvec_regty tvty_bytes = {
  *             value and @p1@ is printed as the input reference.
  */
 
-int tvec_claimeq_string(struct tvec_state *tv,
-                       const char *p0, size_t sz0,
-                       const char *p1, size_t sz1,
-                       const char *file, unsigned lno, const char *expr)
+int tvec_claimeq_text(struct tvec_state *tv,
+                     const char *p0, size_t sz0,
+                     const char *p1, size_t sz1,
+                     const char *file, unsigned lno, const char *expr)
 {
-  tv->out[0].v.str.p = (/*unconst*/ char *)p0; tv->out[0].v.str.sz = sz0;
-  tv->in[0].v.str.p =(/*unconst*/ char *) p1; tv->in[0].v.str.sz = sz1;
-  return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
+  tv->out[0].v.text.p = (/*unconst*/ char *)p0; tv->out[0].v.text.sz = sz0;
+  tv->in[0].v.text.p =(/*unconst*/ char *) p1; tv->in[0].v.text.sz = sz1;
+  return (tvec_claimeq(tv, &tvty_text, 0, file, lno, expr));
 }
 
-/* --- @tvec_claimeq_strz@ --- *
+/* --- @tvec_claimeq_textz@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *p0, *p1@ = two strings to compare
@@ -2320,15 +2955,15 @@ int tvec_claimeq_string(struct tvec_state *tv,
  *             explicitly.
  */
 
-int tvec_claimeq_strz(struct tvec_state *tv,
-                     const char *p0, const char *p1,
-                     const char *file, unsigned lno, const char *expr)
+int tvec_claimeq_textz(struct tvec_state *tv,
+                      const char *p0, const char *p1,
+                      const char *file, unsigned lno, const char *expr)
 {
-  tv->out[0].v.str.p = (/*unconst*/ char *)p0;
-    tv->out[0].v.str.sz = strlen(p0);
-  tv->in[0].v.str.p = (/*unconst*/ char *)p1;
-    tv->in[0].v.str.sz = strlen(p1);
-  return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
+  tv->out[0].v.text.p = (/*unconst*/ char *)p0;
+    tv->out[0].v.text.sz = strlen(p0);
+  tv->in[0].v.text.p = (/*unconst*/ char *)p1;
+    tv->in[0].v.text.sz = strlen(p1);
+  return (tvec_claimeq(tv, &tvty_text, 0, file, lno, expr));
 }
 
 /* --- @tvec_claimeq_bytes@ --- *
@@ -2362,7 +2997,7 @@ int tvec_claimeq_bytes(struct tvec_state *tv,
   return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
 }
 
-/* --- @tvec_allocstring@, @tvec_allocbytes@ --- *
+/* --- @tvec_alloctext@, @tvec_allocbytes@ --- *
  *
  * Arguments:  @union tvec_regval *rv@ = register value
  *             @size_t sz@ = required size
@@ -2378,15 +3013,15 @@ int tvec_claimeq_bytes(struct tvec_state *tv,
  *             the old buffer contents are simply discarded if reallocation
  *             is necessary.  Instead, use a @dbuf@ or @dstr@.
  *
- *             The @tvec_allocstring@ function sneakily allocates an extra
+ *             The @tvec_alloctext@ function sneakily allocates an extra
  *             byte for a terminating zero.  The @tvec_allocbytes@ function
  *             doesn't do this.
  */
 
-void tvec_allocstring(union tvec_regval *rv, size_t sz)
+void tvec_alloctext(union tvec_regval *rv, size_t sz)
 {
-  if (rv->str.sz <= sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz + 1); }
-  rv->str.sz = sz;
+  if (rv->text.sz <= sz) { xfree(rv->text.p); rv->text.p = xmalloc(sz + 1); }
+  rv->text.sz = sz;
 }
 
 void tvec_allocbytes(union tvec_regval *rv, size_t sz)
@@ -2397,15 +3032,73 @@ void tvec_allocbytes(union tvec_regval *rv, size_t sz)
 
 /*----- Buffer type -------------------------------------------------------*/
 
+/* Buffers are initialized and released as binary strings. */
+
+/* --- @eq_buffer@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv0, *rv1@ = register values
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Nonzero if the values are equal, zero if unequal
+ *
+ * Use:                Compare register values for equality.
+ *
+ *             Buffer values are equal if and only if their sizes are equal;
+ *             their contents are %%\emph{not}%% compared.
+ */
+
 static int eq_buffer(const union tvec_regval *rv0,
                     const union tvec_regval *rv1,
                     const struct tvec_regdef *rd)
   { return (rv0->bytes.sz == rv1->bytes.sz); }
 
+/* --- @tobuf_buffer@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Serialize a register value to a buffer.
+ *
+ *             Buffer values are serialized as just their lengths, as
+ *             unsigned integers.
+ */
+
 static int tobuf_buffer(buf *b, const union tvec_regval *rv,
                         const struct tvec_regdef *rd)
   { return (unsigned_to_buf(b, rv->bytes.sz)); }
 
+/* --- @allocate_buffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @size_t sz@ = size to allocate
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
+ *             distinctive pattern.
+ */
+
+static void allocate_buffer(union tvec_regval *rv, size_t sz)
+  { tvec_allocbytes(rv, sz); memset(rv->bytes.p, '?', sz); }
+
+/* --- @frombuf_buffer@ --- *
+ *
+ * Arguments:  @buf *b@ = buffer
+ *             @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Deserialize a register value from a buffer.
+ *
+ *             Buffer values are serialized as just their lengths, as
+ *             unsigned integers.  The buffer is allocated on
+ *             deserialization and filled with a distinctive pattern.
+ */
+
 static int frombuf_buffer(buf *b, union tvec_regval *rv,
                          const struct tvec_regdef *rd)
 {
@@ -2413,10 +3106,29 @@ static int frombuf_buffer(buf *b, union tvec_regval *rv,
 
   if (unsigned_from_buf(b, &u)) return (-1);
   if (u > (size_t)-1) return (-1);
-  tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u);
+  allocate_buffer(rv, u);
   return (0);
 }
 
+/* --- @parse_buffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             The input format for a buffer value consists of an unsigned
+ *             integer followed by an optional unit specifier consisting of
+ *             an SI unit prefix and (optionally) the letter `B'.  Unit
+ *             prefixes denote %%\emph{binary}%% multipliers, not decimal.
+ *
+ *             The buffer is allocated and filled with a distinctive
+ *             pattern.
+ */
+
 static const char units[] = "kMGTPEZY";
 
 static int parse_buffer(union tvec_regval *rv,
@@ -2453,7 +3165,7 @@ static int parse_buffer(union tvec_regval *rv,
   if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
 
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
-  tvec_allocbytes(rv, u); memset(rv->bytes.p, '?', u);
+  allocate_buffer(rv, u);
   rc = 0;
 end:
   DDESTROY(&d); return (rc);
@@ -2469,6 +3181,22 @@ rangerr:
 #undef f_range
 }
 
+/* --- @dump_buffer@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Buffer values are dumped as their size with an appropriate
+ *             unit specifier.  A unit prefix is only used if the size is an
+ *             exact multiple of the relevant power of two.
+ */
+
 static void dump_buffer(const union tvec_regval *rv,
                        const struct tvec_regdef *rd,
                        unsigned style,
@@ -2485,6 +3213,7 @@ static void dump_buffer(const union tvec_regval *rv,
   }
 }
 
+/* Buffer type definition. */
 const struct tvec_regty tvty_buffer = {
   init_bytes, release_bytes, eq_buffer,
   tobuf_buffer, frombuf_buffer,
index 38c462b..5d37c3f 100644 (file)
@@ -193,7 +193,7 @@ union tvec_regval {
   void *p;                             /* pointer */
   double f;                            /* floating point */
   struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */
-  struct { char *p; size_t sz; } str;  /* text string */
+  struct { char *p; size_t sz; } text; /* text string */
 #ifdef TVEC_REGSLOTS
   TVEC_REGSLOTS
 #endif
@@ -351,8 +351,9 @@ typedef void tvec_envsetupfn(struct tvec_state */*tv*/,
 typedef int tvec_envsetfn(struct tvec_state */*tv*/,
                          const char */*var*/, void */*ctx*/);
   /* Called when the parser finds a %|@var|%' setting to parse and store the
-   * value.  If @setup@ failed, this is still called (so as to skip the
-   * value), but @ctx@ is null.
+   * value.  Return %$+1$% on success, %$0$% if the variable name was not
+   * recognized, or %$-1$% on any other error (which should have been
+   * reported via @tvec_error@).
    */
 
 typedef void tvec_envbeforefn(struct tvec_state */*tv*/, void */*ctx*/);
@@ -1219,6 +1220,19 @@ typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/,
                           int */*outfd_out*/, int */*errfd_out*/,
                           struct tvec_state */*tv*/,
                           const struct tvec_remoteenv */*env*/);
+  /* A connection function.  On entry, @tv@ holds the test-vector state, and
+   * @env@ is the test group's remote environment structure, which will
+   * typically really be some subclass of @struct tvec_remoteenv@ containing
+   * additional parameters for establishing the child process.
+   *
+   * On successful completion, the function stores input and output
+   * descriptors (which need not be distinct) in @*infd_out@ and
+   * @*outfd_out@, and returns zero; if it creates a child process, it should
+   * additionally store the child's process-id in @*kid_out@ and store in
+   * @*errfd_out@ a descriptor from which the child's error output can be
+   * read.  On error, the function should report an appropriate message via
+   * @tvec_error@ and return %$-1$%.
+   */
 
 struct tvec_remoteenv_slots {
   tvec_connectfn *connect;             /* connection function */
@@ -1257,6 +1271,30 @@ union tvec_remoteenv_subclass_kludge {
   struct tvec_remoteexec exec;
 };
 
+/* Exit status.
+ *
+ * We don't use the conventional encoding returned by the @wait@(2) family of
+ * system calls because it's too hard for our flags type to decode.  Instead,
+ * we use our own encoding.
+ *
+ * The exit code or signal number ends up in the `value' field in the low 12
+ * bits; bit 12 is set if the value field holds a signal, and it if holds an
+ * exit code.  Bits 13--15 hold a code which describes the status of a child
+ * process or connection.
+ */
+#define TVXF_VALMASK 0x0fffu           /* value (exit code or signal) */
+#define TVXF_SIG 0x1000u               /* value is signal, not exit code */
+#define TVXF_CAUSEMASK 0xe000u         /* mask for cause bits */
+#define TVXST_RUN 0x0000u              /*   still running */
+#define TVXST_EXIT 0x2000u             /*   child exited */
+#define TVXST_KILL 0x4000u             /*   child killed by signal */
+#define TVXST_CONT 0x6000u             /*   child continued (?) */
+#define TVXST_STOP 0x8000u             /*   child stopped (?) */
+#define TVXST_DISCONN 0xa000u          /*   disconnected */
+#define TVXST_UNK 0xc000u              /*   unknown */
+#define TVXST_ERR 0xe000u            /*   local error prevented diagnosis */
+
+/* Remote environment. */
 extern tvec_envsetupfn tvec_remotesetup;
 extern tvec_envsetfn tvec_remoteset;
 extern tvec_envrunfn tvec_remoterun;
@@ -1271,9 +1309,54 @@ extern tvec_envteardownfn tvec_remoteteardown;
     tvec_remoteafter,                                                  \
     tvec_remoteteardown }
 
+/* --- @tvec_setprogress@, @tvec_setprogress_v@ --- *
+ *
+ * Arguments:  @const char *status@ = progress status token format
+ *             @va_list ap@ = argument tail
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports the progress of a test execution to the client.
+ *
+ *             The framework makes use of tokens beginning with %|%|%:
+ *
+ *               * %|%IDLE|%: during the top-level server code;
+ *
+ *               * %|%SETUP|%: during the enclosing environment's @before@
+ *                 function;
+ *
+ *               * %|%RUN|%: during the environment's @run@ function, or the
+ *                 test function; and
+ *
+ *               * %|%DONE|%: during the enclosing environment's @after@
+ *                 function.
+ *
+ *             The intent is that a test can use the progress token to check
+ *             that a function which is expected to crash does so at the
+ *             correct point, so it's expected that more complex test
+ *             functions and/or environments will set their own progress
+ *             tokens to reflect what's going on.
+ */
+
 extern PRINTF_LIKE(1, 2) int tvec_setprogress(const char */*status*/, ...);
 extern int tvec_setprogress_v(const char */*status*/, va_list */*ap*/);
 
+/* --- @tvec_remoteserver@ --- *
+ *
+ * Arguments:  @int infd@, @int outfd@ = input and output file descriptors
+ *             @const struct tvec_config *config@ = test configuration
+ *
+ * Returns:    Suggested exit code.
+ *
+ * Use:                Run a test server, reading packets from @infd@ and writing
+ *             responses and notifications to @outfd@, and invoking tests as
+ *             described by @config@.
+ *
+ *             This function is not particularly general purpose.  It
+ *             expects to `take over' the process, and makes use of private
+ *             global variables.
+ */
+
 extern int tvec_remoteserver(int /*infd*/, int /*outfd*/,
                             const struct tvec_config */*config*/);
 
@@ -1289,15 +1372,15 @@ extern tvec_connectfn tvec_fork, tvec_exec;
 
 struct tvec_timeoutenv {
   struct tvec_env _env;
-  unsigned timer;
-  double t;
-  const struct tvec_env *env;
+  unsigned timer;                      /* the timer (@ITIMER_...@) */
+  double t;                            /* time to wait (in seconds) */
+  const struct tvec_env *env;          /* subsidiary environment */
 };
 
 struct tvec_timeoutctx {
-  const struct tvec_timeoutenv *te;
-  unsigned timer;
-  double t;
+  const struct tvec_timeoutenv *te;    /* saved environment description */
+  unsigned timer;                      /* timer code (as overridden) */
+  double t;                            /* time to wait (as overridden) */
   unsigned f;                          /* flags */
 #define TVTF_SETTMO 1u                 /*   set `@timeout' */
 #define TVTF_SETMASK (TVTF_SETTMO)     /*   mask of @TVTF_SET...@ */
@@ -1742,10 +1825,11 @@ extern int tvec_claimeq_uint(struct tvec_state */*tv*/,
 
 /*----- Floating-point type -----------------------------------------------*/
 
-/* Floating-point are either NaN (`#nan', if supported by the platform);
- * positive or negative infinity (`#inf', `+#inf', or, preferred, `#+inf',
- * and `-#inf' or, preferred, `#-inf', if supported by the platform), or a
- * number in strtod(3) syntax.
+/* Floating-point values are either NaN (%|#nan|%, if supported by the
+ * platform); positive or negative infinity (%|#inf|%, %|+#inf|%, or
+ * %|#+inf|% (preferring the last), and %|-#inf|% or %|#-inf|% (preferring
+ * the latter), if supported by the platform); or a number in strtod(3)
+ * syntax.
  *
  * The comparison rules for floating-point numbers are complex: see
  * @tvec_claimeqish_float@ for details.
@@ -1863,7 +1947,7 @@ extern const struct tvec_floatinfo tvflt_finite, tvflt_nonneg;
  * On input, an enumerated value may be given by name or as a literal value.
  * For enumerations based on numeric types, the literal values can be written
  * in the same syntax as the underlying values.  For enumerations based on
- * pointers, the only permitted literal is `#nil', which denotes a null
+ * pointers, the only permitted literal is %|#nil|%, which denotes a null
  * pointer.  On output, names are preferred (with the underlying value given
  * in a comment).
  */
@@ -1964,18 +2048,19 @@ TVEC_MISCSLOTS(DECLCLAIM)
  * fields; more precisely, each name is associated with a value and a
  * covering bitmask.
  *
- * The input syntax is a sequence of items separated by `|' signs.  Each item
- * may be the symbolic name of a field value, or a literal unsigned integer.
- * The masks associated with the given symbolic names must be disjoint.  The
- * resulting numerical value is simply the bitwise OR of the given values.
+ * The input syntax is a sequence of items separated by `%|||%' signs.  Each
+ * item may be the symbolic name of a field value, or a literal unsigned
+ * integer.  The masks associated with the given symbolic names must be
+ * disjoint.  The resulting numerical value is simply the bitwise OR of the
+ * given values.
  *
  * On output, the table of symbolic names and their associated values and
  * masks is repeatedly scanned, in order, to find disjoint matches -- i.e.,
  * entries whose value matches the target value in the bit positions
  * indicated by the mask, and whose mask doesn't overlap with any previously
- * found matches; the names are then output, separated by `|'.  Any remaining
- * nonzero bits not covered by any of the matching masks are output as a
- * single literal integer, in hex.
+ * found matches; the names are then output, separated by `%|||%'.  Any
+ * remaining nonzero bits not covered by any of the matching masks are output
+ * as a single literal integer, in hex.
  */
 
 extern const struct tvec_regty tvty_flags;
@@ -2038,8 +2123,9 @@ extern int tvec_claimeq_flags(struct tvec_state */*tv*/,
 /* A character value holds a character, as read by @fgetc@.  The special
  * @EOF@ value can also be represented.
  *
- * On input, a character value can be given by name, with a leading `%|#|%';
- * or a character or `%|\|%'-escape sequence, optionally in single quotes.
+ * On input, a character value can be given by symbolic name, with a leading
+ * `%|#|%'; or a character or `%|\|%'-escape sequence, optionally in single
+ * quotes.
  *
  * The following escape sequences and character names are recognized.
  *
@@ -2156,6 +2242,9 @@ extern int tvec_claimeq_char(struct tvec_state */*tv*/,
  *   * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%', which includes
  *     %$n$% copies of the (compound) string.
  *
+ * The only difference between text and binary strings is that the initial
+ * coding scheme is %|bare|% for text strings and %|hex|% for binary strings.
+ *
  * Either kind of string can contain internal nul characters.  A trailing nul
  * is appended -- beyond the stated input length -- to input strings as a
  * convenience to test functions.  Test functions may include such a nul
@@ -2165,9 +2254,9 @@ extern int tvec_claimeq_char(struct tvec_state */*tv*/,
  * string (in bytes) will be checked against the permitted range.
  */
 
-extern const struct tvec_regty tvty_string, tvty_bytes;
+extern const struct tvec_regty tvty_text, tvty_bytes;
 
-/* --- @tvec_claimeq_string@, @TVEC_CLAIMEQ_STRING@ --- *
+/* --- @tvec_claimeq_text@, @TVEC_CLAIMEQ_TEXT@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *p0@, @size_t sz0@ = first string with length
@@ -2185,22 +2274,22 @@ extern const struct tvec_regty tvty_string, tvty_bytes;
  *             mismatched values are dumped: @p0@ is printed as the output
  *             value and @p1@ is printed as the input reference.
  *
- *             The @TVEC_CLAIM_STRING@ macro is similar, only it (a)
+ *             The @TVEC_CLAIM_TEXT@ macro is similar, only it (a)
  *             identifies the file and line number of the call site
  *             automatically, and (b) implicitly quotes the source text of
  *             the @ch0@ and @ch1@ arguments in the failure message.
  */
 
-extern int tvec_claimeq_string(struct tvec_state */*tv*/,
-                              const char */*p0*/, size_t /*sz0*/,
-                              const char */*p1*/, size_t /*sz1*/,
-                              const char */*file*/, unsigned /*lno*/,
-                              const char */*expr*/);
-#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1)                      \
-       (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__,  \
-                            #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
+extern int tvec_claimeq_text(struct tvec_state */*tv*/,
+                            const char */*p0*/, size_t /*sz0*/,
+                            const char */*p1*/, size_t /*sz1*/,
+                            const char */*file*/, unsigned /*lno*/,
+                            const char */*expr*/);
+#define TVEC_CLAIMEQ_TEXT(tv, p0, sz0, p1, sz1)                                \
+       (tvec_claimeq_text(tv, p0, sz0, p1, sz1, __FILE__, __LINE__,    \
+                          #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
 
-/* --- @tvec_claimeq_strz@, @TVEC_CLAIMEQ_STRZ@ --- *
+/* --- @tvec_claimeq_textz@, @TVEC_CLAIMEQ_TEXTZ@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *p0, *p1@ = two strings to compare
@@ -2213,16 +2302,15 @@ extern int tvec_claimeq_string(struct tvec_state */*tv*/,
  * Use:                Check that strings at @p0@ and @p1@ are equal, as for
  *             @tvec_claimeq_string@, except that the strings are assumed
  *             null-terminated, so their lengths don't need to be supplied
- *             explicitly.  The macro is similarly like
- *             @TVEC_CLAIMEQ_STRING@.
+ *             explicitly.  The macro is similarly like @TVEC_CLAIMEQ_TEXT@.
  */
 
-extern int tvec_claimeq_strz(struct tvec_state */*tv*/,
-                            const char */*p0*/, const char */*p1*/,
-                            const char */*file*/, unsigned /*lno*/,
-                            const char */*expr*/);
-#define TVEC_CLAIMEQ_STRZ(tv, p0, p1)                                  \
-       (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1))
+extern int tvec_claimeq_textz(struct tvec_state */*tv*/,
+                             const char */*p0*/, const char */*p1*/,
+                             const char */*file*/, unsigned /*lno*/,
+                             const char */*expr*/);
+#define TVEC_CLAIMEQ_TEXTZ(tv, p0, p1)                                 \
+       (tvec_claimeq_textz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1))
 
 /* --- @tvec_claimeq_bytes@, @TVEC_CLAIMEQ_BYTES@ --- *
  *
@@ -2257,7 +2345,7 @@ extern int tvec_claimeq_bytes(struct tvec_state */*tv*/,
        (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__,         \
                      #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
 
-/* --- @tvec_allocstring@, @tvec_allocbytes@ --- *
+/* --- @tvec_alloctext@, @tvec_allocbytes@ --- *
  *
  * Arguments:  @union tvec_regval *rv@ = register value
  *             @size_t sz@ = required size
@@ -2273,12 +2361,12 @@ extern int tvec_claimeq_bytes(struct tvec_state */*tv*/,
  *             the old buffer contents are simply discarded if reallocation
  *             is necessary.  Instead, use a @dbuf@ or @dstr@.
  *
- *             The @tvec_allocstring@ function sneakily allocates an extra
+ *             The @tvec_alloctext@ function sneakily allocates an extra
  *             byte for a terminating zero.  The @tvec_allocbytes@ function
  *             doesn't do this.
  */
 
-extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/);
+extern void tvec_alloctext(union tvec_regval */*rv*/, size_t /*sz*/);
 extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
 
 /*----- Buffer type -------------------------------------------------------*/
@@ -2291,6 +2379,9 @@ extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
  * with a unit `kB', `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or
  * without the `B') denoting a power of 1024.  Units are used on output only
  * when the size would be expressed exactly.
+ *
+ * No @claimeq@ functions or macros are provided for buffers because they
+ * don't seem very useful.
  */
 
 extern const struct tvec_regty tvty_buffer;
index ffbe920..240a046 100644 (file)
@@ -52,7 +52,7 @@ check_PROGRAMS                += t/bits.t
 t_bits_t_SOURCES        = t/bits-test.c
 t_bits_t_CPPFLAGS       = $(TEST_CPPFLAGS)
 t_bits_t_LDFLAGS        = -static
-EXTRA_DIST             += t/bits-testgen.py
+EXTRA_DIST             += t/bits-testgen
 
 ## Control flow.
 pkginclude_HEADERS     += control.h
similarity index 100%
rename from utils/t/bits-testgen.py
rename to utils/t/bits-testgen
index b2fd2c7..44c4d2f 100644 (file)
@@ -183,7 +183,7 @@ int main(int argc, char *argv[])
     FOR_FIZZBUZZ(fb, 19, 32)
       TEST
        if (TVEC_CLAIM(&tvstate, ref[i]))
-         { TVEC_CLAIMEQ_STRZ(&tvstate, fb, ref[i]); i++; }
+         { TVEC_CLAIMEQ_TEXTZ(&tvstate, fb, ref[i]); i++; }
     TVEC_CLAIM(&tvstate, !ref[i]);
   }
 
index 396325a..fb462d0 100644 (file)
@@ -35,7 +35,7 @@ enum {
 
 static void test_versioncmp(const struct tvec_reg *in, struct tvec_reg *out,
                            void *ctx)
-  { out[RRC].v.i = versioncmp(in[RV0].v.str.p, in[RV1].v.str.p); }
+  { out[RRC].v.i = versioncmp(in[RV0].v.text.p, in[RV1].v.text.p); }
 
 static void swap_test(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 {
@@ -50,8 +50,8 @@ 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_regdef versioncmp_regs[] = {
-  { "v0", RV0, &tvty_string, 0 },
-  { "v1", RV1, &tvty_string, 0 },
+  { "v0", RV0, &tvty_text, 0 },
+  { "v1", RV1, &tvty_text, 0 },
   { "rc", RRC, &tvty_ienum, 0, { &tvenum_cmp } },
   TVEC_ENDREGS
 };
index 8d1b0f0..32b8111 100644 (file)
@@ -30,7 +30,7 @@
 ## bits
 AT_SETUP([utilities: bits])
 AT_KEYWORDS([utils bits])
-$PYTHON SRCDIR/t/bits-testgen.py 0xaca98e08 0x0b6e95fb - >bits.tests
+$PYTHON SRCDIR/t/bits-testgen 0xaca98e08 0x0b6e95fb - >bits.tests
 AT_CHECK([BUILDDIR/t/bits.t -fh bits.tests], [0], [ignore])
 AT_CLEANUP