@@@ remote works?
authorMark Wooding <mdw@distorted.org.uk>
Wed, 21 Feb 2024 02:39:21 +0000 (02:39 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 21 Feb 2024 02:39:21 +0000 (02:39 +0000)
16 files changed:
hash/t/hash-test.c
struct/buf.c
struct/buf.h
test/Makefile.am
test/bench.h
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.h
utils/bits.h
utils/compiler.h
utils/control.h
utils/macros.h

index 1718a98..1bb21e9 100644 (file)
@@ -81,9 +81,9 @@ static void test_unihash(const struct tvec_reg *in, struct tvec_reg *out,
 static void bench_unihash(const struct tvec_reg *in, struct tvec_reg *out,
                          void *ctx)
   { unihash_hash(ctx, 0, in[RM].v.bytes.p, in[RM].v.bytes.sz); }
-static int setup_unihash(struct tvec_state *tv,
-                        const struct tvec_env *env, void *pctx, void *ctx)
-  { unihash_setkey(ctx, 0); return (0); }
+static void setup_unihash(struct tvec_state *tv,
+                         const struct tvec_env *env, void *pctx, void *ctx)
+  { unihash_setkey(ctx, 0); }
 static const struct tvec_env unihash_benchenv =
   { sizeof(unihash_info), setup_unihash, 0, 0 };
 
@@ -123,9 +123,9 @@ static const struct tvec_regdef bench_regs[] = {
   TVEC_ENDREGS
 };
 
-static const struct tvec_bench crc32_bench =
+static const struct tvec_benchenv crc32_bench =
   { TVEC_BENCHINIT, 1, -1, RM, 0 };
-static const struct tvec_bench unihash_bench =
+static const struct tvec_benchenv unihash_bench =
   { TVEC_BENCHINIT, 1, -1, RM, &unihash_benchenv };
 
 static const struct tvec_test tests[] = {
index 00163bf..5d1fc27 100644 (file)
@@ -53,7 +53,7 @@ void buf_init(buf *b, void *p, size_t sz)
   b->f = 0;
 }
 
-/* --- @dbuf_init@ --- *
+/* --- @dbuf_create@ --- *
  *
  * Arguments:  @dbuf *db@ = pointer to a dynamic buffer block
  *
@@ -63,7 +63,7 @@ void buf_init(buf *b, void *p, size_t sz)
  *             and ready for writing.
  */
 
-void dbuf_init(dbuf *db)
+void dbuf_create(dbuf *db)
 {
   db->_b.base = db->_b.p = db->_b.limit = 0; db->_b.f = BF_ALLOC | BF_WRITE;
   db->a = &arena_stdlib; db->sz = 0;
@@ -96,7 +96,7 @@ void dbuf_reset(dbuf *db)
 void dbuf_destroy(dbuf *db)
 {
   if (db->_b.base) x_free(db->a, db->_b.base);
-  dbuf_init(db);
+  dbuf_create(db);
 }
 
 /* --- @buf_break@ --- *
index 389d038..6193cd3 100644 (file)
@@ -137,7 +137,7 @@ extern const struct gprintf_ops buf_printops;
 
 extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/);
 
-/* --- @dbuf_init@ --- *
+/* --- @dbuf_create@ --- *
  *
  * Arguments:  @dbuf *db@ = pointer to a dynamic buffer block
  *
@@ -147,7 +147,7 @@ extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/);
  *             and ready for writing.
  */
 
-extern void dbuf_init(dbuf */*db*/);
+extern void dbuf_create(dbuf */*db*/);
 
 /* --- @dbuf_reset@ --- *
  *
index 2f7a041..7990ab7 100644 (file)
@@ -43,13 +43,15 @@ LIBMANS                     += testrig.3
 
 ## New `tvec' testing framework.
 pkginclude_HEADERS     += tvec.h
-libtest_la_SOURCES     += tvec-bench.c
 libtest_la_SOURCES     += tvec-core.c
 libtest_la_SOURCES     += tvec-output.c
 libtest_la_SOURCES     += tvec-types.c
 libtest_la_SOURCES     += tvec-main.c
 #LIBMANS               += tvec.3
 
+libtest_la_SOURCES     += tvec-bench.c
+libtest_la_SOURCES     += tvec-remote.c
+
 check_PROGRAMS         += t/tvec.t
 t_tvec_t_SOURCES        = t/tvec-test.c
 t_tvec_t_CPPFLAGS       = $(TEST_CPPFLAGS)
index e7736c4..c0627e2 100644 (file)
@@ -45,12 +45,12 @@ struct bench_time {
 #define BTF_TIMEOK 1u                  /*   @s@ ad @ns@ slots are value */
 #define BTF_CYOK 2u                    /*   @cy@ slot is valid */
 #define BTF_ANY (BTF_TIMEOK | BTF_CYOK)        /*   some part is useful */
-  kludge64 s; uint32 ns;              /*   real time in seconds and nanos */
-  kludge64 cy;                         /*   count of CPU cycles */
+  kludge64 s; uint32 ns;               /* real time, seconds and nanos */
+  kludge64 cy;                         /* count of CPU cycles */
 };
 
 struct bench_timing {
-  unsigned f;                        /* flags (as in @struct bench_time@) */
+  unsigned f;                          /* flags (@BTF_...@) */
   double n, t, cy;                     /* count, time, and cycles */
 };
 
@@ -67,12 +67,12 @@ struct bench_timerops {
 struct bench_state {
   struct bench_timer *tm;              /* a timer */
   double target_s;                     /* target time to run benchmarks */
-  unsigned f;                       /* flags (@BTF_...@) for calibrations */
+  unsigned f;                          /* calibration flags (@BTF_...@) */
   struct { double m, c; } clk, cy;     /* calculated overheads */
 };
 
 typedef void bench_fn(unsigned long /*n*/, void */*ctx*/);
-/* Run the benchmark @n@ times, given a context pointer @ctx@. */
+  /* Run the benchmark @n@ times, given a context pointer @ctx@. */
 
 /*----- Functions provided ------------------------------------------------*/
 
index 6dd0ef0..a9ef269 100644 (file)
@@ -164,14 +164,13 @@ struct test_context {
 #define SF_SHOW 1u
 };
 
-static int common_setup(struct tvec_state *tv,
+static void common_setup(struct tvec_state *tv,
                         const struct tvec_env *env, void *pctx, void *ctx)
 {
   struct test_context *tctx = ctx;
 
   tctx->tv = tv;
   tctx->f = 0;
-  return (0);
 }
 
 static int common_set(struct tvec_state *tv, const char *name,
@@ -320,16 +319,15 @@ static void test_single_deserialize
 TYPEREGS(SERREG)
 #undef SERREG
 
-static int before_single_serialize(struct tvec_state *tv, void *ctx)
+static void before_single_serialize(struct tvec_state *tv, void *ctx)
 {
   if (!(tv->in[RRC].f&TVRF_LIVE)) {
     tv->in[RRC].v.i = 0; tv->in[RRC].f |= TVRF_LIVE;
     tv->out[RRC].f |= TVRF_LIVE;
   }
-  return (0);
 }
 
-static int before_single_deserialize(struct tvec_state *tv, void *ctx)
+static void before_single_deserialize(struct tvec_state *tv, void *ctx)
 {
   if (!(tv->in[RRC].f&TVRF_LIVE)) {
     tv->in[RRC].v.i = 0; tv->in[RRC].f |= TVRF_LIVE;
@@ -339,7 +337,6 @@ static int before_single_deserialize(struct tvec_state *tv, void *ctx)
     tv->in[RLEFT].v.u = 0; tv->in[RLEFT].f |= TVRF_LIVE;
     tv->out[RLEFT].f |= TVRF_LIVE;
   }
-  return (0);
 }
 
 static const struct tvec_env single_serialize_testenv = {
@@ -428,13 +425,12 @@ static DSGINIT(const) struct tvec_regdef multi_serialize_regs[] = {
   TVEC_ENDREGS
 };
 
-static int before_multi_serialize(struct tvec_state *tv, void *ctx)
+static void before_multi_serialize(struct tvec_state *tv, void *ctx)
 {
   if (!(tv->in[RRC].f&TVRF_LIVE)) {
     tv->in[RRC].v.i = 0; tv->in[RRC].f |= TVRF_LIVE;
     tv->out[RRC].f |= TVRF_LIVE;
   }
-  return (0);
 }
 
 static const struct tvec_env multi_serialize_testenv = {
@@ -444,6 +440,24 @@ static const struct tvec_env multi_serialize_testenv = {
   0
 };
 
+/*----- Crash test --------------------------------------------------------*/
+
+static void test_crash(const struct tvec_reg *in, struct tvec_reg *out, void *ctx)
+{
+  out[RVOUT].v.u = in[RV].v.u;
+  if (in[RSAB].v.i) abort();
+}
+
+static const struct tvec_remotefork crash_testenv =
+  { TVEC_REMOTEFORK(0, 0) };
+
+static const struct tvec_regdef crash_regs[] = {
+  { "crash",   RSAB,   &tvty_ienum,    0,              { &tvenum_bool } },
+  { "x",       RV,     &tvty_uint,     0,              { &tvrange_uint } },
+  { "z",       RVOUT,  &tvty_uint,     0,              { &tvrange_uint } },
+  TVEC_ENDREGS
+};
+
 /*----- Front end ---------------------------------------------------------*/
 
 static const struct tvec_test tests[] = {
@@ -459,6 +473,8 @@ static const struct tvec_test tests[] = {
   TYPEREGS(DEFSINGLE)
 #undef DEFSINGLE
 
+  { "crash",   crash_regs,     &crash_testenv._env, test_crash } ,
+
   TVEC_ENDTESTS
 };
 
index 74f40fa..fa4b1d8 100644 (file)
@@ -55,7 +55,7 @@ $1 = $2
 @show = t
 ])
 check_template([BUILDDIR/t/tvec.t -fh tv], [0],
-[left_pad([matched $1], [17]) = $3
+[left_pad([matched $1], [21]) = $3
 copy-$1: ok
 PASSED all 1 test in 1 group
 ])])
@@ -389,6 +389,43 @@ test_parserr([buffer], [16 EB], [3], [buffer length `16 EB' out of range])
 AT_CLEANUP
 
 ###--------------------------------------------------------------------------
+AT_SETUP([tvec remote])
+
+AT_DATA([tv],
+[;;; -*-conf-*-
+
+@<:@crash@:>@
+
+crash = t
+x = 1
+z = 0
+@progress = %RUN
+@exit = killed | SIGABRT
+
+crash = nil
+x = 0
+z = 0
+@reconnect = skip
+
+crash = nil
+x = 1
+z = 1
+
+crash = nil
+x = 1
+z = 1
+@progress = %DONE
+@exit = running
+])
+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
+])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
 AT_SETUP([tvec serialize])
 
 AT_DATA([tv],
index b56fcbe..8c8cfdf 100644 (file)
@@ -90,12 +90,12 @@ static void normalize(double *x_inout, const char **unit_out, double scale)
  *             @void *pctx@ = parent context (ignored)
  *             @void *ctx@ = context pointer to initialize
  *
- * Returns:    Zero on success, @-1@ on failure.
+ * Returns:    ---
  *
  * Use:                Initialize a benchmarking environment context.
  *
  *             The environment description must really be a @struct
- *             tvec_bench@.  If the @bst@ slot is null, then a temporary
+ *             tvec_benchenv@.  If the @bst@ slot is null, then a temporary
  *             benchmark state is allocated for the current test group and
  *             released at the end.  Otherwise, it must be the address of a
  *             pointer to a benchmark state: if the pointer is null, then a
@@ -104,38 +104,37 @@ static void normalize(double *x_inout, const char **unit_out, double scale)
  *             existing valid benchmark state.
  */
 
-int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
-                   void *pctx, void *ctx)
+void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
+                    void *pctx, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_bench *b = (const struct tvec_bench *)env;
-  const struct tvec_env *subenv = b->env;
+  const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
+  const struct tvec_env *subenv = be->env;
   struct bench_timer *bt;
 
   /* Basic initialization. */
-  bc->b = b; bc->bst = 0; bc->subctx = 0;
+  bc->be = be; bc->bst = 0; bc->subctx = 0;
 
   /* Set up the benchmarking state if it hasn't been done before. */
-  if (!b->bst || !*b->bst) {
+  if (!be->bst || !*be->bst) {
     bt = bench_createtimer(); if (!bt) goto fail_timer;
     bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt);
-    if (b->bst) *b->bst = bc->bst;
-  } else if (!(*b->bst)->tm)
+    if (be->bst) *be->bst = bc->bst;
+  } else if (!(*be->bst)->tm)
     goto fail_timer;
   else
-    bc->bst = *b->bst;
+    bc->bst = *be->bst;
 
   /* Set the default target time. */
   bc->dflt_target = bc->bst->target_s;
 
   /* Initialize the subordinate environment. */
   if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
-  if (subenv && subenv->setup && subenv->setup(tv, subenv, bc, bc->subctx))
-    { xfree(bc->subctx); bc->subctx = 0; return (-1); }
+  if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
 
   /* All done. */
 end:
-  return (0);
+  return;
 fail_timer:
   tvec_skipgroup(tv, "failed to create timer"); goto end;
 }
@@ -163,8 +162,8 @@ int tvec_benchset(struct tvec_state *tv, const char *var,
                  const struct tvec_env *env, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_bench *b = (const struct tvec_bench *)env;
-  const struct tvec_env *subenv = b->env;
+  const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
+  const struct tvec_env *subenv = be->env;
   union tvec_regval rv;
   static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 };
   static const struct tvec_regdef rd =
@@ -185,21 +184,20 @@ int tvec_benchset(struct tvec_state *tv, const char *var,
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @void *ctx@ = context pointer
  *
- * Returns:    Zero on success, @-1@ on failure.
+ * Returns:    ---
  *
  * Use:                Invoke the subordinate environment's @before@ function to
  *             prepare for the benchmark.
  */
 
-int tvec_benchbefore(struct tvec_state *tv, void *ctx)
+void tvec_benchbefore(struct tvec_state *tv, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_bench *b = bc->b;
-  const struct tvec_env *subenv = b->env;
+  const struct tvec_benchenv *be = bc->be;
+  const struct tvec_env *subenv = be->env;
 
   /* Just call the subsidiary environment. */
-  if (subenv && subenv->before) return (subenv->before(tv, bc->subctx));
-  else return (0);
+  if (subenv && subenv->before) subenv->before(tv, bc->subctx);
 }
 
 /* --- @tvec_benchafter@ --- *
@@ -216,8 +214,8 @@ int tvec_benchbefore(struct tvec_state *tv, void *ctx)
 void tvec_benchafter(struct tvec_state *tv, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_bench *b = bc->b;
-  const struct tvec_env *subenv = b->env;
+  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;
@@ -239,11 +237,11 @@ void tvec_benchafter(struct tvec_state *tv, void *ctx)
 void tvec_benchteardown(struct tvec_state *tv, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_bench *b;
+  const struct tvec_benchenv *be;
   const struct tvec_env *subenv;
 
   if (!bc) return;
-  b = bc->b; subenv = b->env;
+  be = bc->be; subenv = be->env;
 
   /* Tear down any subsidiary environment. */
   if (subenv && subenv->teardown && bc->subctx)
@@ -251,7 +249,7 @@ void tvec_benchteardown(struct tvec_state *tv, void *ctx)
 
   /* If the benchmark state was temporary, then dispose of it. */
   if (bc->bst) {
-    if (b->bst) bc->bst->target_s = bc->dflt_target;
+    if (be->bst) bc->bst->target_s = bc->dflt_target;
     else { bench_destroy(bc->bst); xfree(bc->bst); }
   }
 }
@@ -314,15 +312,13 @@ static void benchloop_inner_indirect(unsigned long n, void *ctx)
  * Returns:    ---
  *
  * Use:                Measures and reports the performance of a test function.
- *
- *
  */
 
 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_bench *b = bc->b;
-  const struct tvec_env *subenv = b->env;
+  const struct tvec_benchenv *be = bc->be;
+  const struct tvec_env *subenv = be->env;
   const struct tvec_regdef *rd;
   struct tvec_output *o = tv->output;
   struct bench_timing tm;
@@ -339,8 +335,8 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
   r.in = tv->in; r.out = tv->out; r.fn = fn;
 
   /* Decide on the run function to select. */
-  if (b->riter >= 0) {
-    r.n = &TVEC_REG(tv, in, b->riter)->v.u;
+  if (be->riter >= 0) {
+    r.n = &TVEC_REG(tv, in, be->riter)->v.u;
     loopfn = subenv && subenv->run ?
       benchloop_inner_indirect : benchloop_inner_direct;
   } else {
@@ -350,9 +346,9 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
   }
 
   /* Decide on the kind of unit and the base count. */
-  base = b->niter;
-  if (b->rbuf < 0) unit = TVBU_OP;
-  else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; }
+  base = be->niter;
+  if (be->rbuf < 0) unit = TVBU_OP;
+  else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
 
   /* Construct a description of the test using the identifier registers. */
   for (rd = tv->test->regs; rd->name; rd++)
index 92f31d3..51834f4 100644 (file)
 
 /*----- Output ------------------------------------------------------------*/
 
-/* --- @tvec_error@, @tvec_error_v@ --- *
+/* --- @tvec_report@, @tvec_report_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *msg@, @va_list ap@ = error message
  *
- * Returns:    @-1@.
+ * Returns:    ---
  *
- * Use:                Report an error.  Errors are distinct from test failures,
- *             and indicate that a problem was encountered which compromised
- *             the activity of testing.
+ * Use:                Report an message with a given severity.  Messages with level
+ *             @TVLEV_ERR@ or higher force a nonzero exit code.
  */
 
-int tvec_error(struct tvec_state *tv, const char *msg, ...)
+void tvec_report(struct tvec_state *tv, unsigned level, const char *msg, ...)
 {
   va_list ap;
 
-  va_start(ap, msg); tvec_error_v(tv, msg, &ap); va_end(ap);
-  return (-1);
+  va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap);
 }
-int tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap)
+
+void tvec_report_v(struct tvec_state *tv, unsigned level,
+                  const char *msg, va_list *ap)
 {
-  tv->output->ops->error(tv->output, msg, ap);
-  tv->f |= TVSF_ERROR; return (-1);
+  tv->output->ops->report(tv->output, level, msg, ap);
+  if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR;
 }
 
-/* --- @tvec_notice@, @tvec_notice_v@ --- *
+/* --- @tvec_error@, @tvec_notice@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
- *             @const char *msg@, @va_list ap@ = message
+ *             @const char *msg@, @va_list ap@ = error message
  *
- * Returns:    ---
+ * Returns:    The @tvec_error@ function returns @-1@ as a trivial
+ *             convenience; @tvec_notice@ does not return a value.
  *
- * Use:                Output a notice: essentially, some important information
- *             which doesn't fit into any of the existing categories.
+ * Use:                Report an error or a notice.  Errors are distinct from test
+ *             failures, and indicate that a problem was encountered which
+ *             compromised the activity of testing.  Notices are important
+ *             information which doesn't fit into any other obvious
+ *             category.
  */
 
+int tvec_error(struct tvec_state *tv, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg); tvec_report_v(tv, TVLEV_ERR, msg, &ap); va_end(ap);
+  return (-1);
+}
+
 void tvec_notice(struct tvec_state *tv, const char *msg, ...)
 {
   va_list ap;
-  va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap);
+
+  va_start(ap, msg); tvec_report_v(tv, TVLEV_NOTE, msg, &ap); va_end(ap);
 }
-void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
-  { tv->output->ops->notice(tv->output, msg, ap); }
 
 /*----- Test processing ---------------------------------------------------*/
 
@@ -91,8 +102,10 @@ void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
 }
 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
 {
-  tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
-  tv->output->ops->skipgroup(tv->output, excuse, ap);
+  if (!(tv->f&TVSF_SKIP)) {
+    tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
+    tv->output->ops->skipgroup(tv->output, excuse, ap);
+  }
 }
 
 static void set_outcome(struct tvec_state *tv, unsigned out)
@@ -302,7 +315,7 @@ void tvec_resetoutputs(struct tvec_state *tv)
   }
 }
 
-static void init_registers(struct tvec_state *tv)
+void tvec_initregs(struct tvec_state *tv)
 {
   const struct tvec_regdef *rd;
   struct tvec_reg *r;
@@ -315,7 +328,7 @@ static void init_registers(struct tvec_state *tv)
   }
 }
 
-static void release_registers(struct tvec_state *tv)
+void tvec_releaseregs(struct tvec_state *tv)
 {
   const struct tvec_regdef *rd;
   struct tvec_reg *r;
@@ -392,18 +405,21 @@ 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))
-      tvec_skip(tv, "test setup failed");
+    if (env && env->before) env->before(tv, g->ctx);
+    if (!(tv->f&TVSF_ACTIVE))
+      /* setup forced a skip */;
+    else if (env && env->run)
+      env->run(tv, t->fn, g->ctx);
     else {
-      if (env && env->run) env->run(tv, t->fn, g->ctx);
-      else { t->fn(tv->in, tv->out, g->ctx); tvec_check(tv, 0); }
+      t->fn(tv->in, tv->out, g->ctx);
+      tvec_check(tv, 0);
     }
     if (env && env->after) env->after(tv, g->ctx);
     tvec_endtest(tv);
   }
 
 end:
-  tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv);
+  tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
 }
 
 static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
@@ -414,13 +430,10 @@ static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
 
   tv->output->ops->bgroup(tv->output);
   tv->f &= ~TVSF_SKIP;
-  init_registers(tv);
+  tvec_initregs(tv);
   for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
   if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
-  if (env && env->setup && env->setup(tv, env, 0, g->ctx)) {
-    tvec_skipgroup(tv, "setup failed");
-    xfree(g->ctx); g->ctx = 0;
-  }
+  if (env && env->setup) env->setup(tv, env, 0, g->ctx);
 }
 
 static void report_group(struct tvec_state *tv)
@@ -448,7 +461,7 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g)
   if (tv->f&TVSF_OPEN) check(tv, g);
   if (!(tv->f&TVSF_SKIP)) report_group(tv);
   env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx);
-  release_registers(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
+  tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
 }
 
 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
@@ -611,7 +624,7 @@ int tvec_serialize(const struct tvec_reg *rv, buf *b,
   for (rd = regs, i = 0; rd->name; rd++, i++) {
     if (rd->i >= nr) continue;
     r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
-    bitmap = BBASE(b) + bitoff; bitmap[rd->i/8] |= 1 << rd->i%8;
+    bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8;
     if (rd->ty->tobuf(b, &r->v, rd)) return (-1);
   }
   return (0);
@@ -632,7 +645,7 @@ int tvec_deserialize(struct tvec_reg *rv, buf *b,
   bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
   for (rd = regs, i = 0; rd->name; rd++, i++) {
     if (rd->i >= nr) continue;
-    if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue;
+    if (!(bitmap[i/8]&(1 << i%8))) continue;
     r = TVEC_GREG(rv, rd->i, regsz);
     if (rd->ty->frombuf(b, &r->v, rd)) return (-1);
     r->f |= TVRF_LIVE;
@@ -750,7 +763,7 @@ int tvec_claimeq(struct tvec_state *tv,
   adhoc_claim_setup(tv, &ck, regs, file, lno);
   ok = ty->eq(&tv->in[0].v, &tv->out[0].v, &regs[0]);
   if (!ok)
-    { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
+    { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
   adhoc_claim_teardown(tv, &ck);
   return (ok);
 }
index 9ca4f29..e92ea69 100644 (file)
@@ -110,7 +110,7 @@ static int getenv_boolean(const char *var, int dflt)
 static int register_maxnamelen(const struct tvec_state *tv)
 {
   const struct tvec_regdef *rd;
-  int maxlen = 6, n;
+  int maxlen = 10, n;
 
   for (rd = tv->test->regs; rd->name; rd++)
     { n = strlen(rd->name); if (n > maxlen) maxlen = n; }
@@ -216,7 +216,6 @@ static void init_fmt(struct format *fmt, FILE *fp, const char *prefix)
     fmt->prefix = prefix;
     l = fmt->pfxlim = prefix + strlen(prefix);
     SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q;
-    DPUTM(&fmt->w, q, l - q);
   }
 }
 
@@ -343,11 +342,11 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
      * need to write that.  Otherwise, there's only blank stuff, which we
      * accumulate in the buffer.
      *
-     * If we're at the start of a line here, then
+     * If we're at the start of a line here, then    
      */
 
     if (r > p) {
-      if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
+      if (fmt->f&FMTF_NEWL) { PUT_PREFIX; fmt->f &= ~FMTF_NEWL; }
       PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w);
     }
     SAVE_TAIL;
@@ -357,8 +356,11 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
   /* There is at least one more segment, so we know that there'll be a line
    * to output.
    */
-  if (fmt->f&FMTF_NEWL) PUT_PFXINB;
-  if (r > p) { PUT_SAVED; PUT_NONBLANK; }
+  if (r > p) {
+    if (fmt->f&FMTF_NEWL) PUT_PREFIX;
+    PUT_SAVED; PUT_NONBLANK;
+  } else if (fmt->f&FMTF_NEWL)
+    PUT_PFXINB;
   PUT_NEWLINE; DRESET(&fmt->w);
   SPLIT_SEGMENT;
 
@@ -367,7 +369,9 @@ static int format_string(struct format *fmt, const char *p, size_t sz)
    * newline, so we write the initial prefix and drop the trailing blanks.
    */
   while (q) {
-    PUT_PREFIX; PUT_NONBLANK; PUT_NEWLINE;
+    if (r > p) { PUT_PREFIX; PUT_NONBLANK; }
+    else PUT_PFXINB;
+    PUT_NEWLINE;
     SPLIT_SEGMENT;
   }
 
@@ -660,6 +664,7 @@ static void human_skipgroup(struct tvec_output *o,
 
   if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
     h->f &= ~HOF_PROGRESS;
+    putc(' ', h->fmt.fp);
     setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
   } else {
     fprintf(h->fmt.fp, "%s: ", h->tv->test->name);
@@ -780,7 +785,8 @@ static void human_ebench(struct tvec_output *o,
   fputc('\n', h->fmt.fp);
 }
 
-static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
+static void human_report(struct tvec_output *o, unsigned level,
+                        const char *msg, va_list *ap)
 {
   struct human_output *h = (struct human_output *)o;
   struct tvec_state *tv = h->tv;
@@ -814,7 +820,7 @@ static const struct tvec_outops human_ops = {
   human_bgroup, human_skipgroup, human_egroup,
   human_btest, human_skip, human_fail, human_dumpreg, human_etest,
   human_bbench, human_ebench,
-  human_report, human_report,
+  human_report,
   human_destroy
 };
 
@@ -1007,31 +1013,23 @@ static void tap_ebench(struct tvec_output *o,
   format_char(&t->fmt, '\n');
 }
 
-static void tap_report(struct tap_output *t,
-                      const struct gprintf_ops *gops, void *go,
+static void tap_report(struct tvec_output *o, unsigned level,
                       const char *msg, va_list *ap)
 {
+  struct tap_output *t = (struct tap_output *)o;
   struct tvec_state *tv = t->tv;
+  const struct gprintf_ops *gops; void *go;
 
+  if (level >= TVLEV_ERR) {
+    fputs("Bail out!  ", t->fmt.fp);
+    gops = &file_printops; go = t->fmt.fp;
+  } else {
+    gops = &tap_printops; go = t;
+  }
   if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
   gprintf(gops, go, msg, ap); gops->putch(go, '\n');
 }
 
-static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
-{
-  struct tap_output *t = (struct tap_output *)o;
-
-  fputs("Bail out!  ", t->fmt.fp);
-  tap_report(t, &file_printops, t->fmt.fp, msg, ap);
-}
-
-static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
-{
-  struct tap_output *t = (struct tap_output *)o;
-
-  tap_report(t, &tap_printops, t, msg, ap);
-}
-
 static void tap_destroy(struct tvec_output *o)
 {
   struct tap_output *t = (struct tap_output *)o;
@@ -1046,7 +1044,7 @@ static const struct tvec_outops tap_ops = {
   tap_bgroup, tap_skipgroup, tap_egroup,
   tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest,
   tap_bbench, tap_ebench,
-  tap_error, tap_notice,
+  tap_report,
   tap_destroy
 };
 
index a00f046..3dbb5d3 100644 (file)
 /*----- Header files ------------------------------------------------------*/
 
 #include <errno.h>
+#include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <sys/types.h>
-#include <sys/uio.h>
+#include <sys/wait.h>
 #include <fcntl.h>
 #include <unistd.h>
 
 #include "alloc.h"
+#include "bench.h"
 #include "buf.h"
+#include "compiler.h"
+#include "fdflags.h"
+#include "lbuf.h"
+#include "mdup.h"
+#include "quis.h"
 #include "tvec.h"
 
-/*----- Data structures ---------------------------------------------------*/
+#if GCC_VERSION_P(7, 1)
+#  pragma GCC diagnostic ignored "-Wdangling-else"
+#elif GCC_VERSION_P(4, 2)
+#  pragma GCC diagnostic ignored "-Wparentheses"
+#endif
 
-struct tvec_remote {
-  int infd, outfd;
-  dbuf bin, bout;
-  unsigned f;
-#define TVRF_BROKEN 1u
-};
+#if CLANG_VERSION_P(3, 1)
+#  pragma clang diagnostic ignored "-Wdangling-else"
+#endif
 
-struct tvec_remotectx {
-  struct tvec_remote r;
-  pid_t kid;
-};
+/*----- Basic I/O ---------------------------------------------------------*/
 
-struct remote_output {
-  struct tvec_output _o;
-  struct tvec_remote r;
-};
+static void init_comms(struct tvec_remotecomms *rc)
+{
+  dbuf_create(&rc->bin); dbuf_create(&rc->bout);
+  rc->infd = rc->outfd = -1; rc->f = 0;
+}
 
-/*----- Basic I/O ---------------------------------------------------------*/
+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; }
+}
+
+static void release_comms(struct tvec_remotecomms *rc)
+  { close_comms(rc); dbuf_destroy(&rc->bin); dbuf_destroy(&rc->bout); }
+
+static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
+{
+  rc->infd = infd; rc->outfd = outfd; rc->f &= ~0xffu;
+  dbuf_reset(&rc->bin); dbuf_reset(&rc->bout);
+}
 
 static int PRINTF_LIKE(3, 4)
-  ioerr(struct tvec_state *tv, struct tvec_remote *r, const char *msg, ...)
+  ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
+       const char *msg, ...)
 {
   va_list ap;
 
   va_start(ap, msg);
-  r->f |= TVRF_BROKEN;
-  tvec_write(tv, msg, &ap);
+  close_comms(rc); rc->f |= TVRF_BROKEN;
+  tvec_report_v(tv, TVLEV_ERR, msg, &ap);
   va_end(ap);
   return (-1);
 }
 
-static int send_all(struct tvec_state *tv, struct tvec_remote *r,
+static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
                    const unsigned char *p, size_t sz)
 {
+  void (*opipe)(int) = SIG_ERR;
   ssize_t n;
+  int ret;
 
+  opipe = signal(SIGPIPE, SIG_IGN);
+    if (opipe == SIG_ERR) {
+      ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
+      goto end;
+    }
   while (sz) {
-    n = write(r->outfd, p, sz);
+    n = write(rc->outfd, p, sz);
     if (n > 0)
       { p += n; sz -= n; }
-    else
-      return (ioerr(tv, r, "failed to send: %s",
-                   n ? strerror(errno) : "empty write"));
+    else {
+      ret = ioerr(tv, rc, "failed to send: %s",
+                n ? strerror(errno) : "empty write");
+      goto end;
+    }
   }
-  return (0);
+  ret = 0;
+end:
+  if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
+  return (ret);
 }
 
 #define RCVF_ALLOWEOF 1u
-static int recv_all(struct tvec_state *tv, struct tvec_remote *r,
+enum {
+  RECV_FAIL = -1,
+  RECV_OK = 0,
+  RECV_EOF = 1
+};
+static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
                    unsigned char *p, size_t sz, unsigned f)
 {
   ssize_t n;
@@ -100,196 +137,1146 @@ static int recv_all(struct tvec_state *tv, struct tvec_remote *r,
 #define f_any 1u
 
   while (sz) {
-    n = read(r->infd, p, sz);
+    n = read(rc->infd, p, sz);
     if (n > 0)
       { p += n; sz -= n; ff |= f_any; }
     else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any))
-      return (1);
+      return (RECV_EOF);
     else
-      return (ioerr(tv, r, "failed to receive: %s",
+      return (ioerr(tv, rc, "failed to receive: %s",
                    n ? strerror(errno) : "unexpected end-of-file"));
   }
-  return (0);
+  return (RECV_OK);
 
 #undef f_any
 }
 
-int tvec_send(struct tvec_state *tv, struct tvec_remote *r)
+static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
 {
   kludge64 k; unsigned char lenbuf[8];
-  const char *p; size_t sz;
+  const unsigned char *p; size_t sz;
 
-  if (r->f&TVRF_BROKEN) return (-1);
-  if (BBAD(&r->bout.b))
-    return (ioerr(tv, r, "failed to build output packet buffer");
+  if (rc->f&TVRF_BROKEN) return (-1);
+  if (BBAD(&rc->bout._b))
+    return (ioerr(tv, rc, "failed to build output packet buffer"));
 
-  p = BBASE(r->bout.b); sz = BLEN(&r->bout.b);
+  p = BBASE(&rc->bout._b); sz = BLEN(&rc->bout._b);
   ASSIGN64(k, sz); STORE64_L_(lenbuf, k);
-  if (send_all(tv, r, lenbuf, sizeof(lenbuf))) return (-1);
-  if (send_all(tv, r, p, sz)) return (-1);
+  if (send_all(tv, rc, lenbuf, sizeof(lenbuf))) return (-1);
+  if (send_all(tv, rc, p, sz)) return (-1);
 
   return (0);
 }
 
-int tvec_recv(struct tvec_state *tv, struct tvec_remote *r, buf *b_out)
+static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
+                      unsigned f, buf *b_out)
 {
   kludge64 k, szmax; unsigned char lenbuf[8];
   unsigned char *p;
   size_t sz;
-  int rc;
+  int ret;
 
-  if (r->f&TVRF_BROKEN) return (-1);
-  ASSIGN64(k, (size_t)-1);
-  rc = recv_all(tv, r, lenbuf, sizeof(lenbuf), RCVF_ALLOWEOF);
-    if (rc) return (rc);
+  if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
+  ASSIGN64(szmax, (size_t)-1);
+  ret = recv_all(tv, rc, lenbuf, sizeof(lenbuf), f);
+    if (ret) return (ret);
   LOAD64_L_(k, lenbuf);
   if (CMP64(k, >, szmax))
-    return (ioerr(tv, r, "packet size 0x%08lx%08lx out of range",
+    return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
                  (unsigned long)HI64(k), (unsigned long)LO64(k)));
 
-  sz = GET64(size_t, k); buf_reset(&r->bin); p = buf_get(&r->bin.b, sz);
-    if (!p) return (ioerr(tv, r, "failed to allocate receive buffer"));
-  if (recv_all(tv, r, p, sz, 0)) return (-1);
-  buf_init(b_out, p, sz); return (0);
+  sz = GET64(size_t, k); dbuf_reset(&rc->bin); p = buf_get(&rc->bin._b, sz);
+    if (!p) return (ioerr(tv, rc, "failed to allocate receive buffer"));
+  if (recv_all(tv, rc, p, sz, 0)) return (RECV_FAIL);
+  buf_init(b_out, p, sz); return (RECV_OK);
 }
 
-/*----- Data formatting primitives ----------------------------------------*/
+#define SENDPK(tv, rc, pk)                                             \
+       if ((rc)->f&TVRF_BROKEN) MC_GOELSE(body); else                  \
+       MC_BEFORE(setpk,                                                \
+         { dbuf_reset(&(rc)->bout);                                    \
+           buf_putu16l(&(rc)->bout._b, (pk)); })                       \
+       MC_ALLOWELSE(body)                                              \
+       MC_AFTER(send,                                                  \
+         { if (remote_send(tv, rc)) MC_GOELSE(body); })                \
 
+static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
+  { return (ioerr(tv, rc, "received malformed packet")); }
 
 /*----- Packet types ------------------------------------------------------*/
 
-#define TVPK_ERROR     0x0001          /* msg: string */
-#define TVPK_NOTICE    0x0002          /* msg: string */
-#define TVPK_STATUS    0x0003          /* st: char */
+#define TVPF_ACK       0x0001u
 
-#define TVPK_BGROUP    0x0101          /* name: string */
-#define TVPK_TEST      0x0102          /* in: regs */
-#define TVPK_EGROUP    0x0103          /* --- */
+#define TVPK_VER       0x0000u         /* --> min, max: u16 */
+                                       /* <-- ver: u16 */
 
-#define TVPK_SKIPGRP   0x0201          /* excuse: string */
-#define TVPK_SKIP      0x0202          /* excuse: string */
-#define TVPK_FAIL      0x0203          /* detail: string */
-#define TVPK_MISMATCH  0x0204          /* in, out: regs */
-#define TVPK_BBENCH    0x0205          /* in: regs */
-#define TVPK_EBENCH    0x0206         /* flags: u16; n: u64; t, cy: float */
+#define TVPK_REPORT    0x0100u         /* <-- level: u16; msg: string */
+#define TVPK_PROGRESS  0x0102u         /* <-- st: str16 */
 
-/*----- The output driver -------------------------------------------------*/
+#define TVPK_BGROUP    0x0200u         /* --> name: str16
+                                        * <-- --- */
+#define TVPK_TEST      0x0202u         /* --> in: regs
+                                        * <-- --- */
+#define TVPK_EGROUP    0x0204u         /* --> --- */
 
-#define SENDPK(ro, pk)                                                 \
-       if ((ro)->r.f&TVRF_BROKEN) /* do nothing */; else               \
-       MC_BEFORE(setpk,                                                \
-         { buf_reset(&(ro)->r.bout);                                   \
-           buf_putu16l(&(ro)->r.bout.b, (pk)); })                      \
-       MC_AFTER(send,                                                  \
-         { tvec_send(&ro->_o.tv, &ro->r); })
+#define TVPK_SKIPGRP   0x0300u         /* <-- excuse: str16 */
+#define TVPK_SKIP      0x0302u         /* <-- excuse: str16 */
+#define TVPK_FAIL      0x0304u         /* <-- flag: u8, detail: str16 */
+#define TVPK_DUMPREG   0x0306u         /* <-- ri: u16; disp: u16;
+                                        *     flag: u8, rv: value */
+#define TVPK_BBENCH    0x0308u         /* <-- ident: str32; unit: u16 */
+#define TVPK_EBENCH    0x030au         /* <-- ident: str32; unit: u16;
+                                        *     flags: u16; n, t, cy: f64 */
 
-static int sendstr(struct tvec_output *o, unsigned pk,
-                  const char *p, va_list *ap)
-{
-  struct remote_output *ro = (struct remote_output *)o;
+/*----- Server ------------------------------------------------------------*/
+
+static const struct tvec_outops remote_ops;
+
+static struct tvec_state srvtv;
+static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT;
+static struct tvec_output srvout = { &remote_ops };
 
-  SENDPK(ro, pk) buf_vputstrf16l(&ro->r.bout.b, msg, ap);
-  return (ro->r.f&TVRF_BROKEN ? -1 : 0);
+int tvec_setprogress(const char *status)
+{
+  SENDPK(&srvtv, &srvrc, TVPK_PROGRESS)
+    buf_putstr16l(&srvrc.bout._b, status);
+  else return (-1);
+  return (0);
 }
 
-static void report(struct tvec_output *o, unsigned pk, const char *what,
-                  const char *msg, va_list *ap)
+int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
 {
-  if (sendstr(o, pk, msg, ap)) {
-    fprintf(stderr, "%s %s: ", QUIS, what);
-    vfprintf(stderr, msg, *ap);
-    fputc('\n', stderr);
+  uint16 pk, u, v;
+  unsigned i;
+  buf b;
+  const struct tvec_test *t;
+  void *p; size_t sz;
+  const struct tvec_env *env = 0;
+  unsigned f = 0;
+#define f_regslive 1u
+  void *ctx = 0;
+  int rc;
+
+  setup_comms(&srvrc, infd, outfd);
+  tvec_begin(&srvtv, config, &srvout);
+
+  if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
+  if (buf_getu16l(&b, &pk)) goto bad;
+  if (pk != TVPK_VER) {
+    rc = ioerr(&srvtv, &srvrc,
+              "unexpected packet type 0x%04x instead of client version",
+              pk);
+    goto end;
   }
-}
+  if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
+  SENDPK(&srvtv, &srvrc, TVPK_VER | TVPF_ACK) buf_putu16l(&srvrc.bout._b, 0);
+  else { rc = -1; goto end; }
 
-static void remote_error(struct tvec_output *o, const char *msg, va_list *ap)
-  { report(o, TVPK_ERROR, "ERROR", msg, ap); }
+  tvec_setprogress("%IDLE");
 
-static void remote_notice(struct tvec_output *o,
-                         const char *msg, va_list *ap)
-  { report(o, TVPK_NOTICE, "notice", msg, ap); }
+  for (;;) {
+    rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
+      if (rc == RECV_EOF) break;
+      else if (rc == RECV_FAIL) goto end;
+    if (buf_getu16l(&b, &pk)) goto bad;
 
-static void remote_setstatus(struct tvec_ouptut *o, int st)
-{
-  struct remote_output *ro = (struct remote_output *)o;
-  SENDPK(ro, TVPK_STATUS) buf_putbyte(&ro->r.bout.b, st);
+    switch (pk) {
+
+      case TVPK_BGROUP:
+       p = buf_getmem16l(&b, &sz); if (!p) goto bad;
+       if (BLEFT(&b)) goto bad;
+       for (t = srvtv.tests; t->name; t++)
+         if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
+           goto found_group;
+       rc = ioerr(&srvtv, &srvrc, "unknown test group `%.*s'",
+                  (int)sz, (char *)p);
+       goto end;
+
+      found_group:
+       srvtv.test = t; env = t->env;
+       if (env && env->setup == tvec_remotesetup)
+         env = ((struct tvec_remoteenv *)env)->r.env;
+       if (!env || !env->ctxsz) ctx = 0;
+       else ctx = xmalloc(env->ctxsz);
+       if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
+
+       SENDPK(&srvtv, &srvrc, TVPK_BGROUP | TVPF_ACK);
+       else { rc = -1; goto end; }
+
+       for (;;) {
+         if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
+         if (buf_getu16l(&b, &pk)) goto bad;
+         switch (pk) {
+
+           case TVPK_EGROUP:
+             if (BLEFT(&b)) goto bad;
+             goto endgroup;
+
+           case TVPK_TEST:
+             tvec_initregs(&srvtv); f |= f_regslive;
+             if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
+                                  srvtv.nreg, srvtv.regsz))
+               goto bad;
+             if (BLEFT(&b)) goto bad;
+
+             if (!(srvtv.f&TVSF_SKIP)) {
+               srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
+               tvec_setprogress("%SETUP");
+               if (env && env->before) env->before(&srvtv, ctx);
+               if (!(srvtv.f&TVSF_ACTIVE))
+                 /* setup forced a skip */;
+               else {
+                 for (i = 0; i < srvtv.nrout; i++)
+                   if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
+                     TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
+                 tvec_setprogress("%RUN");
+                 if (env && env->run)
+                   env->run(&srvtv, t->fn, ctx);
+                 else {
+                   t->fn(srvtv.in, srvtv.out, ctx);
+                   tvec_check(&srvtv, 0);
+                 }
+               }
+               tvec_setprogress("%DONE");
+               if (env && env->after) env->after(&srvtv, ctx);
+               tvec_endtest(&srvtv);
+             }
+             tvec_releaseregs(&srvtv); f &= ~f_regslive;
+             SENDPK(&srvtv, &srvrc, TVPK_TEST | TVPF_ACK);
+             else { rc = -1; goto end; }
+             tvec_setprogress("%IDLE");
+             break;
+
+           default:
+             rc = ioerr(&srvtv, &srvrc,
+                        "unexpected packet type 0x%04x", pk);
+             goto end;
+
+         }
+       }
+
+      endgroup:
+       if (env && env->teardown) env->teardown(&srvtv, ctx);
+       xfree(ctx); t = 0; env = 0; ctx = 0;
+       break;
+
+      default:
+       goto bad;
+    }
+  }
+  rc = 0;
+
+end:
+  if (env && env->teardown) env->teardown(&srvtv, ctx);
+  xfree(ctx);
+  if (f&f_regslive) tvec_releaseregs(&srvtv);
+  release_comms(&srvrc);
+  return (rc ? 2 : 0);
+
+bad:
+  rc = malformed(&srvtv, &srvrc); goto end;
+
+#undef f_regslive
 }
 
+/*----- Server output driver ----------------------------------------------*/
+
 static void remote_skipgroup(struct tvec_output *o,
                             const char *excuse, va_list *ap)
-  { sendstr(o, TVPK_SKIPGRP, excuse, ap); }
+{
+  SENDPK(&srvtv, &srvrc, TVPK_SKIPGRP)
+    buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+}
 
 static void remote_skip(struct tvec_output *o,
                        const char *excuse, va_list *ap)
-  { sendstr(o, TVPK_SKIP, excuse, ap); }
+{
+  SENDPK(&srvtv, &srvrc, TVPK_SKIP)
+    buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+}
 
 static void remote_fail(struct tvec_output *o,
                        const char *detail, va_list *ap)
-  { sendstr(o, TVPK_FAIL, detail, ap); }
+{
+  SENDPK(&srvtv, &srvrc, TVPK_FAIL)
+    if (!detail)
+      buf_putbyte(&srvrc.bout._b, 0);
+    else {
+      buf_putbyte(&srvrc.bout._b, 1);
+      buf_vputstrf16l(&srvrc.bout._b, detail, ap);
+    }
+}
 
-static void remote_mismatch(struct tvec_output *o)
+static void remote_dumpreg(struct tvec_output *o,
+                          unsigned disp, const union tvec_regval *rv,
+                          const struct tvec_regdef *rd)
 {
-  struct remote_output *ro = (struct remote_output *)o;
-  struct tvec_state *rv = ro->_o.tv;
+  const struct tvec_regdef *reg;
+  unsigned r;
 
-  SENDPK(ro, TVPK_MISMATCH) {
-    tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz);
-    tvec_serialize(tv, &ro->r.bout.b, tv->out, tv->nrout, tv->regsz);
+  /* Find the register definition. */
+  for (reg = srvtv.test->regs, r = 0; reg->name; reg++, r++)
+    if (reg == rd) goto found;
+  assert(!"unexpected register definition");
+
+found:
+  SENDPK(&srvtv, &srvrc, TVPK_DUMPREG) {
+    buf_putu16l(&srvrc.bout._b, r);
+    buf_putu16l(&srvrc.bout._b, disp);
+    if (!rv)
+      buf_putbyte(&srvrc.bout._b, 0);
+    else {
+      buf_putbyte(&srvrc.bout._b, 1);
+      rd->ty->tobuf(&srvrc.bout._b, rv, rd);
+    }
   }
 }
 
-static void remote_bbench(struct tvec_output *o)
+static void remote_bbench(struct tvec_output *o,
+                         const char *ident, unsigned unit)
 {
-  struct remote_output *ro = (struct remote_output *)o;
-  struct tvec_state *rv = ro->_o.tv;
-
-  SENDPK(ro, TVPK_BBENCH)
-    tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz);
+  SENDPK(&srvtv, &srvrc, TVPK_BBENCH) {
+    buf_putstr32l(&srvrc.bout._b, ident);
+    buf_putu16l(&srvrc.bout._b, unit);
+  }
 }
 
 static void remote_ebench(struct tvec_output *o,
+                         const char *ident, unsigned unit,
                          const struct bench_timing *t)
 {
-  struct remote_output *ro = (struct remote_output *)o;
-  kludge64 k;
+  SENDPK(&srvtv, &srvrc, TVPK_EBENCH) {
+    buf_putstr32l(&srvrc.bout._b, ident);
+    buf_putu16l(&srvrc.bout._b, unit);
+    if (!t || !(t->f&BTF_ANY))
+      buf_putu16l(&srvrc.bout._b, 0);
+    else {
+      buf_putu16l(&srvrc.bout._b, t->f);
+      buf_putf64l(&srvrc.bout._b, t->n);
+      if (t->f&BTF_TIMEOK) buf_putf64l(&srvrc.bout._b, t->t);
+      if (t->f&BTF_CYOK) buf_putf64l(&srvrc.bout._b, t->cy);
+    }
+  }
+}
+
+static void remote_report(struct tvec_output *o, unsigned level,
+                         const char *msg, va_list *ap)
+{
+  const char *what;
 
-  SENDPK(ro, TVPK_EBENCH) {
-    buf_putu16l(&ro->r.bout.b, t->f);
-    ASSIGN64(k, t->n); buf_putk64l(&ro->r.bout.b, k);
-    if (t->f&BTF_TIMEOK) buf_putf64l(&ro->r.bout.b, t->t);
-    if (t->f&BTF_CYOK) buf_putf64l(&ro->r.bout.b, t->cy);
+  SENDPK(&srvtv, &srvrc, TVPK_REPORT) {
+    buf_putu16l(&srvrc.bout._b, level);
+    buf_vputstrf16l(&srvrc.bout._b, msg, ap);
+  } else {
+    switch (level) {
+      case TVLEV_NOTE: what = "notice"; break;
+      case TVLEV_ERR: what = "ERROR"; break;
+      default: what = "(?level)"; break;
+    }
+    fprintf(stderr, "%s %s: ", QUIS, what);
+    vfprintf(stderr, msg, *ap);
+    fputc('\n', stderr);
   }
 }
 
-static void remote_write(struct tvec_output *o, const char *p, size_t sz)
-  { assert(!"remote_write"); }
-static void remote_bsession(struct tvec_output *o)
-  { assert(!"remote_bsession"); }
+static void remote_bsession(struct tvec_output *o, struct tvec_state *tv)
+  { ; }
 static int remote_esession(struct tvec_output *o)
-  { assert(!"remote_esession"); return (-1); }
+  { return (srvtv.f&TVSF_ERROR ? 2 : 0); }
+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 void remote_egroup(struct tvec_output *o, unsigned outcome)
-  { assert(!"remote_egroup"); }
-static void remote_etest(struct tvec_output *o, unsigned outcome)
-  { assert(!"remote_etest"); }
-
-static void remote_destroy(struct tvec_output *o)
-{
-}
 
 static const struct tvec_outops remote_ops = {
-  remote_error, remote_notice, remote_setstatus, remote_write,
   remote_bsession, remote_esession,
-  remote_bgroup, remote_egroup, remote_skip,
-  remote_btest, remote_skip, remote_fail, remote_mismatch, remote_etest,
+  remote_bgroup, remote_skipgroup, remote_egroup,
+  remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest,
   remote_bbench, remote_ebench,
+  remote_report,
   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
+
+static const struct tvec_flag exit_flags[] = {
+  /*
+    ;;; 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
+    ;;; get Emacs to regenerate it.
+
+    (let ((signals '(HUP INT QUIT ILL TRAP ABRT IOT EMT FPE KILL BUS SEGV SYS
+                        PIPE ALRM TERM URG STOP TSTP CONT CHLD CLD TTIN TTOU
+                        POLL IO TIN XCPU XFSZ VTALRM PROF WINCH USR1 USR2
+                        STKFLT INFO PWR THR LWP LIBRT LOST)))
+      (save-excursion
+       (goto-char (point-min))
+       (search-forward (concat "***" "BEGIN siglist" "***"))
+       (beginning-of-line 2)
+       (delete-region (point)
+                      (progn
+                        (search-forward "***END***")
+                        (beginning-of-line)
+                        (point)))
+       (dolist (sig signals)
+         (insert (format "#ifdef SIG%s\n  { \"SIG%s\", TVXF_VALMASK | TVXF_SIG, SIG%s | TVXF_SIG },\n#endif\n"
+                         sig sig sig)))))
+  */
+
+  /***BEGIN siglist***/
+#ifdef SIGHUP
+  { "SIGHUP", TVXF_VALMASK | TVXF_SIG, SIGHUP | TVXF_SIG },
+#endif
+#ifdef SIGINT
+  { "SIGINT", TVXF_VALMASK | TVXF_SIG, SIGINT | TVXF_SIG },
+#endif
+#ifdef SIGQUIT
+  { "SIGQUIT", TVXF_VALMASK | TVXF_SIG, SIGQUIT | TVXF_SIG },
+#endif
+#ifdef SIGILL
+  { "SIGILL", TVXF_VALMASK | TVXF_SIG, SIGILL | TVXF_SIG },
+#endif
+#ifdef SIGTRAP
+  { "SIGTRAP", TVXF_VALMASK | TVXF_SIG, SIGTRAP | TVXF_SIG },
+#endif
+#ifdef SIGABRT
+  { "SIGABRT", TVXF_VALMASK | TVXF_SIG, SIGABRT | TVXF_SIG },
+#endif
+#ifdef SIGIOT
+  { "SIGIOT", TVXF_VALMASK | TVXF_SIG, SIGIOT | TVXF_SIG },
+#endif
+#ifdef SIGEMT
+  { "SIGEMT", TVXF_VALMASK | TVXF_SIG, SIGEMT | TVXF_SIG },
+#endif
+#ifdef SIGFPE
+  { "SIGFPE", TVXF_VALMASK | TVXF_SIG, SIGFPE | TVXF_SIG },
+#endif
+#ifdef SIGKILL
+  { "SIGKILL", TVXF_VALMASK | TVXF_SIG, SIGKILL | TVXF_SIG },
+#endif
+#ifdef SIGBUS
+  { "SIGBUS", TVXF_VALMASK | TVXF_SIG, SIGBUS | TVXF_SIG },
+#endif
+#ifdef SIGSEGV
+  { "SIGSEGV", TVXF_VALMASK | TVXF_SIG, SIGSEGV | TVXF_SIG },
+#endif
+#ifdef SIGSYS
+  { "SIGSYS", TVXF_VALMASK | TVXF_SIG, SIGSYS | TVXF_SIG },
+#endif
+#ifdef SIGPIPE
+  { "SIGPIPE", TVXF_VALMASK | TVXF_SIG, SIGPIPE | TVXF_SIG },
+#endif
+#ifdef SIGALRM
+  { "SIGALRM", TVXF_VALMASK | TVXF_SIG, SIGALRM | TVXF_SIG },
+#endif
+#ifdef SIGTERM
+  { "SIGTERM", TVXF_VALMASK | TVXF_SIG, SIGTERM | TVXF_SIG },
+#endif
+#ifdef SIGURG
+  { "SIGURG", TVXF_VALMASK | TVXF_SIG, SIGURG | TVXF_SIG },
+#endif
+#ifdef SIGSTOP
+  { "SIGSTOP", TVXF_VALMASK | TVXF_SIG, SIGSTOP | TVXF_SIG },
+#endif
+#ifdef SIGTSTP
+  { "SIGTSTP", TVXF_VALMASK | TVXF_SIG, SIGTSTP | TVXF_SIG },
+#endif
+#ifdef SIGCONT
+  { "SIGCONT", TVXF_VALMASK | TVXF_SIG, SIGCONT | TVXF_SIG },
+#endif
+#ifdef SIGCHLD
+  { "SIGCHLD", TVXF_VALMASK | TVXF_SIG, SIGCHLD | TVXF_SIG },
+#endif
+#ifdef SIGCLD
+  { "SIGCLD", TVXF_VALMASK | TVXF_SIG, SIGCLD | TVXF_SIG },
+#endif
+#ifdef SIGTTIN
+  { "SIGTTIN", TVXF_VALMASK | TVXF_SIG, SIGTTIN | TVXF_SIG },
+#endif
+#ifdef SIGTTOU
+  { "SIGTTOU", TVXF_VALMASK | TVXF_SIG, SIGTTOU | TVXF_SIG },
+#endif
+#ifdef SIGPOLL
+  { "SIGPOLL", TVXF_VALMASK | TVXF_SIG, SIGPOLL | TVXF_SIG },
+#endif
+#ifdef SIGIO
+  { "SIGIO", TVXF_VALMASK | TVXF_SIG, SIGIO | TVXF_SIG },
+#endif
+#ifdef SIGTIN
+  { "SIGTIN", TVXF_VALMASK | TVXF_SIG, SIGTIN | TVXF_SIG },
+#endif
+#ifdef SIGXCPU
+  { "SIGXCPU", TVXF_VALMASK | TVXF_SIG, SIGXCPU | TVXF_SIG },
+#endif
+#ifdef SIGXFSZ
+  { "SIGXFSZ", TVXF_VALMASK | TVXF_SIG, SIGXFSZ | TVXF_SIG },
+#endif
+#ifdef SIGVTALRM
+  { "SIGVTALRM", TVXF_VALMASK | TVXF_SIG, SIGVTALRM | TVXF_SIG },
+#endif
+#ifdef SIGPROF
+  { "SIGPROF", TVXF_VALMASK | TVXF_SIG, SIGPROF | TVXF_SIG },
+#endif
+#ifdef SIGWINCH
+  { "SIGWINCH", TVXF_VALMASK | TVXF_SIG, SIGWINCH | TVXF_SIG },
+#endif
+#ifdef SIGUSR1
+  { "SIGUSR1", TVXF_VALMASK | TVXF_SIG, SIGUSR1 | TVXF_SIG },
+#endif
+#ifdef SIGUSR2
+  { "SIGUSR2", TVXF_VALMASK | TVXF_SIG, SIGUSR2 | TVXF_SIG },
+#endif
+#ifdef SIGSTKFLT
+  { "SIGSTKFLT", TVXF_VALMASK | TVXF_SIG, SIGSTKFLT | TVXF_SIG },
+#endif
+#ifdef SIGINFO
+  { "SIGINFO", TVXF_VALMASK | TVXF_SIG, SIGINFO | TVXF_SIG },
+#endif
+#ifdef SIGPWR
+  { "SIGPWR", TVXF_VALMASK | TVXF_SIG, SIGPWR | TVXF_SIG },
+#endif
+#ifdef SIGTHR
+  { "SIGTHR", TVXF_VALMASK | TVXF_SIG, SIGTHR | TVXF_SIG },
+#endif
+#ifdef SIGLWP
+  { "SIGLWP", TVXF_VALMASK | TVXF_SIG, SIGLWP | TVXF_SIG },
+#endif
+#ifdef SIGLIBRT
+  { "SIGLIBRT", TVXF_VALMASK | TVXF_SIG, SIGLIBRT | TVXF_SIG },
+#endif
+#ifdef SIGLOST
+  { "SIGLOST", TVXF_VALMASK | TVXF_SIG, SIGLOST | TVXF_SIG },
+#endif
+  /***END***/
+
+  { "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
+};
+
+static const struct tvec_flaginfo exit_flaginfo =
+  { "exit-status", exit_flags, &tvrange_uint };
+static const struct tvec_regdef exit_regdef =
+  { "@exit", 0, &tvty_flags, 0, { &exit_flaginfo } };
+
+static const struct tvec_regdef progress_regdef =
+  { "@progress", 0, &tvty_string, 0 };
+
+static const struct tvec_uassoc reconn_assocs[] = {
+  { "on-demand",       TVRCN_DEMAND },
+  { "force",           TVRCN_FORCE },
+  { "skip",            TVRCN_SKIP },
+  TVEC_ENDENUM
+};
+
+enum {
+  CONN_BROKEN = -2,                    /* previously broken */
+  CONN_FAILED = -1,                    /* attempt freshly failed */
+  CONN_ESTABLISHED = 0,                        /* previously established */
+  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 } };
+
+static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r,
+                         unsigned f, uint16 end, buf *b_out)
+{
+  struct tvec_output *o = tv->output;
+  uint16 pk, u, v;
+  const char *p; size_t n;
+  dstr d = DSTR_INIT;
+  buf *b = b_out;
+  const struct tvec_regdef *rd;
+  struct bench_timing bt;
+  struct tvec_reg *reg = 0;
+  unsigned i;
+  int rc;
+
+  for (;;) {
+    rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end;
+    if (buf_getu16l(b, &pk)) goto bad;
+
+    switch (pk) {
+
+      case TVPK_PROGRESS:
+       p = buf_getmem16l(b, &n); if (!p) goto bad;
+       if (BLEFT(b)) goto bad;
+
+       DRESET(&r->progress); DPUTM(&r->progress, p, n); DPUTZ(&r->progress);
+       break;
+
+      case TVPK_REPORT:
+       if (buf_getu16l(b, &u)) goto bad;
+       p = buf_getmem16l(b, &n); if (!p) goto bad;
+       if (BLEFT(b)) goto bad;
+
+       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+       tvec_report(tv, u, "%s", d.buf);
+       break;
+
+      case TVPK_SKIPGRP:
+       p = buf_getmem16l(b, &n); if (!p) goto bad;
+       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+       if (BLEFT(b)) goto bad;
+
+       tvec_skipgroup(tv, "%s", d.buf);
+       break;
+
+      case TVPK_SKIP:
+       if (!(tv->f&TVSF_ACTIVE)) {
+         rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name);
+         goto end;
+       }
+
+       p = buf_getmem16l(b, &n); if (!p) goto bad;
+       if (BLEFT(b)) goto bad;
+
+       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+       tvec_skip(tv, "%s", d.buf);
+       break;
+
+      case TVPK_FAIL:
+       if (!(tv->f&TVSF_ACTIVE) &&
+           ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) {
+         rc = ioerr(tv, &r->rc, "test `%s' not active or failing",
+                    tv->test->name);
+         goto end;
+       }
+
+       rc = buf_getbyte(b); if (rc < 0) goto bad;
+       if (rc) { p = buf_getmem16l(b, &n); if (!p) goto bad; }
+       else p = 0;
+       if (BLEFT(b)) goto bad;
 
-/*----- Main code ---------------------------------------------------------*/
+       if (!p)
+         tvec_fail(tv, 0);
+       else {
+         DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+         tvec_fail(tv, "%s", d.buf);
+       }
+       break;
 
+      case TVPK_DUMPREG:
+       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;
+       rc = ioerr(tv, &r->rc,
+                  "register definition %u out of range for test `%s'",
+                  u, tv->test->name);
+       goto end;
+      found_reg:
+       if (v >= TVRD_LIMIT) {
+         rc = ioerr(tv, &r->rc, "register disposition %u out of range", v);
+         goto end;
+       }
 
+       rc = buf_getbyte(b); if (rc < 0) goto bad;
+       if (!rc)
+         tvec_dumpreg(tv, v, 0, rd);
+       else {
+         if (!reg) reg = xmalloc(tv->regsz);
+         rd->ty->init(&reg->v, rd);
+         rc = rd->ty->frombuf(b, &reg->v, rd);
+         if (!rc) tvec_dumpreg(tv, v, &reg->v, rd);
+         rd->ty->release(&reg->v, rd);
+         if (rc) goto bad;
+       }
+       if (BLEFT(b)) goto bad;
+       break;
+
+      case TVPK_BBENCH:
+       p = buf_getmem32l(b, &n); if (!p) goto bad;
+       if (buf_getu16l(b, &u)) goto bad;
+       if (BLEFT(b)) goto bad;
+       if (u >= TVBU_LIMIT) {
+         rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
+         goto end;
+       }
+
+       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+       o->ops->bbench(o, d.buf, u);
+       break;
+
+      case TVPK_EBENCH:
+       p = buf_getmem32l(b, &n); if (!p) goto bad;
+       if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
+       if (u >= TVBU_LIMIT)
+         { rc = ioerr(tv, &r->rc, "unit code %u out of range", u); goto end; }
+       if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
+       if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
+       if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
+       if (BLEFT(b)) goto bad;
+
+       DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d);
+       o->ops->ebench(o, d.buf, u, v&BTF_ANY ? &bt : 0);
+       break;
+
+      default:
+       if (pk == end) { rc = 0; goto end; }
+       rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
+       goto end;
+    }
+  }
+
+end:
+  DDESTROY(&d);
+  xfree(reg);
+  return (rc);
+bad:
+  rc = malformed(tv, &r->rc); goto end;
+}
+
+static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+  pid_t kid;
+  int st;
+
+  if (!r->kid)
+    { r->exit = TVXST_DISCONN; r->kid = -1; }
+  else if (r->kid > 0) {
+    kid = waitpid(r->kid, &st, 0);
+    if (kid < 0) {
+      tvec_notice(tv, "failed to wait for remote child: %s",
+                 strerror(errno));
+      r->exit = TVXST_ERR;
+    } else if (!kid) {
+      tvec_notice(tv, "remote child vanished without a trace");
+      r->exit = TVXST_ERR;
+    } else if (WIFCONTINUED(st))
+      r->exit = TVXST_CONT;
+    else if (WIFSIGNALED(st))
+      r->exit = TVXST_KILL | TVXF_SIG | WTERMSIG(st);
+    else if (WIFSTOPPED(st))
+      r->exit = TVXST_STOP | TVXF_SIG | WSTOPSIG(st);
+    else if (WIFEXITED(st))
+      r->exit = TVXST_EXIT | WEXITSTATUS(st);
+    else {
+      tvec_notice(tv, "remote child died with unknown status 0x%04x",
+                 (unsigned)st);
+      r->exit = TVXST_UNK;
+    }
+    r->kid = -1;
+  }
+}
+
+static void report_errline(char *p, size_t n, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  struct tvec_state *tv = r->tv;
+
+  if (p && !(r->rc.f&TVRF_MUFFLE))
+    tvec_notice(tv, "child process stderr: %s", p);
+}
+
+#define ERF_SILENT 0x0001u
+#define ERF_CLOSE 0x0002u
+static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r,
+                      unsigned f)
+{
+  char *p; size_t sz;
+  ssize_t n;
+  int rc;
+
+  if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
+  else r->rc.f &= ~TVRF_MUFFLE;
+  if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
+    rc = ioerr(tv, &r->rc, "failed to %s error non-blocking flag",
+              f&ERF_CLOSE ? "clear" : "set");
+    goto end;
+  }
+
+  for (;;) {
+    sz = lbuf_free(&r->errbuf, &p);
+    n = read(r->errfd, p, sz);
+      if (!n) break;
+      if (n < 0) {
+       if (errno == EINTR) continue;
+       if (!(f&ERF_CLOSE) && (errno == EWOULDBLOCK || errno == EAGAIN))
+         break;
+       rc = ioerr(tv, &r->rc, "failed to read child stderr: %s",
+                  strerror(errno));
+       goto end;
+      }
+    lbuf_flush(&r->errbuf, p, n);
+  }
+  rc = 0;
+end:
+  if (f&ERF_CLOSE) {
+    lbuf_close(&r->errbuf);
+    close(r->errfd);
+  }
+  return (rc);
+}
+
+#define DCF_KILL 0x0100u
+static void disconnect_remote(struct tvec_state *tv,
+                             struct tvec_remotectx *r, unsigned f)
+{
+  if (r->kid < 0) return;
+  if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
+  close_comms(&r->rc);
+  if (r->kid > 0) kill(r->kid, SIGTERM);
+  drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
+}
+
+static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+  const struct tvec_remoteenv *re = r->re;
+  pid_t kid = 0;
+  buf b;
+  uint16 v;
+  int infd = -1, outfd = -1, errfd = -1, rc;
+
+  DRESET(&r->progress); DPUTS(&r->progress, "%INIT");
+  if (r->kid >= 0) { rc = 0; goto end; }
+  if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re))
+    { rc = -1; goto end; }
+  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;
+
+  SENDPK(tv, &r->rc, TVPK_VER) {
+    buf_putu16l(&r->rc.bout._b, 0);
+    buf_putu16l(&r->rc.bout._b, 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;
+  if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
+  if (v) {
+    rc = ioerr(tv, &r->rc, "protocol version %u not supported", v);
+    goto end;
+  }
+
+  SENDPK(tv, &r->rc, TVPK_BGROUP)
+    buf_putstr16l(&r->rc.bout._b, 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;
+end:
+  if (rc) disconnect_remote(tv, r, DCF_KILL);
+  return (rc);
+bad:
+  rc = malformed(tv, &r->rc); goto end;
+}
+
+static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+  if (r->kid < 0)
+    return (CONN_BROKEN);
+  else if (r->rc.f&TVRF_BROKEN)
+    { disconnect_remote(tv, r, DCF_KILL); return (CONN_FAILED); }
+  else
+    return (CONN_ESTABLISHED);
+}
+
+static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
+{
+  int rc;
+
+  switch (r->rc.f&TVRF_RCNMASK) {
+    case TVRCN_DEMAND:
+      rc = check_comms(tv, r);
+      if (rc < CONN_ESTABLISHED) {
+       close_comms(&r->rc);
+       if (connect_remote(tv, r)) rc = CONN_FAILED;
+       else rc = CONN_FRESH;
+      }
+      break;
+    case TVRCN_FORCE:
+      disconnect_remote(tv, r, DCF_KILL);
+      if (connect_remote(tv, r)) rc = CONN_FAILED;
+      else rc = CONN_FRESH;
+      break;
+    case TVRCN_SKIP:
+      rc = check_comms(tv, r);
+      break;
+    default:
+      abort();
+  }
+  return (rc);
+}
+
+static void reset_vars(struct tvec_remotectx *r)
+{
+  r->exwant = TVXST_RUN; r->rc.f = (r->rc.f&~TVRF_RCNMASK) | TVRCN_DEMAND;
+  DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
+}
+
+void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
+                     void *pctx, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env;
+
+  assert(!re->r.env || tv->test->env == &re->_env);
+
+  r->tv = tv;
+  init_comms(&r->rc);
+  r->re = re; r->kid = -1;
+  DCREATE(&r->prgwant); DCREATE(&r->progress);
+  if (connect_remote(tv, r))
+    tvec_skipgroup(tv, "failed to connect to test backend");
+  reset_vars(r);
+}
+
+int tvec_remoteset(struct tvec_state *tv, const char *var,
+                  const struct tvec_env *env, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  union tvec_regval rv;
+  int rc;
+
+  if (STRCMP(var, ==, "@exit")) {
+    if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; }
+    if (r) r->exwant = rv.u;
+    rc = 1;
+  } else if (STRCMP(var, ==, "@progress")) {
+    tvty_string.init(&rv, &progress_regdef);
+    rc = tvty_string.parse(&rv, &progress_regdef, tv);
+    if (r && !rc)
+      { DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); }
+    tvty_string.release(&rv, &progress_regdef);
+    if (rc) { rc = -1; goto end; }
+    rc = 1;
+  } else if (STRCMP(var, ==, "@reconnect")) {
+    if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; }
+    if (r) r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK);
+    rc = 1;
+  } else
+    rc = 0;
+
+end:
+  return (rc);
+}
+
+void tvec_remoteafter(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+
+  reset_vars(r);
+}
+
+void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  union tvec_regval rv;
+  unsigned f = 0;
+#define f_exit 1u
+#define f_progress 2u
+#define f_fail 4u
+  buf b;
+  int rc;
+
+  switch (try_reconnect(tv, r)) {
+    case CONN_FAILED:
+      tvec_skip(tv, "failed to connect to test backend"); return;
+    case CONN_BROKEN:
+      tvec_skip(tv, "no connection"); return;
+  }
+
+  SENDPK(tv, &r->rc, TVPK_TEST)
+    tvec_serialize(tv->in, &r->rc.bout._b,
+                  tv->test->regs, tv->nreg, tv->regsz);
+  else { rc = -1; goto end; }
+  rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
+  switch (rc) {
+    case RECV_FAIL:
+      goto end;
+    case RECV_EOF:
+      reap_kid(tv, r);
+      /* fall through */
+    case RECV_OK:
+      if (r->exit != r->exwant) f |= f_exit;
+      if (r->progress.len != r->prgwant.len ||
+         MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len))
+       f |= f_progress;
+      if (f && (tv->f&TVSF_ACTIVE))
+       { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); }
+      if (!(tv->f&TVSF_ACTIVE) &&
+         (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) {
+       f |= f_fail;
+
+       rv.u = r->exit;
+       tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
+                    &rv, &exit_regdef);
+       if (f&f_exit) {
+         rv.u = r->exwant;
+         tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef);
+       }
+
+       rv.str.p = r->progress.buf; rv.str.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;
+         tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef);
+       }
+      }
+
+      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
+}
+
+void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+
+  if (r) {
+    disconnect_remote(tv, r, 0); release_comms(&r->rc);
+    DDESTROY(&r->prgwant); DDESTROY(&r->progress);
+  }
+}
+
+/*----- Connectors --------------------------------------------------------*/
+
+static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out,
+                      int *errfd_out, struct tvec_state *tv)
+{
+  int p0[2] = { -1, -1 }, p1[2] = { -1, -1 }, pe[2] = { -1, -1 };
+  pid_t kid = -1;
+  int rc;
+
+  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) ||
+      fdflags(pe[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC)) {
+    tvec_error(tv, "pipe failed: %s", strerror(errno));
+    rc = -1; goto end;
+  }
+
+  fflush(0);
+
+  kid = fork();
+  if (kid < 0) {
+    tvec_error(tv, "fork failed: %s", strerror(errno));
+    rc = -1; goto end;
+  }
+
+  if (!kid) {
+    *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);
+    }
+  } else {
+    *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;
+  }
+
+  rc = 0;
+end:
+  if (p0[0] >= 0) close(p0[0]);
+  if (p0[1] >= 0) close(p0[1]);
+  if (p1[0] >= 0) close(p1[0]);
+  if (p1[1] >= 0) close(p1[1]);
+  if (pe[0] >= 0) close(pe[0]);
+  if (pe[1] >= 0) close(pe[1]);
+  return (rc);
+}
+
+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)
+{
+  struct tvec_config config;
+  const struct tvec_remotefork *rf = (const struct tvec_remotefork *)env;
+  pid_t kid = -1;
+  int infd = -1, outfd = -1, errfd = -1;
+  int rc;
+
+  if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
+  if (!kid) {
+    config.tests = rf->f.tests ? rf->f.tests : tv->tests;
+    config.nrout = tv->nrout; config.nreg = tv->nreg;
+    config.regsz = tv->regsz;
+    _exit(tvec_remoteserver(infd, outfd, &config));
+  }
+
+  *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
+  rc = 0;
+end:
+  return (rc);
+}
+
+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)
+{
+  const struct tvec_remoteexec *rx = (const struct tvec_remoteexec *)env;
+  pid_t kid = -1;
+  int infd = -1, outfd = -1, errfd = -1;
+  mdup_fd v[2];
+  int rc;
+
+  if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
+  if (!kid) {
+    v[0].cur = infd; v[0].want = STDIN_FILENO;
+    v[1].cur = outfd; v[1].want = STDOUT_FILENO;
+    if (mdup(v, 2)) {
+      fprintf(stderr, "failed to establish standard file descriptors: %s",
+             strerror(errno));
+      exit(127);
+    }
+    execvp(rx->x.args[0], (/*uncosnt*/ char *const *)rx->x.args);
+    fprintf(stderr, "failed to invoke test runner: %s", strerror(errno));
+    exit(127);
+  }
+
+  *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd;
+  rc = 0;
+end:
+  return (rc);
+}
 
 /*----- That's all, folks -------------------------------------------------*/
