@@@ tvec setvar
authorMark Wooding <mdw@distorted.org.uk>
Fri, 8 Mar 2024 22:46:21 +0000 (22:46 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 8 Mar 2024 22:46:21 +0000 (22:46 +0000)
test/t/tvec-test.c
test/tests.at
test/tvec-bench.c
test/tvec-core.c
test/tvec-remote.c
test/tvec-timeout.c
test/tvec-types.c
test/tvec.h

index aaa4465..0d93bc1 100644 (file)
@@ -177,22 +177,26 @@ static void common_setup(struct tvec_state *tv,
   tctx->f = 0;
 }
 
-static int common_set(struct tvec_state *tv, const char *name, void *ctx)
+static int common_setvar(struct tvec_state *tv, const char *var,
+                        const union tvec_regval *rv, void *ctx)
 {
   struct test_context *tctx = ctx;
-  union tvec_regval rv;
-  static const struct tvec_regdef rd =
-    { "@show", -1, &tvty_ienum, 0, { &tvenum_bool } };
-
-  if (STRCMP(name, ==, "@show")) {
-    if (tvty_ienum.parse(&rv, &rd, tv)) return (-1);
-    if (tctx) {
-      if (rv.i) tctx->f |= SF_SHOW;
-      else tctx->f &= ~SF_SHOW;
-    }
-    return (1);
-  } else
-    return (0);
+
+  if (STRCMP(var, ==, "@show")) {
+    if (rv->i) tctx->f |= SF_SHOW;
+  } else assert(!"unknown var");
+  return (0);
+}
+
+static const struct tvec_vardef show_var =
+  { sizeof(struct tvec_reg), common_setvar,
+    { "@show", -1, &tvty_ienum, 0, { &tvenum_bool } } };
+
+static const struct tvec_vardef *common_findvar
+  (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
+{
+  if (STRCMP(var, ==, "@show")) { *ctx_out = ctx; return (&show_var); }
+  return (0);
 }
 
 static void common_run(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
@@ -210,7 +214,7 @@ static void common_after(struct tvec_state *tv, void *ctx)
 
 static const struct tvec_env common_testenv = {
   sizeof(struct test_context),
-  common_setup, common_set,
+  common_setup, common_findvar,
   0, common_run, common_after,
   0
 };
@@ -344,12 +348,12 @@ static void before_single_deserialize(struct tvec_state *tv, void *ctx)
 
 static const struct tvec_env single_serialize_testenv = {
   sizeof(struct test_context),
-  common_setup, common_set,
+  common_setup, common_findvar,
   before_single_serialize, common_run, common_after,
   0
 }, single_deserialize_testenv = {
   sizeof(struct test_context),
-  common_setup, common_set,
+  common_setup, common_findvar,
   before_single_deserialize, common_run, common_after,
   0
 };
@@ -438,7 +442,7 @@ static void before_multi_serialize(struct tvec_state *tv, void *ctx)
 
 static const struct tvec_env multi_serialize_testenv = {
   sizeof(struct test_context),
-  common_setup, common_set,
+  common_setup, common_findvar,
   before_multi_serialize, common_run, common_after,
   0
 };
index 4bea513..456729d 100644 (file)
@@ -70,8 +70,9 @@ $1 = $2
 check_template([BUILDDIR/t/tvec.t -fh tv], [2],
 [tv:$3: ERROR: $4
 tv:={N:\d+}: ERROR: required register `$1' not set in test `copy-$1'
-copy-$1 skipped: no tests to run
-PASSED 0 tests in 0 groups (1 skipped)
+tv:={N:\d+}: `copy-$1' skipped: erroneous test data
+copy-$1 skipped
+PASSED 0 tests (1 skipped) in 0 groups (1 skipped)
 ERRORS found in input; tests may not have run correctly
 ],
 [tvec.t: tv:$3: ERROR: $4
index c1d8cc0..ad23043 100644 (file)
@@ -243,43 +243,54 @@ fail_timer:
   tvec_skipgroup(tv, "failed to create timer"); goto end;
 }
 
-/* --- @tvec_benchset@ --- *
+/* --- @tvec_benchfindvar@, @setvar@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @const char *var@ = variable name to set
+ *             @const union tvec_regval *rv@ = register value
+ *             @void **ctx_out@ = where to put the @setvar@ context
  *             @void *ctx@ = context pointer
  *
- * Returns:    %$+1$% on success, %$0$% if the variable name was not
- *             recognized, or %$-1$% on any other error.
+ * Returns:    @tvec_benchfindvar@ returns a pointer to the variable
+ *             definition, or null; @setvar@ returns zero on success or
+ *             %$-1$% on error.
  *
- * Use:                Set a special variable.  The following special variables are
- *             supported.
+ * Use:                Find a definition for a special variable.  The following
+ *             special variables are supported.
  *
- *               * %|@target|% is the (approximate) number of seconds to run
+ *               * %|@target|% is the (approximate) duration to run
  *                 the benchmark.
  *
  *             Unrecognized variables are passed to the subordinate
  *             environment, if there is one.
  */
 
-int tvec_benchset(struct tvec_state *tv, const char *var, void *ctx)
+static int setvar(struct tvec_state *tv, const char *var,
+                 const union tvec_regval *rv, void *ctx)
 {
   struct tvec_benchctx *bc = ctx;
-  const struct tvec_benchenv *be = bc->be;
-  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 =
-    { "@target", -1, &tvty_float, 0, { &fi } };
 
   if (STRCMP(var, ==, "@target")) {
     if (bc->f&TVBF_SETTRG) return (tvec_dupreg(tv, var));
-    if (tvty_float.parse(&rv, &rd, tv)) return (-1);
-    bc->bst->target_s = rv.f; bc->f |= TVBF_SETTRG; return (1);
-  } else if (subenv && subenv->set)
-    return (subenv->set(tv, var, bc->subctx));
-  else
-    return (0);
+    bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG;
+  } else assert("unknown var");
+  return (0);
+}
+
+static const struct tvec_vardef target_var =
+  { sizeof(struct tvec_reg), setvar, { "@target", -1, &tvty_duration, 0 } };
+
+const struct tvec_vardef *tvec_benchfindvar
+  (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
+{
+  struct tvec_benchctx *bc = ctx;
+  const struct tvec_benchenv *be = bc->be;
+  const struct tvec_env *subenv = be->env;
+
+  if (STRCMP(var, ==, "@target")) { *ctx_out = bc; return (&target_var); }
+  else if (subenv && subenv->findvar)
+    return (subenv->findvar(tv, var, ctx_out, bc->subctx));
+  else return (0);
 }
 
 /* --- @tvec_benchbefore@ --- *
@@ -299,7 +310,6 @@ void tvec_benchbefore(struct tvec_state *tv, void *ctx)
   const struct tvec_benchenv *be = bc->be;
   const struct tvec_env *subenv = be->env;
 
-  /* Just call the subsidiary environment. */
   if (subenv && subenv->before) subenv->before(tv, bc->subctx);
 }
 
index 65c74cf..51b46d2 100644 (file)
@@ -317,6 +317,23 @@ int tvec_syntax_v(struct tvec_state *tv, int ch,
   dstr_destroy(&d); return (-1);
 }
 
+/* --- @tvec_unkreg@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *name@ = register or pseudoregister name
+ *
+ * Returns:    %$-1$%.
+ *
+ * Use:                Reports an error that the register or pseudoregister is
+ *             unrecognized.
+ */
+
+int tvec_unkreg(struct tvec_state *tv, const char *name)
+{
+  return (tvec_error(tv, "unknown special register `%s' for test `%s'",
+                    name, tv->test->name));
+}
+
 /* --- @tvec_dupreg@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -513,8 +530,11 @@ int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
 
 struct groupstate {
   void *ctx;                           /* test environment context */
+  unsigned f;                          /* flags */
+#define GRPF_SETOUTC 1u                        /*   set outcome */
+#define GRPF_SETMASK (GRPF_SETOUTC)    /*   mask of all variable flags */
 };
-#define GROUPSTATE_INIT { 0 }
+#define GROUPSTATE_INIT { 0, 0 }
 
 /* --- @tvec_initregs@, @tvec_releaseregs@ --- *
  *
@@ -746,6 +766,8 @@ static void check(struct tvec_state *tv, struct groupstate *g)
   const struct tvec_test *t = tv->test;
   const struct tvec_env *env = t->env;
   const struct tvec_regdef *rd;
+  unsigned f = 0;
+#define f_err 1u
 
   if (!(tv->f&TVSF_OPEN)) return;
 
@@ -755,15 +777,16 @@ static void check(struct tvec_state *tv, struct groupstate *g)
     else if (!(rd->f&TVRF_OPT)) {
       tvec_error(tv, "required register `%s' not set in test `%s'",
                 rd->name, t->name);
-      goto end;
+      f |= f_err;
     }
   }
 
   if (!(tv->f&TVSF_SKIP)) {
     begin_test(tv);
+    if (f&f_err) tvec_skip(tv, "erroneous test data");
     if (env && env->before) env->before(tv, g->ctx);
     if (!(tv->f&TVSF_ACTIVE))
-      /* setup forced a skip */;
+      /* forced a skip */;
     else if (env && env->run)
       env->run(tv, t->fn, g->ctx);
     else {
@@ -772,10 +795,12 @@ static void check(struct tvec_state *tv, struct groupstate *g)
     }
     tvec_endtest(tv);
   }
