X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/c5e0e40378b7e209521d2e9a52f055575a948313..3efcfd2df21aa11bd9d1ba5ea2f5f490fd4d5b84:/test/tvec.h diff --git a/test/tvec.h b/test/tvec.h index 740b6be..03c3ba4 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -260,8 +260,14 @@ extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, /*----- Test state --------------------------------------------------------*/ -/* Possible test outcomes. */ -enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT }; +enum { + /* Possible test outcomes. */ + + TVOUT_LOSE, /* test failed */ + TVOUT_SKIP, /* test skipped */ + TVOUT_WIN, /* test passed */ + TVOUT_LIMIT /* (number of possible outcomes) */ +}; struct tvec_state { /* The primary state structure for the test vector machinery. */ @@ -271,8 +277,8 @@ struct tvec_state { #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 +#define TVSF_OUTMASK 0xf0 /* test outcome (@TVOUT_...@) */ +#define TVSF_OUTSHIFT 4 /* shift applied to outcome */ /* Registers. Available to execution environments. */ unsigned nrout, nreg; /* number of output/total registers */ @@ -398,7 +404,7 @@ enum { /* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list ap@ = reason why group skipped + * @const char *excuse@, @va_list *ap@ = reason why skipped * * Returns: --- * @@ -415,7 +421,7 @@ extern void tvec_skipgroup_v(struct tvec_state */*tv*/, /* --- @tvec_skip@, @tvec_skip_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list ap@ = reason why test skipped + * @const char *excuse@, @va_list *ap@ = reason why test skipped * * Returns: --- * @@ -464,7 +470,7 @@ extern int tvec_checkregs(struct tvec_state */*tv*/); /* --- @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: --- * @@ -526,7 +532,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: --- * @@ -727,194 +733,778 @@ 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. + */ + +extern void tvec_endgroup(struct tvec_state */*tv*/); + +/* --- @TVEC_TESTGROUP@, @TVEC_TESTGROUP_TAG@ --- * + * + * Arguments: @tag@ = label-disambiguation tag + * @const struct tvec_state *tv = test-vector state + * @const char *name@ = test-group name + * + * Returns: --- + * + * 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. + */ + +#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: 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_begintest(struct tvec_state */*tv*/, + const char */*file*/, unsigned /*lno*/); +#define TVEC_BEGINTEST(tv) \ + do tvec_begintest(tv, __FILE__, __LINE__); while (0) + +/* --- *tvec_endtest@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End a ad-hoc test case, The statistics are updated and the + * outcome is reported to the output formatter. + */ + +extern void tvec_endtest(struct tvec_state */*tv*/); + +/* --- @TVEC_TEST@, @TVEC_TEST_TAG@ --- * + * + * Arguments: @tag@ = label-disambiguation tag + * @struct tvec_test *t@ = space for a test definition + * + * Returns: --- + * + * 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 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_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 int PRINTF_LIKE(5, 6) + 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@ --- * + * + * 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*/); + /*----- Output formatting -------------------------------------------------*/ struct tvec_output { - const struct tvec_outops *ops; - struct tvec_state *tv; + /* An output formatter. */ + const struct tvec_outops *ops; /* pointer to operations */ }; -enum { TVBU_OP, TVBU_BYTE }; - -struct bench_timing; +/* Benchmarking details. */ +enum { + TVBU_OP, /* counting operations of some kind */ + TVBU_BYTE /* counting bytes (@RBUF >= 0@) */ +}; +struct bench_timing; /* forward declaration */ 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*/); + /* 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. + */ - void (*bsession)(struct tvec_output */*o*/); int (*esession)(struct tvec_output */*o*/); + /* End a session, and return the suggested exit code. */ void (*bgroup)(struct tvec_output */*o*/); - void (*egroup)(struct tvec_output */*o*/, unsigned /*outcome*/); + /* 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. */ }; +/* --- @tvec_error@, @tvec_error_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *msg@, @va_list ap@ = error message + * + * Returns: @-1@. + * + * Use: Report an error. Errors are distinct from test failures, + * and indicate that a problem was encountered which compromised + * the activity of testing. + */ + 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*/); +/* --- @tvec_notice@, @tvec_notice_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *msg@, @va_list ap@ = message + * + * Returns: --- + * + * Use: Output a notice: essentially, some important information + * which doesn't fit into any of the existing categories. + */ + 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*/); +/* --- @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 produces TAP 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*/); /*----- Register types ----------------------------------------------------*/ struct tvec_regty { + /* A register type. */ + 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@. + */ + void (*release)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Release any resources associated with the value in @*rv@. */ + 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. + */ }; +/*----- Integer types: signed and unsigned ------------------------------- */ + +/* 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)) +/*----- Floating-point type -----------------------------------------------*/ + +/* Floating-point are either NaN (`#nan', if supported by the platform); + * positive or negative infinity (`#inf', `+#inf', or, preferred, `#+inf', + * and `-#inf' or, preferred, `#-inf', 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 */ }; +/* --- @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 @u1@ 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 - y/x| < \delta$%. (Note that this + * criterion is asymmetric + * + * 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; +/*----- Enumerated types --------------------------------------------------*/ + +/* 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. + */ +#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; -}; +/* 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) */ +TVEC_MISCSLOTS(DEFINFO) + +#undef EXTRA_INT_INFOSLOTS +#undef EXTRA_UINT_INFOSLOTS +#undef EXTRA_FLT_INFOSLOTS +#undef EXTRA_PTR_INFOSLOTS + +#undef DEFINFO + +/* Standard enumerations. */ const struct tvec_ienuminfo tvenum_bool; +/* --- @tvec_claimeq_tenum@, @TVEC_CLAIMEQ_TENUM@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @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: @u0@ is printed as the output + * value and @u1@ 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. + */ + #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 -------------------------------------------------------*/ 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@. On input, + * exactly + */ + + const char *tag; /* name */ + unsigned long m, v; /* mask and value */ +}; + struct tvec_flaginfo { const char *name; const struct tvec_flag *fv; @@ -968,53 +1558,6 @@ 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 {