index 5ea18ca..d18411c 100644 (file)
   extern "C" {
 #endif
 
+/* Here's the overall flow for a testing session.
+ *
+ * @tvec_begin@
+ *                     -> output @bsession@
+ * @tvec_read@
+ *                     -> output @bgroup@
+ *                     -> env @setup@
+ *   one or more tests
+ *                     -> type @init@ (input and output)
+ *                     -> type @parse@ (input)
+ *                     -> output @btest@
+ *                     -> env @before@
+ *                             -> @tvec_skipgroup@
+ *                                     -> output @skipgroup@
+ *                     -> env @run@
+ *                             -> @tvec_skip@
+ *                                     -> output @skip@
+ *                             -> test @fn@
+ *                             -> @tvec_checkregs@
+ *                                     -> type @eq@
+ *                             -> @tvec_fail@
+ *                                     -> output @fail@
+ *                             -> @tvec_mismatch@
+ *                                     -> output @dumpreg@
+ *                                             -> type @dump@
+ *                     -> env @after@
+ *                     -> output @etest@
+ *   finally
+ *                     -> output @egroup@
+ *                     -> env @teardown@
+ *
+ * @tvec_adhoc@
+ *   @tvec_begingroup@
+ *                     -> output @bgroup@
+ *                     -> env @setup@
+ *     @tvec_begintest@
+ *                     -> output @btest@
+ *     @tvec_skip@
+ *                     -> output @skip@
+ *     @tvec_claimeq@
+ *                     -> @tvec_fail@
+ *                             -> output @fail@
+ *                     -> @tvec_mismatch@
+ *                             -> output @dumpreg@
+ *                             -> type @dump@
+ *     @tvec_endtest@
+ *                     -> output @etest@
+ *     or @tvec_skipgroup@
+ *                     -> output @skipgroup@
+ * @tvec_endgroup@
+ *                     -> output @egroup@
+ *
+ * @tvec_end@
+ *                     -> output @esession@
+ *                     -> output @destroy@
+ *
+ * @tvec_benchrun@
+ *                     -> type @dump@ (compact style)
+ *                     -> output @bbench@
+ *                     -> subenv @run@
+ *                             -> test @fn@
+ *                     -> output @ebench@
+ *                             -> @tvec_benchreport@
+ *
+ * The output functions @error@ and @notice@ can be called at arbitrary
+ * times.
+ */
+
 /*----- Header files ------------------------------------------------------*/
 
 #include <stdarg.h>
 #  include "gprintf.h"
 #endif
 
+#ifndef MLIB_LBUF_H
+#  include "lbuf.h"
+#endif
+
 #ifndef MLIB_MACROS_H
 #  include "macros.h"
 #endif
@@ -186,6 +258,172 @@ struct tvec_regdef {
 #define TVEC_GREG(vec, i, regsz)                                       \
        ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz)))
 