-  if (env && env->after) env->after(tv, g->ctx);
 
-end:
+  if (env && env->after) env->after(tv, g->ctx);
+  g->f &= ~GRPF_SETMASK;
   tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
+
+#undef f_err
 }
 
 /* --- @begin_test_group@ --- *
@@ -864,21 +889,44 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g)
   tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
 }
 
-/* --- @tvec_read@ --- *
+/* --- @core_findvar@, @core_setvar@ --- *
  *
- * Arguments:  @struct tvec_state *tv@ = test-vector state
- *             @const char *infile@ = the name of the input file
- *             @FILE *fp@ = stream to read from
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @const char *var@ = variable name to set
+ *             @const union tvec_regval *rv@ = register value
+ *             @void **ctx_out@ = where to put the @setvar@ context
+ *             @void *ctx@ = context pointer
  *
- * Returns:    Zero on success, @-1@ on error.
+ * Returns:    @core_findvar@ returns a pointer to the variable definition,
+ *             or null; @core_setvar@ returns zero on success or %$-1$% on
+ *             error.
  *
- * Use:                Read test vector data from @fp@ and exercise test functions.
- *             THe return code doesn't indicate test failures: it's only
- *             concerned with whether there were problems with the input
- *             file or with actually running the tests.
+ * Use:                Find a definition for a special variable.  The following
+ *             special variables are supported.
+ *
+ *               * %|@outcome|% is a token describing how a successful
+ *                 outcome of the test should be interpreted: %|success|% or
+ *                 %|win|% are the default: a successful test is counted as
+ *                 a pass; or %|expected-failure|% or %|xfail|% means a
+ *                 successful test is counted as an expected failure.  A
+ *                 mismatch is always considered a failure.
  */
 
 enum { WIN, XFAIL, NOUT };
