* <-- 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))