| 1 | /* -*-c-*- |
| 2 | * |
| 3 | * Test vector processing framework |
| 4 | * |
| 5 | * (c) 2023 Straylight/Edgeware |
| 6 | */ |
| 7 | |
| 8 | /*----- Licensing notice --------------------------------------------------* |
| 9 | * |
| 10 | * This file is part of the mLib utilities library. |
| 11 | * |
| 12 | * mLib is free software: you can redistribute it and/or modify it under |
| 13 | * the terms of the GNU Library General Public License as published by |
| 14 | * the Free Software Foundation; either version 2 of the License, or (at |
| 15 | * your option) any later version. |
| 16 | * |
| 17 | * mLib is distributed in the hope that it will be useful, but WITHOUT |
| 18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
| 20 | * License for more details. |
| 21 | * |
| 22 | * You should have received a copy of the GNU Library General Public |
| 23 | * License along with mLib. If not, write to the Free Software |
| 24 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| 25 | * USA. |
| 26 | */ |
| 27 | |
| 28 | #ifndef MLIB_TVEC_H |
| 29 | #define MLIB_TVEC_H |
| 30 | |
| 31 | #ifdef __cplusplus |
| 32 | extern "C" { |
| 33 | #endif |
| 34 | |
| 35 | /*----- Header files ------------------------------------------------------*/ |
| 36 | |
| 37 | #include <stdarg.h> |
| 38 | #include <stddef.h> |
| 39 | #include <stdio.h> |
| 40 | #include <string.h> |
| 41 | |
| 42 | #ifndef MLIB_BUF_H |
| 43 | # include "buf.h" |
| 44 | #endif |
| 45 | |
| 46 | #ifndef MLIB_CONTROL_H |
| 47 | # include "control.h" |
| 48 | #endif |
| 49 | |
| 50 | #ifndef MLIB_BUF_H |
| 51 | # include "dstr.h" |
| 52 | #endif |
| 53 | |
| 54 | #ifndef MLIB_GPRINTF_H |
| 55 | # include "gprintf.h" |
| 56 | #endif |
| 57 | |
| 58 | #ifndef MLIB_MACROS_H |
| 59 | # include "macros.h" |
| 60 | #endif |
| 61 | |
| 62 | /*----- Miscellaneous values ----------------------------------------------*/ |
| 63 | |
| 64 | /* These are attached to structures which represent extension points, as a |
| 65 | * way to pass an opaque parameter to whatever things are hooked onto them. |
| 66 | */ |
| 67 | |
| 68 | #define TVEC_MISCSLOTS(_) \ |
| 69 | _(PTR, const void *, p) /* arbitrary pointer */ \ |
| 70 | _(INT, long, i) /* signed integer */ \ |
| 71 | _(UINT, unsigned long, u) /* signed integer */ \ |
| 72 | _(FLT, double, f) /* floating point */ |
| 73 | |
| 74 | union tvec_misc { |
| 75 | #define TVEC_DEFSLOT(tag, ty, slot) ty slot; |
| 76 | TVEC_MISCSLOTS(TVEC_DEFSLOT) |
| 77 | #undef TVEC_DEFSLOT |
| 78 | }; |
| 79 | enum { |
| 80 | #define TVEC_DEFCONST(tag, ty, slot) TVMISC_##tag, |
| 81 | TVEC_MISCSLOTS(TVEC_DEFCONST) |
| 82 | TVMISC_LIMIT |
| 83 | }; |
| 84 | |
| 85 | /*----- Register values ---------------------------------------------------*/ |
| 86 | |
| 87 | /* The framework doesn't have a preconceived idea about what's in a register |
| 88 | * value: it just allocates them and accesses them through the register type |
| 89 | * functions. It doesn't even have a baked-in idea of how big a register |
| 90 | * value is: instead, it gets that via the `regsz' slot in `struct |
| 91 | * tvec_testinfo'. So, as far as the framework is concerned, it's safe to |
| 92 | * add new slots to this union, even if they make the overall union larger. |
| 93 | * This can be done by defining the preprocessor macro `TVEC_REGSLOTS' to be |
| 94 | * a `union' fragment defining any additional union members. |
| 95 | * |
| 96 | * This creates a distinction between code which does and doesn't know the |
| 97 | * size of a register value. Code which does, which typically means the test |
| 98 | * functions, benchmarking setup and teardown functions, and tightly-bound |
| 99 | * runner functions, is free to index the register vectors directly. Code |
| 100 | * which doesn't, which means the framework core itself and output formatting |
| 101 | * machinery, must use the `TVEC_REG' macro (or its more general `TVEC_GREG' |
| 102 | * companion) for indexing register vectors. (In principle, register type |
| 103 | * handlers also fit into this category, but they have no business peering |
| 104 | * into register values other than the one's they're given.) |
| 105 | */ |
| 106 | |
| 107 | union tvec_regval { |
| 108 | /* The actual register value. This is what the type handler sees. |
| 109 | * Additional members can be added by setting `TVEC_REGSLOTS' before |
| 110 | * including this file. |
| 111 | * |
| 112 | * A register value can be /initialized/, which simply means that its |
| 113 | * contents represent a valid value according to its type -- the |
| 114 | * register can be compared, dumped, serialized, parsed into, etc. |
| 115 | * You can't do anything safely to an uninitialized register value |
| 116 | * other than initialize it. |
| 117 | */ |
| 118 | |
| 119 | long i; /* signed integer */ |
| 120 | unsigned long u; /* unsigned integer */ |
| 121 | void *p; /* pointer */ |
| 122 | double f; /* floating point */ |
| 123 | struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */ |
| 124 | struct { char *p; size_t sz; } str; /* text string */ |
| 125 | #ifdef TVEC_REGSLOTS |
| 126 | TVEC_REGSLOTS |
| 127 | #endif |
| 128 | }; |
| 129 | |
| 130 | struct tvec_reg { |
| 131 | /* A register. |
| 132 | * |
| 133 | * Note that all of the registers listed as being used by a |
| 134 | * particular test group are initialized at all times[1] while that |
| 135 | * test group is being processed. (The other register slots don't |
| 136 | * even have types associated with them, so there's nothing useful we |
| 137 | * could do with them.) |
| 138 | * |
| 139 | * The `TVRF_LIVE' flag indicates that the register was assigned a |
| 140 | * value by the test vector file: it's the right thing to use to |
| 141 | * check whether an optional register is actually present. Even |
| 142 | * `dead' registers are still initialized, though. |
| 143 | * |
| 144 | * [1] This isn't quite true. Between individual tests, the |
| 145 | * registers are released and reinitialized in order to reset |
| 146 | * them to known values ready for the next test. But you won't |
| 147 | * see them at this point. |
| 148 | */ |
| 149 | |
| 150 | unsigned f; /* flags */ |
| 151 | #define TVRF_LIVE 1u /* used in current test */ |
| 152 | union tvec_regval v; /* register value */ |
| 153 | }; |
| 154 | |
| 155 | struct tvec_regdef { |
| 156 | /* A register definition. Register definitions list the registers |
| 157 | * which are used by a particular test group (see `struct tvec_test' |
| 158 | * below). |
| 159 | * |
| 160 | * A vector of register definitions is terminated by a definition |
| 161 | * whose `name' slot is null. |
| 162 | */ |
| 163 | |
| 164 | const char *name; /* register name (for input files) */ |
| 165 | unsigned i; /* register index */ |
| 166 | const struct tvec_regty *ty; /* register type descriptor */ |
| 167 | unsigned f; /* flags */ |
| 168 | #define TVRF_OPT 1u /* optional register */ |
| 169 | #define TVRF_ID 2u /* part of test identity */ |
| 170 | union tvec_misc arg; /* extra detail for the type */ |
| 171 | }; |
| 172 | |
| 173 | /* @TVEC_GREG(vec, i, regsz)@ |
| 174 | * |
| 175 | * If @vec@ is a data pointer which happens to contain the address of a |
| 176 | * vector of @struct tvec_reg@ objects, @i@ is an integer, and @regsz@ is the |
| 177 | * size of a @struct tvec_reg@, then this evaluates to the address of the |
| 178 | * @i@th element of the vector. |
| 179 | * |
| 180 | * This is the general tool you need for accessing register vectors when you |
| 181 | * don't have absolute knowledge of the size of a @union tvec_regval@. |
| 182 | * Usually you want to access one of the register vectors in a @struct |
| 183 | * tvec_state@, and @TVEC_REG@ will be more convenient. |
| 184 | */ |
| 185 | #define TVEC_GREG(vec, i, regsz) \ |
| 186 | ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz))) |
| 187 | |
| 188 | /*------ Serialization utilities ------------------------------------------*/ |
| 189 | |
| 190 | /* --- @tvec_serialize@ --- * |
| 191 | * |
| 192 | * Arguments: @const struct tvec_reg *rv@ = vector of registers |
| 193 | * @buf *b@ = buffer to write on |
| 194 | * @const struct tvec_regdef *regs@ = vector of register |
| 195 | * descriptions, terminated by an entry with a null |
| 196 | * @name@ slot |
| 197 | * @unsigned nr@ = number of entries in the @rv@ vector |
| 198 | * @size_t regsz@ = true size of each element of @rv@ |
| 199 | * |
| 200 | * Returns: Zero on success, @-1@ on failure. |
| 201 | * |
| 202 | * Use: Serialize a collection of register values. |
| 203 | * |
| 204 | * The `candidate register definitions' are those entries @r@ in |
| 205 | * the @regs@ vector whose index @r.i@ is strictly less than |
| 206 | * @nr@. The `selected register definitions' are those |
| 207 | * candidate register definitions @r@ for which the indicated |
| 208 | * register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The |
| 209 | * serialized output begins with a header bitmap: if there are |
| 210 | * %$n$% candidate register definitions then the header bitmap |
| 211 | * consists of %$\lceil n/8 \rceil$% bytes. Bits are ordered |
| 212 | * starting from the least significant bit of the first byte, |
| 213 | * end ending at the most significant bit of the final byte. |
| 214 | * The bit corresponding to a candidate register definition is |
| 215 | * set if and only if that register defintion is selected. The |
| 216 | * header bitmap is then followed by the serializations of the |
| 217 | * selected registers -- i.e., for each selected register |
| 218 | * definition @r@, the serialized value of register @rv[r.i]@ -- |
| 219 | * simply concatenated together, with no padding or alignment. |
| 220 | * |
| 221 | * The serialized output is written to the buffer @b@. Failure |
| 222 | * can be caused by running out of buffer space, or a failing |
| 223 | * type handler. |
| 224 | */ |
| 225 | |
| 226 | extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, |
| 227 | const struct tvec_regdef */*regs*/, |
| 228 | unsigned /*nr*/, size_t /*regsz*/); |
| 229 | |
| 230 | /* --- @tvec_deserialize@ --- * |
| 231 | * |
| 232 | * Arguments: @struct tvec_reg *rv@ = vector of registers |
| 233 | * @buf *b@ = buffer to write on |
| 234 | * @const struct tvec_regdef *regs@ = vector of register |
| 235 | * descriptions, terminated by an entry with a null |
| 236 | * @name@ slot |
| 237 | * @unsigned nr@ = number of entries in the @rv@ vector |
| 238 | * @size_t regsz@ = true size of each element of @rv@ |
| 239 | * |
| 240 | * Returns: Zero on success, @-1@ on failure. |
| 241 | * |
| 242 | * Use: Deserialize a collection of register values. |
| 243 | * |
| 244 | * The size of the register vector @nr@ and the register |
| 245 | * definitions @regs@ must match those used when producing the |
| 246 | * serialization. For each serialized register value, |
| 247 | * deserialize and store the value into the appropriate register |
| 248 | * slot, and set the @TVRF_LIVE@ flag on the register. See |
| 249 | * @tvec_serialize@ for a description of the format. |
| 250 | * |
| 251 | * On successful completion, store the address of the first byte |
| 252 | * after the serialized data in @*end_out@ if @end_out@ is not |
| 253 | * null. Failure results only from a failing register type |
| 254 | * handler. |
| 255 | */ |
| 256 | |
| 257 | extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, |
| 258 | const struct tvec_regdef */*regs*/, |
| 259 | unsigned /*nr*/, size_t /*regsz*/); |
| 260 | |
| 261 | /*----- Test state --------------------------------------------------------*/ |
| 262 | |
| 263 | /* Possible test outcomes. */ |
| 264 | enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT }; |
| 265 | |
| 266 | struct tvec_state { |
| 267 | /* The primary state structure for the test vector machinery. */ |
| 268 | |
| 269 | unsigned f; /* flags */ |
| 270 | #define TVSF_SKIP 1u /* skip this test group */ |
| 271 | #define TVSF_OPEN 2u /* test is open */ |
| 272 | #define TVSF_ACTIVE 4u /* test is active */ |
| 273 | #define TVSF_ERROR 8u /* an error occurred */ |
| 274 | #define TVSF_OUTMASK 0xf0 /* test outcome */ |
| 275 | #define TVSF_OUTSHIFT 4 |
| 276 | |
| 277 | /* Registers. Available to execution environments. */ |
| 278 | unsigned nrout, nreg; /* number of output/total registers */ |
| 279 | size_t regsz; /* size of register entry */ |
| 280 | struct tvec_reg *in, *out; /* register vectors */ |
| 281 | |
| 282 | /* Test groups state. Available to output formatters. */ |
| 283 | const struct tvec_test *tests, *test; /* all tests and current test */ |
| 284 | |
| 285 | /* Test scoreboard. Available to output formatters. */ |
| 286 | unsigned curr[TVOUT_LIMIT], all[TVOUT_LIMIT], grps[TVOUT_LIMIT]; |
| 287 | |
| 288 | /* Output machinery. */ |
| 289 | struct tvec_output *output; /* output formatter */ |
| 290 | |
| 291 | /* Input machinery. Available to type parsers. */ |
| 292 | const char *infile; unsigned lno, test_lno; /* input file name, line */ |
| 293 | FILE *fp; /* input file stream */ |
| 294 | }; |
| 295 | |
| 296 | /* @TVEC_REG(tv, vec, i)@ |
| 297 | * |
| 298 | * If @tv@ is a pointer to a @struct tvec_state@, @vec@ is either @in@ or |
| 299 | * @out@, and @i@ is an integer, then this evaluates to the address of the |
| 300 | * @i@th register in the selected vector. |
| 301 | */ |
| 302 | #define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz) |
| 303 | |
| 304 | /*----- Test descriptions -------------------------------------------------*/ |
| 305 | |
| 306 | typedef void tvec_testfn(const struct tvec_reg */*in*/, |
| 307 | struct tvec_reg */*out*/, |
| 308 | void */*ctx*/); |
| 309 | /* A test function. It should read inputs from @in@ and write outputs to |
| 310 | * @out@. The @TVRF_LIVE@ is set on inputs which are actually present, and |
| 311 | * on outputs which are wanted to test. A test function can set additional |
| 312 | * `gratuitous outputs' by setting @TVRF_LIVE@ on them; clearing |
| 313 | * @TVRF_LIVE@ on a wanted output causes a mismatch. |
| 314 | * |
| 315 | * A test function may be called zero or more times by the environment. In |
| 316 | * particular, it may be called multiple times, though usually by prior |
| 317 | * arrangement with the environment. |
| 318 | * |
| 319 | * The @ctx@ is supplied by the environment's @run@ function (see below). |
| 320 | * The default environment calls the test function once, with a null |
| 321 | * @ctx@. There is no expectation that the environment's context has |
| 322 | * anything to do with the test function's context. |
| 323 | */ |
| 324 | |
| 325 | struct tvec_env { |
| 326 | /* A test environment sets things up for and arranges to run the test. |
| 327 | * |
| 328 | * The caller is responsible for allocating storage for the environment's |
| 329 | * context, based on the @ctxsz@ slot, and freeing it later; this space is |
| 330 | * passed in as the @ctx@ parameter to the remaining functions; if @ctxsz@ |
| 331 | * is zero then @ctx@ is null. |
| 332 | */ |
| 333 | |
| 334 | size_t ctxsz; /* environment context size */ |
| 335 | |
| 336 | int (*setup)(struct tvec_state */*tv*/, const struct tvec_env */*env*/, |
| 337 | void */*pctx*/, void */*ctx*/); |
| 338 | /* Initialize the context; called at the start of a test group. Return |
| 339 | * zero on success, or @-1@ on failure. If setup fails, the context is |
| 340 | * freed, and the test group is skipped. |
| 341 | */ |
| 342 | |
| 343 | int (*set)(struct tvec_state */*tv*/, const char */*var*/, |
| 344 | const struct tvec_env */*env*/, void */*ctx*/); |
| 345 | /* Called when the parser finds a %|@var|%' setting to parse and store |
| 346 | * the value. If @setup@ failed, this is still called (so as to skip the |
| 347 | * value), but @ctx@ is null. |
| 348 | */ |
| 349 | |
| 350 | int (*before)(struct tvec_state */*tv*/, void */*ctx*/); |
| 351 | /* Called prior to running a test. This is the right place to act on any |
| 352 | * `%|@var|%' settings. Return zero on success or @-1@ on failure (which |
| 353 | * causes the test to be skipped). This function is never called if the |
| 354 | * test group is skipped. |
| 355 | */ |
| 356 | |
| 357 | void (*run)(struct tvec_state */*tv*/, tvec_testfn */*fn*/, void */*ctx*/); |
| 358 | /* Run the test. It should either call @tvec_skip@, or run @fn@ one or |
| 359 | * more times. In the latter case, it is responsible for checking the |
| 360 | * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will |
| 361 | * check the register values against the supplied test vector, while |
| 362 | * @tvec_check@ does pretty much everything necessary. This function is |
| 363 | * never called if the test group is skipped. |
| 364 | */ |
| 365 | |
| 366 | void (*after)(struct tvec_state */*tv*/, void */*ctx*/); |
| 367 | /* Called after running or skipping a test. Typical actions involve |
| 368 | * resetting whatever things were established by @set@. This function is |
| 369 | * never called if the test group is skipped. |
| 370 | */ |
| 371 | |
| 372 | void (*teardown)(struct tvec_state */*tv*/, void */*ctx*/); |
| 373 | /* Tear down the environment: called at the end of a test group. If the |
| 374 | * setup failed, then this function is still called, with a null @ctx@. |
| 375 | */ |
| 376 | }; |
| 377 | |
| 378 | struct tvec_test { |
| 379 | /* A test description. */ |
| 380 | |
| 381 | const char *name; /* name of the test */ |
| 382 | const struct tvec_regdef *regs; /* descriptions of the registers */ |
| 383 | const struct tvec_env *env; /* environment to run test in */ |
| 384 | tvec_testfn *fn; /* test function */ |
| 385 | }; |
| 386 | |
| 387 | |
| 388 | enum { |
| 389 | /* Register output dispositions. */ |
| 390 | |
| 391 | TVRD_INPUT, /* input-only register */ |
| 392 | TVRD_OUTPUT, /* output-only (input is dead) */ |
| 393 | TVRD_MATCH, /* matching (equal) registers */ |
| 394 | TVRD_FOUND, /* mismatching output register */ |
| 395 | TVRD_EXPECT /* mismatching input register */ |
| 396 | }; |
| 397 | |
| 398 | /* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * |
| 399 | * |
| 400 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 401 | * @const char *excuse@, @va_list ap@ = reason why group skipped |
| 402 | * |
| 403 | * Returns: --- |
| 404 | * |
| 405 | * Use: Skip the current group. This should only be called from a |
| 406 | * test environment @setup@ function; a similar effect occurs if |
| 407 | * the @setup@ function fails. |
| 408 | */ |
| 409 | |
| 410 | extern void PRINTF_LIKE(2, 3) |
| 411 | tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...); |
| 412 | extern void tvec_skipgroup_v(struct tvec_state */*tv*/, |
| 413 | const char */*excuse*/, va_list */*ap*/); |
| 414 | |
| 415 | /* --- @tvec_skip@, @tvec_skip_v@ --- * |
| 416 | * |
| 417 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 418 | * @const char *excuse@, @va_list ap@ = reason why test skipped |
| 419 | * |
| 420 | * Returns: --- |
| 421 | * |
| 422 | * Use: Skip the current test. This should only be called from a |
| 423 | * test environment @run@ function; a similar effect occurs if |
| 424 | * the @before@ function fails. |
| 425 | */ |
| 426 | |
| 427 | extern void PRINTF_LIKE(2, 3) |
| 428 | tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); |
| 429 | extern void tvec_skip_v(struct tvec_state */*tv*/, |
| 430 | const char */*excuse*/, va_list */*ap*/); |
| 431 | |
| 432 | /* --- @tvec_resetoutputs@ --- * |
| 433 | * |
| 434 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 435 | * |
| 436 | * Returns: --- |
| 437 | * |
| 438 | * Use: Reset (releases and reinitializes) the output registers in |
| 439 | * the test state. This is mostly of use to test environment |
| 440 | * @run@ functions, between invocations of the test function. |
| 441 | */ |
| 442 | |
| 443 | extern void tvec_resetoutputs(struct tvec_state */*tv*/); |
| 444 | |
| 445 | /* --- @tvec_checkregs@ --- * |
| 446 | * |
| 447 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 448 | * |
| 449 | * Returns: Zero on success, @-1@ on mismatch. |
| 450 | * |
| 451 | * Use: Compare the active output registers (according to the current |
| 452 | * test group definition) with the corresponding input register |
| 453 | * values. A mismatch occurs if the two values differ |
| 454 | * (according to the register type's @eq@ method), or if the |
| 455 | * input is live but the output is dead. |
| 456 | * |
| 457 | * This function only checks for a mismatch and returns the |
| 458 | * result; it takes no other action. In particular, it doesn't |
| 459 | * report a failure, or dump register values. |
| 460 | */ |
| 461 | |
| 462 | extern int tvec_checkregs(struct tvec_state */*tv*/); |
| 463 | |
| 464 | /* --- @tvec_fail@, @tvec_fail_v@ --- * |
| 465 | * |
| 466 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 467 | * @const char *detail@, @va_list ap@ = description of test |
| 468 | * |
| 469 | * Returns: --- |
| 470 | * |
| 471 | * Use: Report the current test as a failure. This function can be |
| 472 | * called multiple times for a single test, e.g., if the test |
| 473 | * environment's @run@ function invokes the test function |
| 474 | * repeatedly; but a single test that fails repeatedly still |
| 475 | * only counts as a single failure in the statistics. The |
| 476 | * @detail@ string and its format parameters can be used to |
| 477 | * distinguish which of several invocations failed; it can |
| 478 | * safely be left null if the test function is run only once. |
| 479 | */ |
| 480 | |
| 481 | extern void PRINTF_LIKE(2, 3) |
| 482 | tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); |
| 483 | extern void tvec_fail_v(struct tvec_state */*tv*/, |
| 484 | const char */*detail*/, va_list */*ap*/); |
| 485 | |
| 486 | /* --- @tvec_dumpreg@ --- * |
| 487 | * |
| 488 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 489 | * @unsigned disp@ = the register disposition (@TVRD_...@) |
| 490 | * @const union tvec_regval *tv@ = register value |
| 491 | * @const struct tvec_regdef *rd@ = register definition |
| 492 | * |
| 493 | * Returns: --- |
| 494 | * |
| 495 | * Use: Dump a register value to the output. This is the lowest- |
| 496 | * level function for dumping registers, and calls the output |
| 497 | * formatter directly. |
| 498 | * |
| 499 | * Usually @tvec_mismatch@ is much more convenient. Low-level |
| 500 | * access is required for reporting `virtual' registers |
| 501 | * corresponding to test environment settings. |
| 502 | */ |
| 503 | |
| 504 | extern void tvec_dumpreg(struct tvec_state */*tv*/, |
| 505 | unsigned /*disp*/, const union tvec_regval */*rv*/, |
| 506 | const struct tvec_regdef */*rd*/); |
| 507 | |
| 508 | /* --- @tvec_mismatch@ --- * |
| 509 | * |
| 510 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 511 | * @unsigned f@ = flags (@TVMF_...@) |
| 512 | * |
| 513 | * Returns: --- |
| 514 | * |
| 515 | * Use: Dumps registers suitably to report a mismatch. The flag bits |
| 516 | * @TVMF_IN@ and @TVF_OUT@ select input-only and output |
| 517 | * registers. If both are reset then nothing happens. |
| 518 | * Suppressing the output registers may be useful, e.g., if the |
| 519 | * test function crashed rather than returning outputs. |
| 520 | */ |
| 521 | |
| 522 | #define TVMF_IN 1u |
| 523 | #define TVMF_OUT 2u |
| 524 | extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/); |
| 525 | |
| 526 | /* --- @tvec_check@, @tvec_check_v@ --- * |
| 527 | * |
| 528 | * Arguments: @struct tvec_state *tv@ = test-vector state |
| 529 | * @const char *detail@, @va_list ap@ = description of test |
| 530 | * |
| 531 | * Returns: --- |
| 532 | * |
| 533 | * Use: Check the register values, reporting a failure and dumping |
| 534 | * the registers in the event of a mismatch. This just wraps up |
| 535 | * @tvec_checkregs@, @tvec_fail@ and @tvec_mismatch@ in the |
| 536 | * obvious way. |
| 537 | */ |
| 538 | |
| 539 | extern void PRINTF_LIKE(2, 3) |
| 540 | tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); |
| 541 | extern void tvec_check_v(struct tvec_state */*tv*/, |
| 542 | const char */*detail*/, va_list */*ap*/); |
| 543 | |
| 544 | /*----- Session lifecycle -------------------------------------------------*/ |
| 545 | |
| 546 | struct tvec_info { |
| 547 | const struct tvec_test *tests; |
| 548 | unsigned nrout, nreg; |
| 549 | size_t regsz; |
| 550 | }; |
| 551 | |
| 552 | extern void tvec_begin(struct tvec_state */*tv_out*/, |
| 553 | const struct tvec_info */*info*/, |
| 554 | struct tvec_output */*o*/); |
| 555 | extern int tvec_end(struct tvec_state */*tv*/); |
| 556 | |
| 557 | extern int tvec_read(struct tvec_state */*tv*/, |
| 558 | const char */*infile*/, FILE */*fp*/); |
| 559 | |
| 560 | /*----- Benchmarking ------------------------------------------------------*/ |
| 561 | |
| 562 | struct tvec_bench { |
| 563 | struct tvec_env _env; /* benchmarking is an environment */ |
| 564 | struct bench_state **bst; /* benchmark state anchor or null */ |
| 565 | unsigned long niter; /* iterations done per unit */ |
| 566 | int riter, rbuf; /* iterations and buffer registers */ |
| 567 | const struct tvec_env *env; /* environment (per test, not grp) */ |
| 568 | }; |
| 569 | #define TVEC_BENCHENV \ |
| 570 | { sizeof(struct tvec_benchctx), \ |
| 571 | tvec_benchsetup, \ |
| 572 | tvec_benchset, \ |
| 573 | tvec_benchbefore, \ |
| 574 | tvec_benchrun, \ |
| 575 | tvec_benchafter, \ |
| 576 | tvec_benchteardown } |
| 577 | #define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate |
| 578 | |
| 579 | struct tvec_benchctx { |
| 580 | const struct tvec_bench *b; |
| 581 | struct bench_state *bst; |
| 582 | double dflt_target; |
| 583 | void *subctx; |
| 584 | }; |
| 585 | |
| 586 | struct bench_timing; |
| 587 | extern struct bench_state *tvec_benchstate; |
| 588 | |
| 589 | extern int tvec_benchsetup(struct tvec_state */*tv*/, |
| 590 | const struct tvec_env */*env*/, |
| 591 | void */*pctx*/, void */*ctx*/); |
| 592 | extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/, |
| 593 | const struct tvec_env */*env*/, void */*ctx*/); |
| 594 | extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/); |
| 595 | extern void tvec_benchrun(struct tvec_state */*tv*/, |
| 596 | tvec_testfn */*fn*/, void */*ctx*/); |
| 597 | extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/); |
| 598 | extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/); |
| 599 | |
| 600 | extern void tvec_benchreport |
| 601 | (const struct gprintf_ops */*gops*/, void */*go*/, |
| 602 | unsigned /*unit*/, const struct bench_timing */*tm*/); |
| 603 | |
| 604 | /*----- Ad-hoc testing ----------------------------------------------------*/ |
| 605 | |
| 606 | extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); |
| 607 | |
| 608 | extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/, |
| 609 | const char */*file*/, unsigned /*lno*/); |
| 610 | extern void tvec_reportgroup(struct tvec_state */*tv*/); |
| 611 | extern void tvec_endgroup(struct tvec_state */*tv*/); |
| 612 | |
| 613 | #define TVEC_BEGINGROUP(tv, name) \ |
| 614 | do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0) |
| 615 | |
| 616 | #define TVEC_TESTGROUP(tag, tv, name) \ |
| 617 | MC_WRAP(tag##__around, \ |
| 618 | { TVEC_BEGINGROUP(tv, name); }, \ |
| 619 | { tvec_endgroup(tv); }, \ |
| 620 | { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \ |
| 621 | tvec_endgroup(tv); }) |
| 622 | |
| 623 | extern void tvec_begintest(struct tvec_state */*tv*/, |
| 624 | const char */*file*/, unsigned /*lno*/); |
| 625 | extern void tvec_endtest(struct tvec_state */*tv*/); |
| 626 | |
| 627 | #define TVEC_BEGINTEST(tv) \ |
| 628 | do tvec_begintest(tv, __FILE__, __LINE__); while (0) |
| 629 | |
| 630 | #define TVEC_TEST(tag, tv) \ |
| 631 | MC_WRAP(tag##__around, \ |
| 632 | { TVEC_BEGINTEST(tv); }, \ |
| 633 | { tvec_endtest(tv); }, \ |
| 634 | { if ((tv)->f&TVSF_ACTIVE) tvec_skipgroup((tv), 0); \ |
| 635 | tvec_endtest(tv); }) |
| 636 | |
| 637 | extern int PRINTF_LIKE(5, 6) |
| 638 | tvec_claim(struct tvec_state */*tv*/, int /*ok*/, |
| 639 | const char */*file*/, unsigned /*lno*/, |
| 640 | const char */*expr*/, ...); |
| 641 | |
| 642 | #define TVEC_CLAIM(tv, cond) \ |
| 643 | (tvec_claim(tv, !!(cond), __FILE__, __LINE__, #cond " untrue")) |
| 644 | |
| 645 | extern int tvec_claimeq(struct tvec_state */*tv*/, |
| 646 | const struct tvec_regty */*ty*/, |
| 647 | const union tvec_misc */*arg*/, |
| 648 | const char */*file*/, unsigned /*lno*/, |
| 649 | const char */*expr*/); |
| 650 | |
| 651 | /*----- Command-line interface --------------------------------------------*/ |
| 652 | |
| 653 | extern const struct tvec_info tvec_adhocinfo; |
| 654 | |
| 655 | extern void tvec_parseargs(int /*argc*/, char */*argv*/[], |
| 656 | struct tvec_state */*tv_out*/, |
| 657 | int */*argpos_out*/, |
| 658 | const struct tvec_info */*info*/); |
| 659 | |
| 660 | extern int tvec_readstdin(struct tvec_state */*tv*/); |
| 661 | extern int tvec_readfile(struct tvec_state */*tv*/, const char */*file*/); |
| 662 | extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/); |
| 663 | extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/); |
| 664 | |
| 665 | extern int tvec_readargs(int /*argc*/, char */*argv*/[], |
| 666 | struct tvec_state */*tv*/, |
| 667 | int */*argpos_inout*/, const char */*dflt*/); |
| 668 | |
| 669 | extern int tvec_main(int /*argc*/, char */*argv*/[], |
| 670 | const struct tvec_info */*info*/, |
| 671 | const char */*dflt*/); |
| 672 | |
| 673 | /*----- Output formatting -------------------------------------------------*/ |
| 674 | |
| 675 | struct tvec_output { |
| 676 | const struct tvec_outops *ops; |
| 677 | struct tvec_state *tv; |
| 678 | }; |
| 679 | |
| 680 | enum { TVBU_OP, TVBU_BYTE }; |
| 681 | |
| 682 | struct tvec_outops { |
| 683 | void (*error)(struct tvec_output */*o*/, |
| 684 | const char */*msg*/, va_list */*ap*/); |
| 685 | void (*notice)(struct tvec_output */*o*/, |
| 686 | const char */*msg*/, va_list */*ap*/); |
| 687 | |
| 688 | void (*bsession)(struct tvec_output */*o*/); |
| 689 | int (*esession)(struct tvec_output */*o*/); |
| 690 | |
| 691 | void (*bgroup)(struct tvec_output */*o*/); |
| 692 | void (*egroup)(struct tvec_output */*o*/, unsigned /*outcome*/); |
| 693 | void (*skipgroup)(struct tvec_output */*o*/, |
| 694 | const char */*excuse*/, va_list */*ap*/); |
| 695 | |
| 696 | void (*btest)(struct tvec_output */*o*/); |
| 697 | void (*skip)(struct tvec_output */*o*/, |
| 698 | const char */*excuse*/, va_list */*ap*/); |
| 699 | void (*fail)(struct tvec_output */*o*/, |
| 700 | const char */*detail*/, va_list */*ap*/); |
| 701 | void (*dumpreg)(struct tvec_output */*o*/, |
| 702 | unsigned /*disp*/, const union tvec_regval */*rv*/, |
| 703 | const struct tvec_regdef */*rd*/); |
| 704 | void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); |
| 705 | |
| 706 | void (*bbench)(struct tvec_output */*o*/, |
| 707 | const char */*ident*/, unsigned /*unit*/); |
| 708 | void (*ebench)(struct tvec_output */*o*/, |
| 709 | const char */*ident*/, unsigned /*unit*/, |
| 710 | const struct bench_timing */*tm*/); |
| 711 | |
| 712 | void (*destroy)(struct tvec_output */*o*/); |
| 713 | }; |
| 714 | |
| 715 | extern int PRINTF_LIKE(2, 3) |
| 716 | tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); |
| 717 | extern int tvec_error_v(struct tvec_state */*tv*/, |
| 718 | const char */*msg*/, va_list */*ap*/); |
| 719 | |
| 720 | extern void PRINTF_LIKE(2, 3) |
| 721 | tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); |
| 722 | extern void tvec_notice_v(struct tvec_state */*tv*/, |
| 723 | const char */*msg*/, va_list */*ap*/); |
| 724 | |
| 725 | extern int PRINTF_LIKE(3, 4) |
| 726 | tvec_syntax(struct tvec_state */*tv*/, int /*ch*/, |
| 727 | const char */*expect*/, ...); |
| 728 | extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/, |
| 729 | const char */*expect*/, va_list */*ap*/); |
| 730 | |
| 731 | extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); |
| 732 | extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); |
| 733 | extern struct tvec_output *tvec_dfltout(FILE */*fp*/); |
| 734 | |
| 735 | /*----- Input utilities ---------------------------------------------------*/ |
| 736 | |
| 737 | extern void tvec_skipspc(struct tvec_state */*tv*/); |
| 738 | |
| 739 | #define TVFF_ALLOWANY 1u |
| 740 | extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); |
| 741 | |
| 742 | extern int PRINTF_LIKE(4, 5) |
| 743 | tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, |
| 744 | const char */*delims*/, const char */*expect*/, ...); |
| 745 | extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/, |
| 746 | const char */*delims*/, const char */*expect*/, |
| 747 | va_list */*ap*/); |
| 748 | |
| 749 | extern int tvec_nexttoken(struct tvec_state */*tv*/); |
| 750 | |
| 751 | /*----- Register types ----------------------------------------------------*/ |
| 752 | |
| 753 | struct tvec_regty { |
| 754 | void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); |
| 755 | void (*release)(union tvec_regval */*rv*/, |
| 756 | const struct tvec_regdef */*rd*/); |
| 757 | int (*eq)(const union tvec_regval */*rv0*/, |
| 758 | const union tvec_regval */*rv1*/, |
| 759 | const struct tvec_regdef */*rd*/); |
| 760 | int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, |
| 761 | const struct tvec_regdef */*rd*/); |
| 762 | int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, |
| 763 | const struct tvec_regdef */*rd*/); |
| 764 | int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, |
| 765 | struct tvec_state */*tv*/); |
| 766 | void (*dump)(const union tvec_regval */*rv*/, |
| 767 | const struct tvec_regdef */*rd*/, |
| 768 | unsigned /*style*/, |
| 769 | const struct gprintf_ops */*gops*/, void */*go*/); |
| 770 | #define TVSF_COMPACT 1u |
| 771 | }; |
| 772 | |
| 773 | extern const struct tvec_regty tvty_int, tvty_uint; |
| 774 | struct tvec_irange { long min, max; }; |
| 775 | struct tvec_urange { unsigned long min, max; }; |
| 776 | |
| 777 | extern const struct tvec_irange |
| 778 | tvrange_schar, tvrange_short, tvrange_int, tvrange_long, |
| 779 | tvrange_sbyte, tvrange_i16, tvrange_i32; |
| 780 | extern const struct tvec_urange |
| 781 | tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size, |
| 782 | tvrange_byte, tvrange_u16, tvrange_u32; |
| 783 | extern const struct tvec_frange |
| 784 | tvrange_float, tvrange_double; |
| 785 | |
| 786 | extern int tvec_claimeq_int(struct tvec_state */*tv*/, |
| 787 | long /*i0*/, long /*i1*/, |
| 788 | const char */*file*/, unsigned /*lno*/, |
| 789 | const char */*expr*/); |
| 790 | extern int tvec_claimeq_uint(struct tvec_state */*tv*/, |
| 791 | unsigned long /*u0*/, unsigned long /*u1*/, |
| 792 | const char */*file*/, unsigned /*lno*/, |
| 793 | const char */*expr*/); |
| 794 | #define TVEC_CLAIMEQ_INT(tv, i0, i1) \ |
| 795 | (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1)) |
| 796 | #define TVEC_CLAIMEQ_UINT(tv, u0, u1) \ |
| 797 | (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1)) |
| 798 | |
| 799 | extern const struct tvec_regty tvty_float; |
| 800 | struct tvec_floatinfo { |
| 801 | unsigned f; |
| 802 | #define TVFF_NOMIN 1u |
| 803 | #define TVFF_NOMAX 2u |
| 804 | #define TVFF_NANOK 4u |
| 805 | #define TVFF_EXACT 0u |
| 806 | #define TVFF_ABSDELTA 0x10 |
| 807 | #define TVFF_RELDELTA 0x20 |
| 808 | #define TVFF_EQMASK 0xf0 |
| 809 | double min, max; |
| 810 | double delta; |
| 811 | }; |
| 812 | |
| 813 | extern int tvec_claimeqish_float(struct tvec_state */*tv*/, |
| 814 | double /*f0*/, double /*f1*/, |
| 815 | unsigned /*f*/, double /*delta*/, |
| 816 | const char */*file*/, unsigned /*lno*/, |
| 817 | const char */*expr*/); |
| 818 | extern int tvec_claimeq_float(struct tvec_state */*tv*/, |
| 819 | double /*f0*/, double /*f1*/, |
| 820 | const char */*file*/, unsigned /*lno*/, |
| 821 | const char */*expr*/); |
| 822 | #define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta) \ |
| 823 | (tvec_claimeqish_float(tv, f0, f1, f, delta, , __FILE__, __LINE__, \ |
| 824 | #f0 " /= " #f1 " (+/- " #delta ")")) |
| 825 | #define TVEC_CLAIMEQ_FLOAT(tv, f0, f1) \ |
| 826 | (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1)) |
| 827 | |
| 828 | extern const struct tvec_regty tvty_enum; |
| 829 | |
| 830 | #define DEFASSOC(tag_, ty, slot) \ |
| 831 | struct tvec_##slot##assoc { const char *tag; ty slot; }; |
| 832 | TVEC_MISCSLOTS(DEFASSOC) |
| 833 | #undef DEFASSOC |
| 834 | |
| 835 | struct tvec_enuminfo { const char *name; unsigned mv; /* ... */ }; |
| 836 | struct tvec_ienuminfo { |
| 837 | struct tvec_enuminfo _ei; |
| 838 | const struct tvec_iassoc *av; |
| 839 | const struct tvec_irange *ir; |
| 840 | }; |
| 841 | struct tvec_uenuminfo { |
| 842 | struct tvec_enuminfo _ei; |
| 843 | const struct tvec_uassoc *av; |
| 844 | const struct tvec_urange *ur; |
| 845 | }; |
| 846 | struct tvec_fenuminfo { |
| 847 | struct tvec_enuminfo _ei; |
| 848 | const struct tvec_fassoc *av; |
| 849 | const struct tvec_floatinfo *fi; |
| 850 | }; |
| 851 | struct tvec_penuminfo { |
| 852 | struct tvec_enuminfo _ei; |
| 853 | const struct tvec_passoc *av; |
| 854 | }; |
| 855 | |
| 856 | const struct tvec_ienuminfo tvenum_bool; |
| 857 | |
| 858 | #define DECLCLAIM(tag, ty, slot) \ |
| 859 | extern int tvec_claimeq_##slot##enum \ |
| 860 | (struct tvec_state */*tv*/, \ |
| 861 | const struct tvec_##slot##enuminfo */*ei*/, \ |
| 862 | ty /*e0*/, ty /*e1*/, \ |
| 863 | const char */*file*/, unsigned /*lno*/, const char */*expr*/); |
| 864 | TVEC_MISCSLOTS(DECLCLAIM) |
| 865 | #undef DECLCLAIM |
| 866 | #define TVEC_CLAIMEQ_IENUM(tv, ei, e0, e1) \ |
| 867 | (tvec_claimeq_ienum(tv, ei, e0, e1, \ |
| 868 | __FILE__, __LINE__, #e0 " /= " #e1)) |
| 869 | #define TVEC_CLAIMEQ_UENUM(tv, ei, e0, e1) \ |
| 870 | (tvec_claimeq_uenum(tv, ei, e0, e1, \ |
| 871 | __FILE__, __LINE__, #e0 " /= " #e1)) |
| 872 | #define TVEC_CLAIMEQ_FENUM(tv, ei, e0, e1) \ |
| 873 | (tvec_claimeq_fenum(tv, ei, e0, e1, \ |
| 874 | __FILE__, __LINE__, #e0 " /= " #e1)) |
| 875 | #define TVEC_CLAIMEQ_PENUM(tv, ei, e0, e1) \ |
| 876 | (tvec_claimeq_penum(tv, ei, e0, e1, \ |
| 877 | __FILE__, __LINE__, #e0 " /= " #e1)) |
| 878 | |
| 879 | extern const struct tvec_regty tvty_flags; |
| 880 | struct tvec_flag { const char *tag; unsigned long m, v; }; |
| 881 | struct tvec_flaginfo { |
| 882 | const char *name; |
| 883 | const struct tvec_flag *fv; |
| 884 | const struct tvec_urange *range; |
| 885 | }; |
| 886 | |
| 887 | extern int tvec_claimeq_flags(struct tvec_state */*tv*/, |
| 888 | const struct tvec_flaginfo */*fi*/, |
| 889 | unsigned long /*f0*/, unsigned long /*f1*/, |
| 890 | const char */*file*/, unsigned /*lno*/, |
| 891 | const char */*expr*/); |
| 892 | #define TVEC_CLAIMEQ_FLAGS(tv, fi, f0, f1) \ |
| 893 | (tvec_claimeq_flags(tv, fi, f0, f1, \ |
| 894 | __FILE__, __LINE__, #f0 " /= " #f1)) |
| 895 | |
| 896 | extern const struct tvec_regty tvty_char; |
| 897 | extern int tvec_claimeq_char(struct tvec_state */*tv*/, |
| 898 | int /*ch0*/, int /*ch1*/, |
| 899 | const char */*file*/, unsigned /*lno*/, |
| 900 | const char */*expr*/); |
| 901 | #define TVEC_CLAIMEQ_CHAR(tv, c0, c1) \ |
| 902 | (tvec_claimeq_char(tv, c0, c1, __FILE__, __LINE__, #c0 " /= " #c1)) |
| 903 | |
| 904 | extern const struct tvec_regty tvty_string, tvty_bytes; |
| 905 | |
| 906 | extern int tvec_claimeq_string(struct tvec_state */*tv*/, |
| 907 | const char */*p0*/, size_t /*sz0*/, |
| 908 | const char */*p1*/, size_t /*sz1*/, |
| 909 | const char */*file*/, unsigned /*lno*/, |
| 910 | const char */*expr*/); |
| 911 | extern int tvec_claimeq_strz(struct tvec_state */*tv*/, |
| 912 | const char */*p0*/, const char */*p1*/, |
| 913 | const char */*file*/, unsigned /*lno*/, |
| 914 | const char */*expr*/); |
| 915 | extern int tvec_claimeq_bytes(struct tvec_state */*tv*/, |
| 916 | const void */*p0*/, size_t /*sz0*/, |
| 917 | const void */*p1*/, size_t /*sz1*/, |
| 918 | const char */*file*/, unsigned /*lno*/, |
| 919 | const char */*expr*/); |
| 920 | #define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \ |
| 921 | (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ |
| 922 | #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) |
| 923 | #define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \ |
| 924 | (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1)) |
| 925 | #define TVEC_CLAIMEQ_BYTES(tv, p0, sz0, p1, sz1) \ |
| 926 | (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ |
| 927 | #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) |
| 928 | |
| 929 | extern const struct tvec_regty tvty_buffer; |
| 930 | |
| 931 | extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/); |
| 932 | extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); |
| 933 | |
| 934 | /*----- That's all, folks -------------------------------------------------*/ |
| 935 | |
| 936 | #ifdef __cplusplus |
| 937 | } |
| 938 | #endif |
| 939 | |
| 940 | #endif |