+
+static int core_setvar(struct tvec_state *tv, const char *name,
+                      const union tvec_regval *rv, void *ctx)
+{
+  struct groupstate *g = ctx;
+
+  if (STRCMP(name, ==, "@outcome")) {
+    if (g->f&GRPF_SETOUTC) return (tvec_dupreg(tv, name));
+    if (rv->u == XFAIL) tvec_xfail(tv);
+    g->f |= GRPF_SETOUTC;
+  } else assert(!"unknown var");
+  return (0);
+}
+
 static const struct tvec_uassoc outcome_assoc[] = {
   { "success",         WIN },
   { "win",             WIN },
@@ -889,8 +937,32 @@ static const struct tvec_uassoc outcome_assoc[] = {
 static const struct tvec_urange outcome_range = { 0, NOUT - 1 };
 static const struct tvec_uenuminfo outcome_enum =
   { "test-outcome", outcome_assoc, &outcome_range };
-static const struct tvec_regdef outcome_regdef =
-  { "outcome", 0, &tvty_uenum, 0, { &outcome_enum } };
+static const struct tvec_vardef outcome_vardef =
+  { sizeof(struct tvec_reg), core_setvar,
+    { "@outcome", 0, &tvty_uenum, 0, { &outcome_enum } } };
+
+static const struct tvec_vardef *core_findvar
+  (struct tvec_state *tv, const char *name, void **ctx_out, void *ctx)
+{
+  if (STRCMP(name, ==, "@outcome"))
+    { *ctx_out = ctx; return (&outcome_vardef); }
+  else
+    return (0);
+}
+
+/* --- @tvec_read@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *infile@ = the name of the input file
+ *             @FILE *fp@ = stream to read from
+ *
+ * Returns:    Zero on success, @-1@ on error.
+ *
+ * Use:                Read test vector data from @fp@ and exercise test functions.
+ *             THe return code doesn't indicate test failures: it's only
+ *             concerned with whether there were problems with the input
+ *             file or with actually running the tests.
+ */
 
 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
 {
@@ -898,10 +970,10 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
   const struct tvec_test *test;
   const struct tvec_env *env;
   const struct tvec_regdef *rd;
-  struct tvec_reg *r;
+  const struct tvec_vardef *vd = 0; void *varctx;
+  struct tvec_reg *r = 0, rbuf, *r_alloc = 0; size_t rsz = 0;
   struct groupstate g = GROUPSTATE_INIT;
-  union tvec_regval rv;
-  int ch, ret, rc = 0;
+  int ch, rc = 0;
 
   /* Set the initial location. */
   tv->infile = infile; tv->lno = 1; tv->fp = fp;
@@ -1014,34 +1086,40 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
 
          if (d.buf[0] == '@') {
            /* A special register assignment.  */
-           env = tv->test->env;
-
-           /* See if it's one of the core settings. */
-           if (STRCMP(d.buf, ==, "@outcome")) {
 
-             /* Parse the value. */
-             if (tvty_uenum.parse(&rv, &outcome_regdef, tv))
-               ret = -1;
-             else {
+           env = tv->test->env;
 
-               /* Act on the result. */
-               if (rv.u == XFAIL) tvec_xfail(tv);
-               ret = 1;
+           /* Find a variable definition. */
+           vd = core_findvar(tv, d.buf, &varctx, &g);
+             if (vd) goto found_var;
+           if (env && env->findvar) {
+             vd = env->findvar(tv, d.buf, &varctx, g.ctx);
+               if (vd) goto found_var;
+           }
+           tvec_unkreg(tv, d.buf); goto flush_line;
+         found_var:
+
+           /* Set up the register. */
+           if (vd->regsz <= sizeof(rbuf))
+             r = &rbuf;
+           else {
+             if (rsz < vd->regsz) {
+               xfree(r_alloc);
+               if (!rsz) rsz = 8*sizeof(void *);
+               while (rsz < vd->regsz) rsz *= 2;
+               r_alloc = xmalloc(rsz);
              }
+             r = r_alloc;
            }
 
-           /* If there's no environment, this is an unknown setting. */
-           else if (!env || !env->set) ret = 0;
+           /* Read and set the value. */
+           vd->def.ty->init(&r->v, &vd->def);
+           if (vd->def.ty->parse(&r->v, &vd->def, tv)) goto flush_line;
+           if (!(tv->f&TVSF_SKIP) && vd->setvar(tv, d.buf, &r->v, varctx))
+             goto bad;
 
-           /* Otherwise pass the setting on to the environment. */
-           else ret = env->set(tv, d.buf, g.ctx);
-
-           /* If it wasn't understood, report an error and flush. */
-           if (ret <= 0) {
-             if (!ret)
-               tvec_error(tv, "unknown special register `%s'", d.buf);
-             goto flush_line;
-           }
+           /* Clean up. */
+           vd->def.ty->release(&r->v, &vd->def); vd = 0;
          } else {
            /* A standard register. */
 
@@ -1071,7 +1149,10 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
     /* This is a general parse-failure handler.  Skip to the next line and
      * remember that things didn't go so well.
      */
-    tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1;
+    tvec_flushtoeol(tv, TVFF_ALLOWANY);
+  bad:
+    if (vd) { vd->def.ty->release(&r->v, &vd->def); vd = 0; }
+    rc = -1;
   }
 
   /* We reached the end.  If that was actually an I/O error then report it.
@@ -1088,7 +1169,10 @@ end:
   /* Clean up. */
   tv->infile = 0; tv->fp = 0;
   dstr_destroy(&d);
+  xfree(r_alloc);
   return (rc);
+
+#undef rlive
 }
 
 /*----- Session lifecycle -------------------------------------------------*/
index 052ce13..b87351b 100644 (file)
@@ -483,9 +483,11 @@ static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
                                         * <-- ver: u16 */
 #define TVPK_BGROUP    0x0002u         /* --> name: str16
                                         * <-- --- */
-#define TVPK_TEST      0x0004u         /* --> in: regs
+#define TVPK_SETVAR    0x0004u         /* --> name: str16, rv: value
+                                        * <-- rc: u8 */
+#define TVPK_TEST      0x0006u         /* --> in: regs
                                         * <-- --- */
-#define TVPK_EGROUP    0x0006u         /* --> --- *
+#define TVPK_EGROUP    0x0008u         /* --> --- *
                                         * <-- --- */
 
 #define TVPK_REPORT    0x0100u         /* <-- level: u16; msg: string */
@@ -579,9 +581,12 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
   uint16 pk, u, v;
   unsigned i;
   buf b;
+  dstr d = DSTR_INIT;
   const struct tvec_test *t;
   void *p; size_t sz;
   const struct tvec_env *env = 0;
+  const struct tvec_vardef *vd = 0; void *varctx;
+  struct tvec_reg *r = 0, rbuf, *r_alloc = 0; size_t rsz = 0;
   void *ctx = 0;
   int rc;
 
@@ -688,6 +693,48 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
              /* Leave the group loop. */
              goto endgroup;
 
+           case TVPK_SETVAR:
+             /* Set a subenvironment variable. */
+
+             /* Get the variable name. */
+             p = buf_getmem16l(&b, &sz); if (!p) goto bad;
+             DRESET(&d); DPUTM(&d, p, sz); DPUTZ(&d);
+
+             /* Look up the variable definition. */
+             if (env && env->findvar) {
+               vd = env->findvar(&srvtv, d.buf, &varctx, ctx);
+                 if (vd) goto found_var;
+             }
+             rc = tvec_unkreg(&srvtv, d.buf); goto setvar_end;
+           found_var:
+
+             /* Set up the register. */
+             if (vd->regsz <= sizeof(rbuf))
+               r = &rbuf;
+             else {
+               if (rsz < vd->regsz) {
+                 xfree(r_alloc);
+                 if (!rsz) rsz = 8*sizeof(void *);
+                 while (rsz < vd->regsz) rsz *= 2;
+                 r_alloc = xmalloc(rsz);
+               }
+               r = r_alloc;
+             }
+
+             /* Collect and set the value. */
+             vd->def.ty->init(&r->v, &vd->def);
+             if (vd->def.ty->frombuf(&b, &r->v, &vd->def)) goto bad;
+             if (BLEFT(&b)) goto bad;
+             rc = vd->setvar(&srvtv, d.buf, &r->v, varctx);
+
+             /* Send the reply. */
+           setvar_end:
+             QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_SETVAR | TVPF_ACK)
+               dbuf_putbyte(&srvrc.bout, rc ? 0xff : 0);
+             else { rc = -1; goto end; }
+             if (vd) { vd->def.ty->release(&r->v, &vd->def); vd = 0; }
+             break;
+
            case TVPK_TEST:
              /* Run a test. */
 
@@ -773,7 +820,8 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
 end:
   /* Clean up and return. */
   if (env && env->teardown) env->teardown(&srvtv, ctx);
-  xfree(ctx);
+  if (vd) vd->def.ty->release(&r->v, &vd->def);
+  xfree(ctx); xfree(r_alloc);
   if (srvtv.test) tvec_releaseregs(&srvtv);
   release_comms(&srvrc); tvec_end(&srvtv);
   return (rc ? 2 : 0);
@@ -1078,6 +1126,8 @@ static const struct tvec_outops remote_ops = {
 
 /*----- Pseudoregister definitions ----------------------------------------*/
 
+static tvec_setvarfn setvar_local, setvar_remote;
+
 static const struct tvec_flag exit_flags[] = {
 
   /* Cause codes. */
@@ -1244,16 +1294,17 @@ static const struct tvec_flag exit_flags[] = {
 
   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_vardef exit_var =
+  { sizeof(struct tvec_reg), setvar_local,
+    { "@exit", -1, &tvty_flags, 0, { &exit_flaginfo } } };
 
 /* Progress. */
 
-static const struct tvec_regdef progress_regdef =
-  { "@progress", 0, &tvty_text, 0 };
+static const struct tvec_vardef progress_var =
+  { sizeof(struct tvec_reg), setvar_local,
+    { "@progress", -1, &tvty_text, 0 } };
 
 /* Reconnection. */
 
@@ -1263,11 +1314,11 @@ static const struct tvec_uassoc reconn_assocs[] = {
   { "skip",            TVRCN_SKIP },
   TVEC_ENDENUM
 };
-
 static const struct tvec_uenuminfo reconn_enuminfo =
   { "remote-reconnection", reconn_assocs, &tvrange_uint };
-static const struct tvec_regdef reconn_regdef =
-  { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } };
+static const struct tvec_vardef reconn_var =
+  { sizeof(struct tvec_reg), setvar_local,
+    { "@reconnect", -1, &tvty_uenum, 0, { &reconn_enuminfo } } };
 
 /*----- Client ------------------------------------------------------------*/
 
@@ -1811,8 +1862,11 @@ static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r)
 
 static void reset_vars(struct tvec_remotectx *r)
 {
+  const struct tvec_remoteenv *re = r->re;
+
   r->exwant = TVXST_RUN;
-  r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) | TVRCN_DEMAND;
+  r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) |
+           (re->r.dflt_reconn&TVRF_RCNMASK);
   DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
 }
 
@@ -1837,8 +1891,7 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
 {
   struct tvec_remotectx *r = ctx;
   const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env;
-
-  assert(!re->r.env || tv->test->env == &re->_env);
+  const struct tvec_env *subenv = re->r.env;
 
   r->tv = tv;
   init_comms(&r->rc);
@@ -1847,16 +1900,22 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
   if (connect_remote(tv, r))
     tvec_skipgroup(tv, "failed to connect to test backend");
   reset_vars(r);
+  if (subenv && subenv->ctxsz) r->subctx = xmalloc(subenv->ctxsz);
+  else r->subctx = 0;
+  if (subenv && subenv->setup) subenv->setup(tv, subenv, r, r->subctx);
 }
 
-/* --- @tvec_remoteset@ --- *
+/* --- @tvec_remotefindvar@, @setvar_local@, @setvar_remote@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @const char *var@ = variable name to set
+ *             @const union tvec_regval *rv@ = register value
+ *             @void **ctx_out@ = where to put the @setvar@ context
  *             @void *ctx@ = context pointer
  *
- * Returns:    %$+1$% on success, %$0$% if the variable name was not
- *             recognized, or %$-1$% on any other error.
+ * Returns:    @tvec_remotefindvar@ returns a pointer to the variable
+ *             definition, or null; @remote_setvar@ returns zero on success
+ *             or %$-1$% on error.
  *
  * Use:                Set a special variable.  The following special variables are
  *             supported.
@@ -1872,39 +1931,94 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env,
  *               * %|reconnect|% is a reconnection policy; see @TVRCN_...@.
  */
 
-int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx)
+static int setvar_local(struct tvec_state *tv, const char *var,
+                       const union tvec_regval *rv, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
-  union tvec_regval rv;
-  int rc;
 
   if (STRCMP(var, ==, "@exit")) {
-    if (r->rc.f&TVRF_SETEXIT) { rc = tvec_dupreg(tv, var); goto end; }
-    if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; }
-    r->exwant = rv.u; r->rc.f |= TVRF_SETEXIT; rc = 1;
+    if (r->rc.f&TVRF_SETEXIT) return (tvec_dupreg(tv, var));
+    r->exwant = rv->u; r->rc.f |= TVRF_SETEXIT; return (0);
   } else if (STRCMP(var, ==, "@progress")) {
-    if (r->rc.f&TVRF_SETPRG) { rc = tvec_dupreg(tv, var); goto end; }
-    tvty_text.init(&rv, &progress_regdef);
-    rc = tvty_text.parse(&rv, &progress_regdef, tv);
-    if (!rc) {
-      DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.text.p, rv.text.sz);
-      r->rc.f |= TVRF_SETPRG;
-    }
-    tvty_text.release(&rv, &progress_regdef);
-    if (rc) { rc = -1; goto end; }
-    rc = 1;
+    if (r->rc.f&TVRF_SETPRG) return (tvec_dupreg(tv, var));
+    DRESET(&r->prgwant); DPUTM(&r->prgwant, rv->text.p, rv->text.sz);
+    DPUTZ(&r->prgwant);
+    r->rc.f |= TVRF_SETPRG; return (0);
   } else if (STRCMP(var, ==, "@reconnect")) {
-    if (r->rc.f&TVRF_SETRCN) { rc = tvec_dupreg(tv, var); goto end; }
-    if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; }
-    r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK) | TVRF_SETRCN;
-    rc = 1;
-  } else
-    rc = 0;
+    if (r->rc.f&TVRF_SETRCN) return (tvec_dupreg(tv, var));
+    r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv->u&TVRF_RCNMASK) | TVRF_SETRCN;
+    return (0);
+  } else assert(!"unknown var");
+}
+
+static int setvar_remote(struct tvec_state *tv, const char *var,
+                        const union tvec_regval *rv, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  buf b;
+  int ch, rc;
 
+  if (try_reconnect(tv, r) < 0) { rc = 0; goto end; }
+
+  QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_SETVAR) {
+    dbuf_putstr16l(&r->rc.bout, var);
+    r->vd.def.ty->tobuf(DBUF_BUF(&r->rc.bout), rv, &r->vd.def);
+  } else { rc = -1; goto end; }
+
+  rc = handle_packets(tv, r, 0, TVPK_SETVAR | TVPF_ACK, &b);
+    if (rc) goto end;
+  ch = buf_getbyte(&b);
+    if (ch < 0) { rc = malformed(tv, &r->rc); goto end; }
+  if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; }
+
+  rc = ch ? -1 : 0;
 end:
   return (rc);
 }
 
