#include <string.h>
#include "alloc.h"
+#include "growbuf.h"
#include "tvec.h"
/*----- Output ------------------------------------------------------------*/
-int tvec_error(struct tvec_state *tv, const char *msg, ...)
+/* --- @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.
+ */
+
+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_error_v(tv, msg, &ap); va_end(ap);
- tv->f |= TVSF_ERROR; return (-1);
+ va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap);
}
-int tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap)
- { tv->output->ops->error(tv->output, msg, ap); return (-1); }
-void tvec_notice(struct tvec_state *tv, const char *msg, ...)
+void tvec_report_v(struct tvec_state *tv, unsigned level,
+ const char *msg, va_list *ap)
{
- va_list ap;
- va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap);
+ tv->output->ops->report(tv->output, level, msg, ap);
+ if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR;
}
-void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
- { tv->output->ops->notice(tv->output, msg, ap); }
-int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
+/* --- @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);
}
-int 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, "#<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", expect, found);
- return (-1);
+ 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);
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) ||
set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap);
}
+/* --- @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.
+ */
+
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); }
+/* --- @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.
+ */
+
void tvec_mismatch(struct tvec_state *tv, unsigned f)
{
const struct tvec_regdef *rd;
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);
}
}
-/*----- Main machinery ----------------------------------------------------*/
+/*----- Parsing -----------------------------------------------------------*/
-struct groupstate {
- void *ctx;
-};
-#define GROUPSTATE_INIT { 0 }
+/* --- @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_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
+{
+ va_list ap;
+
+ va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
+ return (-1);
+}
+
+int tvec_syntax_v(struct tvec_state *tv, int ch,
+ const char *expect, va_list *ap)
+{
+ dstr d = DSTR_INIT;
+ char found[8];
+
+ 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);
+}
+
+/* --- @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
+ *
+ * Returns: %$-1$%.
+ *
+ * Use: Reports an error that the register or pseudoregister has been
+ * assigned already in the current test.
+ */
+
+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
+ *
+ * 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)
{
}
}
+/* --- @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, rc = 0;
}
}
+/* --- @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 };
}
}
-int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
- const char *expect, ...)
+/* --- @tvec_readword@, @tvec_readword_v@ --- *
+ *
+ * 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
+ *
+ * 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.
+ *
+ * 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 **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);
}
-void tvec_resetoutputs(struct tvec_state *tv)
+/*----- Main machinery ----------------------------------------------------*/
+
+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, 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;
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;
+ 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->cfg.nrout)
+ { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
}
}
-static void init_registers(struct tvec_state *tv)
+void tvec_releaseregs(struct tvec_state *tv)
{
const struct tvec_regdef *rd;
struct tvec_reg *r;
for (rd = tv->test->regs; rd->name; rd++) {
- assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
- rd->ty->init(&r->v, rd); r->f = 0;
- if (rd->i < tv->nrout)
- { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
+ 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->cfg.nrout)
+ { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
}
}
-static void release_registers(struct tvec_state *tv)
+/* --- @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)
{
const struct tvec_regdef *rd;
struct tvec_reg *r;
for (rd = tv->test->regs; rd->name; rd++) {
- assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
- rd->ty->release(&r->v, rd); r->f = 0;
- if (rd->i < tv->nrout)
- { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
+ 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);
+ r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE;
}
}
+/* --- @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;
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);
}
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.
+ */
+
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);
}
+
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); }
}
+/* --- @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)
+{
+ 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->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;
}
+/* --- @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,
+ * 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;
+ 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);
- env = t->env;
- if (env && env->before && env->before(tv, g->ctx))
- tvec_skip(tv, "test setup failed");
+ if (f&f_err) tvec_skip(tv, "erroneous test data");
+ if (env && env->before) env->before(tv, g->ctx);
+ if (!(tv->f&TVSF_ACTIVE))
+ /* forced a skip */;
+ else if (env && env->run)
+ env->run(tv, t->fn, g->ctx);
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); }
+ t->fn(tv->in, tv->out, g->ctx);
+ tvec_check(tv, 0);
}
- if (env && env->after) env->after(tv, g->ctx);
tvec_endtest(tv);
}
-end:
- tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv);
+ 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@ --- *
+ *
+ * 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;
+ 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;
- init_registers(tv);
+ tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE);
+ tvec_initregs(tv);
for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
- if (env && env->setup && env->setup(tv, env, 0, g->ctx)) {
- tvec_skipgroup(tv, "setup failed");
- xfree(g->ctx); g->ctx = 0;
- }
+ if (env && env->setup) env->setup(tv, env, 0, g->ctx);
}
+/* --- @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;
}
}
+/* --- @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)
{
const struct tvec_test *t = tv->test;
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);
- release_registers(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
+ tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
+}
+
+/* --- @core_findvar@, @core_setvar@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *var@ = variable name to set
+ * @const union tvec_regval *rv@ = register value
+ * @void **ctx_out@ = where to put the @setvar@ context
+ * @void *ctx@ = context pointer
+ *
+ * Returns: @core_findvar@ returns a pointer to the variable definition,
+ * or null; @core_setvar@ returns zero on success or %$-1$% on
+ * error.
+ *
+ * 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 },
+ { "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_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)
{
dstr d = DSTR_INIT;
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;
- int ch, ret, rc = 0;
+ int ch, 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);
- tvec_skipspc(tv);
- DRESET(&d); tvec_readword(tv, &d, "];", "group name");
+
+ /* Read the group name. There may be leading and trailing
+ * whitespace.
+ */
+ DRESET(&d); tvec_readword(tv, &d, 0, "];", "group name");
tvec_skipspc(tv);
ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
- for (test = tv->tests; test->name; test++)
+
+ /* Find the matching test definition. */
+ for (test = tv->cfg.tests; test->name; test++)
if (STRCMP(d.buf, ==, test->name)) goto found_test;
- tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line;
+
+ /* 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_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv, &g);
+ /* Eat trailing whitespace and comments. */
+ tvec_flushtoeol(tv, 0);
+
+ /* 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, &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);
+ /* 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 if (ch == ';')
- tvec_flushtoeol(tv, TVFF_ALLOWANY);
- else {
+ } else {
+ /* Some non-whitespace thing. */
+
+ /* 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;
+ }
+
+ /* 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;
- tvec_skipspc(tv); ch = getc(tv->fp);
- if (ch != '=' && ch != ':')
- { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; }
- tvec_skipspc(tv);
- if (!tv->test)
- { tvec_error(tv, "no current test"); goto flush_line; }
+ 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;
- if (!env || !env->set) ret = 0;
- else ret = env->set(tv, d.buf, env, g.ctx);
- 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;
}
- if (!(tv->f&TVSF_OPEN))
- { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
+ tvec_unkregerr(tv, d.buf); goto flush_line;
+ found_var:
+ rd = &vd->def;
} else {
+ /* 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);
+ 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);
+
+ 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;
}
- if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
- r->f |= TVRF_LIVE;
+ 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;
continue;
flush_line:
- tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1;
+ /* 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);
+ 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.
+ */
if (ferror(tv->fp))
{ tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
+
end:
+ /* 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);
+ xfree(r_alloc);
return (rc);
+
+#undef rlive
}
/*----- 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)
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);
}
+/* --- @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)
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[rd->i/8] |= 1 << rd->i%8;
+ 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)
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[rd->i/8]&(1 << rd->i%8))) 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;
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 = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
- tv->tests = t;
+ tv->cfg.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)
{
- 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;
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)) 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 {
if (ck->f&ACF_FRESH) tvec_endtest(tv);
}
+/* --- @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)
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, TVMF_IN | TVMF_OUT); }
+ { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
adhoc_claim_teardown(tv, &ck);
return (ok);
}