X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/b64eb60f6c1fdb12f3922e04913e137199838807..c81c35dfd10050ffef85d57dc2ad73f52f38a3f2:/test/tvec-core.c diff --git a/test/tvec-core.c b/test/tvec-core.c index dc2f8ab..65c74cf 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -29,72 +29,152 @@ #include #include +#include #include #include "alloc.h" -#include "bench.h" #include "tvec.h" /*----- Output ------------------------------------------------------------*/ -void tvec_error(struct tvec_state *tv, const char *msg, ...) - { va_list ap; va_start(ap, msg); tvec_error_v(tv, msg, &ap); } -void tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap) - { tv->output->ops->error(tv->output, msg, ap); exit(2); } +/* --- @tvec_strlevel@ --- * + * + * Arguments: @unsigned level@ = level code + * + * Returns: A human-readable description. + * + * Use: Converts a level code into something that you can print in a + * message. + */ -void tvec_notice(struct tvec_state *tv, const char *msg, ...) +const char *tvec_strlevel(unsigned level) +{ + switch (level) { +#define CASE(tag, name, val) \ + case TVLEV_##tag: return (name); + TVEC_LEVELS(CASE) +#undef CASE + default: return ("??"); + } +} + +/* --- @tvec_report@, @tvec_report_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned level@ = severity level (@TVlEV_...@) + * @const char *msg@, @va_list ap@ = error message + * + * Returns: --- + * + * Use: Report an message with a given severity. Messages with level + * @TVLEV_ERR@ or higher force a nonzero exit code. + */ + +void tvec_report(struct tvec_state *tv, unsigned level, const char *msg, ...) { va_list ap; - va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap); + + va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap); } -void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap) - { tv->output->ops->notice(tv->output, msg, ap); } -void tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...) +void tvec_report_v(struct tvec_state *tv, unsigned level, + const char *msg, va_list *ap) +{ + tv->output->ops->report(tv->output, level, msg, ap); + if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR; +} + +/* --- @tvec_error@, @tvec_notice@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *msg@, @va_list ap@ = error message + * + * Returns: The @tvec_error@ function returns @-1@ as a trivial + * convenience; @tvec_notice@ does not return a value. + * + * Use: Report an error or a notice. Errors are distinct from test + * failures, and indicate that a problem was encountered which + * compromised the activity of testing. Notices are important + * information which doesn't fit into any other obvious + * category. + */ + +int tvec_error(struct tvec_state *tv, const char *msg, ...) { va_list ap; - va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap); + + va_start(ap, msg); tvec_report_v(tv, TVLEV_ERR, msg, &ap); va_end(ap); + return (-1); } -void tvec_syntax_v(struct tvec_state *tv, int ch, - const char *expect, va_list *ap) + +void tvec_notice(struct tvec_state *tv, const char *msg, ...) { - dstr d = DSTR_INIT; - char found[8]; + va_list ap; - switch (ch) { - case EOF: strcpy(found, ""); break; - case '\n': strcpy(found, ""); break; - default: - if (isprint(ch)) sprintf(found, "`%c'", ch); - else sprintf(found, "<#x%02x>", ch); - break; - } - dstr_vputf(&d, expect, ap); - tvec_error(tv, "syntax error: expected %s but found %s", expect, found); + va_start(ap, msg); tvec_report_v(tv, TVLEV_NOTE, msg, &ap); va_end(ap); } +/*----- Test processing ---------------------------------------------------*/ + +/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list *ap@ = reason why skipped + * + * Returns: --- + * + * Use: Skip the current group. This should only be called from a + * test environment @setup@ function; a similar effect occurs if + * the @setup@ function fails. + */ + void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...) { va_list ap; + va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap); } + void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap) { - tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++; - tv->output->ops->skipgroup(tv->output, excuse, ap); + if (!(tv->f&TVSF_SKIP)) { + tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++; + tv->output->ops->skipgroup(tv->output, excuse, ap); + } } +/* --- @set_outcome@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned out@ = the new outcome + * + * Returns: --- + * + * Use: Sets the outcome bits in the test state flags, and clears + * @TVSF_ACTIVE@. + */ + static void set_outcome(struct tvec_state *tv, unsigned out) -{ - tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK); - tv->f |= out << TVSF_OUTSHIFT; -} + { tv->f = (tv->f&~(TVSF_ACTIVE | TVSF_OUTMASK)) | (out << TVSF_OUTSHIFT); } + +/* --- @tvec_skip@, @tvec_skip_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list *ap@ = reason why test skipped + * + * Returns: --- + * + * Use: Skip the current test. This should only be called from a + * test environment @run@ function; a similar effect occurs if + * the @before@ function fails. + */ void tvec_skip(struct tvec_state *tv, const char *excuse, ...) { va_list ap; va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap); } + void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap) { assert(tv->f&TVSF_ACTIVE); @@ -102,11 +182,29 @@ void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap) tv->output->ops->skip(tv->output, excuse, ap); } +/* --- @tvec_fail@, @tvec_fail_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *detail@, @va_list *ap@ = description of test + * + * Returns: --- + * + * Use: Report the current test as a failure. This function can be + * called multiple times for a single test, e.g., if the test + * environment's @run@ function invokes the test function + * repeatedly; but a single test that fails repeatedly still + * only counts as a single failure in the statistics. The + * @detail@ string and its format parameters can be used to + * distinguish which of several invocations failed; it can + * safely be left null if the test function is run only once. + */ + void tvec_fail(struct tvec_state *tv, const char *detail, ...) { va_list ap; va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap); } + void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap) { assert((tv->f&TVSF_ACTIVE) || @@ -114,162 +212,135 @@ void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap) set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap); } -void tvec_mismatch(struct tvec_state *tv) - { tv->output->ops->mismatch(tv->output); } - -void tvec_write(struct tvec_state *tv, const char *p, ...) -{ - va_list ap; - va_start(ap, p); tvec_write_v(tv, p, &ap); va_end(ap); -} -void tvec_write_v(struct tvec_state *tv, const char *p, va_list *ap) -{ - dstr d = DSTR_INIT; - - dstr_vputf(&d, p, ap); tv->output->ops->write(tv->output, d.buf, d.len); - DDESTROY(&d); -} - -/*----- Serialization and deserialization ---------------------------------*/ - -int tvec_serialize(const struct tvec_reg *rv, - const struct tvec_regdef *regs, - unsigned nr, size_t regsz, - void **p_out, size_t *sz_out) -{ - void *p = 0; buf b; - unsigned char *bitmap; - size_t i, nbits, bitsz, sz; - const struct tvec_regdef *rd; - const struct tvec_reg *r; - int rc; - - for (rd = regs, nbits = 0, sz = 0; rd->name; rd++, nbits++) { - if (rd->i >= nr) continue; - r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; - sz += rd->ty->measure(&r->v, rd); - } - bitsz = (nbits + 7)/8; sz += bitsz; +/* --- @tvec_dumpreg@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned disp@ = the register disposition (@TVRD_...@) + * @const union tvec_regval *tv@ = register value, or null + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Dump a register value to the output. This is the lowest- + * level function for dumping registers, and calls the output + * formatter directly. + * + * Usually @tvec_mismatch@ is much more convenient. Low-level + * access is required for reporting `virtual' registers + * corresponding to test environment settings. + */ - p = xmalloc(sz); buf_init(&b, p, sz); - bitmap = buf_get(&b, bitsz); assert(bitmap); memset(bitmap, 0, bitsz); - for (rd = regs, i = 0; rd->name; rd++, i++) { - if (rd->i >= nr) continue; - r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; - bitmap[rd->i/8] |= 1 << rd->i%8; - if (rd->ty->tobuf(&b, &r->v, rd)) { rc = -1; goto end; } - } +void tvec_dumpreg(struct tvec_state *tv, + unsigned disp, const union tvec_regval *r, + const struct tvec_regdef *rd) + { tv->output->ops->dumpreg(tv->output, disp, r, rd); } - if (BBAD(&b)) { rc = -1; goto end; } - *p_out = p; *sz_out = BLEN(&b); p = 0; rc = 0; -end: - xfree(p); - return (rc); -} +/* --- @tvec_mismatch@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned f@ = flags (@TVMF_...@) + * + * Returns: --- + * + * Use: Dumps registers suitably to report a mismatch. The flag bits + * @TVMF_IN@ and @TVF_OUT@ select input-only and output + * registers. If both are reset then nothing happens. + * Suppressing the output registers may be useful, e.g., if the + * test function crashed rather than returning outputs. + */ -int tvec_deserialize(struct tvec_reg *rv, - const struct tvec_regdef *regs, - unsigned nr, size_t regsz, - const void *p, size_t sz) +void tvec_mismatch(struct tvec_state *tv, unsigned f) { - buf b; - const unsigned char *bitmap; - size_t i, nbits, bitsz; const struct tvec_regdef *rd; - struct tvec_reg *r; - int rc; - - for (rd = regs, nbits = 0; rd->name; rd++, nbits++); - bitsz = (nbits + 7)/8; sz += bitsz; + const struct tvec_reg *rin, *rout; - buf_init(&b, (/*unconst*/ void *)p, sz); - bitmap = buf_get(&b, bitsz); if (!bitmap) { rc = -1; goto end; } - for (rd = regs, i = 0; rd->name; rd++, i++) { - if (rd->i >= nr) continue; - if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue; - r = TVEC_GREG(rv, rd->i, regsz); - if (rd->ty->frombuf(&b, &r->v, rd)) { rc = -1; goto end; } - r->f |= TVRF_LIVE; + for (rd = tv->test->regs; rd->name; rd++) { + if (rd->i >= tv->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); + } else { + if (!(f&TVMF_OUT)) continue; + rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i); + if (!(rin->f&TVRF_LIVE)) + tvec_dumpreg(tv, TVRD_OUTPUT, rout->f&TVRF_LIVE ? &rout->v : 0, rd); + else if ((rout->f&TVRF_LIVE) && rd->ty->eq(&rin->v, &rout->v, rd)) + tvec_dumpreg(tv, TVRD_MATCH, &rin->v, rd); + else { + tvec_dumpreg(tv, TVRD_FOUND, rout->f&TVRF_LIVE ? &rout->v : 0, rd); + tvec_dumpreg(tv, TVRD_EXPECT, &rin->v, rd); + } + } } - - if (BBAD(&b)) { rc = -1; goto end; } - rc = 0; -end: - return (rc); } -/*----- Benchmarking ------------------------------------------------------*/ - -struct bench_state *tvec_benchstate; - -struct benchrun { - unsigned long *n; - tvec_testfn *fn; - const struct tvec_reg *in; struct tvec_reg *out; - void *ctx; -}; +/*----- Parsing -----------------------------------------------------------*/ -static void benchloop_outer(unsigned long n, void *p) - { struct benchrun *r = p; while (n--) r->fn(r->in, r->out, r->ctx); } - -static void benchloop_inner(unsigned long n, void *p) - { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); } +/* --- @tvec_syntax@, @tvec_syntax_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @int ch@ = the character found (in @fgetc@ format) + * @const char *expect@, @va_list ap@ = what was expected + * + * Returns: %$-1$%. + * + * Use: Report a syntax error quoting @ch@ and @expect@. If @ch@ is + * a newline, then back up so that it can be read again (e.g., + * by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also + * advance the line number). + */ -int tvec_ensurebench(struct tvec_state *tv, struct bench_state **b_out) +int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...) { - const struct tvec_bench *tvb = tv->test->arg.p; - struct bench_state **bb; - struct bench_timer *bt; - - if (tvb->b) bb = tvb->b; - else bb = &tvec_benchstate; - - if (!*bb) { - bt = bench_createtimer(); - if (!bt) { tvec_skip(tv, "failed to create timer"); return (-1); } - *bb = xmalloc(sizeof(**bb)); bench_init(*bb, bt); - } else if (!(*bb)->tm) - { tvec_skip(tv, "failed to create timer"); return (-1); } + va_list ap; - *b_out = *bb; - return (0); + va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap); + return (-1); } -void tvec_bench(struct tvec_state *tv) +int tvec_syntax_v(struct tvec_state *tv, int ch, + const char *expect, va_list *ap) { - const struct tvec_bench *tvb = tv->test->arg.p; - struct bench_state *b; - struct bench_timing tm; - struct benchrun r; - bench_fn *loopfn; - - if (tvec_ensurebench(tv, &b)) goto end_0; - - r.in = tv->in; r.out = tv->out; r.fn = tv->test->fn; - if (tvb->ctxsz) r.ctx = xmalloc(tvb->ctxsz); - else r.ctx = 0; - if (tvb->setup && tvb->setup(tv->in, tv->out, &tvb->arg, r.ctx)) - { tvec_skip(tv, "benchmark setup failed"); goto end_1; } + dstr d = DSTR_INIT; + char found[8]; - if (tvb->riter < 0) - { r.n = 0; loopfn = benchloop_outer; } - else - { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; } + switch (ch) { + case EOF: strcpy(found, "#eof"); break; + case '\n': strcpy(found, "#eol"); ungetc(ch, tv->fp); break; + default: + if (isprint(ch)) sprintf(found, "`%c'", ch); + else sprintf(found, "#\\x%02x", ch); + break; + } + dstr_vputf(&d, expect, ap); + tvec_error(tv, "syntax error: expected %s but found %s", d.buf, found); + dstr_destroy(&d); return (-1); +} - tv->output->ops->bbench(tv->output); - if (bench_measure(&tm, b, loopfn, &r)) - { tv->output->ops->ebench(tv->output, 0); goto end_2; } - tv->output->ops->ebench(tv->output, &tm); +/* --- @tvec_dupreg@ --- * + * + * 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 has been + * assigned already in the current test. + */ -end_2: - if (tvb->teardown) tvb->teardown(r.ctx); -end_1: - if (r.ctx) xfree(r.ctx); -end_0: - return; -} +int tvec_dupreg(struct tvec_state *tv, const char *name) + { return (tvec_error(tv, "register `%s' is already set", name)); } -/*----- Main machinery ----------------------------------------------------*/ +/* --- @tvec_skipspc@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Advance over any whitespace characters other than newlines. + * This will stop at `;', end-of-file, or any other kind of + * non-whitespace; and it won't consume a newline. + */ void tvec_skipspc(struct tvec_state *tv) { @@ -282,24 +353,67 @@ void tvec_skipspc(struct tvec_state *tv) } } -void tvec_flushtoeol(struct tvec_state *tv, unsigned f) +/* --- @tvec_flushtoeol@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned f@ = flags (@TVFF_...@) + * + * Returns: Zero on success, @-1@ on error. + * + * Use: Advance to the start of the next line, consuming the + * preceding newline. + * + * A syntax error is reported if no newline character is found, + * i.e., the file ends in mid-line. A syntax error is also + * reported if material other than whitespace or a comment is + * found before the end of the line end, and @TVFF_ALLOWANY@ is + * not set in @f@. The line number count is updated + * appropriately. + */ + +int tvec_flushtoeol(struct tvec_state *tv, unsigned f) { - int ch; + int ch, rc = 0; for (;;) { ch = getc(tv->fp); switch (ch) { - case '\n': tv->lno++; return; - case EOF: return; + case '\n': tv->lno++; return (rc); + case EOF: return (rc); case ';': f |= TVFF_ALLOWANY; break; default: - if (!(f&TVFF_ALLOWANY) && !isspace(ch)) + if (!(f&TVFF_ALLOWANY) && !isspace(ch)) { tvec_syntax(tv, ch, "end-of-line"); + rc = -1; f |= TVFF_ALLOWANY; + } break; } } } +/* --- @tvec_nexttoken@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero if there is a next token which can be read; @-1@ if no + * token is available. + * + * Use: Advance to the next whitespace-separated token, which may be + * on the next line. + * + * Tokens are separated by non-newline whitespace, comments, and + * newlines followed by whitespace; a newline /not/ followed by + * whitespace instead begins the next assignment, and two + * newlines separated only by whitespace terminate the data for + * a test. + * + * If this function returns zero, then the next character in the + * file begins a suitable token which can be read and + * processed. If it returns @-1@ then there is no such token, + * and the file position is left correctly. The line number + * count is updated appropriately. + */ + int tvec_nexttoken(struct tvec_state *tv) { enum { TAIL, NEWLINE, INDENT, COMMENT }; @@ -334,6 +448,34 @@ int tvec_nexttoken(struct tvec_state *tv) } } +/* --- @tvec_readword@, @tvec_readword_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @dstr *d@ = string to append the word to + * @const char *delims@ = additional delimiters to stop at + * @const char *expect@, @va_list ap@ = what was expected + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: A `word' consists of characters other than whitespace, null + * characters, and other than those listed in @delims@; + * furthermore, a word does not begin with a `;'. (If you want + * reading to stop at comments not preceded by whitespace, then + * include `;' in @delims@. This is a common behaviour.) + * + * If there is no word beginning at the current file position, + * then return @-1@; furthermore, if @expect@ is not null, then + * report an appropriate error via @tvec_syntax@. + * + * Otherwise, the word is accumulated in @d@ and zero is + * returned; if @d@ was not empty at the start of the call, the + * newly read word is separated from the existing material by a + * single space character. Since null bytes are never valid + * word constituents, a null terminator is written to @d@, and + * it is safe to treat the string in @d@ as being null- + * terminated. + */ + int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims, const char *expect, ...) { @@ -345,27 +487,48 @@ int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims, 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 ch; ch = getc(tv->fp); - if (ch == '\n' || ch == EOF || ch == ';' || + if (!ch || ch == '\n' || ch == EOF || ch == ';' || (delims && strchr(delims, ch))) { - if (expect) tvec_syntax(tv, ch, expect, ap); + if (expect) return (tvec_syntax(tv, ch, expect, ap)); else { ungetc(ch, tv->fp); return (-1); } } if (d->len) DPUTC(d, ' '); do { DPUTC(d, ch); ch = getc(tv->fp); - } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch))); + } while (ch && ch != EOF && !isspace(ch) && + (!delims || !strchr(delims, ch))); DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp); return (0); } -static void init_registers(struct tvec_state *tv) +/*----- Main machinery ----------------------------------------------------*/ + +struct groupstate { + void *ctx; /* test environment context */ +}; +#define GROUPSTATE_INIT { 0 } + +/* --- @tvec_initregs@, @tvec_releaseregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Initialize or release, respectively, the registers required + * by the current test. All of the registers, both input and + * output, are effected. Initialized registers are not marked + * live. + */ + +void tvec_initregs(struct tvec_state *tv) { const struct tvec_regdef *rd; struct tvec_reg *r; @@ -376,10 +539,9 @@ static void init_registers(struct tvec_state *tv) if (rd->i < tv->nrout) { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; } } - tv->expst = '.'; } -static void release_registers(struct tvec_state *tv) +void tvec_releaseregs(struct tvec_state *tv) { const struct tvec_regdef *rd; struct tvec_reg *r; @@ -392,88 +554,271 @@ static void release_registers(struct tvec_state *tv) } } -void tvec_check(struct tvec_state *tv, const char *detail, ...) +/* --- @tvec_resetoutputs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Reset (releases and reinitializes) the output registers in + * the test state. This is mostly of use to test environment + * @run@ functions, between invocations of the test function. + * Output registers are marked live if and only if the + * corresponding input register is live. + */ + +void tvec_resetoutputs(struct tvec_state *tv) { - va_list ap; - va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap); + const struct tvec_regdef *rd; + struct tvec_reg *r; + + for (rd = tv->test->regs; rd->name; rd++) { + assert(rd->i < tv->nreg); + if (rd->i >= tv->nrout) continue; + r = TVEC_REG(tv, out, rd->i); + rd->ty->release(&r->v, rd); + rd->ty->init(&r->v, rd); + r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE; + } } -void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap) + +/* --- @tvec_checkregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, @-1@ on mismatch. + * + * Use: Compare the active output registers (according to the current + * test group definition) with the corresponding input register + * values. A mismatch occurs if the two values differ + * (according to the register type's @eq@ method), or if the + * input is live but the output is dead. + * + * This function only checks for a mismatch and returns the + * result; it takes no other action. In particular, it doesn't + * report a failure, or dump register values. + */ + +int tvec_checkregs(struct tvec_state *tv) { const struct tvec_regdef *rd; const struct tvec_reg *rin, *rout; - unsigned f = 0; -#define f_mismatch 1u - if (tv->expst != tv->st) f |= f_mismatch; for (rd = tv->test->regs; rd->name; rd++) { if (rd->i >= tv->nrout) continue; rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i); if (!rin->f&TVRF_LIVE) continue; - if (!rd->ty->eq(&rin->v, &rout->v, rd)) f |= f_mismatch; + if (!(rout->f&TVRF_LIVE) || !rd->ty->eq(&rin->v, &rout->v, rd)) + return (-1); } - if (!(f&f_mismatch)) return; + return (0); +} + +/* --- @tvec_check@, @tvec_check_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *detail@, @va_list *ap@ = description of test + * + * Returns: --- + * + * Use: Check the register values, reporting a failure and dumping + * the registers in the event of a mismatch. This just wraps up + * @tvec_checkregs@, @tvec_fail@ and @tvec_mismatch@ in the + * obvious way. + */ - tvec_fail_v(tv, detail, ap); - tvec_mismatch(tv); +void tvec_check(struct tvec_state *tv, const char *detail, ...) +{ + va_list ap; + va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap); +} -#undef f_mismatch +void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap) +{ + if (tvec_checkregs(tv)) + { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } } -void tvec_runtest(struct tvec_state *tv) +/* --- @open_test@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Note that we are now collecting data for a new test. The + * current line number is recorded in @test_lno@. The + * @TVSF_OPEN@ flag is set, and @TVSF_XFAIL@ is reset. + * + * If a test is already open, then do nothing. + */ + +static void open_test(struct tvec_state *tv) { - tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p); - tvec_check(tv, 0); + if (!(tv->f&TVSF_OPEN)) { + tv->test_lno = tv->lno; + tv->f |= TVSF_OPEN; tv->f &= ~TVSF_XFAIL; + } } +/* --- @begin_test@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Note that we're about to try running a state. This is called + * before the test environment's @before@ function. Mark the + * test as active, clear the outcome, and inform the output + * driver. + */ + static void begin_test(struct tvec_state *tv) { - tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.'; + tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->output->ops->btest(tv->output); } +/* --- @tvec_endtest@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End an ad-hoc test case, The statistics are updated and the + * outcome is reported to the output formatter. + */ + void tvec_endtest(struct tvec_state *tv) { unsigned out; - if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN; - else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT; + if (!(tv->f&TVSF_ACTIVE)) /* nothing to do */; + else if (tv->f&TVSF_XFAIL) set_outcome(tv, TVOUT_XFAIL); + else set_outcome(tv, TVOUT_WIN); + out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT; assert(out < TVOUT_LIMIT); tv->curr[out]++; tv->output->ops->etest(tv->output, out); tv->f &= ~TVSF_OPEN; } -static void check(struct tvec_state *tv) +/* --- @tvec_xfail@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Mark the current test as an `expected failure'. That is, the + * behaviour -- if everything works as expected -- is known to + * be incorrect, perhaps as a result of a longstanding bug, so + * calling it a `success' would be inappropriate. A failure, as + * reported by @tvec_fail@, means that the behaviour is not as + * expected -- either the long-standing bug has been fixed, or a + * new bug has been introduced -- so investigation is required. + * + * An expected failure doesn't cause the test group or the + * session as a whole to fail, but it isn't counted as a win + * either. + */ + +void tvec_xfail(struct tvec_state *tv) + { tv->f |= TVSF_XFAIL; } + +/* --- @check@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct groupstate *g@ = private test group state + * + * Returns: --- + * + * Use: Run the current test. + * + * This function is called once the data for a test has been + * collected. It's responsible for checking that all of the + * necessary registers have been assigned values. It marks the + * 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. + */ + +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; if (!(tv->f&TVSF_OPEN)) return; - for (rd = tv->test->regs; rd->name; rd++) { + 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)) + else if (!(rd->f&TVRF_OPT)) { tvec_error(tv, "required register `%s' not set in test `%s'", - rd->name, tv->test->name); + rd->name, t->name); + goto end; + } } - if (!(tv->f&TVSF_SKIP)) - { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); } + if (!(tv->f&TVSF_SKIP)) { + begin_test(tv); + if (env && env->before) env->before(tv, g->ctx); + if (!(tv->f&TVSF_ACTIVE)) + /* setup forced a skip */; + else if (env && env->run) + env->run(tv, t->fn, g->ctx); + else { + t->fn(tv->in, tv->out, g->ctx); + tvec_check(tv, 0); + } + tvec_endtest(tv); + } + if (env && env->after) env->after(tv, g->ctx); - tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv); +end: + tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv); } -static void begin_test_group(struct tvec_state *tv) +/* --- @begin_test_group@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct groupstate *g@ = private test group state + * + * Returns: --- + * + * Use: Begins a test group. Expects @tv->test@ to have been set + * already. Calls the output driver, initializes the registers, + * clears the @tv->curr@ counters, allocates the environment + * context and calls the environment @setup@ function. + */ + +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; unsigned i; tv->output->ops->bgroup(tv->output); - tv->f &= ~TVSF_SKIP; - init_registers(tv); + tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE); + tvec_initregs(tv); for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0; - if (tv->test->preflight) tv->test->preflight(tv); + if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz); + if (env && env->setup) env->setup(tv, env, 0, g->ctx); } -void tvec_reportgroup(struct tvec_state *tv) +/* --- @report_group@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Reports the result of the test group to the output driver. + * + * If all of the tests have been skipped then report this as a + * group skip. Otherwise, determine and report the group + * outcome. + */ + +static void report_group(struct tvec_state *tv) { unsigned i, out, nrun; @@ -485,130 +830,462 @@ void tvec_reportgroup(struct tvec_state *tv) else { if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE; else out = TVOUT_WIN; - tv->grps[out]++; tv->output->ops->egroup(tv->output, out); + tv->grps[out]++; tv->output->ops->egroup(tv->output); } } -static void end_test_group(struct tvec_state *tv) +/* --- @end_test_group@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct groupstate *g@ = private test group state + * + * Returns: --- + * + * Use: Handles the end of a test group. Called at the end of the + * input file or when a new test group header is found. + * + * If a test is open, call @check@ to see whether it worked. If + * the test group is not being skipped, report the group + * result. Call the test environment @teardown@ function. Free + * the environment context and release the registers. + * + * If there's no test group active, then nothing happens. + */ + +static void end_test_group(struct tvec_state *tv, struct groupstate *g) { - if (!tv->test) return; - if (tv->f&TVSF_OPEN) check(tv); - if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); - release_registers(tv); tv->test = 0; + const struct tvec_test *t = tv->test; + const struct tvec_env *env; + + if (!t) return; + if (tv->f&TVSF_OPEN) check(tv, g); + if (!(tv->f&TVSF_SKIP)) report_group(tv); + env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx); + tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0; } -void tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) +/* --- @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. + */ + +enum { WIN, XFAIL, NOUT }; +static const struct tvec_uassoc outcome_assoc[] = { + { "success", WIN }, + { "win", WIN }, + { "expected-failure", XFAIL }, + { "xfail", XFAIL }, + TVEC_ENDENUM +}; +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 } }; + +int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) { dstr d = DSTR_INIT; const struct tvec_test *test; + const struct tvec_env *env; const struct tvec_regdef *rd; struct tvec_reg *r; - int ch; + struct groupstate g = GROUPSTATE_INIT; + union tvec_regval rv; + int ch, ret, rc = 0; + /* Set the initial location. */ tv->infile = infile; tv->lno = 1; tv->fp = fp; for (;;) { + + /* Get the next character and dispatch. Note that we're always at the + * start of a line here. + */ ch = getc(tv->fp); switch (ch) { case EOF: + /* End of the file. Exit the loop. */ + goto end; case '[': + /* A test group header. */ + + /* End the current group, if there is one. */ + end_test_group(tv, &g); + + /* Read the group name. There may be leading and trailing + * whitespace. + */ tvec_skipspc(tv); DRESET(&d); tvec_readword(tv, &d, "];", "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++) if (STRCMP(d.buf, ==, test->name)) goto found_test; + + /* There wasn't one. Report the error. Muffle errors about the + * contents of this section because they won't be interesting. + */ tvec_error(tv, "unknown test group `%s'", d.buf); + tv->f |= TVSF_MUFFLE; goto flush_line; + found_test: - tvec_skipspc(tv); - ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'"); + /* Eat trailing whitespace and comments. */ tvec_flushtoeol(tv, 0); - end_test_group(tv); - tv->test = test; - begin_test_group(tv); + + /* Set up the new test group. */ + tv->test = test; begin_test_group(tv, &g); break; case '\n': + /* A newline, so this was a completely empty line. Advance the line + * counter, and run the current test. + */ + tv->lno++; - if (tv->f&TVSF_OPEN) check(tv); + if (tv->f&TVSF_OPEN) check(tv, &g); + break; + + case ';': + /* A semicolon. Skip the comment. */ + + tvec_flushtoeol(tv, TVFF_ALLOWANY); break; default: + /* Something else. */ + if (isspace(ch)) { - tvec_skipspc(tv); - ch = getc(tv->fp); - if (ch == EOF) - goto end; - else if (ch == ';') - { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; } - else - { tvec_flushtoeol(tv, 0); check(tv); } - } else if (ch == ';') - { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; } - else { + /* Whitespace. Skip and see what we find. */ + + tvec_skipspc(tv); ch = getc(tv->fp); + + /* If the file ends, then we're done. If we find a comment then we + * skip it. If there's some non-whitespace, then report an error. + * Otherwise the line was effectively blank, so run the test. + */ + if (ch == EOF) goto end; + else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY); + else if (tvec_flushtoeol(tv, 0)) rc = -1; + else check(tv, &g); + } 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); tvec_readword(tv, &d, "=:;", "register name"); + 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 `:'"); + 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. + */ + if (!tv->test) { + if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test"); + 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. + */ + open_test(tv); + if (d.buf[0] == '@') { - if (STRCMP(d.buf, ==, "@status")) { - if (!(tv->f&TVSF_OPEN)) - { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; } - ch = getc(tv->fp); - if (ch == EOF || ch == '\n' || ch == ';') - tvec_syntax(tv, ch, "status character"); - if (ch == '\\') { - ch = getc(tv->fp); - if (ch == EOF || ch == '\n') - tvec_syntax(tv, ch, "escaped status character"); + /* 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; } - tv->expst = ch; - tvec_flushtoeol(tv, 0); - } else - tvec_error(tv, "unknown special register `%s'", d.buf); + } + + /* 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); + + /* 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; + } } else { - if (!tv->test) tvec_error(tv, "no current test"); + /* A standard register. */ + + /* Find the definition. */ for (rd = tv->test->regs; rd->name; rd++) if (STRCMP(rd->name, ==, d.buf)) goto found_reg; tvec_error(tv, "unknown register `%s' for test `%s'", d.buf, tv->test->name); + goto flush_line; + found_reg: - if (!(tv->f&TVSF_OPEN)) - { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; } - tvec_skipspc(tv); + /* Complain if the register is already set. */ r = TVEC_REG(tv, in, rd->i); if (r->f&TVRF_LIVE) - tvec_error(tv, "register `%s' already set", rd->name); - rd->ty->parse(&r->v, rd, tv); r->f |= TVRF_LIVE; + { tvec_dupreg(tv, rd->name); goto flush_line; } + + /* Parse a value and mark the register as live. */ + if (rd->ty->parse(&r->v, rd, tv)) goto flush_line; + r->f |= TVRF_LIVE; } } break; } + continue; + + flush_line: + /* 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; } + + /* We reached the end. If that was actually an I/O error then report it. + */ + if (ferror(tv->fp)) + { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; } + end: - end_test_group(tv); + /* Process the final test, if there was one, and wrap up the final + * group. + */ + end_test_group(tv, &g); + + /* Clean up. */ tv->infile = 0; tv->fp = 0; dstr_destroy(&d); + return (rc); +} + +/*----- Session lifecycle -------------------------------------------------*/ + +/* --- @tvec_begin@ --- * + * + * Arguments: @struct tvec_state *tv_out@ = state structure to fill in + * @const struct tvec_config *config@ = test configuration + * @struct tvec_output *o@ = output driver + * + * Returns: --- + * + * Use: Initialize a state structure ready to do some testing. + */ + +void tvec_begin(struct tvec_state *tv_out, + const struct tvec_config *config, + struct tvec_output *o) +{ + unsigned i; + + 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++) { + TVEC_REG(tv_out, in, i)->f = 0; + if (i < tv_out->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->infile = 0; tv_out->lno = 0; tv_out->fp = 0; + tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out); +} + +/* --- @tvec_end@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: A proposed exit code. + * + * Use: Conclude testing and suggests an exit code to be returned to + * the calling program. (The exit code comes from the output + * driver's @esession@ method.) + */ + +int tvec_end(struct tvec_state *tv) +{ + int rc = tv->output->ops->esession(tv->output); + + if (tv->test) tvec_releaseregs(tv); + tv->output->ops->destroy(tv->output); + xfree(tv->in); xfree(tv->out); + return (rc); +} + +/*----- Serialization and deserialization ---------------------------------*/ + +/* --- @tvec_serialize@ --- * + * + * Arguments: @const struct tvec_reg *rv@ = vector of registers + * @buf *b@ = buffer to write on + * @const struct tvec_regdef *regs@ = vector of register + * descriptions, terminated by an entry with a null + * @name@ slot + * @unsigned nr@ = number of entries in the @rv@ vector + * @size_t regsz@ = true size of each element of @rv@ + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Serialize a collection of register values. + * + * The serialized output is written to the buffer @b@. Failure + * can be caused by running out of buffer space, or a failing + * type handler. + */ + +int tvec_serialize(const struct tvec_reg *rv, buf *b, + const struct tvec_regdef *regs, + unsigned nr, size_t regsz) +{ + unsigned char *bitmap; + size_t i, bitoff, nbits, bitsz; + const struct tvec_regdef *rd; + const struct tvec_reg *r; + + bitoff = BLEN(b); + for (rd = regs, nbits = 0; rd->name; rd++, nbits++); + bitsz = (nbits + 7)/8; + + bitmap = buf_get(b, bitsz); if (!bitmap) return (-1); + memset(bitmap, 0, bitsz); + for (rd = regs, i = 0; rd->name; rd++, i++) { + if (rd->i >= nr) continue; + r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; + bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8; + if (rd->ty->tobuf(b, &r->v, rd)) return (-1); + } + return (0); +} + +/* --- @tvec_deserialize@ --- * + * + * Arguments: @struct tvec_reg *rv@ = vector of registers + * @buf *b@ = buffer to write on + * @const struct tvec_regdef *regs@ = vector of register + * descriptions, terminated by an entry with a null + * @name@ slot + * @unsigned nr@ = number of entries in the @rv@ vector + * @size_t regsz@ = true size of each element of @rv@ + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Deserialize a collection of register values. + * + * The size of the register vector @nr@ and the register + * definitions @regs@ must match those used when producing the + * serialization. For each serialized register value, + * deserialize and store the value into the appropriate register + * slot, and set the @TVRF_LIVE@ flag on the register. See + * @tvec_serialize@ for a description of the format. + * + * Failure results only from a failing register type handler. + */ + +int tvec_deserialize(struct tvec_reg *rv, buf *b, + const struct tvec_regdef *regs, + unsigned nr, size_t regsz) +{ + const unsigned char *bitmap; + size_t i, nbits, bitsz; + const struct tvec_regdef *rd; + struct tvec_reg *r; + + for (rd = regs, nbits = 0; rd->name; rd++, nbits++); + bitsz = (nbits + 7)/8; + + bitmap = buf_get(b, bitsz); if (!bitmap) return (-1); + for (rd = regs, i = 0; rd->name; rd++, i++) { + if (rd->i >= nr) continue; + if (!(bitmap[i/8]&(1 << i%8))) continue; + r = TVEC_GREG(rv, rd->i, regsz); + if (rd->ty->frombuf(b, &r->v, rd)) return (-1); + r->f |= TVRF_LIVE; + } + return (0); } /*----- Ad-hoc testing ----------------------------------------------------*/ static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } }; -static void fakerun(struct tvec_state *tv) - { assert(!"fake run function"); } static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p) { assert(!"fake test function"); } +/* --- @tvec_adhoc@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_test *t@ = space for a test definition + * + * Returns: --- + * + * Use: Begin ad-hoc testing, i.e., without reading a file of + * test-vector data. + * + * The structure at @t@ will be used to record information about + * the tests underway, which would normally come from a static + * test definition. The other functions in this section assume + * that @tvec_adhoc@ has been called. + */ + void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t) { - t->name = ""; t->regs = &no_regs; - t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0; + t->name = ""; t->regs = &no_regs; t->env = 0; t->fn = fakefn; tv->tests = t; } +/* --- @tvec_begingroup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *name@ = name for this test group + * @const char *file@, @unsigned @lno@ = calling file and line + * + * Returns: --- + * + * Use: Begin an ad-hoc test group with the given name. The @file@ + * and @lno@ can be anything, but it's usually best if they + * refer to the source code performing the test: the macro + * @TVEC_BEGINGROUP@ does this automatically. + */ + void tvec_begingroup(struct tvec_state *tv, const char *name, const char *file, unsigned lno) { @@ -616,19 +1293,42 @@ void tvec_begingroup(struct tvec_state *tv, const char *name, t->name = name; tv->test = t; tv->infile = file; tv->lno = tv->test_lno = lno; - begin_test_group(tv); + begin_test_group(tv, 0); } +/* --- @tvec_endgroup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End an ad-hoc test group. The statistics are updated and the + * outcome is reported to the output formatter. + */ + void tvec_endgroup(struct tvec_state *tv) { - if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); + if (!(tv->f&TVSF_SKIP)) report_group(tv); tv->test = 0; } +/* --- @tvec_begintest@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *file@, @unsigned @lno@ = calling file and line + * + * Returns: --- + * + * Use: Begin an ad-hoc test case. The @file@ and @lno@ can be + * anything, but it's usually best if they refer to the source + * code performing the test: the macro @TVEC_BEGINGROUP@ does + * this automatically. + */ + void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno) { tv->infile = file; tv->lno = tv->test_lno = lno; - begin_test(tv); tv->f |= TVSF_OPEN; + open_test(tv); begin_test(tv); } struct adhoc_claim { @@ -652,8 +1352,6 @@ static void adhoc_claim_setup(struct tvec_state *tv, ck->saved_file = tv->infile; if (file) tv->infile = file; ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno; t->regs = regs ? regs : &no_regs; - - tv->st = '.'; } static void adhoc_claim_teardown(struct tvec_state *tv, @@ -667,19 +1365,52 @@ static void adhoc_claim_teardown(struct tvec_state *tv, if (ck->f&ACF_FRESH) tvec_endtest(tv); } -int tvec_claim(struct tvec_state *tv, int ok, - const char *file, unsigned lno, const char *expr, ...) +/* --- @tvec_claim@, @tvec_claim_v@, @TVEC_CLAIM@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @int ok@ = a flag + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *msg@, @va_list *ap@ = message to report on + * failure + * + * Returns: The value @ok@. + * + * Use: Check that a claimed condition holds, as (part of) a test. + * If no test case is underway (i.e., if @TVSF_OPEN@ is reset in + * @tv->f@), then a new test case is begun and ended. The + * @file@ and @lno@ are passed to the output formatter to be + * reported in case of a failure. If @ok@ is nonzero, then + * nothing else happens; so, in particular, if @tvec_claim@ + * established a new test case, then the test case succeeds. If + * @ok@ is zero, then a failure is reported, quoting @msg@. + * + * The @TVEC_CLAIM@ macro is similar, only it (a) identifies the + * file and line number of the call site automatically, and (b) + * implicitly quotes the source text of the @ok@ condition in + * the failure message. + */ + +int tvec_claim_v(struct tvec_state *tv, int ok, + const char *file, unsigned lno, + const char *msg, va_list *ap) { struct adhoc_claim ck; - va_list ap; adhoc_claim_setup(tv, &ck, 0, file, lno); - if (!ok) - { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); } + if (!ok) tvec_fail_v(tv, msg, ap); adhoc_claim_teardown(tv, &ck); return (ok); } +int tvec_claim(struct tvec_state *tv, int ok, + const char *file, unsigned lno, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); tvec_claim_v(tv, ok, file, lno, msg, &ap); va_end(ap); + return (ok); +} + int tvec_claimeq(struct tvec_state *tv, const struct tvec_regty *ty, const union tvec_misc *arg, const char *file, unsigned lno, const char *expr) @@ -699,48 +1430,10 @@ int tvec_claimeq(struct tvec_state *tv, adhoc_claim_setup(tv, &ck, regs, file, lno); ok = ty->eq(&tv->in[0].v, &tv->out[0].v, ®s[0]); - if (!ok) { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv); } + if (!ok) + { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } adhoc_claim_teardown(tv, &ck); return (ok); } -/*----- Session lifecycle -------------------------------------------------*/ - -void tvec_begin(struct tvec_state *tv_out, - const struct tvec_info *info, - struct tvec_output *o) -{ - unsigned i; - - tv_out->f = 0; - - assert(info->nrout <= info->nreg); - tv_out->nrout = info->nrout; tv_out->nreg = info->nreg; - tv_out->regsz = info->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++) { - TVEC_REG(tv_out, in, i)->f = 0; - if (i < tv_out->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 = info->tests; tv_out->test = 0; - tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0; - o->tv = tv_out; tv_out->output = o; - - tv_out->output->ops->bsession(tv_out->output); -} - -int tvec_end(struct tvec_state *tv) -{ - int rc = tv->output->ops->esession(tv->output); - - tv->output->ops->destroy(tv->output); - xfree(tv->in); xfree(tv->out); - return (rc); -} - /*----- That's all, folks -------------------------------------------------*/