+const struct tvec_vardef *tvec_remotefindvar
+  (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  const struct tvec_remoteenv *re = r->re;
+  const struct tvec_env *subenv = re->r.env;
+  const struct tvec_vardef *vd; void *varctx;
+
+  if (STRCMP(var, ==, "@exit"))
+    { *ctx_out = r; return (&exit_var); }
+  else if (STRCMP(var, ==, "@progress"))
+    { *ctx_out = r; return (&progress_var); }
+  else if (STRCMP(var, ==, "@reconnect"))
+    { *ctx_out = r; return (&reconn_var); }
+  else if (subenv && subenv->findvar) {
+    vd = subenv->findvar(tv, var, &varctx, r->subctx);
+    if (!vd) return (0);
+    r->vd.regsz = vd->regsz; r->vd.setvar = setvar_remote;
+    r->vd.def = vd->def;
+    *ctx_out = r; return (&r->vd);
+  } else
+    return (0);
+}
+
+/* --- @tvec_remotebefore@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test vector state
+ *             @void *ctx@ = context pointer
+ *
+ * Returns:    ---
+ *
+ * Use:                Invoke the subordinate environment's @before@ function.
+ */
+
+void tvec_remotebefore(struct tvec_state *tv, void *ctx)
+{
+  struct tvec_remotectx *r = ctx;
+  const struct tvec_remoteenv *re = r->re;
+  const struct tvec_env *subenv = re->r.env;
+
+  if (subenv && subenv->before) subenv->before(tv, r->subctx);
+}
+
 /* --- @tvec_remoterun@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
@@ -1992,19 +2106,19 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
        /* Report exit status. */
        rv.u = r->exit;
        tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH,
