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)
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
};
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
};
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
};
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
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@ --- *
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);
}
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
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@ --- *
*
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;
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 {
}
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@ --- *
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 },
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)
{
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;
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. */
/* 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.
/* Clean up. */
tv->infile = 0; tv->fp = 0;
dstr_destroy(&d);
+ xfree(r_alloc);
return (rc);
+
+#undef rlive
}
/*----- Session lifecycle -------------------------------------------------*/
* <-- 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 */
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;
/* 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. */
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);
/*----- Pseudoregister definitions ----------------------------------------*/
+static tvec_setvarfn setvar_local, setvar_remote;
+
static const struct tvec_flag exit_flags[] = {
/* Cause codes. */
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. */
{ "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 ------------------------------------------------------------*/
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");
}
{
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);
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.
* * %|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
/* 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);
}
}
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@ --- *
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))
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@ --- *
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);
}
/* --- @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
* 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)
{
double x;
int olderr, rc;
+ if (q_out) *q_out = 0;
+
/* Check for special tokens. */
if (STRCMP(p, ==, "#nan")) {
#ifdef NAN
/* 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;
/*----- Floating-point type -----------------------------------------------*/
-/* --- @float_int@ --- *
+/* --- @int_float@ --- *
*
* Arguments: @union tvec_regval *rv@ = register value
* @const struct tvec_regdef *rd@ = register definition
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:
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@ --- *
#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'"
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*/);
* 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*/,
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*/);
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 */
*/
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;
#define TVEC_BENCHENV \
{ sizeof(struct tvec_benchctx), \
tvec_benchsetup, \
- tvec_benchset, \
+ tvec_benchfindvar, \
tvec_benchbefore, \
tvec_benchrun, \
tvec_benchafter, \
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 */
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' */
struct tvec_remoteenv_slots {
tvec_connectfn *connect; /* connection function */
const struct tvec_env *env; /* subsidiary environment */
+ unsigned dflt_reconn; /* default reconnection */
};
struct tvec_remoteenv {
/* 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 }
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;
#define TVEC_TIMEOUTENV \
{ sizeof(struct tvec_timeoutctx), \
tvec_timeoutsetup, \
- tvec_timeoutset, \
+ tvec_timeoutfindvar, \
tvec_timeoutbefore, \
tvec_timeoutrun, \
tvec_timeoutafter, \
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
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