+/*----- Register types ----------------------------------------------------*/
+
+struct tvec_state;                     /* forward declaration */
+
+struct tvec_regty {
+  /* A register type. */
+
+  void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/);
+       /* Initialize the value in @*rv@.  This will be called before any
+        * other function acting on the value, including @release@.
+        */
+
+  void (*release)(union tvec_regval */*rv*/,
+                 const struct tvec_regdef */*rd*/);
+       /* Release any resources associated with the value in @*rv@. */
+
+  int (*eq)(const union tvec_regval */*rv0*/,
+           const union tvec_regval */*rv1*/,
+           const struct tvec_regdef */*rd*/);
+       /* Return nonzero if @*rv0@ and @*rv1@ are equal values.
+        * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@
+        * with the input register as @rv0@ and the output as @rv1@.
+        */
+
+  int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/,
+              const struct tvec_regdef */*rd*/);
+       /* Serialize the value @*rv@, writing the result to @b@.  Return
+        * zero on success, or @-1@ on error.
+        */
+
+  int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/,
+                const struct tvec_regdef */*rd*/);
+       /* Deserialize a value from @b@, storing it in @*rv@.  Return zero on
+        * success, or @-1@ on error.
+        */
+
+  int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/,
+              struct tvec_state */*tv*/);
+       /* Parse a value from @tv->fp@, storing it in @*rv@.  Return zero on
+        * success, or @-1@ on error, having reported one or more errors via
+        * @tvec_error@ or @tvec_syntax@.  A successful return should leave
+        * the input position at the start of the next line; the caller will
+        * flush the remainder of the line itself.
+        */
+
+  void (*dump)(const union tvec_regval */*rv*/,
+              const struct tvec_regdef */*rd*/,
+              unsigned /*style*/,
+              const struct gprintf_ops */*gops*/, void */*go*/);
+#define TVSF_COMPACT 1u
+       /* Write a human-readable representation of the value @*rv@ using
+        * @gprintf@ on @gops@ and @go@.  The @style@ is a collection of
+        * flags: if @TVSF_COMPACT@ is set, then output should be minimal,
+        * and must fit on a single line; otherwise, output may consist of
+        * multiple lines and may contain redundant information if that is
+        * likely to be useful to a human reader.
+        */
+};
+
+/*----- Test descriptions -------------------------------------------------*/
+
+typedef void tvec_testfn(const struct tvec_reg */*in*/,
+                        struct tvec_reg */*out*/,
+                        void */*ctx*/);
+  /* A test function.  It should read inputs from @in@ and write outputs to
+   * @out@.  The @TVRF_LIVE@ is set on inputs which are actually present, and
+   * on outputs which are wanted to test.  A test function can set additional
+   * `gratuitous outputs' by setting @TVRF_LIVE@ on them; clearing
+   * @TVRF_LIVE@ on a wanted output causes a mismatch.
+   *
+   * A test function may be called zero or more times by the environment.  In
+   * particular, it may be called multiple times, though usually by prior
+   * arrangement with the environment.
+   *
+   * The @ctx@ is supplied by the environment's @run@ function (see below).
+   * The default environment calls the test function once, with a null
+   * @ctx@.  There is no expectation that the environment's context has
+   * anything to do with the test function's context.
+   */
+
+struct tvec_env;
+
+typedef void tvec_envsetupfn(struct tvec_state */*tv*/,
+                            const struct tvec_env */*env*/,
+                            void */*pctx*/, void */*ctx*/);
+  /* Initialize the context; called at the start of a test group; @pctx@ is
+   * null for environments called by the core, but may be non-null for
+   * subordinate environments.  If setup fails, the function should call
+   * @tvec_skipgroup@ with a suitable excuse.  The @set@ and @teardown@ entry
+   * points will still be called, but @before@, @run@, and @after@ will not.
+   */
+
+typedef int tvec_envsetfn(struct tvec_state */*tv*/, const char */*var*/,
+                         const struct tvec_env */*env*/, 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.
+   */
+
+typedef void tvec_envbeforefn(struct tvec_state */*tv*/, void */*ctx*/);
+  /* Called prior to running a test.  This is the right place to act on any
+   * `%|@var|%' settings.  If preparation fails, the function should call
+   * @tvec_skip@ with a suitable excuse.  This function is never called if
+   * the test group is skipped.
+   */
+
+typedef void tvec_envrunfn(struct tvec_state */*tv*/,
+                          tvec_testfn */*fn*/, void */*ctx*/);
+  /* Run the test.  It should either call @tvec_skip@, or run @fn@ one or
+   * more times.  In the latter case, it is responsible for checking the
+   * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will
+   * check the register values against the supplied test vector, while
+   * @tvec_check@ does pretty much everything necessary.  This function is
+   * never called if the test group is skipped.
+   */
+
+typedef void tvec_envafterfn(struct tvec_state */*tv*/, void */*ctx*/);
+  /* Called after running or skipping a test.  Typical actions involve
+   * resetting whatever things were established by @set@.  This function is
+   * never called if the test group is skipped.
+   */
+
+typedef void tvec_envteardownfn(struct tvec_state */*tv*/, void */*ctx*/);
+  /* Tear down the environment: called at the end of a test group. */
+
+
+struct tvec_env {
+  /* A test environment sets things up for and arranges to run the test.
+   *
+   * The caller is responsible for allocating storage for the environment's
+   * context, based on the @ctxsz@ slot, and freeing it later; this space is
+   * passed in as the @ctx@ parameter to the remaining functions; if @ctxsz@
+   * is zero then @ctx@ is null.
+   */
+
+  size_t ctxsz;                                /* environment context size */
+
+  tvec_envsetupfn *setup;              /* setup for group */
+  tvec_envsetfn *set;                  /* set variable */
+  tvec_envbeforefn *before;            /* prepare for test */
+  tvec_envrunfn *run;                  /* run test function */
+  tvec_envafterfn *after;              /* clean up after test */
+  tvec_envteardownfn *teardown;                /* tear down after group */
+};
+
+struct tvec_test {
+  /* A test description. */
+
+  const char *name;                    /* name of the test */
+  const struct tvec_regdef *regs;      /* descriptions of the registers */
+  const struct tvec_env *env;          /* environment to run test in */
+  tvec_testfn *fn;                     /* test function */
+};
+#define TVEC_ENDTESTS { 0, 0, 0, 0 }
+
+enum {
+  /* Register output dispositions. */
+
+  TVRD_INPUT,                          /* input-only register */
+  TVRD_OUTPUT,                         /* output-only (input is dead) */
+  TVRD_MATCH,                          /* matching (equal) registers */
+  TVRD_FOUND,                          /* mismatching output register */
+  TVRD_EXPECT,                         /* mismatching input register */
+  TVRD_LIMIT                           /* (number of dispositions) */
+};
+
 /*----- Test state --------------------------------------------------------*/
 
 enum {
@@ -253,7 +491,8 @@ struct tvec_output {
 /* Benchmarking details. */
 enum {
   TVBU_OP,                            /* counting operations of some kind */
-  TVBU_BYTE                            /* counting bytes (@rbuf >= 0@) */
+  TVBU_BYTE,                           /* counting bytes (@rbuf >= 0@) */
+  TVBU_LIMIT                           /* (number of units) */
 };
 struct bench_timing;                   /* forward declaration */
 
@@ -320,7 +559,8 @@ struct tvec_outops {
         * The register value is at @rv@, and its definition, including its
         * type, at @rd@.  Note that this function may be called on virtual
         * registers which aren't in either of the register vectors or
-        * mentioned by the test description.
+        * mentioned by the test description.  It may also be called with
+        * @rv@ null, indicating that the register is not live.
         */
 
   void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/);
@@ -347,245 +587,23 @@ struct tvec_outops {
         * indicated by @unit@) processed in the indicated time.
         */
 
-  void (*error)(struct tvec_output */*o*/,
-               const char */*msg*/, va_list */*ap*/);
-       /* Report an error.  The driver should ideally report the filename
-        * (@infile@) and line number (@lno@) prompting the error.
-        */
-
-  void (*notice)(struct tvec_output */*o*/,
+  void (*report)(struct tvec_output */*o*/, unsigned /*level*/,
                 const char */*msg*/, va_list */*ap*/);
-       /* Report a miscellaneous message.  The driver should ideally report
-        * the filename (@infile@) and line number (@lno@) prompting the
-        * error.
+       /* Report a message.  The driver should ideally report the filename
+        * (@infile@) and line number (@lno@) prompting the error.
         */
 
   void (*destroy)(struct tvec_output */*o*/);
        /* Release any resources acquired by the driver. */
 };
 
-/*----- Test descriptions -------------------------------------------------*/
-
-typedef void tvec_testfn(const struct tvec_reg */*in*/,
-                        struct tvec_reg */*out*/,
-                        void */*ctx*/);
-  /* A test function.  It should read inputs from @in@ and write outputs to
-   * @out@.  The @TVRF_LIVE@ is set on inputs which are actually present, and
-   * on outputs which are wanted to test.  A test function can set additional
-   * `gratuitous outputs' by setting @TVRF_LIVE@ on them; clearing
-   * @TVRF_LIVE@ on a wanted output causes a mismatch.
-   *
-   * A test function may be called zero or more times by the environment.  In
-   * particular, it may be called multiple times, though usually by prior
-   * arrangement with the environment.
-   *
-   * The @ctx@ is supplied by the environment's @run@ function (see below).
-   * The default environment calls the test function once, with a null
-   * @ctx@.  There is no expectation that the environment's context has
-   * anything to do with the test function's context.
-   */
-
-struct tvec_env {
-  /* A test environment sets things up for and arranges to run the test.
-   *
-   * The caller is responsible for allocating storage for the environment's
-   * context, based on the @ctxsz@ slot, and freeing it later; this space is
-   * passed in as the @ctx@ parameter to the remaining functions; if @ctxsz@
-   * is zero then @ctx@ is null.
-   */
-
-  size_t ctxsz;                              /* environment context size */
-
-  int (*setup)(struct tvec_state */*tv*/, const struct tvec_env */*env*/,
-              void */*pctx*/, void */*ctx*/);
-    /* Initialize the context; called at the start of a test group; @pctx@ is
-     * null for environments called by the core, but may be non-null for
-     * subordinate environments.  Return zero on success, or @-1@ on failure.
-     * If setup fails, the context is freed, and the test group is skipped.
-     */
-
-  int (*set)(struct tvec_state */*tv*/, const char */*var*/,
-            const struct tvec_env */*env*/, 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.
-     */
-
-  int (*before)(struct tvec_state */*tv*/, void */*ctx*/);
-    /* Called prior to running a test.  This is the right place to act on any
-     * `%|@var|%' settings.  Return zero on success or @-1@ on failure (which
-     * causes the test to be skipped).  This function is never called if the
-     * test group is skipped.
-     */
-
-  void (*run)(struct tvec_state */*tv*/, tvec_testfn */*fn*/, void */*ctx*/);
-    /* Run the test.  It should either call @tvec_skip@, or run @fn@ one or
-     * more times.  In the latter case, it is responsible for checking the
-     * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will
-     * check the register values against the supplied test vector, while
-     * @tvec_check@ does pretty much everything necessary.  This function is
-     * never called if the test group is skipped.
-     */
-
-  void (*after)(struct tvec_state */*tv*/, void */*ctx*/);
-    /* Called after running or skipping a test.  Typical actions involve
-     * resetting whatever things were established by @set@.  This function is
-     * never called if the test group is skipped.
-     */
-
-  void (*teardown)(struct tvec_state */*tv*/, void */*ctx*/);
-    /* Tear down the environment: called at the end of a test group.  If the
-     * setup failed, then this function is still called, with a null @ctx@.
-     */
-};
-
-struct tvec_test {
-  /* A test description. */
-
-  const char *name;                    /* name of the test */
-  const struct tvec_regdef *regs;      /* descriptions of the registers */
-  const struct tvec_env *env;          /* environment to run test in */
-  tvec_testfn *fn;                     /* test function */
-};
-#define TVEC_ENDTESTS { 0, 0, 0, 0 }
-
 enum {
-  /* Register output dispositions. */
-
-  TVRD_INPUT,                          /* input-only register */
-  TVRD_OUTPUT,                         /* output-only (input is dead) */
-  TVRD_MATCH,                          /* matching (equal) registers */
-  TVRD_FOUND,                          /* mismatching output register */
-  TVRD_EXPECT                          /* mismatching input register */
-};
-
-/*----- Register types ----------------------------------------------------*/
-
-struct tvec_regty {
-  /* A register type. */
-
-  void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/);
-       /* Initialize the value in @*rv@.  This will be called before any
-        * other function acting on the value, including @release@.
-        */
-
-  void (*release)(union tvec_regval */*rv*/,
-                 const struct tvec_regdef */*rd*/);
-       /* Release any resources associated with the value in @*rv@. */
-
-  int (*eq)(const union tvec_regval */*rv0*/,
-           const union tvec_regval */*rv1*/,
-           const struct tvec_regdef */*rd*/);
-       /* Return nonzero if @*rv0@ and @*rv1@ are equal values.
-        * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@
-        * with the input register as @rv0@ and the output as @rv1@.
-        */
-
-  int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/,
-              const struct tvec_regdef */*rd*/);
-       /* Serialize the value @*rv@, writing the result to @b@.  Return
-        * zero on success, or @-1@ on error.
-        */
-
-  int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/,
-                const struct tvec_regdef */*rd*/);
-       /* Deserialize a value from @b@, storing it in @*rv@.  Return zero on
-        * success, or @-1@ on error.
-        */
-
-  int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/,
-              struct tvec_state */*tv*/);
-       /* Parse a value from @tv->fp@, storing it in @*rv@.  Return zero on
-        * success, or @-1@ on error, having reported one or more errors via
-        * @tvec_error@ or @tvec_syntax@.  A successful return should leave
-        * the input position at the start of the next line; the caller will
-        * flush the remainder of the line itself.
-        */
-
-  void (*dump)(const union tvec_regval */*rv*/,
-              const struct tvec_regdef */*rd*/,
-              unsigned /*style*/,
-              const struct gprintf_ops */*gops*/, void */*go*/);
-#define TVSF_COMPACT 1u
-       /* Write a human-readable representation of the value @*rv@ using
-        * @gprintf@ on @gops@ and @go@.  The @style@ is a collection of
-        * flags: if @TVSF_COMPACT@ is set, then output should be minimal,
-        * and must fit on a single line; otherwise, output may consist of
-        * multiple lines and may contain redundant information if that is
-        * likely to be useful to a human reader.
-        */
+  TVLEV_NOTE = 4,                      /* notice */
+  TVLEV_ERR = 8                                /* error */
 };
 
 /*----- Session lifecycle -------------------------------------------------*/
 
