X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/db2bf4111cde36048ac66bbac58547d105bc7e80..67b5031ec6d160b5cae425466a34d1be3b211dd4:/test/tvec.h diff --git a/test/tvec.h b/test/tvec.h index 7f19f50..5ea18ca 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -186,79 +186,6 @@ 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. - */ - -extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, - const struct tvec_regdef */*regs*/, - unsigned /*nr*/, size_t /*regsz*/); - /*----- Test state --------------------------------------------------------*/ enum { @@ -308,6 +235,135 @@ struct tvec_state { */ #define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz) +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 */ +}; + +/*----- Output formatting -------------------------------------------------*/ + +struct tvec_output { + /* An output formatter. */ + const struct tvec_outops *ops; /* pointer to operations */ +}; + +/* Benchmarking details. */ +enum { + TVBU_OP, /* counting operations of some kind */ + TVBU_BYTE /* counting bytes (@rbuf >= 0@) */ +}; +struct bench_timing; /* forward declaration */ + +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. + */ + + 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 (*error)(struct tvec_output */*o*/, + const char */*msg*/, va_list */*ap*/); + /* Report an error. The driver should ideally report the filename + * (@infile@) and line number (@lno@) prompting the error. + */ + + void (*notice)(struct tvec_output */*o*/, + const char */*msg*/, va_list */*ap*/); + /* Report a miscellaneous 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. */ +}; + /*----- Test descriptions -------------------------------------------------*/ typedef void tvec_testfn(const struct tvec_reg */*in*/, @@ -342,9 +398,10 @@ struct tvec_env { 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. + /* 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. 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*/, @@ -402,55 +459,328 @@ enum { TVRD_EXPECT /* mismatching input register */ }; -/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list *ap@ = reason why skipped - * - * Returns: --- - * - * Use: Skip the current group. This should only be called from a - * test environment @setup@ function; a similar effect occurs if - * the @setup@ function fails. - */ - -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*/); - -/* --- @tvec_skip@, @tvec_skip_v@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list *ap@ = reason why test skipped - * - * Returns: --- - * - * Use: Skip the current test. This should only be called from a - * test environment @run@ function; a similar effect occurs if - * the @before@ function fails. - */ +/*----- Register types ----------------------------------------------------*/ -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*/); +struct tvec_regty { + /* A register type. */ -/* --- @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. - */ + 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@. + */ -extern void tvec_resetoutputs(struct tvec_state */*tv*/); + void (*release)(union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Release any resources associated with the value in @*rv@. */ -/* --- @tvec_checkregs@ --- * - * + 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@. + */ + + 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. + */ + + 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. + */ + + 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. + */ + + 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. + */ +}; + +/*----- Session lifecycle -------------------------------------------------*/ + +/* 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@ + * -> env @run@ + * -> @tvec_skip@ + * -> output @skip@ + * -> test @fn@ + * -> @tvec_checkregs@ + * -> type @eq@ + * -> @tvec_fail@ + * -> output @fail@ + * -> @tvec_mismatch@ + * -> output @dumpreg@ + * -> type @dump@ + * -> env @after@ + * -> output @etest@ + * or + * -> output @skipgroup@ + * 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. + */ + +/* --- @tvec_begin@ --- * + * + * Arguments: @struct tvec_state *tv_out@ = state structure to fill in + * @const struct tvec_config *config@ = test configuration + * @struct tvec_output *o@ = output driver + * + * Returns: --- + * + * Use: Initialize a state structure ready to do some testing. + */ + +extern void tvec_begin(struct tvec_state */*tv_out*/, + const struct tvec_config */*config*/, + struct tvec_output */*o*/); + +/* --- @tvec_end@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: A proposed exit code. + * + * Use: Conclude testing and suggests an exit code to be returned to + * the calling program. (The exit code comes from the output + * driver's @esession@ method.) + */ + +extern int tvec_end(struct tvec_state */*tv*/); + +/* --- @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: 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_parseargs(int /*argc*/, char */*argv*/[], + struct tvec_state */*tv_out*/, + int */*argpos_out*/, + const struct tvec_config */*config*/); + +/* --- @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: --- + * + * 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 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*/); + +/* --- @tvec_skip@, @tvec_skip_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list *ap@ = reason why test skipped + * + * Returns: --- + * + * Use: Skip the current test. This should only be called from a + * test environment @run@ function; a similar effect occurs if + * the @before@ function fails. + */ + +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*/); + +/* --- @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. + */ + +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. @@ -548,239 +878,53 @@ extern void PRINTF_LIKE(2, 3) 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. - */ - -extern int tvec_read(struct tvec_state */*tv*/, - const char */*infile*/, FILE */*fp*/); - -/*----- 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_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_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 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*/); - -/* --- @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, - * and the file position is left correctly. The line number - * count is updated appropriately. - */ - -extern int tvec_nexttoken(struct tvec_state */*tv*/); - -/* --- @tvec_readword@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * @dstr *d@ = string to append the word to - * @const char *delims@ = additional delimiters to stop at - * @const char *expect@, @va_list ap@ = what was expected - * - * Returns: Zero on success, @-1@ on failure. - * - * Use: A `word' consists of characters other than whitespace, null - * characters, and other than those listed in @delims@; - * furthermore, a word does not begin with a `;'. (If you want - * reading to stop at comments not preceded by whitespace, then - * include `;' in @delims@. This is a common behaviour.) - * - * If there is no word beginning at the current file position, - * then return @-1@; furthermore, if @expect@ is not null, then - * report an appropriate error via @tvec_syntax@. - * - * Otherwise, the word is accumulated in @d@ and zero is - * returned; if @d@ was not empty at the start of the call, the - * newly read word is separated from the existing material by a - * single space character. Since null bytes are never valid - * word constituents, a null terminator is written to @d@, and - * it is safe to treat the string in @d@ as being null- - * terminated. - */ - -extern int PRINTF_LIKE(4, 5) - tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, - 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*/); - -/*----- Ad-hoc testing ----------------------------------------------------*/ - -/* --- @tvec_adhoc@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * @struct tvec_test *t@ = space for a test definition - * - * Returns: --- - * - * Use: Begin ad-hoc testing, i.e., without reading a file of - * test-vector data. - * - * The structure at @t@ will be used to record information about - * the tests underway, which would normally come from a static - * test definition. The other functions in this section assume - * that @tvec_adhoc@ has been called. - */ - -extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); - -/* --- @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: --- - * - * 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 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_endgroup@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * - * Returns: --- - * - * Use: End an ad-hoc test group. The statistics are updated and the - * outcome is reported to the output formatter. + * Use: End an ad-hoc test group. The statistics are updated and the + * outcome is reported to the output formatter. */ extern void tvec_endgroup(struct tvec_state */*tv*/); @@ -925,126 +1069,154 @@ extern int tvec_claimeq(struct tvec_state */*tv*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); -/*----- Output formatting -------------------------------------------------*/ +/*----- Benchmarking ------------------------------------------------------*/ -struct tvec_output { - /* An output formatter. */ - const struct tvec_outops *ops; /* pointer to operations */ +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; /* subordinate environment */ }; +#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 -/* Benchmarking details. */ -enum { - TVBU_OP, /* counting operations of some kind */ - TVBU_BYTE /* counting bytes (@RBUF >= 0@) */ +struct tvec_benchctx { + const struct tvec_bench *b; + struct bench_state *bst; + double dflt_target; + void *subctx; }; -struct bench_timing; /* forward declaration */ -struct tvec_outops { - /* Output operations. */ +extern struct bench_state *tvec_benchstate; - 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. - */ +/* --- @tvec_benchsetup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const struct tvec_env *env@ = environment description + * @void *pctx@ = parent context (ignored) + * @void *ctx@ = context pointer to initialize + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Initialize a benchmarking environment context. + * + * The environment description must really be a @struct + * tvec_bench@. If the @bst@ slot is null, then a temporary + * benchmark state is allocated for the current test group and + * released at the end. Otherwise, it must be the address of a + * pointer to a benchmark state: if the pointer is null, then a + * fresh state is allocated and initialized and the pointer is + * updated; otherwise, the pointer is assumed to refer to an + * existing valid benchmark state. + */ - int (*esession)(struct tvec_output */*o*/); - /* End a session, and return the suggested exit code. */ +extern int tvec_benchsetup(struct tvec_state */*tv*/, + const struct tvec_env */*env*/, + void */*pctx*/, void */*ctx*/); - void (*bgroup)(struct tvec_output */*o*/); - /* Begin a test group. The test group description is @tv->test@. */ +/* --- @tvec_benchset@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const char *var@ = variable name to set + * @const struct tvec_env *env@ = environment description + * @void *ctx@ = context pointer + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Set a special variable. 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. + */ - 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. - */ +extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/, + const struct tvec_env */*env*/, void */*ctx*/); - 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. - */ +/* --- @tvec_benchbefore@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Invoke the subordinate environment's @before@ function to + * prepare for the benchmark. + */ - void (*btest)(struct tvec_output */*o*/); - /* Begin a test case. */ +extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/); - 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. - */ +/* --- @tvec_benchrun@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @tvec_testfn *fn@ = test function to run + * @void *ctx@ = context pointer for the test function + * + * Returns: --- + * + * Use: Measures and reports the performance of a test function. + * + * + */ - 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! - */ +extern void tvec_benchrun(struct tvec_state */*tv*/, + tvec_testfn */*fn*/, void */*ctx*/); - 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. - */ +/* --- @tvec_benchafter@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Invoke the subordinate environment's @after@ function to + * clean up after the benchmark. + */ - void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); - /* The test case concluded with the given @outcome@ (one of the - * @TVOUT_...@ codes. - */ +extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/); - 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. - */ +/* --- @tvec_benchteardown@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Tear down the benchmark environment. + */ - 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. - */ +extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/); - void (*error)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); - /* Report an error. The driver should ideally report the filename - * (@infile@) and line number (@lno@) prompting the error. - */ +/* --- @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. + */ - void (*notice)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); - /* Report a miscellaneous message. The driver should ideally report - * the filename (@infile@) and line number (@lno@) prompting the - * error. - */ +extern void tvec_benchreport + (const struct gprintf_ops */*gops*/, void */*go*/, + unsigned /*unit*/, const struct bench_timing */*tm*/); - void (*destroy)(struct tvec_output */*o*/); - /* Release any resources acquired by the driver. */ -}; +/*----- Output functions --------------------------------------------------*/ /* --- @tvec_error@, @tvec_error_v@ --- * * @@ -1134,64 +1306,211 @@ extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); extern struct tvec_output *tvec_dfltout(FILE */*fp*/); -/*----- Register types ----------------------------------------------------*/ +/*------ Serialization utilities ------------------------------------------*/ -struct tvec_regty { - /* A register type. */ +/* --- @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. + */ - 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@. - */ +extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/); - void (*release)(union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - /* Release any resources associated with the value in @*rv@. */ +/* --- @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. + */ - 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@. - */ +extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, + const struct tvec_regdef */*regs*/, + unsigned /*nr*/, size_t /*regsz*/); - 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. - */ +/*----- 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_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_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 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*/); + +/* --- @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, + * and the file position is left correctly. The line number + * count is updated appropriately. + */ - 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. - */ +extern int tvec_nexttoken(struct tvec_state */*tv*/); - 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. - */ +/* --- @tvec_readword@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @dstr *d@ = string to append the word to + * @const char *delims@ = additional delimiters to stop at + * @const char *expect@, @va_list ap@ = what was expected + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: A `word' consists of characters other than whitespace, null + * characters, and other than those listed in @delims@; + * furthermore, a word does not begin with a `;'. (If you want + * reading to stop at comments not preceded by whitespace, then + * include `;' in @delims@. This is a common behaviour.) + * + * If there is no word beginning at the current file position, + * then return @-1@; furthermore, if @expect@ is not null, then + * report an appropriate error via @tvec_syntax@. + * + * Otherwise, the word is accumulated in @d@ and zero is + * returned; if @d@ was not empty at the start of the call, the + * newly read word is separated from the existing material by a + * single space character. Since null bytes are never valid + * word constituents, a null terminator is written to @d@, and + * it is safe to treat the string in @d@ as being null- + * terminated. + */ - 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. - */ -}; +extern int PRINTF_LIKE(4, 5) + tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, + 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*/); -/*----- Integer types: signed and unsigned ------------------------------- */ +/*----- Integer types: signed and unsigned --------------------------------*/ /* Integers may be input in decimal, hex, binary, or octal, following * approximately usual conventions. @@ -1233,7 +1552,7 @@ extern const struct tvec_urange tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size, tvrange_byte, tvrange_u16, tvrange_u32; -/* --- tvec_claimeq_int@, @TVEC_CLAIMEQ_INT@ --- * +/* --- @tvec_claimeq_int@, @TVEC_CLAIMEQ_INT@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @long i0, i1@ = two signed integers @@ -1399,10 +1718,22 @@ extern int tvec_claimeq_float(struct tvec_state */*tv*/, /*----- Enumerated types --------------------------------------------------*/ -/* There is a distinct enumerated type for each of the branches of +/* 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) \ @@ -1416,6 +1747,8 @@ TVEC_MISCSLOTS(DEFENUMTY) TVEC_MISCSLOTS(DEFASSOC) #undef DEFASSOC +#define TVEC_ENDENUM { 0, 0 } + /* Information about an enumerated type. */ #define DEFINFO(tag, ty, slot) \ struct tvec_##slot##enuminfo { \ @@ -1450,6 +1783,7 @@ const struct tvec_ienuminfo tvenum_bool; /* --- @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 @@ -1460,13 +1794,13 @@ const struct tvec_ienuminfo tvenum_bool; * @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. + * 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 @u0@ and @u1@ arguments in the failure message. + * the @t0@ and @t1@ arguments in the failure message. */ #define DECLCLAIM(tag, ty, slot) \ @@ -1490,7 +1824,26 @@ TVEC_MISCSLOTS(DECLCLAIM) (tvec_claimeq_penum(tv, ei, p0, p1, \ __FILE__, __LINE__, #p0 " /= " #p1)) -/*----- Flags type -------------------------------------------------------*/ +/*----- 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; @@ -1498,20 +1851,46 @@ 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@. On input, - * exactly + * 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*/, @@ -1521,7 +1900,89 @@ 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 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*/, @@ -1529,100 +1990,177 @@ 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)) +/*----- Text and binary string types --------------------------------------*/ + +/* 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. + * + * 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_string, tvty_bytes; +/* --- @tvec_claimeq_string@, @TVEC_CLAIMEQ_STRING@ --- * + * + * 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_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_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*/); +#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \ + (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ + #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) + +/* --- @tvec_claimeq_strz@, @TVEC_CLAIMEQ_STRZ@ --- * + * + * 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_STRING@. + */ + extern int tvec_claimeq_strz(struct tvec_state */*tv*/, const char */*p0*/, const char */*p1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); +#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \ + (tvec_claimeq_strz(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; +/* --- @tvec_allocstring@, @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_allocstring@ function sneakily allocates an extra + * byte for a terminating zero. The @tvec_allocbytes@ function + * doesn't do this. + */ extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/); extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); -/*----- 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*/); - -/*----- Command-line interface --------------------------------------------*/ - -extern const struct tvec_config tvec_adhocconfig; - -extern void tvec_parseargs(int /*argc*/, char */*argv*/[], - struct tvec_state */*tv_out*/, - int */*argpos_out*/, - const struct tvec_config */*config*/); - -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*/); +/*----- Buffer type -------------------------------------------------------*/ -extern int tvec_readargs(int /*argc*/, char */*argv*/[], - struct tvec_state */*tv*/, - int */*argpos_inout*/, const char */*dflt*/); +/* Buffer registers are primarily used for benchmarking. Only a buffer's + * size is significant: its contents are ignored on comparison and output, + * and unspecified on input. + * + * The input is simply the buffer size, 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. Units are used on output only + * when the size would be expressed exactly. + */ -extern int tvec_main(int /*argc*/, char */*argv*/[], - const struct tvec_config */*config*/, - const char */*dflt*/); +extern const struct tvec_regty tvty_buffer; /*----- That's all, folks -------------------------------------------------*/