#include <string.h>
#include "alloc.h"
+#include "growbuf.h"
#include "tvec.h"
/*----- Output ------------------------------------------------------------*/
const struct tvec_reg *rin, *rout;
for (rd = tv->test->regs; rd->name; rd++) {
- if (rd->i >= tv->nrout) {
+ if (rd->i >= tv->cfg.nrout) {
if (!(f&TVMF_IN)) continue;
rin = TVEC_REG(tv, in, rd->i);
tvec_dumpreg(tv, TVRD_INPUT, rin->f&TVRF_LIVE ? &rin->v : 0, rd);
dstr_destroy(&d); return (-1);
}
-/* --- @tvec_dupreg@ --- *
+/* --- @tvec_unkregerr@ --- *
+ *
+ * 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_unkregerr(struct tvec_state *tv, const char *name)
+{
+ return (tvec_error(tv, "unknown special register `%s' for test `%s'",
+ name, tv->test->name));
+}
+
+/* --- @tvec_dupregerr@ --- *
*
* Arguments: @struct tvec_state *tv@ = test-vector state
* @const char *name@ = register or pseudoregister name
* assigned already in the current test.
*/
-int tvec_dupreg(struct tvec_state *tv, const char *name)
+int tvec_dupregerr(struct tvec_state *tv, const char *name)
{ return (tvec_error(tv, "register `%s' is already set", name)); }
/* --- @tvec_skipspc@ --- *
*
* Arguments: @struct tvec_state *tv@ = test-vector state
* @dstr *d@ = string to append the word to
+ * @const char **p_inout@ = pointer into string, updated
* @const char *delims@ = additional delimiters to stop at
* @const char *expect@, @va_list ap@ = what was expected
*
* word constituents, a null terminator is written to @d@, and
* it is safe to treat the string in @d@ as being null-
* terminated.
+ *
+ * If @p_inout@ is not null, then @*p_inout@ must be a pointer
+ * into @d->buf@, which will be adjusted so that it will
+ * continue to point at the same position even if the buffer is
+ * reallocated. As a subtle tweak, if @*p_inout@ initially
+ * points at the end of the buffer, then it will be adjusted to
+ * point at the beginning of the next word, rather than at the
+ * additional intervening space.
*/
-int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
- const char *expect, ...)
+int tvec_readword(struct tvec_state *tv, dstr *d, const char **p_inout,
+ const char *delims, const char *expect, ...)
{
va_list ap;
int rc;
va_start(ap, expect);
- rc = tvec_readword_v(tv, d, delims, expect, &ap);
+ rc = tvec_readword_v(tv, d, p_inout, delims, expect, &ap);
va_end(ap);
return (rc);
}
-int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
- const char *expect, va_list *ap)
+int tvec_readword_v(struct tvec_state *tv, dstr *d, const char **p_inout,
+ const char *delims, const char *expect, va_list *ap)
{
+ size_t pos = 0;
int ch;
+ tvec_skipspc(tv);
+
ch = getc(tv->fp);
if (!ch || ch == '\n' || ch == EOF || ch == ';' ||
(delims && strchr(delims, ch))) {
if (expect) return (tvec_syntax(tv, ch, expect, ap));
else { ungetc(ch, tv->fp); return (-1); }
}
- if (d->len) DPUTC(d, ' ');
+ if (p_inout) pos = *p_inout - d->buf;
+ if (d->len) {
+ if (pos == d->len) pos++;
+ DPUTC(d, ' ');
+ }
do {
DPUTC(d, ch);
ch = getc(tv->fp);
} while (ch && ch != EOF && !isspace(ch) &&
(!delims || !strchr(delims, ch)));
DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
+ if (p_inout) *p_inout = d->buf + pos;
return (0);
}
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@ --- *
*
struct tvec_reg *r;
for (rd = tv->test->regs; rd->name; rd++) {
- assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
+ assert(rd->i < tv->cfg.nreg); r = TVEC_REG(tv, in, rd->i);
rd->ty->init(&r->v, rd); r->f = 0;
- if (rd->i < tv->nrout)
+ if (rd->i < tv->cfg.nrout)
{ r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
}
}
struct tvec_reg *r;
for (rd = tv->test->regs; rd->name; rd++) {
- assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
+ assert(rd->i < tv->cfg.nreg); r = TVEC_REG(tv, in, rd->i);
rd->ty->release(&r->v, rd); r->f = 0;
- if (rd->i < tv->nrout)
+ if (rd->i < tv->cfg.nrout)
{ r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
}
}
struct tvec_reg *r;
for (rd = tv->test->regs; rd->name; rd++) {
- assert(rd->i < tv->nreg);
- if (rd->i >= tv->nrout) continue;
+ assert(rd->i < tv->cfg.nreg);
+ if (rd->i >= tv->cfg.nrout) continue;
r = TVEC_REG(tv, out, rd->i);
rd->ty->release(&r->v, rd);
rd->ty->init(&r->v, rd);
const struct tvec_reg *rin, *rout;
for (rd = tv->test->regs; rd->name; rd++) {
- if (rd->i >= tv->nrout) continue;
+ if (rd->i >= tv->cfg.nrout) continue;
rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
- if (!rin->f&TVRF_LIVE) continue;
+ if (!(rin->f&TVRF_LIVE)) continue;
if (!(rout->f&TVRF_LIVE) || !rd->ty->eq(&rin->v, &rout->v, rd))
return (-1);
}
* output registers as live if the corresponding inputs are
* live. It calls the environment's @before@, @run@, and
* @after@ functions if provided; if there is no @run@ function,
- * it calls @tvec_check@ to verify the output values.
+ * then it calls the test function directly, passing it the
+ * environment's context pointer, and then calls @tvec_check@ to
+ * verify the output values.
*/
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;
+ struct tvec_reg *r;
+ unsigned f = 0;
+#define f_err 1u
if (!(tv->f&TVSF_OPEN)) return;
for (rd = t->regs; rd->name; rd++) {
- if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE)
- { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; }
- else if (!(rd->f&TVRF_OPT)) {
+ r = TVEC_REG(tv, in, rd->i);
+ if (r->f&TVRF_LIVE) {
+ if (rd->i < tv->cfg.nrout)
+ TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE;
+ } else if (!(r->f&TVRF_SEEN) && !(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@ --- *
{
const struct tvec_test *t = tv->test;
const struct tvec_env *env = t->env;
+ const struct tvec_regdef *rd0, *rd1;
unsigned i;
+#ifndef NDEBUG
+ /* Check that the register names and indices are distinct. */
+ for (rd0 = t->regs; rd0->name; rd0++) {
+ assert(rd0->i < tv->cfg.nreg);
+ for (rd1 = t->regs; rd1->name; rd1++)
+ if (rd0 != rd1) {
+ assert(rd0->i != rd1->i);
+ assert(STRCMP(rd0->name, !=, rd1->name));
+ }
+ }
+#endif
+
tv->output->ops->bgroup(tv->output);
tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE);
tvec_initregs(tv);
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_dupregerr(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", &tvty_uenum, -1, 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;
/* Read the group name. There may be leading and trailing
* whitespace.
*/
- tvec_skipspc(tv);
- DRESET(&d); tvec_readword(tv, &d, "];", "group name");
+ DRESET(&d); tvec_readword(tv, &d, 0, "];", "group name");
tvec_skipspc(tv);
ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
/* Find the matching test definition. */
- for (test = tv->tests; test->name; test++)
+ for (test = tv->cfg.tests; test->name; test++)
if (STRCMP(d.buf, ==, test->name)) goto found_test;
/* There wasn't one. Report the error. Muffle errors about the
} else {
/* Some non-whitespace thing. */
- /* Put the character back and read a word, which ought to be a
- * register name.
- */
- ungetc(ch, tv->fp);
- DRESET(&d);
- if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line;
-
- /* Now there should be a separator. */
- tvec_skipspc(tv); ch = getc(tv->fp);
- if (ch != '=' && ch != ':')
- { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; }
- tvec_skipspc(tv);
-
/* If there's no test, then report an error. Set the muffle flag,
* because there's no point in complaining about every assignment
* in this block.
tv->f |= TVSF_MUFFLE; goto flush_line;
}
- /* Open the test. This is syntactically a stanza of settings, so
- * it's fair to report on missing register assignments.
+ /* Put the character back and read a word, which ought to be a
+ * register name.
+ */
+ ungetc(ch, tv->fp);
+ DRESET(&d);
+ if (tvec_readword(tv, &d, 0, "=:*;", "register name"))
+ goto flush_line;
+
+ /* Open the test. This is syntactically a paragraph of settings,
+ * so it's fair to report on missing register assignments.
*/
open_test(tv);
+ /* See what sort of thing we have found. */
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 {
-
- /* Act on the result. */
- if (rv.u == XFAIL) tvec_xfail(tv);
- ret = 1;
- }
- }
-
- /* If there's no environment, this is an unknown setting. */
- else if (!env || !env->set) ret = 0;
-
- /* Otherwise pass the setting on to the environment. */
- else ret = env->set(tv, d.buf, g.ctx);
+ env = tv->test->env;
- /* 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;
+ /* 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_unkregerr(tv, d.buf); goto flush_line;
+ found_var:
+ rd = &vd->def;
} else {
/* A standard register. */
tvec_error(tv, "unknown register `%s' for test `%s'",
d.buf, tv->test->name);
goto flush_line;
-
found_reg:
+
/* Complain if the register is already set. */
r = TVEC_REG(tv, in, rd->i);
- if (r->f&TVRF_LIVE)
- { tvec_dupreg(tv, rd->name); goto flush_line; }
+ if (r->f&TVRF_SEEN)
+ { tvec_dupregerr(tv, rd->name); goto flush_line; }
+ }
+
+ /* Now there should be a separator. */
+ tvec_skipspc(tv); ch = getc(tv->fp);
- /* Parse a value and mark the register as live. */
- if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
- r->f |= TVRF_LIVE;
+ if (ch == '*') {
+ /* Register explicitly marked unset. */
+
+ if (vd) {
+ tvec_error(tv, "can't unset special variables");
+ goto flush_line;
+ }
+ if (!(rd->f&(TVRF_OPT | TVRF_UNSET))) {
+ tvec_error(tv, "register `%s' must be assigned "
+ "a value in test `%s'", rd->name, tv->test->name);
+ goto flush_line;
+ }
+ r->f |= TVRF_SEEN;
+ if (tvec_flushtoeol(tv, 0)) goto bad;
+ } else {
+ /* Common case of a proper assignment. */
+
+ /* We must have a separator. */
+ if (ch != '=' && ch != ':')
+ { tvec_syntax(tv, ch, "`=', `:', or `*'"); goto flush_line; }
+ tvec_skipspc(tv);
+
+ if (!vd) {
+ /* An ordinary register. Parse a value and mark the register
+ * as live.
+ */
+
+ if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
+ r->f |= TVRF_LIVE | TVRF_SEEN;
+ } else {
+ /* A special register defined by an environment. */
+
+ /* Set up the register. */
+ if (vd->regsz <= sizeof(rbuf))
+ r = &rbuf;
+ else {
+ GROWBUF_REPLACE(&arena_stdlib, r_alloc, rsz, vd->regsz,
+ 8*sizeof(void *), 1);
+ r = r_alloc;
+ }
+
+ /* Read and set the value. */
+ rd->ty->init(&r->v, rd);
+ if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
+ if (!(tv->f&TVSF_SKIP) && vd->setvar(tv, d.buf, &r->v, varctx))
+ goto bad;
+
+ /* Clean up. */
+ rd->ty->release(&r->v, &vd->def); vd = 0;
+ }
}
}
break;
/* 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 -------------------------------------------------*/
tv_out->f = 0;
assert(config->nrout <= config->nreg);
- tv_out->nrout = config->nrout; tv_out->nreg = config->nreg;
- tv_out->regsz = config->regsz;
- tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz);
- tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz);
- for (i = 0; i < tv_out->nreg; i++) {
+ tv_out->cfg = *config;
+ tv_out->in = xmalloc(tv_out->cfg.nreg*tv_out->cfg.regsz);
+ tv_out->out = xmalloc(tv_out->cfg.nrout*tv_out->cfg.regsz);
+ for (i = 0; i < tv_out->cfg.nreg; i++) {
TVEC_REG(tv_out, in, i)->f = 0;
- if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0;
+ if (i < tv_out->cfg.nrout) TVEC_REG(tv_out, out, i)->f = 0;
}
for (i = 0; i < TVOUT_LIMIT; i++)
tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
- tv_out->tests = config->tests; tv_out->test = 0;
+ tv_out->test = 0;
tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out);
}
void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
{
t->name = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
- tv->tests = t;
+ tv->cfg.tests = t;
}
/* --- @tvec_begingroup@ --- *
void tvec_begingroup(struct tvec_state *tv, const char *name,
const char *file, unsigned lno)
{
- struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
+ struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->cfg.tests;
t->name = name; tv->test = t;
tv->infile = file; tv->lno = tv->test_lno = lno;