-/* Here's the overall flow for a testing session.
- *
- * @tvec_begin@
- *                     -> output @bsession@
- * @tvec_read@
- *                     -> output @bgroup@
- *                     -> env @setup@
- *   one or more tests
- *                     -> type @init@ (input and output)
- *                     -> type @parse@ (input)
- *                     -> output @btest@
- *                     -> env @before@
- *                     -> env @run@
- *                             -> @tvec_skip@
- *                                     -> output @skip@
- *                             -> test @fn@
- *                             -> @tvec_checkregs@
- *                                     -> type @eq@
- *                             -> @tvec_fail@
- *                                     -> output @fail@
- *                             -> @tvec_mismatch@
- *                                     -> output @dumpreg@
- *                                             -> type @dump@
- *                     -> env @after@
- *                     -> output @etest@
- *   or
- *                     -> output @skipgroup@
- *   finally
- *                     -> output @egroup@
- *                     -> env @teardown@
- *
- * @tvec_adhoc@
- *   @tvec_begingroup@
- *                     -> output @bgroup@
- *                     -> env @setup@
- *     @tvec_begintest@
- *                     -> output @btest@
- *     @tvec_skip@
- *                     -> output @skip@
- *     @tvec_claimeq@
- *                     -> @tvec_fail@
- *                             -> output @fail@
- *                     -> @tvec_mismatch@
- *                             -> output @dumpreg@
- *                             -> type @dump@
- *     @tvec_endtest@
- *                     -> output @etest@
- *     or @tvec_skipgroup@
- *                     -> output @skipgroup@
- * @tvec_endgroup@
- *                     -> output @egroup@
- *
- * @tvec_end@
- *                     -> output @esession@
- *                     -> output @destroy@
- *
- * @tvec_benchrun@
- *                     -> type @dump@ (compact style)
- *                     -> output @bbench@
- *                     -> subenv @run@
- *                             -> test @fn@
- *                     -> output @ebench@
- *                             -> @tvec_benchreport@
- *
- * The output functions @error@ and @notice@ can be called at arbitrary
- * times.
- */
-
 /* --- @tvec_begin@ --- *
  *
  * Arguments:  @struct tvec_state *tv_out@ = state structure to fill in
@@ -779,6 +797,9 @@ extern void tvec_skip_v(struct tvec_state */*tv*/,
 
 extern void tvec_resetoutputs(struct tvec_state */*tv*/);
 
