X-Git-Url: https://git.distorted.org.uk/~mdw/mLib/blobdiff_plain/c5e0e40378b7e209521d2e9a52f055575a948313..6e683a79101025ee0d371f0b9bece811856edd8d:/test/tvec.h diff --git a/test/tvec.h b/test/tvec.h index 740b6be..f7512bd 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -32,6 +32,74 @@ extern "C" { #endif +/* Here's the overall flow for a testing session. + * + * @tvec_begin@ + * -> output @bsession@ + * @tvec_read@ + * -> output @bgroup@ + * -> env @setup@ + * one or more tests + * -> type @init@ (input and output) + * -> type @parse@ (input) + * -> output @btest@ + * -> env @before@ + * -> @tvec_skipgroup@ + * -> output @skipgroup@ + * -> env @run@ + * -> @tvec_skip@ + * -> output @skip@ + * -> test @fn@ + * -> @tvec_checkregs@ + * -> type @eq@ + * -> @tvec_fail@ + * -> output @fail@ + * -> @tvec_mismatch@ + * -> output @dumpreg@ + * -> type @dump@ + * -> output @etest@ + * -> env @after@ + * finally + * -> output @egroup@ + * -> env @teardown@ + * + * @tvec_adhoc@ + * @tvec_begingroup@ + * -> output @bgroup@ + * -> env @setup@ + * @tvec_begintest@ + * -> output @btest@ + * @tvec_skip@ + * -> output @skip@ + * @tvec_claimeq@ + * -> @tvec_fail@ + * -> output @fail@ + * -> @tvec_mismatch@ + * -> output @dumpreg@ + * -> type @dump@ + * @tvec_endtest@ + * -> output @etest@ + * or @tvec_skipgroup@ + * -> output @skipgroup@ + * @tvec_endgroup@ + * -> output @egroup@ + * + * @tvec_end@ + * -> output @esession@ + * -> output @destroy@ + * + * @tvec_benchrun@ + * -> type @dump@ (compact style) + * -> output @bbench@ + * -> subenv @run@ + * -> test @fn@ + * -> output @ebench@ + * -> @tvec_benchreport@ + * + * The output functions @error@ and @notice@ can be called at arbitrary + * times. + */ + /*----- Header files ------------------------------------------------------*/ #include @@ -55,6 +123,10 @@ # include "gprintf.h" #endif +#ifndef MLIB_LBUF_H +# include "lbuf.h" +#endif + #ifndef MLIB_MACROS_H # include "macros.h" #endif @@ -105,70 +177,75 @@ enum { */ union tvec_regval { - /* The actual register value. This is what the type handler sees. - * Additional members can be added by setting `TVEC_REGSLOTS' before - * including this file. - * - * A register value can be /initialized/, which simply means that its - * contents represent a valid value according to its type -- the - * register can be compared, dumped, serialized, parsed into, etc. - * You can't do anything safely to an uninitialized register value - * other than initialize it. - */ + /* The actual register value. This is what the type handler sees. + * Additional members can be added by setting `TVEC_REGSLOTS' before + * including this file. + * + * A register value can be /initialized/, which simply means that its + * contents represent a valid value according to its type -- the register + * can be compared, dumped, serialized, parsed into, etc. You can't do + * anything safely to an uninitialized register value other than initialize + * it. + */ long i; /* signed integer */ unsigned long u; /* unsigned integer */ void *p; /* pointer */ double f; /* floating point */ + struct { char *p; size_t sz; } text; /* text string */ struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */ - struct { char *p; size_t sz; } str; /* text string */ + struct { /* buffer */ + unsigned char *p; size_t sz; /* binary string */ + size_t a, m; /* residue and modulus */ + size_t off; /* offset into full buffer */ + } buf; #ifdef TVEC_REGSLOTS TVEC_REGSLOTS #endif }; struct tvec_reg { - /* A register. - * - * Note that all of the registers listed as being used by a - * particular test group are initialized at all times[1] while that - * test group is being processed. (The other register slots don't - * even have types associated with them, so there's nothing useful we - * could do with them.) - * - * The `TVRF_LIVE' flag indicates that the register was assigned a - * value by the test vector file: it's the right thing to use to - * check whether an optional register is actually present. Even - * `dead' registers are still initialized, though. - * - * [1] This isn't quite true. Between individual tests, the - * registers are released and reinitialized in order to reset - * them to known values ready for the next test. But you won't - * see them at this point. - */ + /* A register. + * + * Note that all of the registers listed as being used by a particular test + * group are initialized at all times[1] while that test group is being + * processed. (The other register slots don't even have types associated + * with them, so there's nothing useful we could do with them.) + * + * The `TVRF_LIVE' flag indicates that the register was assigned a value by + * the test vector file: it's the right thing to use to check whether an + * optional register is actually present. Even `dead' registers are still + * initialized, though. + * + * [1] This isn't quite true. Between individual tests, the registers are + * released and reinitialized in order to reset them to known values + * ready for the next test. But you won't see them at this point. + */ unsigned f; /* flags */ -#define TVRF_LIVE 1u /* used in current test */ +#define TVRF_SEEN 1u /* assignment seen in file */ +#define TVRF_LIVE 2u /* used in current test */ union tvec_regval v; /* register value */ }; struct tvec_regdef { - /* A register definition. Register definitions list the registers - * which are used by a particular test group (see `struct tvec_test' - * below). - * - * A vector of register definitions is terminated by a definition - * whose `name' slot is null. - */ + /* A register definition. Register definitions list the registers which + * are used by a particular test group (see `struct tvec_test' below). + * + * A vector of register definitions is terminated by a definition whose + * `name' slot is null. + */ const char *name; /* register name (for input files) */ - unsigned i; /* register index */ const struct tvec_regty *ty; /* register type descriptor */ + unsigned i; /* register index */ unsigned f; /* flags */ -#define TVRF_OPT 1u /* optional register */ -#define TVRF_ID 2u /* part of test identity */ +#define TVRF_UNSET 1u /* register may be marked unset */ +#define TVRF_OPT 2u /* register need not be assigned */ +#define TVRF_ID 4u /* part of test identity */ union tvec_misc arg; /* extra detail for the type */ }; +#define TVEC_ENDREGS { 0, 0, 0, 0, { 0 } } /* @TVEC_GREG(vec, i, regsz)@ * @@ -185,124 +262,73 @@ struct tvec_regdef { #define TVEC_GREG(vec, i, regsz) \ ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz))) -/*------ Serialization utilities ------------------------------------------*/ - -/* --- @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 `candidate register definitions' are those entries @r@ in - * the @regs@ vector whose index @r.i@ is strictly less than - * @nr@. The `selected register definitions' are those - * candidate register definitions @r@ for which the indicated - * register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The - * serialized output begins with a header bitmap: if there are - * %$n$% candidate register definitions then the header bitmap - * consists of %$\lceil n/8 \rceil$% bytes. Bits are ordered - * starting from the least significant bit of the first byte, - * end ending at the most significant bit of the final byte. - * The bit corresponding to a candidate register definition is - * set if and only if that register defintion is selected. The - * header bitmap is then followed by the serializations of the - * selected registers -- i.e., for each selected register - * definition @r@, the serialized value of register @rv[r.i]@ -- - * simply concatenated together, with no padding or alignment. - * - * The serialized output is written to the buffer @b@. Failure - * can be caused by running out of buffer space, or a failing - * type handler. - */ - -extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, - const struct tvec_regdef */*regs*/, - unsigned /*nr*/, size_t /*regsz*/); - -/* --- @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. - * - * On successful completion, store the address of the first byte - * after the serialized data in @*end_out@ if @end_out@ is not - * null. Failure results only from a failing register type - * handler. - */ +/*----- Register types ----------------------------------------------------*/ -extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, - const struct tvec_regdef */*regs*/, - unsigned /*nr*/, size_t /*regsz*/); +struct tvec_state; /* forward declaration */ -/*----- Test state --------------------------------------------------------*/ +struct tvec_regty { + /* A register type. */ -/* Possible test outcomes. */ -enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT }; + void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Initialize the value in @*rv@. This will be called before any other + * function acting on the value, including @release@. Following @init@, + * the register value must be valid to use for all other type entry + * points. + */ -struct tvec_state { - /* The primary state structure for the test vector machinery. */ + void (*release)(union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Release any resources associated with the value in @*rv@. The + * register value may be left in an invalid state. + */ - unsigned f; /* flags */ -#define TVSF_SKIP 1u /* skip this test group */ -#define TVSF_OPEN 2u /* test is open */ -#define TVSF_ACTIVE 4u /* test is active */ -#define TVSF_ERROR 8u /* an error occurred */ -#define TVSF_OUTMASK 0xf0 /* test outcome */ -#define TVSF_OUTSHIFT 4 - - /* Registers. Available to execution environments. */ - unsigned nrout, nreg; /* number of output/total registers */ - size_t regsz; /* size of register entry */ - struct tvec_reg *in, *out; /* register vectors */ + int (*eq)(const union tvec_regval */*rv0*/, + const union tvec_regval */*rv1*/, + const struct tvec_regdef */*rd*/); + /* Return nonzero if @*rv0@ and @*rv1@ are equal values. Asymmetric + * criteria are permitted: @tvec_checkregs@ calls @eq@ with the input + * register as @rv0@ and the output as @rv1@. + */ - /* Test groups state. Available to output formatters. */ - const struct tvec_test *tests, *test; /* all tests and current test */ + int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Serialize the value @*rv@, writing the result to @b@. Return zero on + * success, or %$-1$% on error. + */ - /* Test scoreboard. Available to output formatters. */ - unsigned curr[TVOUT_LIMIT], all[TVOUT_LIMIT], grps[TVOUT_LIMIT]; + int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Deserialize a value from @b@, storing it in @*rv@. Return zero on + * success, or %$-1$% on error. + */ - /* Output machinery. */ - struct tvec_output *output; /* output formatter */ + int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, + struct tvec_state */*tv*/); + /* Parse a value from @tv->fp@, storing it in @*rv@. Return zero on + * success, or %$-1$% on error, having reported one or more errors via + * @tvec_error@ or @tvec_syntax@. A successful return should leave the + * input position at the start of the next line; the caller will flush + * the remainder of the line itself. + */ - /* Input machinery. Available to type parsers. */ - const char *infile; unsigned lno, test_lno; /* input file name, line */ - FILE *fp; /* input file stream */ + void (*dump)(const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/, + unsigned /*style*/, + const struct gprintf_ops */*gops*/, void */*go*/); +#define TVSF_COMPACT 1u + /* Write a human-readable representation of the value @*rv@ using + * @gprintf@ on @gops@ and @go@. The @style@ is a collection of flags: + * if @TVSF_COMPACT@ is set, then output should be minimal, and must fit + * on a single line; otherwise, output may consist of multiple lines and + * may contain redundant information if that is likely to be useful to a + * human reader. + */ }; -/* @TVEC_REG(tv, vec, i)@ - * - * If @tv@ is a pointer to a @struct tvec_state@, @vec@ is either @in@ or - * @out@, and @i@ is an integer, then this evaluates to the address of the - * @i@th register in the selected vector. - */ -#define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz) - /*----- Test descriptions -------------------------------------------------*/ +struct tvec_env; + typedef void tvec_testfn(const struct tvec_reg */*in*/, struct tvec_reg */*out*/, void */*ctx*/); @@ -322,6 +348,70 @@ typedef void tvec_testfn(const struct tvec_reg */*in*/, * anything to do with the test function's context. */ +typedef int tvec_setvarfn(struct tvec_state */*tv*/, const char */*var*/, + const union tvec_regval */*rv*/, void */*ctx*/); + /* Called after a variable is read. Return zero on success or %$-1$% on + * error. This function is never called if the test group is skipped. + */ + +struct tvec_vardef { + size_t regsz; /* (minimum) register size */ + tvec_setvarfn *setvar; /* function to set variable */ + struct tvec_regdef def; /* register definition */ +}; + +typedef void tvec_envsetupfn(struct tvec_state */*tv*/, + const struct tvec_env */*env*/, + void */*pctx*/, void */*ctx*/); + /* Initialize the context; called at the start of a test group; @pctx@ is + * null for environments called by the core, but may be non-null for + * subordinate environments. If setup fails, the function should call + * @tvec_skipgroup@ with a suitable excuse. The @set@, @after@, and + * @teardown@ entry points will still be called, but @before@ and @run@ + * will not. + */ + +typedef const struct tvec_vardef *tvec_envfindvarfn + (struct tvec_state */*tv*/, const char */*name*/, + void **/*ctx_out*/, void */*ctx*/); + /* Called when the parser finds a %|@var|%' special variable. If a + * suitable variable was found, set @*ctx_out@ to a suitable context and + * return the variable definition; the context will be passed to the + * variable definition's @setvar@ function. If no suitable variable was + * found, then return null. + */ + +typedef void tvec_envbeforefn(struct tvec_state */*tv*/, void */*ctx*/); + /* Called prior to running a test. This is the right place to act on any + * `%|@var|%' settings. If preparation fails, the function should call + * @tvec_skip@ with a suitable excuse. This function is never called if + * the test group is skipped. It %%\emph{is}%% called if the test will be + * skipped due to erroneous test data. It should check the @TVSF_ACTIVE@ + * flag if necessary. + */ + +typedef void tvec_envrunfn(struct tvec_state */*tv*/, + tvec_testfn */*fn*/, void */*ctx*/); + /* Run the test. It should either call @tvec_skip@, or run @fn@ one or + * more times. In the latter case, it is responsible for checking the + * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will + * check the register values against the supplied test vector, while + * @tvec_check@ does pretty much everything necessary. This function is + * never called if the test group is skipped. + */ + +typedef void tvec_envafterfn(struct tvec_state */*tv*/, void */*ctx*/); + /* Called after running or skipping a test. Typical actions involve + * resetting whatever things were established by @set@. This function + * %%\emph{is}%% called if the test group is skipped or the test data is + * erroneous, so that the test environment can reset variables set by the + * @set@ entry point. It should check the @TVSF_SKIP@ flag if necessary. + */ + +typedef void tvec_envteardownfn(struct tvec_state */*tv*/, void */*ctx*/); + /* Tear down the environment: called at the end of a test group. */ + + struct tvec_env { /* A test environment sets things up for and arranges to run the test. * @@ -331,48 +421,14 @@ struct tvec_env { * is zero then @ctx@ is null. */ - size_t ctxsz; /* environment context size */ - - int (*setup)(struct tvec_state */*tv*/, const struct tvec_env */*env*/, - void */*pctx*/, void */*ctx*/); - /* Initialize the context; called at the start of a test group. Return - * zero on success, or @-1@ on failure. If setup fails, the context is - * freed, and the test group is skipped. - */ - - int (*set)(struct tvec_state */*tv*/, const char */*var*/, - const struct tvec_env */*env*/, void */*ctx*/); - /* Called when the parser finds a %|@var|%' setting to parse and store - * the value. If @setup@ failed, this is still called (so as to skip the - * value), but @ctx@ is null. - */ - - int (*before)(struct tvec_state */*tv*/, void */*ctx*/); - /* Called prior to running a test. This is the right place to act on any - * `%|@var|%' settings. Return zero on success or @-1@ on failure (which - * causes the test to be skipped). This function is never called if the - * test group is skipped. - */ - - void (*run)(struct tvec_state */*tv*/, tvec_testfn */*fn*/, void */*ctx*/); - /* Run the test. It should either call @tvec_skip@, or run @fn@ one or - * more times. In the latter case, it is responsible for checking the - * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will - * check the register values against the supplied test vector, while - * @tvec_check@ does pretty much everything necessary. This function is - * never called if the test group is skipped. - */ - - void (*after)(struct tvec_state */*tv*/, void */*ctx*/); - /* Called after running or skipping a test. Typical actions involve - * resetting whatever things were established by @set@. This function is - * never called if the test group is skipped. - */ + size_t ctxsz; /* environment context size */ - void (*teardown)(struct tvec_state */*tv*/, void */*ctx*/); - /* Tear down the environment: called at the end of a test group. If the - * setup failed, then this function is still called, with a null @ctx@. - */ + tvec_envsetupfn *setup; /* setup for group */ + tvec_envfindvarfn *findvar; /* find variable */ + tvec_envbeforefn *before; /* prepare for test */ + tvec_envrunfn *run; /* run test function */ + tvec_envafterfn *after; /* clean up after test */ + tvec_envteardownfn *teardown; /* tear down after group */ }; struct tvec_test { @@ -383,7 +439,79 @@ struct tvec_test { const struct tvec_env *env; /* environment to run test in */ tvec_testfn *fn; /* test function */ }; +#define TVEC_ENDTESTS { 0, 0, 0, 0 } + +/*----- Test state --------------------------------------------------------*/ + +enum { + /* Possible test outcomes. */ + + TVOUT_LOSE, /* test failed */ + TVOUT_SKIP, /* test skipped */ + TVOUT_WIN, /* test passed */ + TVOUT_XFAIL, /* test passed, but shouldn't have */ + TVOUT_LIMIT /* (number of possible outcomes) */ +}; + +struct tvec_config { + /* An overall test configuration. */ + + const struct tvec_test *tests; /* the tests to be performed */ + unsigned nrout, nreg; /* number of output/total regs */ + size_t regsz; /* size of a register */ +}; + +struct tvec_state { + /* The primary state structure for the test vector machinery. */ + + /* Flags. Read-only for all callers. */ + unsigned f; /* flags */ +#define TVSF_SKIP 0x0001u /* skip this test group */ +#define TVSF_OPEN 0x0002u /* test is open */ +#define TVSF_ACTIVE 0x0004u /* test is active */ +#define TVSF_ERROR 0x0008u /* an error occurred */ +#define TVSF_OUTMASK 0x00f0u /* test outcome (@TVOUT_...@) */ +#define TVSF_OUTSHIFT 4 /* shift applied to outcome */ +#define TVSF_XFAIL 0x0100u /* test expected to fail */ +#define TVSF_MUFFLE 0x0200u /* muffle errors */ + + /* Test configuration. Read-only for all callers. */ + struct tvec_config cfg; /* test configuration */ + + /* Registers. Available to execution environments, which may modify the + * contents of the active registers, as defined by the current test group, + * but not the vector pointers themselves or inactive registers. + */ + struct tvec_reg *in, *out; /* register vectors */ + + /* Test group state. Read-only for all callers. */ + const struct tvec_test *test; /* current test */ + + /* Test scoreboard. Available to output formatters. */ + unsigned curr[TVOUT_LIMIT], all[TVOUT_LIMIT], grps[TVOUT_LIMIT]; + + /* Output machinery. */ + struct tvec_output *output; /* output formatter */ + + /* Input machinery. Available to type parsers. */ + const char *infile; unsigned lno, test_lno; /* input file name, line */ + FILE *fp; /* input file stream */ +}; + +/* @TVEC_REG(tv, vec, i)@ + * + * If @tv@ is a pointer to a @struct tvec_state@, @vec@ is either @in@ or + * @out@, and @i@ is an integer, then this evaluates to the address of the + * @i@th register in the selected vector. + */ +#define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->cfg.regsz) +/*----- Output formatting -------------------------------------------------*/ + +struct tvec_output { + /* An output formatter. */ + const struct tvec_outops *ops; /* pointer to operations */ +}; enum { /* Register output dispositions. */ @@ -392,79 +520,314 @@ enum { TVRD_OUTPUT, /* output-only (input is dead) */ TVRD_MATCH, /* matching (equal) registers */ TVRD_FOUND, /* mismatching output register */ - TVRD_EXPECT /* mismatching input register */ + TVRD_EXPECT, /* mismatching input register */ + TVRD_LIMIT /* (number of dispositions) */ }; -/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * +#define TVEC_LEVELS(_) \ + _(NOTE, "notice", 4) \ + _(ERR, "ERROR", 8) +enum { +#define TVEC_DEFLEVEL(tag, name, val) TVLEV_##tag = val, + TVEC_LEVELS(TVEC_DEFLEVEL) +#undef TVEC_DEFLEVEL + TVLEV_LIMIT +}; + +/* Benchmarking details. */ +enum { + TVBU_OP, /* counting operations of some kind */ + TVBU_BYTE, /* counting bytes (@rbuf >= 0@) */ + TVBU_LIMIT /* (number of units) */ +}; +struct bench_timing; /* include for the real definition */ + +struct tvec_outops { + /* Output operations. */ + + void (*bsession)(struct tvec_output */*o*/, struct tvec_state */*tv*/); + /* Begin a test session. The output driver will probably want to + * save @tv@, because this isn't provided to any other methods. + */ + + int (*esession)(struct tvec_output */*o*/); + /* End a session, and return the suggested exit code. */ + + void (*bgroup)(struct tvec_output */*o*/); + /* Begin a test group. The test group description is @tv->test@. */ + + void (*skipgroup)(struct tvec_output */*o*/, + const char */*excuse*/, va_list */*ap*/); + /* The group is being skipped; @excuse@ may be null or a format + * string explaining why. The @egroup@ method will not be called + * separately. + */ + + void (*egroup)(struct tvec_output */*o*/); + /* End a test group. At least one test was attempted or @skipgroup@ + * would have been called instead. If @tv->curr[TVOUT_LOSE]@ is nonzero + * then the test group as a whole failed; otherwise it passed. + */ + + void (*btest)(struct tvec_output */*o*/); + /* Begin a test case. */ + + void (*skip)(struct tvec_output */*o*/, + const char */*excuse*/, va_list */*ap*/); + /* The test case is being skipped; @excuse@ may be null or a format + * string explaining why. The @etest@ function will still be called (so + * this works differently from @skipgroup@ and @egroup@). A test case + * can be skipped at most once, and, if skipped, it cannot fail. + */ + + void (*fail)(struct tvec_output */*o*/, + const char */*detail*/, va_list */*ap*/); + /* The test case failed. + * + * The output driver should preferably report the filename (@infile@) and + * line number (@test_lno@, not @lno@) for the failing test. + * + * The @detail@ may be null or a format string describing detail about + * how the failing test was run which can't be determined from the + * registers; a @detail@ is usually provided when (and only when) the + * test environment potentially invokes the test function more than once. + * + * A single test case can fail multiple times! + */ + + void (*dumpreg)(struct tvec_output */*o*/, + unsigned /*disp*/, const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Dump a register. The `disposition' @disp@ explains what condition the + * register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes. The + * register value is at @rv@, and its definition, including its type, at + * @rd@. Note that this function may be called on virtual registers + * which aren't in either of the register vectors or mentioned by the + * test description. It may also be called with @rv@ null, indicating + * that the register is not live. + */ + + void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); + /* The test case concluded with the given @outcome@ (one of the + * @TVOUT_...@ codes. + */ + + void (*bbench)(struct tvec_output */*o*/, + const char */*ident*/, unsigned /*unit*/); + /* Begin a benchmark. The @ident@ is a formatted string identifying the + * benchmark based on the values of the input registered marked + * @TVRF_ID@; the output driver is free to use this or come up with its + * own way to identify the test, e.g., by inspecting the register values + * for itself. The @unit@ is one of the @TVBU_...@ constants explaining + * what sort of thing is being measured. + */ + + void (*ebench)(struct tvec_output */*o*/, + const char */*ident*/, unsigned /*unit*/, + const struct bench_timing */*tm*/); + /* End a benchmark. The @ident@ and @unit@ arguments are as for + * @bbench@. If @tm@ is zero then the measurement failed; otherwise + * @tm->n@ counts total number of things (operations or bytes, as + * indicated by @unit@) processed in the indicated time. + */ + + void (*report)(struct tvec_output */*o*/, unsigned /*level*/, + const char */*msg*/, va_list */*ap*/); + /* Report a message. The driver should ideally report the filename + * (@infile@) and line number (@lno@) prompting the error. + */ + + void (*destroy)(struct tvec_output */*o*/); + /* Release any resources acquired by the driver. */ +}; + +/*----- Session lifecycle -------------------------------------------------*/ + +/* --- @tvec_begin@ --- * * - * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list ap@ = reason why group skipped + * 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: Skip the current group. This should only be called from a - * test environment @setup@ function; a similar effect occurs if - * the @setup@ function fails. + * Use: Initialize a state structure ready to do some testing. */ -extern void PRINTF_LIKE(2, 3) - tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...); -extern void tvec_skipgroup_v(struct tvec_state */*tv*/, - const char */*excuse*/, va_list */*ap*/); +extern void tvec_begin(struct tvec_state */*tv_out*/, + const struct tvec_config */*config*/, + struct tvec_output */*o*/); -/* --- @tvec_skip@, @tvec_skip_v@ --- * +/* --- @tvec_end@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list ap@ = reason why test skipped * - * Returns: --- + * Returns: A proposed exit code. * - * 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. + * 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.) */ -extern void PRINTF_LIKE(2, 3) - tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); -extern void tvec_skip_v(struct tvec_state */*tv*/, - const char */*excuse*/, va_list */*ap*/); +extern int tvec_end(struct tvec_state */*tv*/); -/* --- @tvec_resetoutputs@ --- * +/* --- @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. + */ + +extern int tvec_read(struct tvec_state */*tv*/, + const char */*infile*/, FILE */*fp*/); + +/*----- Command-line interface --------------------------------------------*/ + +extern const struct tvec_config tvec_adhocconfig; + /* A special @struct tvec_config@ to use for programs which perform ad-hoc + * testing. + */ + +/* --- @tvec_parseargs@ --- * + * + * Arguments: @int argc@ = number of command-line arguments + * @char *argv[]@ = vector of argument strings + * @struct tvec_state *tv_out@ = test vector state to initialize + * @int *argpos_out@ = where to leave unread argument index + * @const struct tvec_config *cofig@ = test vector configuration * * 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. + * Use: Parse arguments and set up the test vector state @*tv_out@. + * If errors occur, print messages to standard error and exit + * with status 2. */ -extern void tvec_resetoutputs(struct tvec_state */*tv*/); +extern void tvec_parseargs(int /*argc*/, char */*argv*/[], + struct tvec_state */*tv_out*/, + int */*argpos_out*/, + const struct tvec_config */*config*/); -/* --- @tvec_checkregs@ --- * +/* --- @tvec_readstdin@, @tvec_readfile@, @tvec_readarg@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const char *file@ = pathname of file to read + * @const char *arg@ = argument to interpret + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Read test vector data from stdin or a named file. The + * @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%', + * and from the named file otherwise. + */ + +extern int tvec_readstdin(struct tvec_state */*tv*/); +extern int tvec_readfile(struct tvec_state */*tv*/, const char */*file*/); +extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/); + +/* --- @tvec_readdflt@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const char *dflt@ = defsault filename or null + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Reads from the default test vector data. If @file@ is null, + * then read from standard input, unless that's a terminal; if + * @file@ is not null, then read the named file, looking in the + * directory named by the `%|srcdir|%' environment variable if + * that's set, or otherwise in the current directory. + */ + +extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/); + +/* --- @tvec_readargs@ --- * + * + * Arguments: @int argc@ = number of command-line arguments + * @char *argv[]@ = vector of argument strings + * @struct tvec_state *tv@ = test vector state + * @int *argpos_inout@ = current argument position (updated) + * @const char *dflt@ = default filename or null + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Reads from the sources indicated by the command-line + * arguments, in order, interpreting each as for @tvec_readarg@; + * if no arguments are given then read from @dflt@ as for + * @tvec_readdflt@. + */ + +extern int tvec_readargs(int /*argc*/, char */*argv*/[], + struct tvec_state */*tv*/, + int */*argpos_inout*/, const char */*dflt*/); + +/* --- @tvec_main@ --- * + * + * Arguments: @int argc@ = number of command-line arguments + * @char *argv[]@ = vector of argument strings + * @const struct tvec_config *cofig@ = test vector configuration + * @const char *dflt@ = default filename or null + * + * Returns: Exit code. + * + * Use: All-in-one test vector front-end. Parse options from the + * command-line as for @tvec_parseargs@, and then process the + * remaining positional arguments as for @tvec_readargs@. The + * function constructs and disposes of a test vector state. + */ + +extern int tvec_main(int /*argc*/, char */*argv*/[], + const struct tvec_config */*config*/, + const char */*dflt*/); + +/*----- 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: Zero on success, @-1@ on mismatch. + * Returns: --- * - * 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. + * 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. + */ + +extern PRINTF_LIKE(2, 3) + void tvec_skipgroup(struct tvec_state */*tv*/, + const char */*excuse*/, ...); +extern void tvec_skipgroup_v(struct tvec_state */*tv*/, + const char */*excuse*/, va_list */*ap*/); + +/* --- @tvec_skip@, @tvec_skip_v@ --- * * - * 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. + * 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. */ -extern int tvec_checkregs(struct tvec_state */*tv*/); +extern PRINTF_LIKE(2, 3) + void tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); +extern void tvec_skip_v(struct tvec_state */*tv*/, + const char */*excuse*/, va_list */*ap*/); /* --- @tvec_fail@, @tvec_fail_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *detail@, @va_list ap@ = description of test + * @const char *detail@, @va_list *ap@ = description of test * * Returns: --- * @@ -478,8 +841,8 @@ extern int tvec_checkregs(struct tvec_state */*tv*/); * safely be left null if the test function is run only once. */ -extern void PRINTF_LIKE(2, 3) - tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); extern void tvec_fail_v(struct tvec_state */*tv*/, const char */*detail*/, va_list */*ap*/); @@ -487,7 +850,7 @@ extern void tvec_fail_v(struct tvec_state */*tv*/, * * Arguments: @struct tvec_state *tv@ = test-vector state * @unsigned disp@ = the register disposition (@TVRD_...@) - * @const union tvec_regval *tv@ = register value + * @const union tvec_regval *tv@ = register value, or null * @const struct tvec_regdef *rd@ = register definition * * Returns: --- @@ -505,6 +868,55 @@ extern void tvec_dumpreg(struct tvec_state */*tv*/, unsigned /*disp*/, const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); +/* --- @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. + */ + +extern void tvec_initregs(struct tvec_state */*tv*/); +extern void tvec_releaseregs(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. + */ + +extern void tvec_resetoutputs(struct tvec_state */*tv*/); + +/* --- @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. + */ + +extern int tvec_checkregs(struct tvec_state */*tv*/); + /* --- @tvec_mismatch@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state @@ -526,7 +938,7 @@ extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/); /* --- @tvec_check@, @tvec_check_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *detail@, @va_list ap@ = description of test + * @const char *detail@, @va_list *ap@ = description of test * * Returns: --- * @@ -536,170 +948,808 @@ extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/); * obvious way. */ -extern void PRINTF_LIKE(2, 3) - tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); extern void tvec_check_v(struct tvec_state */*tv*/, const char */*detail*/, va_list */*ap*/); -/*----- Session lifecycle -------------------------------------------------*/ - -struct tvec_config { - /* An overall test configuration. */ - - const struct tvec_test *tests; /* the tests to be performed */ - unsigned nrout, nreg; /* number of output/total regs */ - size_t regsz; /* size of a register */ -}; +/*----- Ad-hoc testing ----------------------------------------------------*/ -/* --- @tvec_begin@ --- * +/* --- @tvec_adhoc@ --- * * - * Arguments: @struct tvec_state *tv_out@ = state structure to fill in - * @const struct tvec_config *config@ = test configuration - * @struct tvec_output *o@ = output driver + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_test *t@ = space for a test definition * * Returns: --- * - * Use: Initialize a state structure ready to do some testing. + * 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. */ -extern void tvec_begin(struct tvec_state */*tv_out*/, - const struct tvec_config */*config*/, - struct tvec_output */*o*/); +extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); -/* --- @tvec_end@ --- * +/* --- @tvec_begingroup@, @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: A proposed exit code. + * Returns: --- * - * 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.) + * 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. */ -extern int tvec_end(struct tvec_state */*tv*/); +extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/, + const char */*file*/, unsigned /*lno*/); +#define TVEC_BEGINGROUP(tv, name) \ + do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0) -/* --- @tvec_read@ --- * +/* --- @tvec_endgroup@ --- * * * 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. + * Returns: --- * - * Use: Read test vector data from @fp@ and exercise test functions. - * THe return code doesn't indicate test failures: it's only - * concerned with whether there were problems with the input - * file or with actually running the tests. + * Use: End an ad-hoc test group. The statistics are updated and the + * outcome is reported to the output formatter. */ -extern int tvec_read(struct tvec_state */*tv*/, - const char */*infile*/, FILE */*fp*/); - -/*----- Input utilities ---------------------------------------------------*/ +extern void tvec_endgroup(struct tvec_state */*tv*/); -/* These are provided by the core for the benefit of type @parse@ methods, - * and test-environment @set@ functions, which get to read from the test - * input file. The latter are usually best implemented by calling on the - * former. +/* --- @TVEC_TESTGROUP@, @TVEC_TESTGROUP_TAG@ --- * * - * The two main rules are as follows. + * Arguments: @tag@ = label-disambiguation tag + * @const struct tvec_state *tv = test-vector state + * @const char *name@ = test-group name * - * * Leave the file position at the beginning of the line following - * whatever it was that you read. + * Returns: --- * - * * When you read and consume a newline (which you do at least once, by - * the previous rule), then increment @tv->lno@ to keep track of the - * current line number. + * Use: Control-structure macro: @TVEC_TESTGROUP(tv, name) stmt@ + * establishes a test group with the given @name@ (attributing + * it to the source file and lie number), executes @stmt@, and + * ends the test group. If @stmt@ invokes @break@ then the test + * group is skipped. @TVEC_TESTGROUP_TAG@ is the same, with an + * additional @tag@ argument for use in higher-level macros. */ -/* --- @tvec_skipspc@ --- * +#define TVEC_TESTGROUP_TAG(tag, tv, name) \ + MC_WRAP(tag##__around, \ + { TVEC_BEGINGROUP(tv, name); }, \ + { tvec_endgroup(tv); }, \ + { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \ + tvec_endgroup(tv); }) +#define TVEC_TESTGROUP(tv, name) TVEC_TESTGROUP_TAG(grp, tv, name) + +/* --- @tvec_begintest@, @TVEC_BEGINTEST@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *file@, @unsigned @lno@ = calling file and line * * 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. + * 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. */ -extern void tvec_skipspc(struct tvec_state */*tv*/); +extern void tvec_begintest(struct tvec_state */*tv*/, + const char */*file*/, unsigned /*lno*/); +#define TVEC_BEGINTEST(tv) \ + do tvec_begintest(tv, __FILE__, __LINE__); while (0) -/* --- @tvec_syntax@, @tvec_syntax_v@ --- * +/* --- @tvec_endtest@ --- * * * 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@ + * Returns: --- * - * 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). + * Use: End an ad-hoc test case, The statistics are updated and the + * outcome is reported to the output formatter. */ -extern int PRINTF_LIKE(3, 4) - tvec_syntax(struct tvec_state */*tv*/, int /*ch*/, - const char */*expect*/, ...); -extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/, - const char */*expect*/, va_list */*ap*/); +extern void tvec_endtest(struct tvec_state */*tv*/); -/* --- @tvec_flushtoeol@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * @unsigned f@ = flags (@TVFF_...@) +/* --- @TVEC_TEST@, @TVEC_TEST_TAG@ --- * * - * Returns: Zero on success, @-1@ on error. + * Arguments: @tag@ = label-disambiguation tag + * @struct tvec_test *t@ = space for a test definition * - * Use: Advance to the start of the next line, consuming the - * preceding newline. + * Returns: --- * - * 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. + * Use: Control-structure macro: @TVEC_TEST(tv) stmt@ begins a test + * case, executes @stmt@, and ends the test case. If @stmt@ + * invokes @break@ then the test case is skipped. + * @TVEC_TEST_TAG@ is the same, with an additional @tag@ argumet + * for use in higher-level macros. */ -#define TVFF_ALLOWANY 1u -extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); +#define TVEC_TEST_TAG(tag, tv) \ + MC_WRAP(tag##__around, \ + { TVEC_BEGINTEST(tv); }, \ + { tvec_endtest(tv); }, \ + { if ((tv)->f&TVSF_ACTIVE) tvec_skip((tv), 0); \ + tvec_endtest(tv); }) +#define TVEC_TEST(tv) TVEC_TEST_TAG(test, tv) -/* --- @tvec_nexttoken@ --- * +/* --- @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. + */ + +extern PRINTF_LIKE(5, 6) + int tvec_claim(struct tvec_state */*tv*/, int /*ok*/, + const char */*file*/, unsigned /*lno*/, + const char */*msg*/, ...); +extern int tvec_claim_v(struct tvec_state */*tv*/, int /*ok*/, + const char */*file*/, unsigned /*lno*/, + const char */*msg*/, va_list */*ap*/); +#define TVEC_CLAIM(tv, cond) \ + (tvec_claim(tv, !!(cond), __FILE__, __LINE__, "%s untrue", #cond)) + +/* --- @tvec_claimeq@ --- * * - * 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 + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const struct tvec_regty *ty@ = register type + * @const union tvec_misc *arg@ = register type argument + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if the input and output values of register 0 are + * equal, zero if they differ. + * + * Use: Check that the input and output values of register 0 are + * equal (according to the register type @ty@). As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped. + * + * This function is not expected to be called directly, but + * through type-specific wrapper functions or macros such as + * @TVEC_CLAIMEQ_INT@. + */ + +extern 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*/); + +/*----- Benchmarking ------------------------------------------------------*/ + +struct tvec_benchenv { + struct tvec_env _env; /* benchmarking is an environment */ + struct bench_state **bst; /* benchmark state anchor or null */ + unsigned long niter; /* iterations done per unit */ + int riter, rbuf; /* iterations and buffer registers */ + const struct tvec_env *env; /* subordinate environment */ +}; + +struct tvec_benchctx { + const struct tvec_benchenv *be; /* environment configuration */ + struct bench_state *bst; /* benchmark state */ + double dflt_target; /* default time in seconds */ + unsigned f; /* flags */ +#define TVBF_SETTRG 1u /* set `@target' */ +#define TVBF_SETMASK (TVBF_SETTRG)) /* mask of @TVBF_SET...@ */ + void *subctx; /* subsidiary environment context */ +}; + +extern struct bench_state *tvec_benchstate; + +/* --- Environment implementation --- * + * + * The following special variables are supported. + * + * * %|@target|% is the (approximate) number of seconds to run the + * benchmark. + * + * Unrecognized variables are passed to the subordinate environment, if there + * is one. Other events are passed through to the subsidiary environment. + */ + +extern tvec_envsetupfn tvec_benchsetup; +extern tvec_envfindvarfn tvec_benchfindvar; +extern tvec_envbeforefn tvec_benchbefore; +extern tvec_envrunfn tvec_benchrun; +extern tvec_envafterfn tvec_benchafter; +extern tvec_envteardownfn tvec_benchteardown; + +#define TVEC_BENCHENV \ + { sizeof(struct tvec_benchctx), \ + tvec_benchsetup, \ + tvec_benchfindvar, \ + tvec_benchbefore, \ + tvec_benchrun, \ + tvec_benchafter, \ + tvec_benchteardown } +#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate + +/* --- @tvec_benchreport@ --- * + * + * Arguments: @const struct gprintf_ops *gops@ = print operations + * @void *go@ = print destination + * @unsigned unit@ = the unit being measured (~TVBU_...@) + * @const struct bench_timing *tm@ = the benchmark result + * + * Returns: --- + * + * Use: Formats a report about the benchmark performance. This + * function is intended to be called on by an output @ebench@ + * function. + */ + +extern void tvec_benchreport + (const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*unit*/, const struct bench_timing */*tm*/); + +/*----- Remote execution --------------------------------------------------*/ + +struct tvec_remoteenv; + +struct tvec_remotecomms { + int infd, outfd; /* input and output descriptors */ + dbuf bout; /* output buffer */ + unsigned char *bin; /* input buffer */ + size_t binoff, binlen, binsz; /* input offset, length, and size */ + size_t t; /* temporary offset */ + unsigned f; /* flags */ +#define TVRF_BROKEN 0x0001u /* communications have failed */ +}; +#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, 0, 0, 0, 0, 0, 0 } + +struct tvec_remotectx { + struct tvec_state *tv; /* test vector state */ + struct tvec_remotecomms rc; /* communication state */ + const struct tvec_remoteenv *re; /* environment configuration */ + void *subctx; /* subenvironment context */ + struct tvec_vardef vd; /* temporary variable definition */ + unsigned ver; /* protocol version */ + pid_t kid; /* child process id */ + int errfd; /* child stderr descriptor */ + lbuf errbuf; /* child stderr line buffer */ + dstr prgwant, progress; /* progress: wanted/reported */ + unsigned exwant, exit; /* exit status wanted/reported */ +#define TVRF_RCNMASK 0x0300u /* reconnection behaviour: */ +#define TVRCN_DEMAND 0x0000u /* connect on demand */ +#define TVRCN_SKIP 0x0100u /* skip unless connected */ +#define TVRCN_FORCE 0x0200u /* force reconnection */ +#define TVRF_MUFFLE 0x0400u /* muffle child stderr */ +#define TVRF_SETEXIT 0x0800u /* set `@exit' */ +#define TVRF_SETPRG 0x1000u /* set `@progress' */ +#define TVRF_SETRCN 0x2000u /* set `@reconnect' */ +#define TVRF_SETMASK (TVRF_SETEXIT | TVRF_SETPRG | TVRF_SETRCN) + /* mask of @TVTF_SET...@ */ +}; + +typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/, + int */*outfd_out*/, int */*errfd_out*/, + struct tvec_state */*tv*/, + const struct tvec_remoteenv */*env*/); + /* A connection function. On entry, @tv@ holds the test-vector state, and + * @env@ is the test group's remote environment structure, which will + * typically really be some subclass of @struct tvec_remoteenv@ containing + * additional parameters for establishing the child process. + * + * On successful completion, the function stores input and output + * descriptors (which need not be distinct) in @*infd_out@ and + * @*outfd_out@, and returns zero; if it creates a child process, it should + * additionally store the child's process-id in @*kid_out@ and store in + * @*errfd_out@ a descriptor from which the child's error output can be + * read. On error, the function should report an appropriate message via + * @tvec_error@ and return %$-1$%. + */ + +struct tvec_remoteenv_slots { + tvec_connectfn *connect; /* connection function */ + const struct tvec_env *env; /* subsidiary environment */ + unsigned dflt_reconn; /* default reconnection */ +}; + +struct tvec_remoteenv { + struct tvec_env _env; + struct tvec_remoteenv_slots r; +}; + +struct tvec_remotefork_slots { + const struct tvec_test *tests; /* child tests (or null) */ +}; + +struct tvec_remotefork { + struct tvec_env _env; + struct tvec_remoteenv_slots r; + struct tvec_remotefork_slots f; +}; + +struct tvec_remoteexec_slots { + const char *const *args; /* command line to execute */ +}; + +struct tvec_remoteexec { + struct tvec_env _env; + struct tvec_remoteenv_slots r; + struct tvec_remoteexec_slots x; +}; + +union tvec_remoteenv_subclass_kludge { + struct tvec_env _env; + struct tvec_remoteenv renv; + struct tvec_remotefork fork; + struct tvec_remoteexec exec; +}; + +/* Exit status. + * + * We don't use the conventional encoding returned by the @wait@(2) family of + * system calls because it's too hard for our flags type to decode. Instead, + * we use our own encoding. + * + * The exit code or signal number ends up in the `value' field in the low 12 + * bits; bit 12 is set if the value field holds a signal, and it if holds an + * exit code. Bits 13--15 hold a code which describes the status of a child + * process or connection. + */ +#define TVXF_VALMASK 0x0fffu /* value (exit code or signal) */ +#define TVXF_SIG 0x1000u /* value is signal, not exit code */ +#define TVXF_CAUSEMASK 0xe000u /* mask for cause bits */ +#define TVXST_RUN 0x0000u /* still running */ +#define TVXST_EXIT 0x2000u /* child exited */ +#define TVXST_KILL 0x4000u /* child killed by signal */ +#define TVXST_CONT 0x6000u /* child continued (?) */ +#define TVXST_STOP 0x8000u /* child stopped (?) */ +#define TVXST_DISCONN 0xa000u /* disconnected */ +#define TVXST_UNK 0xc000u /* unknown */ +#define TVXST_ERR 0xe000u /* local error prevented diagnosis */ + +/* Remote environment. */ +extern tvec_envsetupfn tvec_remotesetup; +extern tvec_envfindvarfn tvec_remotefindvar; +extern tvec_envbeforefn tvec_remotebefore; +extern tvec_envrunfn tvec_remoterun; +extern tvec_envafterfn tvec_remoteafter; +extern tvec_envteardownfn tvec_remoteteardown; +#define TVEC_REMOTEENV \ + { sizeof(struct tvec_remotectx), \ + tvec_remotesetup, \ + tvec_remotefindvar, \ + tvec_remotebefore, \ + tvec_remoterun, \ + tvec_remoteafter, \ + tvec_remoteteardown } + +/* --- @tvec_setprogress@, @tvec_setprogress_v@ --- * + * + * Arguments: @const char *status@ = progress status token format + * @va_list ap@ = argument tail + * + * Returns: --- + * + * Use: Reports the progress of a test execution to the client. + * + * The framework makes use of tokens beginning with %|%|%: + * + * * %|%IDLE|%: during the top-level server code; + * + * * %|%SETUP|%: during the enclosing environment's @before@ + * function; + * + * * %|%RUN|%: during the environment's @run@ function, or the + * test function; and + * + * * %|%DONE|%: during the enclosing environment's @after@ + * function. + * + * The intent is that a test can use the progress token to check + * that a function which is expected to crash does so at the + * correct point, so it's expected that more complex test + * functions and/or environments will set their own progress + * tokens to reflect what's going on. + */ + +extern PRINTF_LIKE(1, 2) int tvec_setprogress(const char */*status*/, ...); +extern int tvec_setprogress_v(const char */*status*/, va_list */*ap*/); + +/* --- @tvec_remoteserver@ --- * + * + * Arguments: @int infd@, @int outfd@ = input and output file descriptors + * @const struct tvec_config *config@ = test configuration + * + * Returns: Suggested exit code. + * + * Use: Run a test server, reading packets from @infd@ and writing + * responses and notifications to @outfd@, and invoking tests as + * described by @config@. + * + * This function is not particularly general purpose. It + * expects to `take over' the process, and makes use of private + * global variables. + */ + +extern int tvec_remoteserver(int /*infd*/, int /*outfd*/, + const struct tvec_config */*config*/); + +extern tvec_connectfn tvec_fork, tvec_exec; + +#define TVEC_REMOTEFORK(subenv, tests) \ + TVEC_REMOTEENV, { tvec_fork, subenv }, { tests } + +#define TVEC_REMOTEEXEC(subenv, args) \ + TVEC_REMOTEENV, { tvec_exec, subenv }, { args } + +/*----- Timeouts ----------------------------------------------------------*/ + +struct tvec_timeoutenv { + struct tvec_env _env; + int timer; /* the timer (@ITIMER_...@) */ + double t; /* time to wait (in seconds) */ + const struct tvec_env *env; /* subsidiary environment */ +}; + +struct tvec_timeoutctx { + const struct tvec_timeoutenv *te; /* saved environment description */ + int timer; /* timer code (as overridden) */ + double t; /* time to wait (as overridden) */ + unsigned f; /* flags */ +#define TVTF_SETTMO 1u /* set `@timeout' */ +#define TVTF_SETTMR 2u /* set `@timer' */ +#define TVTF_SETMASK (TVTF_SETTMO | TVTF_SETTMR) + /* mask of @TVTF_SET...@ */ + void *subctx; +}; + +extern tvec_envsetupfn tvec_timeoutsetup; +extern tvec_envfindvarfn tvec_timeoutfindvar; +extern tvec_envbeforefn tvec_timeoutbefore; +extern tvec_envrunfn tvec_timeoutrun; +extern tvec_envafterfn tvec_timeoutafter; +extern tvec_envteardownfn tvec_timeoutteardown; +#define TVEC_TIMEOUTENV \ + { sizeof(struct tvec_timeoutctx), \ + tvec_timeoutsetup, \ + tvec_timeoutfindvar, \ + tvec_timeoutbefore, \ + tvec_timeoutrun, \ + tvec_timeoutafter, \ + tvec_timeoutteardown } +#define TVEC_TIMEOUTINIT(timer, t) TVEC_TIMEOUTENV, timer, t + +/*----- Output functions --------------------------------------------------*/ + +/* --- @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. + */ + +extern const char *tvec_strlevel(unsigned /*level*/); + +/* --- @tvec_report@, @tvec_report_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @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. + */ + +extern PRINTF_LIKE(3, 4) + void tvec_report(struct tvec_state */*tv*/, unsigned /*level*/, + const char */*msg*/, ...); +extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/, + const char */*msg*/, va_list */*ap*/); + +/* --- @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. + */ + +extern PRINTF_LIKE(2, 3) + int tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); + +/* --- @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. + */ + +extern int tvec_unkregerr(struct tvec_state */*tv*/, const char */*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. + */ + +extern int tvec_dupregerr(struct tvec_state */*tv*/, const char */*name*/); + +/* --- @tvec_humanoutput@ --- * + * + * Arguments: @FILE *fp@ = output file to write on + * + * Returns: An output formatter. + * + * Use: Return an output formatter which writes on @fp@ with the + * expectation that a human will be watching and interpreting + * the output. If @fp@ denotes a terminal, the display shows a + * `scoreboard' indicating the outcome of each test case + * attempted, and may in addition use colour and other + * highlighting. + */ + +extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); + +/* --- @tvec_tapoutput@ --- * + * + * Arguments: @FILE *fp@ = output file to write on + * + * Returns: An output formatter. + * + * Use: Return an output formatter which writes on @fp@ in `TAP' + * (`Test Anything Protocol') format. + * + * TAP comes from the Perl community, but has spread rather + * further. This driver currently produces TAP version 14, but + * pretends to be version 13. The driver produces a TAP `test + * point' -- i.e., a result reported as `ok' or `not ok' -- for + * each input test group. Failure reports and register dumps + * are produced as diagnostic messages before the final group + * result. (TAP permits structuerd YAML data after the + * test-point result, which could be used to report details, but + * (a) postponing the details until after the report is + * inconvenient, and (b) there is no standardization for the + * YAML anyway, so in practice it's no more useful than the + * unstructured diagnostics. + */ + +extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); + +/* --- @tvec_dfltoutput@ --- * + * + * Arguments: @FILE *fp@ = output file to write on + * + * Returns: An output formatter. + * + * Use: Selects and instantiates an output formatter suitable for + * writing on @fp@. The policy is subject to change, but + * currently the `human' output format is selected if @fp@ is + * interactive (i.e., if @isatty(fileno(fp))@ is true), and + * otherwise the `tap' format is used. + */ + +extern struct tvec_output *tvec_dfltout(FILE */*fp*/); + +/*------ Serialization utilities ------------------------------------------*/ + +/* Serialization format. + * + * The `candidate register definitions' are those entries @r@ in the @regs@ + * vector whose index @r.i@ is strictly less than @nr@. The `selected + * register definitions' are those candidate register definitions @r@ for + * which the indicated register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The + * serialized output begins with a header bitmap: if there are %$n$% + * candidate register definitions then the header bitmap consists of %$\lceil + * n/8 \rceil$% bytes. Bits are ordered starting from the least significant + * bit of the first byte, end ending at the most significant bit of the final + * byte. The bit corresponding to a candidate register definition is set if + * and only if that register defintion is selected. The header bitmap is + * then followed by the serializations of the selected registers -- i.e., for + * each selected register definition @r@, the serialized value of register + * @rv[r.i]@ -- simply concatenated together, with no padding or alignment. + */ + +/* --- @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. + */ + +extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/); + +/* --- @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. + */ + +extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/); + +/*----- Input utilities ---------------------------------------------------*/ + +/* These are provided by the core for the benefit of type @parse@ methods, + * and test-environment @set@ functions, which get to read from the test + * input file. The latter are usually best implemented by calling on the + * former. + * + * The two main rules are as follows. + * + * * Leave the file position at the beginning of the line following + * whatever it was that you read. + * + * * When you read and consume a newline (which you do at least once, by + * the previous rule), then increment @tv->lno@ to keep track of the + * current line number. + */ + +/* --- @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). + */ + +extern PRINTF_LIKE(3, 4) + int tvec_syntax(struct tvec_state */*tv*/, int /*ch*/, + const char */*expect*/, ...); +extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/, + const char */*expect*/, va_list */*ap*/); + +/* --- @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. + */ + +extern 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. + */ + +#define TVFF_ALLOWANY 1u +extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); + +/* --- @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, + * 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. */ extern int tvec_nexttoken(struct tvec_state */*tv*/); -/* --- @tvec_readword@ --- * +/* --- @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. + * 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@; @@ -708,8 +1758,8 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/); * 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@. + * 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 @@ -718,209 +1768,530 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/); * word constituents, a null terminator is written to @d@, and * it is safe to treat the string in @d@ as being null- * terminated. + * + * If @p_inout@ is not null, then @*p_inout@ must be a pointer + * into @d->buf@, which will be adjusted so that it will + * continue to point at the same position even if the buffer is + * reallocated. As a subtle tweak, if @*p_inout@ initially + * points at the end of the buffer, then it will be adjusted to + * point at the beginning of the next word, rather than at the + * additional intervening space. */ -extern int PRINTF_LIKE(4, 5) - tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, - const char */*delims*/, const char */*expect*/, ...); +extern PRINTF_LIKE(5, 6) + int tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, + const char **/*p_inout*/, const char */*delims*/, + const char */*expect*/, ...); extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/, - const char */*delims*/, const char */*expect*/, - va_list */*ap*/); - -/*----- Output formatting -------------------------------------------------*/ - -struct tvec_output { - const struct tvec_outops *ops; - struct tvec_state *tv; -}; - -enum { TVBU_OP, TVBU_BYTE }; - -struct bench_timing; - -struct tvec_outops { - void (*error)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); - void (*notice)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); - - void (*bsession)(struct tvec_output */*o*/); - int (*esession)(struct tvec_output */*o*/); - - void (*bgroup)(struct tvec_output */*o*/); - void (*egroup)(struct tvec_output */*o*/, unsigned /*outcome*/); - void (*skipgroup)(struct tvec_output */*o*/, - const char */*excuse*/, va_list */*ap*/); + const char **/*p_inout*/, const char */*delims*/, + const char */*expect*/, va_list */*ap*/); - void (*btest)(struct tvec_output */*o*/); - void (*skip)(struct tvec_output */*o*/, - const char */*excuse*/, va_list */*ap*/); - void (*fail)(struct tvec_output */*o*/, - const char */*detail*/, va_list */*ap*/); - void (*dumpreg)(struct tvec_output */*o*/, - unsigned /*disp*/, const union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); - - void (*bbench)(struct tvec_output */*o*/, - const char */*ident*/, unsigned /*unit*/); - void (*ebench)(struct tvec_output */*o*/, - const char */*ident*/, unsigned /*unit*/, - const struct bench_timing */*tm*/); - - void (*destroy)(struct tvec_output */*o*/); -}; +/*----- Integer types: signed and unsigned --------------------------------*/ -extern int PRINTF_LIKE(2, 3) - tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); -extern int tvec_error_v(struct tvec_state */*tv*/, - const char */*msg*/, va_list */*ap*/); - -extern void PRINTF_LIKE(2, 3) - tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); -extern void tvec_notice_v(struct tvec_state */*tv*/, - const char */*msg*/, va_list */*ap*/); - -extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); -extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); -extern struct tvec_output *tvec_dfltout(FILE */*fp*/); - -/*----- Register types ----------------------------------------------------*/ - -struct tvec_regty { - void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); - void (*release)(union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - int (*eq)(const union tvec_regval */*rv0*/, - const union tvec_regval */*rv1*/, - const struct tvec_regdef */*rd*/); - int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, - struct tvec_state */*tv*/); - void (*dump)(const union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/, - unsigned /*style*/, - const struct gprintf_ops */*gops*/, void */*go*/); -#define TVSF_COMPACT 1u -}; +/* Integers may be input in decimal, hex, binary, or octal, following + * approximately usual conventions. + * + * * Signed integers may be preceded with a `+' or `-' sign. + * + * * Decimal integers are just a sequence of decimal digits `0' ... `9'. + * + * * Octal integers are a sequence of digits `0' ... `7', preceded by `0o' + * or `0O'. + * + * * Hexadecimal integers are a sequence of digits `0' ... `9', `a' + * ... `f', or `A' ... `F', preceded by `0x' or `0X'. + * + * * Radix-B integers are a sequence of digits `0' ... `9', `a' ... `f', or + * `A' ... `F', each with value less than B, preceded by `Br' or `BR', + * where 0 < B < 36 is expressed in decimal without any leading `0' or + * internal underscores `_'. + * + * * A digit sequence may contain internal underscore `_' separators, but + * not before or after all of the digits; and two consecutive `_' + * characters are not permitted. + */ extern const struct tvec_regty tvty_int, tvty_uint; + +/* The @arg.p@ slot may be null or a pointer to @struct tvec_irange@ or + * @struct tvec_urange@ as appropriate. The bounds are inclusive; use, e.g., + * @LONG_MAX@ explicitly if one or the other bound is logically inapplicable. + */ struct tvec_irange { long min, max; }; struct tvec_urange { unsigned long min, max; }; +/* Bounds corresponding to common integer types. */ extern const struct tvec_irange tvrange_schar, tvrange_short, tvrange_int, tvrange_long, tvrange_sbyte, tvrange_i16, tvrange_i32; extern const struct tvec_urange tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size, tvrange_byte, tvrange_u16, tvrange_u32; -extern const struct tvec_frange - tvrange_float, tvrange_double; + +/* --- @tvec_claimeq_int@, @TVEC_CLAIMEQ_INT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @long i0, i1@ = two signed integers + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @i0@ and @i1@ are equal, otherwise zero. + * + * Use: Check that values of @i0@ and @i1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @i0@ is printed as the output + * value and @i1@ is printed as the input reference. + * + * The @TVEC_CLAIM_INT@ 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 @i0@ and @i1@ + * arguments in the failure message. + */ extern int tvec_claimeq_int(struct tvec_state */*tv*/, long /*i0*/, long /*i1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); +#define TVEC_CLAIMEQ_INT(tv, i0, i1) \ + (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1)) + +/* --- @tvec_claimeq_uint@, @TVEC_CLAIMEQ_UINT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned long u0, u1@ = two unsigned integers + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @u0@ and @u1@ are equal, otherwise zero. + * + * Use: Check that values of @u0@ and @u1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @u0@ is printed as the output + * value and @u1@ is printed as the input reference. + * + * The @TVEC_CLAIM_UINT@ 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 @u0@ and @u1@ arguments in the failure message. + */ + extern int tvec_claimeq_uint(struct tvec_state */*tv*/, unsigned long /*u0*/, unsigned long /*u1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); -#define TVEC_CLAIMEQ_INT(tv, i0, i1) \ - (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1)) #define TVEC_CLAIMEQ_UINT(tv, u0, u1) \ (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1)) +/*----- Size type ---------------------------------------------------------*/ + +/* A size is an unsigned integer followed by an optional unit specifier + * consisting of an SI unit prefix and (optionally) the letter `B'. + */ + +extern const struct tvec_regty tvty_size; + +/* --- @tvec_claimeq_size@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned long sz0, sz1@ = two sizes + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @sz0@ and @sz1@ are equal, otherwise zero. + * + * Use: Check that values of @u0@ and @u1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @u0@ is printed as the output + * value and @u1@ is printed as the input reference. + * + * The @TVEC_CLAIM_SIZE@ 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 @u0@ and @u1@ arguments in the failure message. + */ + +int tvec_claimeq_size(struct tvec_state *tv, + unsigned long sz0, unsigned long sz1, + const char *file, unsigned lno, const char *expr); +#define TVEC_CLAIMEQ_UINT(tv, u0, u1) \ + (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1)) + +/*----- Floating-point type -----------------------------------------------*/ + +/* Floating-point values are either NaN (%|#nan|%, if supported by the + * platform); positive or negative infinity (%|#inf|%, %|+#inf|%, or + * %|#+inf|% (preferring the last), and %|-#inf|% or %|#-inf|% (preferring + * the latter), if supported by the platform); or a number in strtod(3) + * syntax. + * + * The comparison rules for floating-point numbers are complex: see + * @tvec_claimeqish_float@ for details. + */ + extern const struct tvec_regty tvty_float; + struct tvec_floatinfo { - unsigned f; -#define TVFF_NOMIN 1u -#define TVFF_NOMAX 2u -#define TVFF_NANOK 4u -#define TVFF_EXACT 0u -#define TVFF_ABSDELTA 0x10 -#define TVFF_RELDELTA 0x20 -#define TVFF_EQMASK 0xf0 - double min, max; - double delta; + /* Details about acceptable floating-point values. */ + + unsigned f; /* flags (@TVFF_...@ bits) */ +#define TVFF_NOMIN 1u /* ignore @min@ (allow -inf) */ +#define TVFF_NOMAX 2u /* ignore @max@ (allow +inf) */ +#define TVFF_NANOK 4u /* permit NaN */ +#define TVFF_EQMASK 0xf0 /* how to compare */ +#define TVFF_EXACT 0x00 /* must equal exactly */ +#define TVFF_ABSDELTA 0x10 /* must be within @delta@ */ +#define TVFF_RELDELTA 0x20 /* diff < @delta@ fraction */ + double min, max; /* smallest/largest value allowed */ + double delta; /* maximum tolerable difference */ }; +extern const struct tvec_floatinfo tvflt_finite, tvflt_nonneg; + +/* --- @tvec_claimeqish_float@, @TVEC_CLAIMEQISH_FLOAT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @double f0, f1@ = two floating-point numbers + * @unsigned f@ = flags (@TVFF_...@) + * @double delta@ = maximum tolerable difference + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @f0@ and @f1@ are sufficiently close, otherwise + * zero. + * + * Use: Check that values of @f0@ and @f1@ are sufficiently close. + * As for @tvec_claim@ above, a test case is automatically begun + * and ended if none is already underway. If the values are + * too far apart, then @tvec_fail@ is called, quoting @expr@, + * and the mismatched values are dumped: @f0@ is printed as the + * output value and @f1@ is printed as the input reference. + * + * The details for the comparison are as follows. + * + * * A NaN value matches any other NaN, and nothing else. + * + * * An infinity matches another infinity of the same sign, + * and nothing else. + * + * * If @f&TVFF_EQMASK@ is @TVFF_EXACT@, then any + * representable number matches only itself: in particular, + * positive and negative zero are considered distinct. + * (This allows tests to check that they land on the correct + * side of branch cuts, for example.) + * + * * If @f&TVFF_EQMASK@ is @TVFF_ABSDELTA@, then %$x$% matches + * %$y$% when %$|x - y| < \delta$%. + * + * * If @f&TVFF_EQMASK@ is @TVFF_RELDELTA@, then %$x$% matches + * %$y$% when %$|1 - x/y| < \delta$%. (Note that this + * criterion is asymmetric. Write %$x \approx_\delta y$% + * if and only if %$|1 - x/y < \delta$%. Then, for example, + * if %$y/(1 + \delta) < x < y (1 - \delta)$%, then + * %$x \approx_\delta y$%, but %$y \not\approx_\delta x$%.) + * + * The @TVEC_CLAIM_FLOAT@ 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 @f0@ and @f1@ arguments (and @delta@) in the failure + * message. + */ + extern int tvec_claimeqish_float(struct tvec_state */*tv*/, double /*f0*/, double /*f1*/, unsigned /*f*/, double /*delta*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); +#define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta) \ + (tvec_claimeqish_float(tv, f0, f1, f, delta, __FILE__, __LINE__, \ + #f0 " /= " #f1 " (+/- " #delta ")")) + +/* --- @tvec_claimeq_float@, @TVEC_CLAIMEQ_FLOAT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @double f0, f1@ = two floating-point numbers + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @f0@ and @u1@ are identical, otherwise zero. + * + * Use: Check that values of @f0@ and @f1@ are identical. The + * function is exactly equivalent to @tvec_claimeqish_float@ + * with @f == TVFF_EXACT@; the macro is similarly like + * @TVEC_CLAIMEQISH_FLOAT@ with @f == TVFF_EXACT@, except that + * it doesn't bother to quote a delta. + */ + extern int tvec_claimeq_float(struct tvec_state */*tv*/, double /*f0*/, double /*f1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); -#define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta) \ - (tvec_claimeqish_float(tv, f0, f1, f, delta, , __FILE__, __LINE__, \ - #f0 " /= " #f1 " (+/- " #delta ")")) #define TVEC_CLAIMEQ_FLOAT(tv, f0, f1) \ (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1)) -extern const struct tvec_regty tvty_enum; +/*----- Durations ---------------------------------------------------------*/ + +/* A duration measures a time interval in seconds. The input format consists + * of a nonnegative decimal floating-point number in @strtod@ format followed + * by an optional unit specification. + */ + +extern const struct tvec_regty tvty_duration; + +/* --- @tvec_parsedurunit@ --- * + * + * Arguments: @double *scale_out@ = where to leave the scale + * @const char **p_inout@ = input unit string, updated + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: If @*p_inout@ begins with a unit string followed by the end + * of the string or some non-alphanumeric character, then store + * the corresponding scale factor in @*scale_out@, advance + * @*p_inout@ past the unit string, and return zero. Otherwise, + * return %$-1$%. + */ + +extern int tvec_parsedurunit(double */*scale_out*/, + const char **/*p_inout*/); + +/* --- @tvec_claimeqish_duration@, @TVEC_CLAIMEQISH_DURATION@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @double to, t1@ = two durations + * @unsigned f@ = flags (@TVFF_...@) + * @double delta@ = maximum tolerable difference + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @t0@ and @t1@ are sufficiently close, otherwise + * zero. + * + * Use: Check that values of @t0@ and @t1@ are sufficiently close. + * This is essentially the same as @tvec_claimeqish_float@, only + * it dumps the values as durations on a mismatch. + * + * The @TVEC_CLAIM_FLOAT@ 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 @t0@ and @t1@ arguments (and @delta@) in the failure + * message. + */ + +extern int tvec_claimeqish_duration(struct tvec_state */*tv*/, + double /*t0*/, double /*t1*/, + unsigned /*f*/, double /*delta*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQISH_DURATION(tv, t0, t1, f, delta) \ + (tvec_claimeqish_duration(tv, t0, t1, f, delta, __FILE__, __LINE__, \ + #t0 " /= " #t1 " (+/- " #delta ")")) + +/* --- @tvec_claimeq_duration@, @TVEC_CLAIMEQ_DURATION@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @double t0, t1@ = two durations + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @t0@ and @t1@ are identical, otherwise zero. + * + * Use: Check that values of @t0@ and @t1@ are identical. The + * function is exactly equivalent to @tvec_claimeqish_duration@ + * with @f == TVFF_EXACT@; the macro is similarly like + * @TVEC_CLAIMEQISH_DURATION@ with @f == TVFF_EXACT@, except + * that it doesn't bother to quote a delta. + */ +int tvec_claimeq_duration(struct tvec_state */*tv*/, + double /*t0*/, double /*t1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_DURATION(tv, t0, t1) \ + (tvec_claimeq_float(tv, t0, t1, __FILE__, __LINE__, #t0 " /= " #t1)) + +/*----- Enumerated types --------------------------------------------------*/ + +/* An enumeration describes a set of values of some underlying type, each of + * which has a symbolic name. Values outside of the defined set can occur -- + * on output, because of bugs in the tested code, or on input to test + * handling of unexpected values. + * + * There is a distinct enumerated type for each of the branches of + * @tvec_misc@. In the following, we write @t@ for the type code, which is + * @i@ for signed integer, @u@ for unsigned integer, @f@ for floating-point, + * and @p@ for pointer. + * + * On input, an enumerated value may be given by name or as a literal value. + * For enumerations based on numeric types, the literal values can be written + * in the same syntax as the underlying values. For enumerations based on + * pointers, the only permitted literal is %|#nil|%, which denotes a null + * pointer. On output, names are preferred (with the underlying value given + * in a comment). + */ + +#define DEFENUMTY(tag, ty, slot) \ + extern const struct tvec_regty tvty_##slot##enum; +TVEC_MISCSLOTS(DEFENUMTY) +#undef DEFENUMTY + +/* A @struct tvec_tassoc@ associates a string tag with a value. */ #define DEFASSOC(tag_, ty, slot) \ struct tvec_##slot##assoc { const char *tag; ty slot; }; TVEC_MISCSLOTS(DEFASSOC) #undef DEFASSOC -struct tvec_enuminfo { const char *name; unsigned mv; /* ... */ }; -struct tvec_ienuminfo { - struct tvec_enuminfo _ei; - const struct tvec_iassoc *av; - const struct tvec_irange *ir; -}; -struct tvec_uenuminfo { - struct tvec_enuminfo _ei; - const struct tvec_uassoc *av; - const struct tvec_urange *ur; -}; -struct tvec_fenuminfo { - struct tvec_enuminfo _ei; - const struct tvec_fassoc *av; - const struct tvec_floatinfo *fi; -}; -struct tvec_penuminfo { - struct tvec_enuminfo _ei; - const struct tvec_passoc *av; -}; +#define TVEC_ENDENUM { 0, 0 } + +/* Information about an enumerated type. */ +#define DEFINFO(tag, ty, slot) \ + struct tvec_##slot##enuminfo { \ + const char *name; /* type name for diagnostics */ \ + const struct tvec_##slot##assoc *av; /* name/value mappings */ \ + EXTRA_##tag##_INFOSLOTS /* type-specific extra info */ \ + }; + +#define EXTRA_INT_INFOSLOTS \ + const struct tvec_irange *ir; /* allowed range of raw values */ + +#define EXTRA_UINT_INFOSLOTS \ + const struct tvec_urange *ur; /* allowed range of raw values */ + +#define EXTRA_FLT_INFOSLOTS \ + const struct tvec_floatinfo *fi; /* range and matching policy */ + +#define EXTRA_PTR_INFOSLOTS /* (nothing) */ -const struct tvec_ienuminfo tvenum_bool; +TVEC_MISCSLOTS(DEFINFO) + +#undef EXTRA_INT_INFOSLOTS +#undef EXTRA_UINT_INFOSLOTS +#undef EXTRA_FLT_INFOSLOTS +#undef EXTRA_PTR_INFOSLOTS + +#undef DEFINFO + +/* Standard enumerations. */ +extern const struct tvec_ienuminfo tvenum_bool; +extern const struct tvec_ienuminfo tvenum_cmp; + +/* --- @tvec_claimeq_tenum@, @TVEC_CLAIMEQ_TENUM@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const struct tvec_typeenuminfo *ei@ = enumeration type info + * @ty t0, t1@ = two values + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @t0@ and @t1@ are equal, otherwise zero. + * + * Use: Check that values of @t0@ and @t1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @t0@ is printed as the output + * value and @t1@ is printed as the input reference. + * + * The @TVEC_CLAIM_TENUM@ 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 @t0@ and @t1@ arguments in the failure message. + */ #define DECLCLAIM(tag, ty, slot) \ extern int tvec_claimeq_##slot##enum \ (struct tvec_state */*tv*/, \ const struct tvec_##slot##enuminfo */*ei*/, \ - ty /*e0*/, ty /*e1*/, \ + ty /*t0*/, ty /*t1*/, \ const char */*file*/, unsigned /*lno*/, const char */*expr*/); TVEC_MISCSLOTS(DECLCLAIM) #undef DECLCLAIM -#define TVEC_CLAIMEQ_IENUM(tv, ei, e0, e1) \ - (tvec_claimeq_ienum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) -#define TVEC_CLAIMEQ_UENUM(tv, ei, e0, e1) \ - (tvec_claimeq_uenum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) -#define TVEC_CLAIMEQ_FENUM(tv, ei, e0, e1) \ - (tvec_claimeq_fenum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) -#define TVEC_CLAIMEQ_PENUM(tv, ei, e0, e1) \ - (tvec_claimeq_penum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) +#define TVEC_CLAIMEQ_IENUM(tv, ei, i0, i1) \ + (tvec_claimeq_ienum(tv, ei, i0, i1, \ + __FILE__, __LINE__, #i0 " /= " #i1)) +#define TVEC_CLAIMEQ_UENUM(tv, ei, u0, u1) \ + (tvec_claimeq_uenum(tv, ei, u0, u1, \ + __FILE__, __LINE__, #u0 " /= " #u1)) +#define TVEC_CLAIMEQ_FENUM(tv, ei, f0, f1) \ + (tvec_claimeq_fenum(tv, ei, f0, f1, \ + __FILE__, __LINE__, #f0 " /= " #f1)) +#define TVEC_CLAIMEQ_PENUM(tv, ei, p0, p1) \ + (tvec_claimeq_penum(tv, ei, p0, p1, \ + __FILE__, __LINE__, #p0 " /= " #p1)) + +/*----- Flags type --------------------------------------------------------*/ + +/* A flags value packs a number of fields into a single nonnegative integer. + * Symbolic names are associated with the possible values of the various + * fields; more precisely, each name is associated with a value and a + * covering bitmask. + * + * The input syntax is a sequence of items separated by `%|||%' signs. Each + * item may be the symbolic name of a field value, or a literal unsigned + * integer. The masks associated with the given symbolic names must be + * disjoint. The resulting numerical value is simply the bitwise OR of the + * given values. + * + * On output, the table of symbolic names and their associated values and + * masks is repeatedly scanned, in order, to find disjoint matches -- i.e., + * entries whose value matches the target value in the bit positions + * indicated by the mask, and whose mask doesn't overlap with any previously + * found matches; the names are then output, separated by `%|||%'. Any + * remaining nonzero bits not covered by any of the matching masks are output + * as a single literal integer, in hex. + */ extern const struct tvec_regty tvty_flags; -struct tvec_flag { const char *tag; unsigned long m, v; }; + +struct tvec_flag { + /* Definition of a single flag or bitfield value. + * + * Each named setting comes with a value @v@ and a mask @m@; the mask + * should cover all of the value bits, i.e., @(v&~m) == 0@. + */ + + const char *tag; /* name */ + unsigned long m, v; /* mask and value */ +}; + +#define TVEC_ENDFLAGS { 0, 0, 0 } + struct tvec_flaginfo { - const char *name; - const struct tvec_flag *fv; - const struct tvec_urange *range; + /* Information about a flags type. */ + + const char *name; /* type name for diagnostics */ + const struct tvec_flag *fv; /* name/mask/value mappings */ + const struct tvec_urange *range; /* permitted range for literals */ }; +/* --- @tvec_claimeq_flags@, @TVEC_CLAIMEQ_FLAGS@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const struct tvec_flaginfo *fi@ = flags type info + * @unsigned long f0, f1@ = two values + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @f0@ and @f1@ are equal, otherwise zero. + * + * Use: Check that values of @f0@ and @f1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @f0@ is printed as the output + * value and @f1@ is printed as the input reference. + * + * The @TVEC_CLAIM_FLAGS@ 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 @f0@ and @f1@ arguments in the failure message. + */ + extern int tvec_claimeq_flags(struct tvec_state */*tv*/, const struct tvec_flaginfo */*fi*/, unsigned long /*f0*/, unsigned long /*f1*/, @@ -930,7 +2301,90 @@ extern int tvec_claimeq_flags(struct tvec_state */*tv*/, (tvec_claimeq_flags(tv, fi, f0, f1, \ __FILE__, __LINE__, #f0 " /= " #f1)) +/*----- Character type ----------------------------------------------------*/ + +/* A character value holds a character, as read by @fgetc@. The special + * @EOF@ value can also be represented. + * + * On input, a character value can be given by symbolic name, with a leading + * `%|#|%'; or a character or `%|\|%'-escape sequence, optionally in single + * quotes. + * + * The following escape sequences and character names are recognized. + * + * * `%|#eof|%' is the special end-of-file marker. + * + * * `%|#nul|%' is the NUL character, sometimes used to terminate strings. + * + * * `%|bell|%', `%|bel|%', `%|ding|%', or `%|\a|%' is the BEL character + * used to ring the terminal bell (or do some other thing to attract the + * user's attention). + * + * * %|#backspace|%, %|#bs|%, or %|\b|% is the backspace character, used to + * move the cursor backwords by one cell. + * + * * %|#escape|% %|#esc|%, or%|\e|% is the escape character, used to + * introduce special terminal commands. + * + * * %|#formfeed|%, %|#ff|%, or %|\f|% is the formfeed character, used to + * separate pages of text. + * + * * %|#newline|%, %|#linefeed|%, %|#lf|%, %|#nl|%, or %|\n|% is the + * newline character, used to terminate lines of text or advance the + * cursor to the next line (perhaps without returning it to the start of + * the line). + * + * * %|#return|%, %|#carriage-return|%, %|#cr|%, or %|\r|% is the + * carriage-return character, used to return the cursor to the start of + * the line. + * + * * %|#tab|%, %|#horizontal-tab|%, %|#ht|%, or %|\t|% is the tab + * character, used to advance the cursor to the next tab stop on the + * current line. + * + * * %|#vertical-tab|%, %|#vt|%, %|\v|% is the vertical tab character. + * + * * %|#space|%, %|#spc|% is the space character. + * + * * %|#delete|%, %|#del|% is the delete character, used to erase the most + * recent character. + * + * * %|\'|% is the single-quote character. + * + * * %|\\|% is the backslash character. + * + * * %|\"|% is the double-quote character. + * + * * %|\NNN|% or %|\{NNN}|% is the character with code NNN in octal. The + * NNN may be up to three digits long. + * + * * %|\xNN|% or %|\x{NN}|% is the character with code NNN in hexadecimal. + */ + extern const struct tvec_regty tvty_char; + +/* --- @tvec_claimeq_char@, @TVEC_CLAIMEQ_CHAR@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @int ch0, ch1@ = two character codes + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @ch0@ and @ch1@ are equal, otherwise zero. + * + * Use: Check that values of @ch0@ and @ch1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @ch0@ is printed as the output + * value and @ch1@ is printed as the input reference. + * + * The @TVEC_CLAIM_CHAR@ 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 @ch0@ and @ch1@ arguments in the failure message. + */ + extern int tvec_claimeq_char(struct tvec_state */*tv*/, int /*ch0*/, int /*ch1*/, const char */*file*/, unsigned /*lno*/, @@ -938,147 +2392,220 @@ extern int tvec_claimeq_char(struct tvec_state */*tv*/, #define TVEC_CLAIMEQ_CHAR(tv, c0, c1) \ (tvec_claimeq_char(tv, c0, c1, __FILE__, __LINE__, #c0 " /= " #c1)) -extern const struct tvec_regty tvty_string, tvty_bytes; +/*----- Text and binary string types --------------------------------------*/ -extern int tvec_claimeq_string(struct tvec_state */*tv*/, - const char */*p0*/, size_t /*sz0*/, - const char */*p1*/, size_t /*sz1*/, - const char */*file*/, unsigned /*lno*/, - const char */*expr*/); -extern int tvec_claimeq_strz(struct tvec_state */*tv*/, - const char */*p0*/, const char */*p1*/, +/* A string is a sequence of octets. Text and binary strings differ + * primarily in presentation: text strings are shown as raw characters where + * possible; binary strings are shown as hex dumps with an auxiliary text + * display. + * + * The input format for both kinds of strings is basically the same: a + * `compound string', consisting of + * + * * single-quoted strings, which are interpreted entirely literally, but + * can't contain single quotes or newlines; + * + * * double-quoted strings, in which `%|\|%'-escapes are interpreted as for + * characters; + * + * * character names, marked by an initial `%|#|%' sign; + * + * * special tokens marked by an initial `%|!|%' sign; or + * + * * barewords interpreted according to the current coding scheme. + * + * The special tokens are + * + * * `%|!bare|%', which causes subsequent sequences of barewords to be + * treated as plain text; + * + * * `%|!hex|%', `%|!base32|%', `%|!base64|%', which cause subsequent + * barewords to be decoded in the requested manner. + * + * * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%', which includes + * %$n$% copies of the (compound) string. + * + * The only difference between text and binary strings is that the initial + * coding scheme is %|bare|% for text strings and %|hex|% for binary strings. + * + * Either kind of string can contain internal nul characters. A trailing nul + * is appended -- beyond the stated input length -- to input strings as a + * convenience to test functions. Test functions may include such a nul + * character on output but this is not checked by the equality test. + * + * A @struct tvec_urange@ may be supplied as an argument: the length of the + * string (in bytes) will be checked against the permitted range. + */ + +extern const struct tvec_regty tvty_text, tvty_bytes; + +/* --- @tvec_alloctext@, @tvec_allocbytes@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @size_t sz@ = required size + * + * Returns: --- + * + * Use: Allocated space in a text or binary string register. If the + * current register size is sufficient, its buffer is left + * alone; otherwise, the old buffer, if any, is freed and a + * fresh buffer allocated. These functions are not intended to + * be used to adjust a buffer repeatedly, e.g., while building + * output incrementally: (a) they will perform badly, and (b) + * the old buffer contents are simply discarded if reallocation + * is necessary. Instead, use a @dbuf@ or @dstr@. + * + * The @tvec_alloctext@ function sneakily allocates an extra + * byte for a terminating zero. The @tvec_allocbytes@ function + * doesn't do this. + */ + +extern void tvec_alloctext(union tvec_regval */*rv*/, size_t /*sz*/); +extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); + +/* --- @tvec_claimeq_text@, @TVEC_CLAIMEQ_TEXT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *p0@, @size_t sz0@ = first string with length + * @const char *p1@, @size_t sz1@ = second string with length + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise + * zero. + * + * Use: Check that strings at @p0@ and @p1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @p0@ is printed as the output + * value and @p1@ is printed as the input reference. + * + * The @TVEC_CLAIM_TEXT@ 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 @ch0@ and @ch1@ arguments in the failure message. + */ + +extern int tvec_claimeq_text(struct tvec_state */*tv*/, + const char */*p0*/, size_t /*sz0*/, + const char */*p1*/, size_t /*sz1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); +#define TVEC_CLAIMEQ_TEXT(tv, p0, sz0, p1, sz1) \ + (tvec_claimeq_text(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ + #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) + +/* --- @tvec_claimeq_textz@, @TVEC_CLAIMEQ_TEXTZ@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *p0, *p1@ = two strings to compare + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise + * zero. + * + * Use: Check that strings at @p0@ and @p1@ are equal, as for + * @tvec_claimeq_string@, except that the strings are assumed + * null-terminated, so their lengths don't need to be supplied + * explicitly. The macro is similarly like @TVEC_CLAIMEQ_TEXT@. + */ + +extern int tvec_claimeq_textz(struct tvec_state */*tv*/, + const char */*p0*/, const char */*p1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_TEXTZ(tv, p0, p1) \ + (tvec_claimeq_textz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1)) + +/* --- @tvec_claimeq_bytes@, @TVEC_CLAIMEQ_BYTES@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const void *p0@, @size_t sz0@ = first string with length + * @const void *p1@, @size_t sz1@ = second string with length + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise + * zero. + * + * Use: Check that binary strings at @p0@ and @p1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @p0@ is printed as the output + * value and @p1@ is printed as the input reference. + * + * The @TVEC_CLAIM_STRING@ 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 @ch0@ and @ch1@ arguments in the failure message. + */ + extern int tvec_claimeq_bytes(struct tvec_state */*tv*/, const void */*p0*/, size_t /*sz0*/, const void */*p1*/, size_t /*sz1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); -#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \ - (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ - #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) -#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \ - (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1)) #define TVEC_CLAIMEQ_BYTES(tv, p0, sz0, p1, sz1) \ (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) -extern const struct tvec_regty tvty_buffer; - -extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/); -extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); - -/*----- Ad-hoc testing ----------------------------------------------------*/ - -extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); - -extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/, - const char */*file*/, unsigned /*lno*/); -extern void tvec_reportgroup(struct tvec_state */*tv*/); -extern void tvec_endgroup(struct tvec_state */*tv*/); - -#define TVEC_BEGINGROUP(tv, name) \ - do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0) - -#define TVEC_TESTGROUP(tag, tv, name) \ - MC_WRAP(tag##__around, \ - { TVEC_BEGINGROUP(tv, name); }, \ - { tvec_endgroup(tv); }, \ - { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \ - tvec_endgroup(tv); }) - -extern void tvec_begintest(struct tvec_state */*tv*/, - const char */*file*/, unsigned /*lno*/); -extern void tvec_endtest(struct tvec_state */*tv*/); - -#define TVEC_BEGINTEST(tv) \ - do tvec_begintest(tv, __FILE__, __LINE__); while (0) - -#define TVEC_TEST(tag, tv) \ - MC_WRAP(tag##__around, \ - { TVEC_BEGINTEST(tv); }, \ - { tvec_endtest(tv); }, \ - { if ((tv)->f&TVSF_ACTIVE) tvec_skipgroup((tv), 0); \ - tvec_endtest(tv); }) - -extern int PRINTF_LIKE(5, 6) - tvec_claim(struct tvec_state */*tv*/, int /*ok*/, - const char */*file*/, unsigned /*lno*/, - const char */*expr*/, ...); - -#define TVEC_CLAIM(tv, cond) \ - (tvec_claim(tv, !!(cond), __FILE__, __LINE__, #cond " untrue")) - -extern 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*/); - -/*----- Benchmarking ------------------------------------------------------*/ - -struct tvec_bench { - struct tvec_env _env; /* benchmarking is an environment */ - struct bench_state **bst; /* benchmark state anchor or null */ - unsigned long niter; /* iterations done per unit */ - int riter, rbuf; /* iterations and buffer registers */ - const struct tvec_env *env; /* environment (per test, not grp) */ -}; -#define TVEC_BENCHENV \ - { sizeof(struct tvec_benchctx), \ - tvec_benchsetup, \ - tvec_benchset, \ - tvec_benchbefore, \ - tvec_benchrun, \ - tvec_benchafter, \ - tvec_benchteardown } -#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate - -struct tvec_benchctx { - const struct tvec_bench *b; - struct bench_state *bst; - double dflt_target; - void *subctx; -}; - -extern struct bench_state *tvec_benchstate; - -extern int tvec_benchsetup(struct tvec_state */*tv*/, - const struct tvec_env */*env*/, - void */*pctx*/, void */*ctx*/); -extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/, - const struct tvec_env */*env*/, void */*ctx*/); -extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/); -extern void tvec_benchrun(struct tvec_state */*tv*/, - tvec_testfn */*fn*/, void */*ctx*/); -extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/); -extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/); - -extern void tvec_benchreport - (const struct gprintf_ops */*gops*/, void */*go*/, - unsigned /*unit*/, const struct bench_timing */*tm*/); +/*----- Buffer type -------------------------------------------------------*/ -/*----- Command-line interface --------------------------------------------*/ +/* Buffer registers are primarily used for benchmarking. Only a buffer's + * allocation parameters are significant: its contents are ignored on + * comparison and output, and unspecified on input. + * + * The input format gives the buffer's size, and an optional alignment + * specification, in the form %|SZ [`@' M [`+' A]]|%. Each of %|SZ|%, %|M|% + * and %|A|% are sizes, as an integer, optionally suffixed with a unit `kB', + * `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or without the `B') + * denoting a power of 1024. The %|SZ|% gives the (effective) buffer size. + * %|M|% is the `alignment quantum' and %|A|% is the `alignment offset'; both + * default to zero, but if %|M|% is nonzero then the start of the buffer is + * aligned such that it is %|A|% more than a multiple of %|M|% bytes. Note + * that %|M|% need not be a power of two, though this is common. + * + * Units other than `B' are used on output only when the size would be + * expressed exactly. + * + * Buffers are %%\emph{not}%% allocated by default. In benchmarks, this is + * best done in a @before@ function. + * + * No @claimeq@ functions or macros are provided for buffers because they + * don't seem very useful. + */ -extern const struct tvec_config tvec_adhocconfig; +extern const struct tvec_regty tvty_buffer; -extern void tvec_parseargs(int /*argc*/, char */*argv*/[], - struct tvec_state */*tv_out*/, - int */*argpos_out*/, - const struct tvec_config */*config*/); +/* --- @tvec_initbuffer@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const union tvec_regval *ref@ = reference buffer + * @size_t sz@ = size to allocate + * + * Returns: --- + * + * Use: Initialize the alignment parameters in @rv@ to match @ref@, + * and the size to @sz@. + */ -extern int tvec_readstdin(struct tvec_state */*tv*/); -extern int tvec_readfile(struct tvec_state */*tv*/, const char */*file*/); -extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/); -extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/); +extern void tvec_initbuffer(union tvec_regval */*rv*/, + const union tvec_regval */*ref*/, size_t /*sz*/); -extern int tvec_readargs(int /*argc*/, char */*argv*/[], - struct tvec_state */*tv*/, - int */*argpos_inout*/, const char */*dflt*/); +/* --- @tvec_allocbuffer@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * + * Returns: --- + * + * Use: Allocate @sz@ bytes to the buffer and fill the space with a + * distinctive pattern. + */ -extern int tvec_main(int /*argc*/, char */*argv*/[], - const struct tvec_config */*config*/, - const char */*dflt*/); +extern void tvec_allocbuffer(union tvec_regval */*rv*/); /*----- That's all, folks -------------------------------------------------*/