-                    &rv, &exit_regdef);
+                    &rv, &exit_var.def);
        if (f&f_exit) {
          rv.u = r->exwant;
-         tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef);
+         tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_var.def);
        }
 
        /* Report progress token. */
        rv.text.p = r->progress.buf; rv.text.sz = r->progress.len;
        tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH,
-                    &rv, &progress_regdef);
+                    &rv, &progress_var.def);
        if (f&f_progress) {
          rv.text.p = r->prgwant.buf; rv.text.sz = r->prgwant.len;
-         tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef);
+         tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_var.def);
        }
       }
 
@@ -2034,8 +2148,11 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
 void tvec_remoteafter(struct tvec_state *tv, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
+  const struct tvec_remoteenv *re = r->re;
+  const struct tvec_env *subenv = re->r.env;
 
   reset_vars(r);
+  if (subenv && subenv->after) subenv->after(tv, r->subctx);
 }
 
 /* --- @tvec_remoteteardown@ --- *
@@ -2051,8 +2168,12 @@ void tvec_remoteafter(struct tvec_state *tv, void *ctx)
 void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
 {
   struct tvec_remotectx *r = ctx;
+  const struct tvec_remoteenv *re = r->re;
+  const struct tvec_env *subenv = re->r.env;
   buf b;
 
+  if (subenv && subenv->teardown) subenv->teardown(tv, r->subctx);
+  xfree(r->subctx);
   if (r->rc.outfd >= 0) {
     QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
     if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
index d9a614b..a1800db 100644 (file)
@@ -77,177 +77,73 @@ void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env,
   if (subenv && subenv->setup) subenv->setup(tv, subenv, tc, tc->subctx);
 }
 
-/* --- @tvec_timeoutset@ --- *
+/* --- @tvec_timeoutfindvar@, @setvar@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test vector state
  *             @const char *var@ = variable name to set
+ *             @const union tvec_regval *rv@ = register value
+ *             @void **ctx_out@ = where to put the @setvar@ context
  *             @void *ctx@ = context pointer
  *
- * Returns:    %$+1$% on success, %$0$% if the variable name was not
- *             recognized, or %$-1$% on any other error.
+ * Returns:    @tvec_timeoutfindvar@ returns a pointer to the variable
+ *             definition, or null; @setvar@ returns zero on success or
+ *             %$-1$% on error.
  *
- * Use:                Set a special variable.  The following special variables are
- *             supported.
+ * Use:                Find a definition for a special variable.  The following
+ *             special variables are supported.
  *
- *               * %|@timeout|% is the number of seconds (or other unit) to
- *                 wait before giving up and killing the test process.  The
- *                 string may also include a keyword %|REAL|%, %|VIRTUAL|%,
- *                 or %|PROF|% to select the timer.
+ *               * %|@timeout|% is the duration to wait before killing the
+ *                 process.
+ *
+ *               * %|@timer|% is the timer to use to measure the duration.
  *
  *             Unrecognized variables are passed to the subordinate
  *             environment, if there is one.
  */
 
-int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx)
+static int setvar(struct tvec_state *tv, const char *var,
+                 const union tvec_regval *rv, void *ctx)
+{
+  struct tvec_timeoutctx *tc = ctx;
+
+  if (STRCMP(var, ==, "@timeout")) {
+    if (tc->f&TVTF_SETTMO) return (tvec_dupreg(tv, var));
+    tc->t = rv->f; tc->f |= TVTF_SETTMO;
+  } else if (STRCMP(var, ==, "@timer")) {
+    if (tc->f&TVTF_SETTMR) return (tvec_dupreg(tv, var));
+    tc->timer = rv->i; tc->f |= TVTF_SETTMR;
+  } else assert(!"unknown var");
+  return (0);
+}
+
+static const struct tvec_vardef timeout_var =
+  { sizeof(struct tvec_reg), setvar,
+    { "@timeout", -1, &tvty_duration, 0 } };
+
+static const struct tvec_iassoc timer_assocs[] = {
+  { "REAL",    ITIMER_REAL },
+  { "VIRTUAL", ITIMER_VIRTUAL },
+  { "PROF",    ITIMER_PROF },
+  TVEC_ENDENUM
+};
+static const struct tvec_ienuminfo timer_enum =
+  { "interval-timer", timer_assocs, &tvrange_int };
+static const struct tvec_vardef timer_var =
+  { sizeof(struct tvec_reg), setvar,
+    { "@timer", -1, &tvty_ienum, 0, { &timer_enum } } };
+
+const struct tvec_vardef *tvec_timeoutfindvar
+  (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
 {
   struct tvec_timeoutctx *tc = ctx;
   const struct tvec_timeoutenv *te = tc->te;
   const struct tvec_env *subenv = te->env;
-  dstr d = DSTR_INIT;
-  double t = 0.0; unsigned tmr = 0;
-  const char *p; char *q; size_t pos;
-  int rc;
-  unsigned f = 0;
-#define f_time 1u
-#define f_timer 2u
-#define f_all (f_time | f_timer)
 
-  if (STRCMP(var, ==, "@timeout")) {
-    /* Parse a timeout specification. */
-
-    /* If we've already set one then report an error. */
-    if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; }
-
-    for (;;) {
-      /* Continue until we run out of things. */
-
-      /* Read the next word. */
-      DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break;
-    timeout_primed:
-
-      /* Start parsing the word. */
-      p = d.buf;
-
-      /* Check for timer tokens. */
-      if (!(f&f_timer) && STRCMP(p, ==, "REAL"))
-       { tmr = ITIMER_REAL; f |= f_timer;  }
-      else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL"))
-       { tmr = ITIMER_VIRTUAL; f |= f_timer; }
-      else if (!(f&f_timer) && STRCMP(p, ==, "PROF"))
-       { tmr = ITIMER_PROF; f |= f_timer; }
-
-      /* Otherwise, check for durations. */
-      else if (!(f&f_time)) {
-
-       /* Skip leading stuff that isn't digits.  This is a hedge against
-        * @strtod@ interpreting something unhelpful like a NaN.
-        */
-       if (*p == '+' || *p == '-') p++;
-       if (*p == '.') p++;
-       if (!ISDIGIT(*p)) {
-         tvec_syntax(tv, *d.buf, "floating-point number");
-         rc = -1; goto end;
-       }
-
-       /* Parse the number and check that it's reasonable. */
-       errno = 0; t = strtod(p, &q); f |= f_time;
-       if (errno) {
-         tvec_error(tv, "invalid floating-point number `%s': %s",
-                    d.buf, strerror(errno));
-         rc = -1; goto end;
-       }
-       if (t < 0) {
-         tvec_error(tv, "invalid duration `%s': %s",
-                    d.buf, strerror(errno));
-         rc = -1; goto end;
-       }
-
-       /* We're now on the lookout for units.  If there's nothing here then
-        * fetch the next word.
-        */
-       if (!*q) {
-         tvec_skipspc(tv); pos = d.len;
-         if (!tvec_readword(tv, &d, ";", 0)) pos++;
-         q = d.buf + pos;
-       }
-
-       /* Match various units. */
-       if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec"))
-         /* nothing to do */;
-
-       else if (STRCMP(q, ==, "ds")) t *= 1e-1;
-       else if (STRCMP(q, ==, "cs")) t *= 1e-2;
-       else if (STRCMP(q, ==, "ms")) t *= 1e-3;
-       else if (STRCMP(q, ==, "us") || STRCMP(q, ==, "µs")) t *= 1e-6;
-       else if (STRCMP(q, ==, "ns")) t *= 1e-9;
-       else if (STRCMP(q, ==, "ps")) t *= 1e-12;
-       else if (STRCMP(q, ==, "fs")) t *= 1e-15;
-       else if (STRCMP(q, ==, "as")) t *= 1e-18;
-       else if (STRCMP(q, ==, "zs")) t *= 1e-21;
-       else if (STRCMP(q, ==, "ys")) t *= 1e-24;
-
-       else if (STRCMP(q, ==, "das")) t *= 1e+1;
-       else if (STRCMP(q, ==, "hs")) t *= 1e+2;
-       else if (STRCMP(q, ==, "ks")) t *= 1e+3;
-       else if (STRCMP(q, ==, "Ms")) t *= 1e+6;
-       else if (STRCMP(q, ==, "Gs")) t *= 1e+9;
-       else if (STRCMP(q, ==, "Ts")) t *= 1e+12;
-       else if (STRCMP(q, ==, "Ps")) t *= 1e+15;
-       else if (STRCMP(q, ==, "Es")) t *= 1e+18;
-       else if (STRCMP(q, ==, "Zs")) t *= 1e+21;
-       else if (STRCMP(q, ==, "Ys")) t *= 1e+24;
-
-       else if (STRCMP(q, ==, "m") || STRCMP(q, ==, "min")) t *= 60;
-       else if (STRCMP(q, ==, "h") || STRCMP(q, ==, "hr")) t *= 3600;
-       else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400;
-       else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600;
-
-       /* Not a unit specification after all.  If we've already selected a
-        * timer, then this is just junk so report the error.  Otherwise, we
-        * snarfed the next token too early, so move it to the start of the
-        * buffer and go round again.
-        */
-       else {
-         if (f&f_timer)
-           { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; }
-         pos = q - d.buf; d.len -= pos;
-         memmove(d.buf, q, d.len + 1);
-         goto timeout_primed;
-       }
-      }
-
-      /* If we've read all that we need to, then stop. */
-      if (!(~f&f_all)) break;
-    }
-
-    /* If we didn't get anything, that's a problem. */
-    if (!f) {
-      rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword");
-      goto end;
-    }
-
-    /* Make sure there's nothing else on the line. */
-    rc = tvec_flushtoeol(tv, 0); if (rc) goto end;
-    if (f&f_time) tc->t = t;
-    if (f&f_timer) tc->timer = tmr;
-    tc->f |= TVTF_SETTMO;
-    rc = 1;
-
-  } else if (subenv && subenv->set)
-    /* Not one of ours: pass it on to the sub-environment. */
-    rc = subenv->set(tv, var, tc->subctx);
-  else
-    /* No subenvironment.  Report the error. */
-    rc = 0;
-
-  /* Done. */
-end:
-  dstr_destroy(&d);
-  return (rc);
-
-#undef f_time
-#undef f_timer
-#undef f_all
+  if (STRCMP(var, ==, "@timeout")) { *ctx_out = tc; return (&timeout_var); }
+  else if (STRCMP(var, ==, "@timer")) { *ctx_out = tc; return (&timer_var); }
+  else if (subenv && subenv->findvar)
+    return (subenv->findvar(tv, var, ctx_out, tc->subctx));
+  else return (0);
 }
 
 /* --- @tvec_timeoutbefore@ --- *
@@ -267,7 +163,6 @@ void tvec_timeoutbefore(struct tvec_state *tv, void *ctx)
   const struct tvec_timeoutenv *te = tc->te;
   const struct tvec_env *subenv = te->env;
 
-  /* Just call the subsidiary environment. */
   if (subenv && subenv->before) subenv->before(tv, tc->subctx);
 }
 
