From: Mark Wooding Date: Fri, 8 Mar 2024 22:46:21 +0000 (+0000) Subject: @@@ tvec setvar X-Git-Url: https://git.distorted.org.uk/~mdw/mLib/commitdiff_plain/814e42ff7421d66c0a2e33af5765afa2078bc18f @@@ tvec setvar --- diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c index aaa4465..0d93bc1 100644 --- a/test/t/tvec-test.c +++ b/test/t/tvec-test.c @@ -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 }; diff --git a/test/tests.at b/test/tests.at index 4bea513..456729d 100644 --- a/test/tests.at +++ b/test/tests.at @@ -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 diff --git a/test/tvec-bench.c b/test/tvec-bench.c index c1d8cc0..ad23043 100644 --- a/test/tvec-bench.c +++ b/test/tvec-bench.c @@ -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); } diff --git a/test/tvec-core.c b/test/tvec-core.c index 65c74cf..51b46d2 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -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 -------------------------------------------------*/ diff --git a/test/tvec-remote.c b/test/tvec-remote.c index 052ce13..b87351b 100644 --- a/test/tvec-remote.c +++ b/test/tvec-remote.c @@ -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)) diff --git a/test/tvec-timeout.c b/test/tvec-timeout.c index d9a614b..a1800db 100644 --- a/test/tvec-timeout.c +++ b/test/tvec-timeout.c @@ -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); } diff --git a/test/tvec-types.c b/test/tvec-types.c index acb848c..e76d4a1 100644 --- a/test/tvec-types.c +++ b/test/tvec-types.c @@ -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'" diff --git a/test/tvec.h b/test/tvec.h index 5d37c3f..1e1e154 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -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