X-Git-Url: https://git.distorted.org.uk/~mdw/mLib/blobdiff_plain/c81c35dfd10050ffef85d57dc2ad73f52f38a3f2..5c0f2e080603967952db43eb7b12d44dd64f7169:/test/tvec-core.c?ds=sidebyside diff --git a/test/tvec-core.c b/test/tvec-core.c index 65c74cf..d2a08c3 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -33,6 +33,7 @@ #include #include "alloc.h" +#include "growbuf.h" #include "tvec.h" /*----- Output ------------------------------------------------------------*/ @@ -255,7 +256,7 @@ void tvec_mismatch(struct tvec_state *tv, unsigned f) 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); @@ -317,7 +318,24 @@ int tvec_syntax_v(struct tvec_state *tv, int ch, 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 @@ -328,7 +346,7 @@ int tvec_syntax_v(struct tvec_state *tv, int ch, * 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@ --- * @@ -452,6 +470,7 @@ int tvec_nexttoken(struct tvec_state *tv) * * 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 * @@ -474,38 +493,54 @@ int tvec_nexttoken(struct tvec_state *tv) * 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); } @@ -513,8 +548,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@ --- * * @@ -534,9 +572,9 @@ void tvec_initregs(struct tvec_state *tv) 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; } } } @@ -547,9 +585,9 @@ void tvec_releaseregs(struct tvec_state *tv) 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; } } } @@ -573,8 +611,8 @@ void tvec_resetoutputs(struct tvec_state *tv) 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); @@ -605,9 +643,9 @@ int tvec_checkregs(struct tvec_state *tv) 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); } @@ -738,7 +776,9 @@ void tvec_xfail(struct tvec_state *tv) * 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) @@ -746,24 +786,30 @@ 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 { @@ -772,10 +818,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@ --- * @@ -795,8 +843,21 @@ static void begin_test_group(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 *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); @@ -864,21 +925,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_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 }, @@ -889,8 +973,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", &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) { @@ -898,10 +1006,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; @@ -928,13 +1036,12 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *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 @@ -985,19 +1092,6 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) } 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. @@ -1007,41 +1101,35 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) 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. */ @@ -1051,16 +1139,67 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) 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; @@ -1071,7 +1210,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 +1230,10 @@ end: /* Clean up. */ tv->infile = 0; tv->fp = 0; dstr_destroy(&d); + xfree(r_alloc); return (rc); + +#undef rlive } /*----- Session lifecycle -------------------------------------------------*/ @@ -1113,19 +1258,18 @@ void tvec_begin(struct tvec_state *tv_out, 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); } @@ -1269,7 +1413,7 @@ static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p) void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t) { t->name = ""; t->regs = &no_regs; t->env = 0; t->fn = fakefn; - tv->tests = t; + tv->cfg.tests = t; } /* --- @tvec_begingroup@ --- * @@ -1289,7 +1433,7 @@ void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t) 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;