+extern void tvec_initregs(struct tvec_state */*tv*/);
+extern void tvec_releaseregs(struct tvec_state */*tv*/);
+
 /* --- @tvec_checkregs@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -824,7 +845,7 @@ extern void tvec_fail_v(struct tvec_state */*tv*/,
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @unsigned disp@ = the register disposition (@TVRD_...@)
- *             @const union tvec_regval *tv@ = register value
+ *             @const union tvec_regval *tv@ = register value, or null
  *             @const struct tvec_regdef *rd@ = register definition
  *
  * Returns:    ---
@@ -1071,132 +1092,50 @@ extern int tvec_claimeq(struct tvec_state */*tv*/,
 
 /*----- Benchmarking ------------------------------------------------------*/
 
-struct tvec_bench {
+struct tvec_benchenv {
   struct tvec_env _env;                        /* benchmarking is an environment */
   struct bench_state **bst;            /* benchmark state anchor or null */
   unsigned long niter;                 /* iterations done per unit */
   int riter, rbuf;                     /* iterations and buffer registers */
   const struct tvec_env *env;          /* subordinate environment */
 };
-#define TVEC_BENCHENV                                                  \
-  { sizeof(struct tvec_benchctx),                                      \
-    tvec_benchsetup,                                                   \
-    tvec_benchset,                                                     \
-    tvec_benchbefore,                                                  \
-    tvec_benchrun,                                                     \
-    tvec_benchafter,                                                   \
-    tvec_benchteardown }
-#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate
 
 struct tvec_benchctx {
-  const struct tvec_bench *b;
-  struct bench_state *bst;
-  double dflt_target;
-  void *subctx;
+  const struct tvec_benchenv *be;      /* environment configuration */
+  struct bench_state *bst;             /* benchmark state */
+  double dflt_target;                  /* default time in seconds */
+  void *subctx;                                /* subsidiary environment context */
 };
 
 extern struct bench_state *tvec_benchstate;
 
-/* --- @tvec_benchsetup@ --- *
- *
- * 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:    Zero on success, @-1@ on failure.
- *
- * Use:                Initialize a benchmarking environment context.
- *
- *             The environment description must really be a @struct
- *             tvec_bench@.  If the @bst@ slot is null, then a temporary
- *             benchmark state is allocated for the current test group and
- *             released at the end.  Otherwise, it must be the address of a
- *             pointer to a benchmark state: if the pointer is null, then a
- *             fresh state is allocated and initialized and the pointer is
- *             updated; otherwise, the pointer is assumed to refer to an
- *             existing valid benchmark state.
- */
-
-extern int tvec_benchsetup(struct tvec_state */*tv*/,
-                          const struct tvec_env */*env*/,
-                          void */*pctx*/, void */*ctx*/);
-
-/* --- @tvec_benchset@ --- *
- *
- * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @const char *var@ = variable name to set
- *             @const struct tvec_env *env@ = environment description
- *             @void *ctx@ = context pointer
- *
- * Returns:    Zero on success, @-1@ on failure.
- *
- * Use:                Set a special variable.  The following special variables are
- *             supported.
- *
- *               * %|@target|% is the (approximate) number of seconds to run
- *                 the benchmark.
- *
- *             Unrecognized variables are passed to the subordinate
- *             environment, if there is one.
- */
-
-extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/,
-                        const struct tvec_env */*env*/, void */*ctx*/);
-
-/* --- @tvec_benchbefore@ --- *
- *
- * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @void *ctx@ = context pointer
- *
- * Returns:    Zero on success, @-1@ on failure.
- *
- * Use:                Invoke the subordinate environment's @before@ function to
- *             prepare for the benchmark.
- */
-
-extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/);
-
-/* --- @tvec_benchrun@ --- *
- *
- * 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:                Measures and reports the performance of a test function.
- *
- *
- */
-
-extern void tvec_benchrun(struct tvec_state */*tv*/,
-                         tvec_testfn */*fn*/, void */*ctx*/);
-
-/* --- @tvec_benchafter@ --- *
+/* --- Environment implementation --- *
  *
- * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @void *ctx@ = context pointer
+ * The following special variables are supported.
  *
- * Returns:    ---
+ *   * %|@target|% is the (approximate) number of seconds to run the
+ *     benchmark.
  *
- * Use:                Invoke the subordinate environment's @after@ function to
- *             clean up after the benchmark.
+ * Unrecognized variables are passed to the subordinate environment, if there
+ * is one.  Other events are passed through to the subsidiary environment.
  */
 
-extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/);
+extern tvec_envsetupfn tvec_benchsetup;
+extern tvec_envsetfn tvec_benchset;
+extern tvec_envbeforefn tvec_benchbefore;
+extern tvec_envrunfn tvec_benchrun;
+extern tvec_envafterfn tvec_benchafter;
+extern tvec_envteardownfn tvec_benchteardown;
 
-/* --- @tvec_benchteardown@ --- *
- *
- * Arguments:  @struct tvec_state *tv@ = test vector state
- *             @void *ctx@ = context pointer
- *
- * Returns:    ---
- *
- * Use:                Tear down the benchmark environment.
- */
-
-extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/);
+#define TVEC_BENCHENV                                                  \
+  { sizeof(struct tvec_benchctx),                                      \
+    tvec_benchsetup,                                                   \
+    tvec_benchset,                                                     \
+    tvec_benchbefore,                                                  \
+    tvec_benchrun,                                                     \
+    tvec_benchafter,                                                   \
+    tvec_benchteardown }
+#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate
 
 /* --- @tvec_benchreport@ --- *
  *
@@ -1216,40 +1155,141 @@ extern void tvec_benchreport
   (const struct gprintf_ops */*gops*/, void */*go*/,
    unsigned /*unit*/, const struct bench_timing */*tm*/);
 