index acb848c..e76d4a1 100644 (file)
@@ -525,6 +525,7 @@ static void format_floating(const struct gprintf_ops *gops, void *go,
 /* --- @parse_floating@ --- *
  *
  * Arguments:  @double *x_out@ = where to put the result
+ *             @const char *q_out@ = where to leave end pointer, or null
  *             @const char *p@ = string to parse
  *             @const struct tvec_floatinfo *fi@ = floating-point info
  *             @struct tvec_state *tv@ = test vector state
@@ -532,10 +533,12 @@ static void format_floating(const struct gprintf_ops *gops, void *go,
  * Returns:    Zero on success, @-1@ on error.
  *
  * Use:                Parse a floating-point number from a string.  Reports any
- *             necessary errors.
+ *             necessary errors.  If @q_out@ is not null then trailing
+ *             material is permitted and a pointer to it is left in
+ *             @*q_out@; this will be null if there is no trailing material.
  */
 
-static int parse_floating(double *x_out, const char *p,
+static int parse_floating(double *x_out, const char **q_out, const char *p,
                          const struct tvec_floatinfo *fi,
                          struct tvec_state *tv)
 {
@@ -544,6 +547,8 @@ static int parse_floating(double *x_out, const char *p,
   double x;
   int olderr, rc;
 
+  if (q_out) *q_out = 0;
+
   /* Check for special tokens. */
   if (STRCMP(p, ==, "#nan")) {
 #ifdef NAN
@@ -588,13 +593,12 @@ static int parse_floating(double *x_out, const char *p,
     /* Parse the number using the system parser. */
     olderr = errno; errno = 0;
     x = strtod(p, &q);
-    if (*q) {
-      tvec_syntax(tv, *q, "end-of-line");
-      rc = -1; goto end;
-    }
+    if (!*q) /* nothing to do */;
+    else if (q_out) *q_out = q;
+    else { tvec_syntax(tv, *q, "end-of-line"); rc = -1; goto end; }
     if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
-      tvec_error(tv, "invalid floating-point number `%s': %s",
-                p, strerror(errno));
+      tvec_error(tv, "invalid floating-point number `%.*s': %s",
+                (int)(q - p), p, strerror(errno));
       rc = -1; goto end;
     }
     errno = olderr;
@@ -1515,7 +1519,7 @@ int tvec_claimeq_uint(struct tvec_state *tv,
 
 /*----- Floating-point type -----------------------------------------------*/
 
-/* --- @float_int@ --- *
+/* --- @int_float@ --- *
  *
  * Arguments:  @union tvec_regval *rv@ = register value
  *             @const struct tvec_regdef *rd@ = register definition
@@ -1610,7 +1614,8 @@ static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
 
   if (tvec_readword(tv, &d, ";", "floating-point number"))
     { rc = -1; goto end; }
-  if (parse_floating(&rv->f, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
+  if (parse_floating(&rv->f, 0, d.buf, rd->arg.p, tv))
+    { rc = -1; goto end; }
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
 end:
@@ -1729,6 +1734,151 @@ int tvec_claimeq_float(struct tvec_state *tv,
                                file, lno, expr));
 }
 
+/*----- Durations ---------------------------------------------------------*/
+
+/* A duration is a floating-point number of seconds.  Initialization and
+ * teardown, equality comparison, and serialization are as for floating-point
+ * values.
+ */
+
+static const struct duration_unit {
+  const char *unit;
+  double scale;
+  unsigned f;
+#define DUF_PREFER 1u
+} duration_units[] = {
+  { "Ys",      1e+24,          0 },
+  { "Zs",      1e+21,          0 },
+  { "Es",      1e+18,          0 },
+  { "Ps",      1e+15,          0 },
+  { "Ts",      1e+12,          0 },
+  { "Gs",      1e+9,           0 },
+  { "Ms",      1e+6,           0 },
+  { "ks",      1e+3,           0 },
+  { "hs",      1e+2,           0 },
+  { "das",     1e+1,           0 },
+
+  { "yr",      31557600.0,     DUF_PREFER },
+  { "y",       31557600.0,     0 },
+  { "day",     86400.0,        DUF_PREFER },
+  { "dy",      86400.0,        0 },
+  { "d",       86400.0,        0 },
+  { "hr",      3600.0,         DUF_PREFER },
+  { "hour",    3600.0,         0 },
+  { "h",       3600.0,         0 },
+  { "min",     60.0,           DUF_PREFER },
+  { "m",       60.0,           0 },
+
+  { "s",       1.0,            DUF_PREFER },
+  { "sec",     1.0,            0 },
+
+  { "ds",      1e-1,           0 },
+  { "cs",      1e-2,           0 },
+  { "ms",      1e-3,           DUF_PREFER },
+  { "µs",     1e-6,           DUF_PREFER },
+  { "ns",      1e-9,           DUF_PREFER },
+  { "ps",      1e-12,          DUF_PREFER },
+  { "fs",      1e-15,          DUF_PREFER },
+  { "as",      1e-18,          DUF_PREFER },
+  { "zs",      1e-21,          DUF_PREFER },
+  { "ys",      1e-24,          DUF_PREFER },
+
+  { 0 }
+};
+
+/* --- @parse_duration@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             Duration values are finite nonnegative floating-point
+ *             numbers in @strtod@ syntax, optionally followed by a unit .
+ */
+
+static int parse_duration(union tvec_regval *rv,
+                         const struct tvec_regdef *rd,
+                         struct tvec_state *tv)
+{
+  const struct duration_unit *u;
+  const char *q;
+  dstr d = DSTR_INIT; size_t pos;
+  double t;
+  int rc;
+
+  if (tvec_readword(tv, &d, ";", "duration")) { rc = -1; goto end; }
+  if (parse_floating(&t, &q, d.buf,
+                    rd->arg.p ? rd->arg.p : &tvflt_nonneg, tv))
+    { rc = -1; goto end; }
+
+  if (!q) {
+    tvec_skipspc(tv); pos = d.len;
+    if (!tvec_readword(tv, &d, ";", 0)) q = d.buf + pos + 1;
+  }
+
+  if (q) {
+    for (u = duration_units; u->unit; u++)
+      if (STRCMP(q, ==, u->unit)) { t *= u->scale; goto found_unit; }
+    rc = tvec_syntax(tv, *q, "end-of-line"); goto end;
+  found_unit:;
+  }
+
+  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+  rv->f = t; rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+}
+
+/* --- @dump_duration@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Durations are dumped as a human-palatable scaled value with
+ *             unit, and, if compact style is not requested, as a raw number
+ *             of seconds at full precision as a comment.
+ */
+
+static void dump_duration(const union tvec_regval *rv,
+                         const struct tvec_regdef *rd,
+                         unsigned style,
+                         const struct gprintf_ops *gops, void *go)
+{
+  const struct duration_unit *u;
+  double t = rv->f;
+
+  if (!t) u = 0;
+  else {
+    for (u = duration_units; u->scale > t && u[1].unit; u++);
+    t /= u->scale;
+  }
+
+  gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");
+  if (!(style&TVSF_COMPACT)) {
+    gprintf(gops, go, "; = ");
+    format_floating(gops, go, rv->f);
+    gprintf(gops, go, " s");
+  }
+}
+
+/* Duration type definition. */
+const struct tvec_regty tvty_duration = {
+  init_float, trivial_release, eq_float,
+  tobuf_float, frombuf_float,
+  parse_duration, dump_duration
+};
+
 /*----- Enumerations ------------------------------------------------------*/
 
 /* --- @init_tenum@ --- *
@@ -1908,7 +2058,7 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
 #define LITSTR_FLT     "literal floating-point number, "               \
                          "`#-inf', `#+inf', or `#nan'"
 #define FOUND_FLT      rv->f = a->f;
-#define MISSING_FLT    if (parse_floating(&rv->f, d.buf, ei->fi, tv))  \
+#define MISSING_FLT    if (parse_floating(&rv->f, 0, d.buf, ei->fi, tv)) \
                          { rc = -1; goto end; }
 
 #define LITSTR_PTR     "`#nil'"
index 5d37c3f..1e1e154 100644 (file)
@@ -337,6 +337,18 @@ typedef void tvec_testfn(const struct tvec_reg */*in*/,
 
 struct tvec_env;
 
+typedef int tvec_setvarfn(struct tvec_state */*tv*/, const char */*var*/,
+                         const union tvec_regval */*rv*/, void */*ctx*/);
+  /* Called after a variable is read.  Return zero on success or %$-1$% on
+   * error.  This function is never called if the test group is skipped.
+   */
+
+struct tvec_vardef {
+  size_t regsz;                                /* (minimum) register size */
+  tvec_setvarfn *setvar;               /* function to set variable */
+  struct tvec_regdef def;              /* register definition */
+};
+
 typedef void tvec_envsetupfn(struct tvec_state */*tv*/,
                             const struct tvec_env */*env*/,
                             void */*pctx*/, void */*ctx*/);
@@ -348,19 +360,23 @@ typedef void tvec_envsetupfn(struct tvec_state */*tv*/,
    * will not.
    */
 
-typedef int tvec_envsetfn(struct tvec_state */*tv*/,
-                         const char */*var*/, void */*ctx*/);
-  /* Called when the parser finds a %|@var|%' setting to parse and store the
-   * value.  Return %$+1$% on success, %$0$% if the variable name was not
-   * recognized, or %$-1$% on any other error (which should have been
-   * reported via @tvec_error@).
+typedef const struct tvec_vardef *tvec_envfindvarfn
+  (struct tvec_state */*tv*/, const char */*name*/,
+   void **/*ctx_out*/, void */*ctx*/);
+  /* Called when the parser finds a %|@var|%' special variable.  If a
+   * suitable variable was found, set @*ctx_out@ to a suitable context and
+   * return the variable definition; the context will be passed to the
+   * variable definition's @setvar@ function.  If no suitable variable was
+   * found, then return 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.
+   * the test group is skipped.  It %%\emph{is}%% called if the test will be
+   * skipped due to erroneous test data.  It should check the @TVSF_ACTIVE@
+   * flag if necessary.
    */
 
 typedef void tvec_envrunfn(struct tvec_state */*tv*/,
@@ -376,9 +392,9 @@ typedef void tvec_envrunfn(struct tvec_state */*tv*/,
 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
-   * %%\emph{is}%% called if the test group is skipped, so that the test
-   * environment can reset variables set by the @set@ entry point.  It should
-   * check the @TVSF_SKIP@ flag if necessary.
+   * %%\emph{is}%% called if the test group is skipped or the test data is
+   * erroneous, so that the test environment can reset variables set by the
+   * @set@ entry point.  It should check the @TVSF_SKIP@ flag if necessary.
    */
 
 typedef void tvec_envteardownfn(struct tvec_state */*tv*/, void */*ctx*/);
@@ -397,7 +413,7 @@ struct tvec_env {
   size_t ctxsz;                                /* environment context size */
 
   tvec_envsetupfn *setup;              /* setup for group */
-  tvec_envsetfn *set;                  /* set variable */
+  tvec_envfindvarfn *findvar;          /* find variable */
   tvec_envbeforefn *before;            /* prepare for test */
   tvec_envrunfn *run;                  /* run test function */
   tvec_envafterfn *after;              /* clean up after test */
@@ -1147,7 +1163,7 @@ extern struct bench_state *tvec_benchstate;
  */
 
 extern tvec_envsetupfn tvec_benchsetup;
-extern tvec_envsetfn tvec_benchset;
+extern tvec_envfindvarfn tvec_benchfindvar;
 extern tvec_envbeforefn tvec_benchbefore;
 extern tvec_envrunfn tvec_benchrun;
 extern tvec_envafterfn tvec_benchafter;
@@ -1156,7 +1172,7 @@ extern tvec_envteardownfn tvec_benchteardown;
 #define TVEC_BENCHENV                                                  \
   { sizeof(struct tvec_benchctx),                                      \
     tvec_benchsetup,                                                   \
-    tvec_benchset,                                                     \
+    tvec_benchfindvar,                                                 \
     tvec_benchbefore,                                                  \
     tvec_benchrun,                                                     \
     tvec_benchafter,                                                   \
@@ -1198,6 +1214,8 @@ struct tvec_remotectx {
   struct tvec_state *tv;               /* test vector state */
   struct tvec_remotecomms rc;          /* communication state */
   const struct tvec_remoteenv *re;     /* environment configuration */
+  void *subctx;                                /* subenvironment context */
+  struct tvec_vardef vd;               /* temporary variable definition */
   unsigned ver;                                /* protocol version */
   pid_t kid;                           /* child process id */
   int errfd;                           /* child stderr descriptor */
@@ -1205,8 +1223,8 @@ struct tvec_remotectx {
   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_DEMAND 0x0000u           /*     connect on demand */
+#define TVRCN_SKIP 0x0100u             /*     skip unless connected */
 #define TVRCN_FORCE 0x0200u            /*     force reconnection */
 #define TVRF_MUFFLE 0x0400u            /*   muffle child stderr */
 #define TVRF_SETEXIT 0x0800u           /*   set `@exit' */
@@ -1237,6 +1255,7 @@ typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/,
 struct tvec_remoteenv_slots {
   tvec_connectfn *connect;             /* connection function */
   const struct tvec_env *env;          /* subsidiary environment */
+  unsigned dflt_reconn;                        /* default reconnection */
 };
 
 struct tvec_remoteenv {
@@ -1296,15 +1315,16 @@ union tvec_remoteenv_subclass_kludge {
 
 /* Remote environment. */
 extern tvec_envsetupfn tvec_remotesetup;
-extern tvec_envsetfn tvec_remoteset;
+extern tvec_envfindvarfn tvec_remotefindvar;
+extern tvec_envbeforefn tvec_remotebefore;
 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_remotefindvar,                                                        \
+    tvec_remotebefore,                                                 \
     tvec_remoterun,                                                    \
     tvec_remoteafter,                                                  \
     tvec_remoteteardown }
@@ -1372,23 +1392,25 @@ extern tvec_connectfn tvec_fork, tvec_exec;
 
 struct tvec_timeoutenv {
   struct tvec_env _env;
-  unsigned timer;                      /* the timer (@ITIMER_...@) */
+  int timer;                   /* the timer (@ITIMER_...@) */
   double t;                            /* time to wait (in seconds) */
   const struct tvec_env *env;          /* subsidiary environment */
 };
 
 struct tvec_timeoutctx {
   const struct tvec_timeoutenv *te;    /* saved environment description */
-  unsigned timer;                      /* timer code (as overridden) */
+  int timer;                           /* timer code (as overridden) */
   double t;                            /* time to wait (as overridden) */
   unsigned f;                          /* flags */
 #define TVTF_SETTMO 1u                 /*   set `@timeout' */
-#define TVTF_SETMASK (TVTF_SETTMO)     /*   mask of @TVTF_SET...@ */
+#define TVTF_SETTMR 2u                 /*   set `@timer' */
+#define TVTF_SETMASK (TVTF_SETTMO | TVTF_SETTMR)
+                                       /*   mask of @TVTF_SET...@ */
   void *subctx;
 };
 
 extern tvec_envsetupfn tvec_timeoutsetup;
-extern tvec_envsetfn tvec_timeoutset;
+extern tvec_envfindvarfn tvec_timeoutfindvar;
 extern tvec_envbeforefn tvec_timeoutbefore;
 extern tvec_envrunfn tvec_timeoutrun;
 extern tvec_envafterfn tvec_timeoutafter;
@@ -1396,7 +1418,7 @@ extern tvec_envteardownfn tvec_timeoutteardown;
 #define TVEC_TIMEOUTENV                                                        \
        { sizeof(struct tvec_timeoutctx),                               \
          tvec_timeoutsetup,                                            \
-         tvec_timeoutset,                                              \
+         tvec_timeoutfindvar,                                          \
          tvec_timeoutbefore,                                           \
          tvec_timeoutrun,                                              \
          tvec_timeoutafter,                                            \
@@ -1616,6 +1638,19 @@ extern PRINTF_LIKE(3, 4)
 extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/,
                         const char */*expect*/, va_list */*ap*/);
 
+/* --- @tvec_unkreg@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @const char *name@ = register or pseudoregister name
+ *
+ * Returns:    %$-1$%.
+ *
+ * Use:                Reports an error that the register or pseudoregister is
+ *             unrecognized.
+ */
+
+extern int tvec_unkreg(struct tvec_state */*tv*/, const char */*name*/);
+
 /* --- @tvec_dupreg@ --- *
  *
  * Arguments:  @struct tvec_state *tv@ = test-vector state
@@ -1932,6 +1967,18 @@ extern int tvec_claimeq_float(struct tvec_state */*tv*/,
 
 extern const struct tvec_floatinfo tvflt_finite, tvflt_nonneg;
 
+/*----- Durations ---------------------------------------------------------*/
+
+/* A duration measures a time interval in seconds.  The input format consists
+ * of a nonnegative decimal floating-point number in @strtod@ format followed
+ * by an optional unit specification.
+ *
+ * No @tvec_claimeq_...@ function is provided for durations: use
+ * @tvec_claimeq_float@.
+ */
+
+extern const struct tvec_regty tvty_duration;
+
 /*----- Enumerated types --------------------------------------------------*/
 
 /* An enumeration describes a set of values of some underlying type, each of