+/*----- Remote execution --------------------------------------------------*/
+
+struct tvec_remotecomms {
+  int infd, outfd;                     /* input and output descriptors */
+  dbuf bin, bout;                      /* input and output buffers */
+  unsigned f;                          /* flags */
+#define TVRF_BROKEN 0x0001u            /*   communications have failed */
+                                       /*   bits 8--15 for upper layer */
+};
+#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, DBUF_INIT, 0 }
+
+struct tvec_remotectx {
+  struct tvec_state *tv;               /* test vector state */
+  struct tvec_remotecomms rc;          /* communication state */
+  const struct tvec_remoteenv *re;     /* environment configuration */
+  unsigned ver;                                /* protocol version */
+  pid_t kid;                           /* child process id */
+  int errfd;                           /* child stderr descriptor */
+  lbuf errbuf;                         /* child stderr line buffer */
+  dstr prgwant, progress;              /* progress: wanted/reported */
+  unsigned exwant, exit;               /* exit status wanted/reported */
+#define TVRF_RCNMASK 0x0300u           /*   reconnection behaviour: */
+#define TVRCN_SKIP 0x0000u             /*     skip unless connected */
+#define TVRCN_DEMAND 0x0100u           /*     connect on demand */
+#define TVRCN_FORCE 0x0200u            /*     force reconnection */
+#define TVRF_MUFFLE 0x0400u            /*   muffle child stderr */
+};
+
+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*/);
+
+struct tvec_remoteenv_slots {
+  tvec_connectfn *connect;             /* connection function */
+  const struct tvec_env *env;          /* subsidiary environment */
+};
+
+struct tvec_remoteenv {
+  struct tvec_env _env;
+  struct tvec_remoteenv_slots r;
+};
+
+struct tvec_remotefork_slots {
+  const struct tvec_test *tests;       /* child tests (or null) */
+};
+
+struct tvec_remotefork {
+  struct tvec_env _env;
+  struct tvec_remoteenv_slots r;
+  struct tvec_remotefork_slots f;
+};
+
+struct tvec_remoteexec_slots {
+  const char *const *args;             /* command line to execute */
+};
+
+struct tvec_remoteexec {
+  struct tvec_env _env;
+  struct tvec_remoteenv_slots r;
+  struct tvec_remoteexec_slots x;
+};
+
+union tvec_remoteenv_subclass_kludge {
+  struct tvec_env _env;
+  struct tvec_remoteenv renv;
+  struct tvec_remotefork fork;
+  struct tvec_remoteexec exec;
+};
+
+extern tvec_envsetupfn tvec_remotesetup;
+extern tvec_envsetfn tvec_remoteset;
+extern tvec_envrunfn tvec_remoterun;
+extern tvec_envafterfn tvec_remoteafter;
+extern tvec_envteardownfn tvec_remoteteardown;
+#define TVEC_REMOTEENV                                                 \
+  { sizeof(struct tvec_remotectx),                                     \
+    tvec_remotesetup,                                                  \
+    tvec_remoteset,                                                    \
+    0,                                                                 \
+    tvec_remoterun,                                                    \
+    tvec_remoteafter,                                                  \
+    tvec_remoteteardown }
+
+extern int tvec_setprogress(const char */*status*/);
+
+extern int tvec_remoteserver(int /*infd*/, int /*outfd*/,
+                            const struct tvec_config */*config*/);
+
+extern tvec_connectfn tvec_fork, tvec_exec;
+
+#define TVEC_REMOTEFORK( subenv, tests)                                        \
+       TVEC_REMOTEENV, { tvec_fork, subenv }, { tests }
+
+#define TVEC_REMOTEEXEC(subenv, args)                                  \
+       TVEC_REMOTEENV, { tvec_exec, subenv }, { args }
+
 /*----- Output functions --------------------------------------------------*/
 
-/* --- @tvec_error@, @tvec_error_v@ --- *
+/* --- @tvec_report@, @tvec_report_v@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
  *             @const char *msg@, @va_list ap@ = error message
  *
- * Returns:    @-1@.
+ * Returns:    ---
  *
- * Use:                Report an error.  Errors are distinct from test failures,
- *             and indicate that a problem was encountered which compromised
- *             the activity of testing.
+ * Use:                Report an message with a given severity.  Messages with level
+ *             @TVLEV_ERR@ or higher force a nonzero exit code.
  */
 
-extern int PRINTF_LIKE(2, 3)
-  tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...);
-extern int tvec_error_v(struct tvec_state */*tv*/,
-                       const char */*msg*/, va_list */*ap*/);
+extern void PRINTF_LIKE(3, 4)
+  tvec_report(struct tvec_state */*tv*/, unsigned /*level*/,
+             const char */*msg*/, ...);
+extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/,
+                         const char */*msg*/, va_list */*ap*/);
 
-/* --- @tvec_notice@, @tvec_notice_v@ --- *
+/* --- @tvec_error@, @tvec_notice@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
- *             @const char *msg@, @va_list ap@ = message
+ *             @const char *msg@, @va_list ap@ = error message
  *
- * Returns:    ---
+ * Returns:    The @tvec_error@ function returns @-1@ as a trivial
+ *             convenience; @tvec_notice@ does not return a value.
  *
- * Use:                Output a notice: essentially, some important information
- *             which doesn't fit into any of the existing categories.
+ * Use:                Report an error or a notice.  Errors are distinct from test
+ *             failures, and indicate that a problem was encountered which
+ *             compromised the activity of testing.  Notices are important
+ *             information which doesn't fit into any other obvious
+ *             category.
  */
 
+extern int PRINTF_LIKE(2, 3)
+  tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...);
 extern void PRINTF_LIKE(2, 3)
   tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...);
-extern void tvec_notice_v(struct tvec_state */*tv*/,
-                         const char */*msg*/, va_list */*ap*/);
 
 /* --- @tvec_humanoutput@ --- *
  *
@@ -1308,6 +1348,23 @@ extern struct tvec_output *tvec_dfltout(FILE */*fp*/);
 
 /*------ Serialization utilities ------------------------------------------*/
 
+/* Serialization format.
+ *
+ * The `candidate register definitions' are those entries @r@ in the @regs@
+ * vector whose index @r.i@ is strictly less than @nr@.  The `selected
+ * register definitions' are those candidate register definitions @r@ for
+ * which the indicated register @rv[r.i]@ has the @TVRF_LIVE@ flag set.  The
+ * serialized output begins with a header bitmap: if there are %$n$%
+ * candidate register definitions then the header bitmap consists of %$\lceil
+ * n/8 \rceil$% bytes.  Bits are ordered starting from the least significant
+ * bit of the first byte, end ending at the most significant bit of the final
+ * byte.  The bit corresponding to a candidate register definition is set if
+ * and only if that register defintion is selected.  The header bitmap is
+ * then followed by the serializations of the selected registers -- i.e., for
+ * each selected register definition @r@, the serialized value of register
+ * @rv[r.i]@ -- simply concatenated together, with no padding or alignment.
+ */
+
 /* --- @tvec_serialize@ --- *
  *
  * Arguments:  @const struct tvec_reg *rv@ = vector of registers
@@ -1322,23 +1379,6 @@ extern struct tvec_output *tvec_dfltout(FILE */*fp*/);
  *
  * Use:                Serialize a collection of register values.
  *
- *             The `candidate register definitions' are those entries @r@ in
- *             the @regs@ vector whose index @r.i@ is strictly less than
- *             @nr@.  The `selected register definitions' are those
- *             candidate register definitions @r@ for which the indicated
- *             register @rv[r.i]@ has the @TVRF_LIVE@ flag set.  The
- *             serialized output begins with a header bitmap: if there are
- *             %$n$% candidate register definitions then the header bitmap
- *             consists of %$\lceil n/8 \rceil$% bytes.  Bits are ordered
- *             starting from the least significant bit of the first byte,
- *             end ending at the most significant bit of the final byte.
- *             The bit corresponding to a candidate register definition is
- *             set if and only if that register defintion is selected.  The
- *             header bitmap is then followed by the serializations of the
- *             selected registers -- i.e., for each selected register
- *             definition @r@, the serialized value of register @rv[r.i]@ --
- *             simply concatenated together, with no padding or alignment.
- *
  *             The serialized output is written to the buffer @b@.  Failure
  *             can be caused by running out of buffer space, or a failing
  *             type handler.
@@ -1369,10 +1409,7 @@ extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/,
  *             slot, and set the @TVRF_LIVE@ flag on the register.  See
  *             @tvec_serialize@ for a description of the format.
  *
- *             On successful completion, store the address of the first byte
- *             after the serialized data in @*end_out@ if @end_out@ is not
- *             null.  Failure results only from a failing register type
- *             handler.
+ *             Failure results only from a failing register type handler.
  */
 
 extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/,
index 656eee5..4d1d61a 100644 (file)
@@ -320,13 +320,13 @@ typedef unsigned char octet, uint8;
 
 /* --- Endianness swapping --- */
 
-#if GCC_VERSION_P(4, 8)
+#if GCC_VERSION_P(4, 8) || CLANG_VERSION_P(3, 2)
 #  define ENDSWAP16(x) ((uint16)__builtin_bswap16(x))
 #endif
-#if GCC_VERSION_P(4, 3)
+#if GCC_VERSION_P(4, 3) || CLANG_VERSION_P(3, 2)
 #  define ENDSWAP32(x) ((uint32)__builtin_bswap32(x))
 #endif
-#if GCC_VERSION_P(4, 3) && defined(HAVE_UINT64)
+#if (GCC_VERSION_P(4, 3) || CLANG_VERSION_P(3, 2)) && defined(HAVE_UINT64)
 #  define ENDSWAP64(x) ((uint64)__builtin_bswap64(x))
 #endif
 
@@ -427,7 +427,7 @@ typedef unsigned char octet, uint8;
 
 /* --- Unaligned access (GCC-specific) --- */
 
-#if GCC_VERSION_P(3, 3) && CHAR_BIT == 8
+#if (GCC_VERSION_P(3, 3) || CLANG_VERSION_P(3, 0)) && CHAR_BIT == 8
 #  define MLIB_MISALIGNED __attribute__((aligned(1), may_alias))
 #  if __SIZEOF_SHORT__ == 2
      typedef MLIB_MISALIGNED unsigned short misaligned_uint16;
index edc6e4d..ded6c80 100644 (file)
@@ -34,7 +34,7 @@
 
 /*----- Macros ------------------------------------------------------------*/
 
-#ifdef __GNUC__
+#if defined(__GNUC__) && !defined(__clang__)
 #  define GCC_VERSION_P(maj, min)                                      \
        (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
 #else
index 97be8b6..4026e16 100644 (file)
  * share the same state, so probably don't do this.
  */
 #define MC_FINALLY(tag, cleanup)                                       \
-       MP_WRAP(tag##__final, { ; } cleanup, { cleanup break; })
+       MP_WRAP(tag##__final, { ; }, cleanup, { cleanup break; })
 
 /* @MC_DECL(tag, decl) body@
  *
index 954ed0c..95d0ddb 100644 (file)
 #  define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail)))
 #endif
 
-#if GCC_VERSION_P(2, 7)
+#if GCC_VERSION_P(2, 7) || CLANG_VERSION_P(0, 0)
 #  define LAUNDER(x)                                                   \
        ({ __typeof__(x) _y; __asm__("" : "=g"(_y) : "0"(x)); _y; })
 #  define RELAX do __asm__ __volatile__("" ::: "memory"); while (0)
 #  define MUFFLE_WARNINGS_STMT(warns, body)                            \
        do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0)
 
-#elif GCC_VERSION_P(4, 6)
+#endif
+
+#if GCC_VERSION_P(4, 6)
 
    /* --- Diagnostic suppression in GCC: a tale of woe --- *
     *