From 31d0247cc58abc0b0720aa7e9972011c5a66995c Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 1 Mar 2024 10:05:16 +0000 Subject: [PATCH] @@@ timeout wip --- struct/buf-dstr.c | 16 +- struct/buf-float.c | 234 ++++++++------- struct/buf.c | 167 +++++++---- struct/buf.h | 599 ++++++++++++++++++++++++++++---------- struct/dstr.h | 3 +- struct/t/dstr-putf-test.c | 4 +- sys/mdup.c | 5 +- test/Makefile.am | 1 + test/bench.c | 2 +- test/t/tvec-test.c | 45 ++- test/tests.at | 8 +- test/tvec-bench.c | 16 +- test/tvec-core.c | 721 +++++++++++++++++++++++++++++++++++++++++++--- test/tvec-output.c | 224 ++++++++------ test/tvec-remote.c | 714 +++++++++++++++++++++++++++++++++++---------- test/tvec-timeout.c | 313 ++++++++++++++++++++ test/tvec-types.c | 4 + test/tvec.h | 545 ++++++++++++++++++++--------------- trace/trace.h | 3 +- ui/report.h | 6 +- utils/bits.h | 22 +- utils/gprintf.h | 10 +- 22 files changed, 2781 insertions(+), 881 deletions(-) create mode 100644 test/tvec-timeout.c diff --git a/struct/buf-dstr.c b/struct/buf-dstr.c index 2d91b42..18f98f3 100644 --- a/struct/buf-dstr.c +++ b/struct/buf-dstr.c @@ -33,9 +33,9 @@ /*----- Main code ---------------------------------------------------------*/ -/* --- @buf_getdstr{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_getdstr{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @dstr *d@ = where to put the result * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -54,12 +54,14 @@ return (-1); \ DPUTM(d, p, sz); \ return (0); \ - } + } \ + int (dbuf_getdstr##w)(dbuf *db, dstr *d) \ + { return (dbuf_getdstr##w(db, d)); } BUF_DOSUFFIXES(BUF_GETDSTR_) -/* --- @buf_putdstr{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_putdstr{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @dstr *d@ = string to write * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -69,7 +71,9 @@ BUF_DOSUFFIXES(BUF_GETDSTR_) #define BUF_PUTDSTR_(n, W, w) \ int buf_putdstr##w(buf *b, dstr *d) \ - { return (buf_putmem##w(b, d->buf, d->len)); } + { return (buf_putmem##w(b, d->buf, d->len)); } \ + int (dbuf_putdstr##w)(dbuf *db, dstr *d) \ + { return (dbuf_putdstr##w(db, d)); } BUF_DOSUFFIXES(BUF_PUTDSTR_) /*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/buf-float.c b/struct/buf-float.c index 88d6c44..1f61c02 100644 --- a/struct/buf-float.c +++ b/struct/buf-float.c @@ -63,6 +63,84 @@ * otherwise it's a signalling NaN. */ +/* --- @k64_to_f64@ --- * + * + * Arguments: @double *x_out@ = where to put the result + * @kludge64 k@ = a 64-bit encoding of a floating-point value + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Decodes @k@ as a `binary64' value. See `buf_getf64' for the + * caveats. + */ + +static int k64_to_f64(double *x_out, kludge64 k) +{ + uint32 lo, hi, t; + int s, e; double x; + + /* We're using the IEEE 754 `binary64' format: see `float_to_k64' above. */ + + /* Pick the encoded number apart. */ + hi = HI64(k); lo = LO64(k); + s = (hi >> 31)&1; e = (hi >> 20)&0x07ff; t = hi&0x000fffff; + + /* Deal with various special cases. */ + if (e == 2047) { + /* Maximum exponent indicates (positive or negative) infinity or NaN. */ + + if (t || lo) { + /* It's a NaN. We're not going to be picky about which one. If we + * can't represent it then we'll just have to fail. + */ + +#ifdef NAN + x = NAN; +#else + return (-1); +#endif + } else { + /* It's an infinity. If we don't have one of those to hand, then pick + * something really big. + */ + +#ifdef INFINITY + x = s ? -INFINITY : INFINITY; +#else + x = s ? -DBL_MAX : DBL_MAX; +#endif + } + } else { + /* It's a finite number, though maybe it's weird in some way. */ + + if (e == 0) { + /* Minimum exponent indicates zero or a subnormal number. The + * subnormal exponent is a sentinel value that shouldn't be taken + * literally, so we should fix that. If the number is actually zero + * then the exponent won't matter much so don't bother checking. + */ + + e = 1; + } else { + /* It's a normal number. In which case there's an implicit bit which + * we can now set. + */ + + t |= 0x00100000; + } + + /* All that remains is to stuff the significant and exponent into a + * floating point number. We'll have to do this in pieces, and we'll + * lean on the floating-point machinery to do rounding correctly. + */ + x = ldexp(t, e - 1043) + ldexp(lo, e - 1075); + if (s) x = -x; + } + + /* And we're done. */ + *x_out = x; return (0); +} + /* --- @f64_to_k64@ --- * * * Arguments: @double x@ = a floating-point number @@ -153,113 +231,9 @@ static kludge64 f64_to_k64(double x) SET64(k, hi, lo); return (k); } -/* --- @k64_to_f64@ --- * - * - * Arguments: @double *x_out@ = where to put the result - * @kludge64 k@ = a 64-bit encoding of a floating-point value - * - * Returns: Zero on success, @-1@ on failure. - * - * Use: Decodes @k@ as a `binary64' value. See `buf_getf64' for the - * caveats. - */ - -static int k64_to_f64(double *x_out, kludge64 k) -{ - uint32 lo, hi, t; - int s, e; double x; - - /* We're using the IEEE 754 `binary64' format: see `float_to_k64' above. */ - - /* Pick the encoded number apart. */ - hi = HI64(k); lo = LO64(k); - s = (hi >> 31)&1; e = (hi >> 20)&0x07ff; t = hi&0x000fffff; - - /* Deal with various special cases. */ - if (e == 2047) { - /* Maximum exponent indicates (positive or negative) infinity or NaN. */ - - if (t || lo) { - /* It's a NaN. We're not going to be picky about which one. If we - * can't represent it then we'll just have to fail. - */ - -#ifdef NAN - x = NAN; -#else - return (-1); -#endif - } else { - /* It's an infinity. If we don't have one of those to hand, then pick - * something really big. - */ - -#ifdef INFINITY - x = s ? -INFINITY : INFINITY; -#else - x = s ? -DBL_MAX : DBL_MAX; -#endif - } - } else { - /* It's a finite number, though maybe it's weird in some way. */ - - if (e == 0) { - /* Minimum exponent indicates zero or a subnormal number. The - * subnormal exponent is a sentinel value that shouldn't be taken - * literally, so we should fix that. If the number is actually zero - * then the exponent won't matter much so don't bother checking. - */ - - e = 1; - } else { - /* It's a normal number. In which case there's an implicit bit which - * we can now set. - */ - - t |= 0x00100000; - } - - /* All that remains is to stuff the significant and exponent into a - * floating point number. We'll have to do this in pieces, and we'll - * lean on the floating-point machinery to do rounding correctly. - */ - x = ldexp(t, e - 1043) + ldexp(lo, e - 1075); - if (s) x = -x; - } - - /* And we're done. */ - *x_out = x; return (0); -} - /*----- External functions ------------------------------------------------*/ -/* --- @buf_putf64{,b,l} --- * - * - * Arguments: @buf *b@ = a buffer to write to - * @double x@ = a number to write - * - * Returns: Zero on success, @-1@ on failure (and the buffer is broken). - * - * On C89, this function can't detect negative zero so these - * will be silently written as positive zero. - * - * This function doesn't distinguish NaNs. Any NaN is written - * as a quiet NaN with all payload bits zero. - * - * A finite value with too large a magnitude to be represented - * is rounded to the appropriate infinity. Other finite values - * are rounded as necessary, in the usual IEEE 754 round-to- - * nearest-or-even way. - */ - -int buf_putf64(buf *b, double x) - { return (buf_putk64(b, f64_to_k64(x))); } -int buf_putf64b(buf *b, double x) - { return (buf_putk64b(b, f64_to_k64(x))); } -int buf_putf64l(buf *b, double x) - { return (buf_putk64l(b, f64_to_k64(x))); } - -/* --- @buf_getf64{,b,l} --- * +/* --- @buf_getf64{,l,b} --- * * * Arguments: @buf *b@ = a buffer to read from * @double *x_out@ = where to put the result @@ -285,21 +259,63 @@ int buf_getf64(buf *b, double *x_out) if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); } return (0); } -int buf_getf64b(buf *b, double *x_out) + +int buf_getf64l(buf *b, double *x_out) { kludge64 k; - if (buf_getk64b(b, &k)) return (-1); + if (buf_getk64l(b, &k)) return (-1); if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); } return (0); } -int buf_getf64l(buf *b, double *x_out) + +int buf_getf64b(buf *b, double *x_out) { kludge64 k; - if (buf_getk64l(b, &k)) return (-1); + if (buf_getk64b(b, &k)) return (-1); if (k64_to_f64(x_out, k)) { b->f |= BF_BROKEN; return (-1); } return (0); } +int (dbuf_getf64)(dbuf *db, double *x_out) + { return (dbuf_getf64(db, x_out)); } +int (dbuf_getf64l)(dbuf *db, double *x_out) + { return (dbuf_getf64l(db, x_out)); } +int (dbuf_getf64b)(dbuf *db, double *x_out) + { return (dbuf_getf64b(db, x_out)); } + +/* --- @buf_putf64{,l,b} --- * + * + * Arguments: @buf *b@ = a buffer to write to + * @double x@ = a number to write + * + * Returns: Zero on success, @-1@ on failure (and the buffer is broken). + * + * On C89, this function can't detect negative zero so these + * will be silently written as positive zero. + * + * This function doesn't distinguish NaNs. Any NaN is written + * as a quiet NaN with all payload bits zero. + * + * A finite value with too large a magnitude to be represented + * is rounded to the appropriate infinity. Other finite values + * are rounded as necessary, in the usual IEEE 754 round-to- + * nearest-or-even way. + */ + +int buf_putf64(buf *b, double x) + { return (buf_putk64(b, f64_to_k64(x))); } +int buf_putf64l(buf *b, double x) + { return (buf_putk64l(b, f64_to_k64(x))); } +int buf_putf64b(buf *b, double x) + { return (buf_putk64b(b, f64_to_k64(x))); } + +int (dbuf_putf64)(dbuf *db, double x) + { return (dbuf_putf64(db, x)); } +int (dbuf_putf64l)(dbuf *db, double x) + { return (dbuf_putf64l(db, x)); } +int (dbuf_putf64b)(dbuf *db, double x) + { return (dbuf_putf64b(db, x)); } + /*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/buf.c b/struct/buf.c index 5d1fc27..9efc4fc 100644 --- a/struct/buf.c +++ b/struct/buf.c @@ -37,7 +37,7 @@ /* --- @buf_init@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @void *p@ = pointer to a buffer * @size_t sz@ = size of the buffer * @@ -78,11 +78,7 @@ void dbuf_create(dbuf *db) * Use: Resets a buffer so that it can be written again. */ -void dbuf_reset(dbuf *db) -{ - db->_b.p = db->_b.base; db->_b.limit = db->_b.base + db->sz; - db->_b.f = (db->_b.f&~BF_BROKEN) | BF_WRITE; -} +void dbuf_reset(dbuf *db) { DBRESET(db); } /* --- @dbuf_destroy@ --- * * @@ -99,9 +95,9 @@ void dbuf_destroy(dbuf *db) dbuf_create(db); } -/* --- @buf_break@ --- * +/* --- @{,d}buf_break@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * * Returns: Some negative value. * @@ -109,10 +105,11 @@ void dbuf_destroy(dbuf *db) */ int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); } +int (dbuf_break)(dbuf *db) { return (dbuf_break(db)); } -/* --- @buf_flip@ --- * +/* --- @{,d}buf_flip@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * * Returns: --- * @@ -120,15 +117,12 @@ int buf_break(buf *b) { b->f |= BF_BROKEN; return (-1); } * you can now read from the bit you've written. */ -void buf_flip(buf *b) -{ - b->limit = b->p; b->p = b->base; - b->f &= ~BF_WRITE; -} +void buf_flip(buf *b) { BFLIP(b); } +void (dbuf_flip)(dbuf *db) { dbuf_flip(db); } -/* --- @buf_ensure@ --- * +/* --- @{,d}buf_ensure@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t sz@ = size of data wanted * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -137,10 +131,11 @@ void buf_flip(buf *b) */ int buf_ensure(buf *b, size_t sz) { return (BENSURE(b, sz)); } +int (dbuf_ensure)(dbuf *db, size_t sz) { return (dbuf_ensure(db, sz)); } -/* --- @buf_tryextend@ --- * +/* --- @{,d}buf_tryextend@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t sz@ = size of data wanted * * Returns: Zero if it worked, nonzero if the buffer won't grow. @@ -170,10 +165,12 @@ int buf_tryextend(buf *b, size_t sz) db->_b.limit = db->_b.base + newsz; return (0); } +int (dbuf_tryextend)(dbuf *db, size_t sz) + { return (dbuf_tryextend(db, sz)); } -/* --- @buf_get@ --- * +/* --- @{,d}buf_get@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t sz@ = size of the buffer * * Returns: Pointer to the place in the buffer. @@ -191,10 +188,12 @@ void *buf_get(buf *b, size_t sz) BSTEP(b, sz); return (p); } +void *(dbuf_get)(dbuf *db, size_t sz) + { return (dbuf_get(db, sz)); } -/* --- @buf_put@ --- * +/* --- @{,d}buf_put@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const void *p@ = pointer to a buffer * @size_t sz@ = size of the buffer * @@ -211,10 +210,12 @@ int buf_put(buf *b, const void *p, size_t sz) BSTEP(b, sz); return (0); } +int (dbuf_put)(dbuf *db, const void *p, size_t sz) + { return (dbuf_put(db, p, sz)); } -/* --- @buf_getbyte@ --- * +/* --- @{,d}buf_getbyte@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * * Returns: A byte, or less than zero if there wasn't a byte there. * @@ -227,10 +228,12 @@ int buf_getbyte(buf *b) return (-1); return (*b->p++); } +int (dbuf_getbyte)(dbuf *db) + { return (dbuf_getbyte(db)); } -/* --- @buf_putbyte@ --- * +/* --- @{,d}buf_putbyte@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @int ch@ = byte to write * * Returns: Zero if OK, nonzero if there wasn't enough space. @@ -245,10 +248,12 @@ int buf_putbyte(buf *b, int ch) *b->p++ = ch; return (0); } +int (dbuf_putbyte)(dbuf *db, int ch) + { return (dbuf_putbyte(db, ch)); } -/* --- @buf_getu{8,{16,24,32,64}{,l,b}}@ --- * +/* --- @{,d}buf_getu{8,{16,24,32,64}{,l,b}}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @uintSZ *w@ = where to put the word * * Returns: Zero if OK, or nonzero if there wasn't a word there. @@ -263,12 +268,14 @@ int buf_putbyte(buf *b, int ch) *ww = LOAD##W(b->p); \ BSTEP(b, SZ_##W); \ return (0); \ - } + } \ + int (dbuf_getu##w)(dbuf *db, uint##n *ww) \ + { return (dbuf_getu##w(db, ww)); } DOUINTCONV(BUF_GETU_) -/* --- @buf_getk64{,l,b}@ --- * +/* --- @{,d}buf_getk64{,l,b}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @kludge64 *w@ = where to put the word * * Returns: Zero if OK, or nonzero if there wasn't a word there. @@ -294,9 +301,13 @@ int buf_getk64b(buf *b, kludge64 *w) LOAD64_B_(*w, b->p); BSTEP(b, 8); return (0); } -/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- * +int (dbuf_getk64)(dbuf *db, kludge64 *w) { return (dbuf_getk64(db, w)); } +int (dbuf_getk64l)(dbuf *db, kludge64 *w) { return (dbuf_getk64l(db, w)); } +int (dbuf_getk64b)(dbuf *db, kludge64 *w) { return (dbuf_getk64b(db, w)); } + +/* --- @{,d}buf_putu{8,{16,24,32,64}{,l,b}}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @uintSZ w@ = word to write * * Returns: Zero if OK, or nonzero if there wasn't enough space @@ -311,12 +322,14 @@ int buf_getk64b(buf *b, kludge64 *w) STORE##W(b->p, ww); \ BSTEP(b, SZ_##W); \ return (0); \ - } + } \ + int (dbuf_putu##w)(dbuf *db, uint##n ww) \ + { return (dbuf_putu##w(db, ww)); } DOUINTCONV(BUF_PUTU_) -/* --- @buf_putk64{,l,b}@ --- * +/* --- @{,d}buf_putk64{,l,b}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @kludge64 w@ = word to write * * Returns: Zero if OK, or nonzero if there wasn't enough space @@ -342,9 +355,13 @@ int buf_putk64b(buf *b, kludge64 w) STORE64_B_(b->p, w); BSTEP(b, 8); return (0); } +int (dbuf_putk64)(dbuf *db, kludge64 w) { return (dbuf_putk64(db, w)); } +int (dbuf_putk64l)(dbuf *db, kludge64 w) { return (dbuf_putk64l(db, w)); } +int (dbuf_putk64b)(dbuf *db, kludge64 w) { return (dbuf_putk64b(db, w)); } + /* --- @findz@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t *nn@ = where to put the length * * Returns: Zero if OK, nonzero if there wasn't a null byte to be found. @@ -365,9 +382,9 @@ static int findz(buf *b, size_t *nn) return (0); } -/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_getmem{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t *nn@ = where to put the length * * Returns: Pointer to the buffer data, or null. @@ -385,7 +402,9 @@ static int findz(buf *b, size_t *nn) if (BENSURE(b, sz)) return (0); \ *nn = sz; \ return (buf_get(b, sz)); \ - } + } \ + void *(dbuf_getmem##w)(dbuf *db, size_t *nn) \ + { return (dbuf_getmem##w(db, nn)); } DOUINTCONV(BUF_GETMEM_) void *buf_getmemz(buf *b, size_t *nn) @@ -393,6 +412,8 @@ void *buf_getmemz(buf *b, size_t *nn) if (findz(b, nn)) return (0); return (buf_get(b, *nn)); } +void *(dbuf_getmemz)(dbuf *db, size_t *nn) + { return (dbuf_getmemz(db, nn)); } #ifndef HAVE_UINT64 @@ -414,27 +435,34 @@ void *buf_getmem64(buf *b, size_t *nn) return (getmem_k64(b, nn, k)); } -void *buf_getmem64b(buf *b, size_t *nn) +void *buf_getmem64l(buf *b, size_t *nn) { kludge64 k; - if (buf_getk64b(b, &k)) return (-1); + if (buf_getk64l(b, &k)) return (-1); return (getmem_k64(b, nn, k)); } -void *buf_getmem64l(buf *b, size_t *nn) +void *buf_getmem64b(buf *b, size_t *nn) { kludge64 k; - if (buf_getk64l(b, &k)) return (-1); + if (buf_getk64b(b, &k)) return (-1); return (getmem_k64(b, nn, k)); } +void *(dbuf_getmem64)(dbuf *db, size_t *nn) + { return (dbuf_getmem64(db, nn)); } +void *(dbuf_getmem64l)(dbuf *db, size_t *nn) + { return (dbuf_getmem64l(db, nn)); } +void *(dbuf_getmem64b)(dbuf *db, size_t *nn) + { return (dbuf_getmem64b(db, nn)); } + #endif -/* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_putmem{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const void *p@ = pointer to data to write * @size_t n@ = length to write * @@ -454,7 +482,9 @@ void *buf_getmem64l(buf *b, size_t *nn) if (buf_putu##w(b, sz) || buf_put(b, p, sz)) \ return (-1); \ return (0); \ - } + } \ + int (dbuf_putmem##w)(dbuf *db, const void *p, size_t sz) \ + { return (dbuf_putmem##w(db, p, sz)); } DOUINTCONV(BUF_PUTMEM_) #ifndef HAVE_UINT64 @@ -467,22 +497,29 @@ void *buf_putmem64(buf *b, const void *p, size_t n) return (0); } -void *buf_putmem64b(buf *b, const void *p, size_t n) +void *buf_putmem64l(buf *b, const void *p, size_t n) { kludge64 k; - ASSIGN64(k, n); if (buf_putk64b(b, k) || buf_put(b, p, n)) return (-1); + ASSIGN64(k, n); if (buf_putk64l(b, k) || buf_put(b, p, n)) return (-1); return (0); } -void *buf_putmem64l(buf *b, const void *p, size_t n) +void *buf_putmem64b(buf *b, const void *p, size_t n) { kludge64 k; - ASSIGN64(k, n); if (buf_putk64l(b, k) || buf_put(b, p, n)) return (-1); + ASSIGN64(k, n); if (buf_putk64b(b, k) || buf_put(b, p, n)) return (-1); return (0); } +int (dbuf_putmem64)(dbuf *db, const void *p, size_t n) + { return (dbuf_putmem64(db, p, n)); } +int (dbuf_putmem64l)(dbuf *db, const void *p, size_t n) + { return (dbuf_putmem64l(db, p, n)); } +int (dbuf_putmem64b)(dbuf *db, const void *p, size_t n) + { return (dbuf_putmem64b(db, p, n)); } + #endif int buf_putmemz(buf *b, const void *p, size_t n) @@ -496,10 +533,12 @@ int buf_putmemz(buf *b, const void *p, size_t n) q[n] = 0; return (0); } +int (dbuf_putmemz)(dbuf *db, const void *p, size_t n) + { return (dbuf_putmemz(db, p, n)); } -/* --- @buf_getbuf{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_getbuf{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @buf *bb@ = where to put the result * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -518,12 +557,14 @@ int buf_putmemz(buf *b, const void *p, size_t n) return (-1); \ buf_init(bb, p, sz); \ return (0); \ - } + } \ + int (dbuf_getbuf##w)(dbuf *db, buf *bb) \ + { return (dbuf_getbuf##w(db, bb)); } BUF_DOSUFFIXES(BUF_GETBUF_) -/* --- @buf_putbuf{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_putbuf{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @buf *bb@ = buffer to write * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -533,12 +574,14 @@ BUF_DOSUFFIXES(BUF_GETBUF_) #define BUF_PUTBUF_(n, W, w) \ int buf_putbuf##w(buf *b, buf *bb) \ - { return (buf_putmem##w(b, BBASE(bb), BLEN(bb))); } + { return (buf_putmem##w(b, BBASE(bb), BLEN(bb))); } \ + int (dbuf_putbuf##w)(dbuf *db, buf *bb) \ + { return (dbuf_putbuf##w(db, bb)); } BUF_DOSUFFIXES(BUF_PUTBUF_) -/* --- @buf_putstr{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_putstr{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const char *p@ = string to write * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -548,7 +591,9 @@ BUF_DOSUFFIXES(BUF_PUTBUF_) #define BUF_PUTSTR_(n, W, w) \ int buf_putstr##w(buf *b, const char *p) \ - { return (buf_putmem##w(b, p, strlen(p))); } + { return (buf_putmem##w(b, p, strlen(p))); } \ + int (dbuf_putstr##w)(dbuf *db, const char *p) \ + { return (dbuf_putstr##w(db, p)); } BUF_DOSUFFIXES(BUF_PUTSTR_) /*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/buf.h b/struct/buf.h index 6193cd3..7dd5555 100644 --- a/struct/buf.h +++ b/struct/buf.h @@ -93,14 +93,8 @@ extern const struct gprintf_ops buf_printops; #define BBAD(b) ((b)->f & BF_BROKEN) #define BOK(b) (!BBAD(b)) -#if GCC_VERSION_P(8, 0) -# define BENSURE(b, sz) \ - MUFFLE_WARNINGS_EXPR(GCC_WARNING("-Wint-in-bool-context"), \ - (BBAD(b) ? -1 : (sz) > BLEFT(b) ? buf_tryextend(b, sz) : 0)) -#else -# define BENSURE(b, sz) \ +#define BENSURE(b, sz) \ (BBAD(b) ? -1 : (sz) > BLEFT(b) ? buf_tryextend(b, sz) : 0) -#endif #define DBBASE(db) BBASE(DBUF_BUF(db)) #define DBLIM(db) BLIM(DBUF_BUF(db)) @@ -160,6 +154,11 @@ extern void dbuf_create(dbuf */*db*/); extern void dbuf_reset(dbuf */*db*/); +#define DBRESET(db) do { \ + (db)->_b.p = (db)->_b.base; (db)->_b.limit = (db)->_b.base + (db)->sz; \ + (db)->_b.f = ((db)->_b.f&~BF_BROKEN) | BF_WRITE; \ +} while (0) + /* --- @dbuf_destroy@ --- * * * Arguments: @dbuf *db@ = pointer to a buffer block @@ -171,9 +170,9 @@ extern void dbuf_reset(dbuf */*db*/); extern void dbuf_destroy(dbuf */*db*/); -/* --- @buf_break@ --- * +/* --- @{,d}buf_break@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * * Returns: Some negative value. * @@ -181,10 +180,12 @@ extern void dbuf_destroy(dbuf */*db*/); */ extern int buf_break(buf */*b*/); +extern int dbuf_break(dbuf */*db*/); +#define dbuf_break(db) (buf_break(DBUF_BUF(db))) -/* --- @buf_flip@ --- * +/* --- @{,d}buf_flip@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * * Returns: --- * @@ -193,10 +194,18 @@ extern int buf_break(buf */*b*/); */ extern void buf_flip(buf */*b*/); +extern void dbuf_flip(dbuf */*db*/); +#define dbuf_flip(db) (buf_flip(DBUF_BUF(db))) + +#define BFLIP(b) do { \ + (b)->limit = (b)->p; (b)->p = (b)->base; \ + (b)->f &= ~BF_WRITE; \ +} while (0) +#define DBFLIP(db) BFLIP(DBUF_BUF(db)) -/* --- @buf_ensure@ --- * +/* --- @{,d}buf_ensure@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t sz@ = size of data wanted * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -205,10 +214,12 @@ extern void buf_flip(buf */*b*/); */ extern int buf_ensure(buf */*b*/, size_t /*sz*/); +extern int dbuf_ensure(dbuf */*db*/, size_t /*sz*/); +#define dbuf_ensure(db, sz) (buf_ensure(DBUF_BUF(db), (sz))) -/* --- @buf_tryextend@ --- * +/* --- @{,d}buf_tryextend@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t sz@ = size of data wanted * * Returns: Zero if it worked, nonzero if the buffer won't grow. @@ -218,10 +229,12 @@ extern int buf_ensure(buf */*b*/, size_t /*sz*/); */ extern int buf_tryextend(buf */*b*/, size_t /*sz*/); +extern int dbuf_tryextend(dbuf */*db*/, size_t /*sz*/); +#define dbuf_tryextend(db, sz) (buf_tryextend(DBUF_BUF(db), (sz))) -/* --- @buf_get@ --- * +/* --- @{,d}buf_get@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t sz@ = size of the buffer * * Returns: Pointer to the place in the buffer. @@ -231,10 +244,12 @@ extern int buf_tryextend(buf */*b*/, size_t /*sz*/); */ extern void *buf_get(buf */*b*/, size_t /*sz*/); +extern void *dbuf_get(dbuf */*db*/, size_t /*sz*/); +#define dbuf_get(db, sz) (buf_get(DBUF_BUF(db), (sz))) -/* --- @buf_put@ --- * +/* --- @{,d}buf_put@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const void *p@ = pointer to a buffer * @size_t sz@ = size of the buffer * @@ -244,10 +259,12 @@ extern void *buf_get(buf */*b*/, size_t /*sz*/); */ extern int buf_put(buf */*b*/, const void */*p*/, size_t /*sz*/); +extern int dbuf_put(dbuf */*db*/, const void */*p*/, size_t /*sz*/); +#define dbuf_put(db, p, sz) (buf_put(DBUF_BUF(db), (p), (sz))) -/* --- @buf_getbyte@ --- * +/* --- @{,d}buf_getbyte@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * * Returns: A byte, or less than zero if there wasn't a byte there. * @@ -255,10 +272,12 @@ extern int buf_put(buf */*b*/, const void */*p*/, size_t /*sz*/); */ extern int buf_getbyte(buf */*b*/); +extern int dbuf_getbyte(dbuf */*db*/); +#define dbuf_getbyte(db) (buf_getbyte(DBUF_BUF(db))) -/* --- @buf_putbyte@ --- * +/* --- @{,d}buf_putbyte@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @int ch@ = byte to write * * Returns: Zero if OK, nonzero if there wasn't enough space. @@ -267,10 +286,12 @@ extern int buf_getbyte(buf */*b*/); */ extern int buf_putbyte(buf */*b*/, int /*ch*/); +extern int dbuf_putbyte(dbuf */*db*/, int /*ch*/); +#define dbuf_putbyte(db, ch) (buf_putbyte(DBUF_BUF(db), (ch))) -/* --- @buf_getu{8,{16,24,32,64}{,l,b}}@ --- * +/* --- @{,d}buf_getu{8,{16,24,32,64}{,l,b}}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @uintSZ *w@ = where to put the word * * Returns: Zero if OK, or nonzero if there wasn't a word there. @@ -279,12 +300,28 @@ extern int buf_putbyte(buf */*b*/, int /*ch*/); */ #define BUF_DECL_GETU_(n, W, w) \ - extern int buf_getu##w(buf */*b*/, uint##n */*w*/); + extern int buf_getu##w(buf */*b*/, uint##n */*w*/); \ + extern int dbuf_getu##w(dbuf */*db*/, uint##n */*w*/); DOUINTCONV(BUF_DECL_GETU_) +#define dbuf_getu8(db, w) (buf_getu8(DBUF_BUF(db), (w))) +#define dbuf_getu16(db, w) (buf_getu16(DBUF_BUF(db), (w))) +#define dbuf_getu16l(db, w) (buf_getu16l(DBUF_BUF(db), (w))) +#define dbuf_getu16b(db, w) (buf_getu16b(DBUF_BUF(db), (w))) +#define dbuf_getu24(db, w) (buf_getu24(DBUF_BUF(db), (w))) +#define dbuf_getu24l(db, w) (buf_getu24l(DBUF_BUF(db), (w))) +#define dbuf_getu24b(db, w) (buf_getu24b(DBUF_BUF(db), (w))) +#define dbuf_getu32(db, w) (buf_getu32(DBUF_BUF(db), (w))) +#define dbuf_getu32l(db, w) (buf_getu32l(DBUF_BUF(db), (w))) +#define dbuf_getu32b(db, w) (buf_getu32b(DBUF_BUF(db), (w))) +#ifdef HAVE_UINT64 +# define dbuf_getu64(db, w) (buf_getu64(DBUF_BUF(db), (w))) +# define dbuf_getu64l(db, w) (buf_getu64l(DBUF_BUF(db), (w))) +# define dbuf_getu64b(db, w) (buf_getu64b(DBUF_BUF(db), (w))) +#endif -/* --- @buf_getk64{,l,b}@ --- * +/* --- @{,d}buf_getk64{,l,b}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @kludge64 *w@ = where to put the word * * Returns: Zero if OK, or nonzero if there wasn't a word there. @@ -295,10 +332,16 @@ DOUINTCONV(BUF_DECL_GETU_) extern int buf_getk64(buf */*b*/, kludge64 */*w*/); extern int buf_getk64l(buf */*b*/, kludge64 */*w*/); extern int buf_getk64b(buf */*b*/, kludge64 */*w*/); +extern int dbuf_getk64(dbuf */*db*/, kludge64 */*w*/); +extern int dbuf_getk64l(dbuf */*db*/, kludge64 */*w*/); +extern int dbuf_getk64b(dbuf */*db*/, kludge64 */*w*/); +#define dbuf_getk64(db, w) (buf_getk64(DBUF_BUF(db), (w))) +#define dbuf_getk64l(db, w) (buf_getk64l(DBUF_BUF(db), (w))) +#define dbuf_getk64b(db, w) (buf_getk64b(DBUF_BUF(db), (w))) -/* --- @buf_putu{8,{16,24,32,64}{,l,b}}@ --- * +/* --- @{,d}buf_putu{8,{16,24,32,64}{,l,b}}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @uintSZ w@ = word to write * * Returns: Zero if OK, or nonzero if there wasn't enough space @@ -307,12 +350,28 @@ extern int buf_getk64b(buf */*b*/, kludge64 */*w*/); */ #define BUF_DECL_PUTU_(n, W, w) \ - extern int buf_putu##w(buf */*b*/, uint##n /*w*/); + extern int buf_putu##w(buf */*b*/, uint##n /*w*/); \ + extern int dbuf_putu##w(dbuf */*db*/, uint##n /*w*/); DOUINTCONV(BUF_DECL_PUTU_) +#define dbuf_putu8(db, w) (buf_putu8(DBUF_BUF(db), (w))) +#define dbuf_putu16(db, w) (buf_putu16(DBUF_BUF(db), (w))) +#define dbuf_putu16l(db, w) (buf_putu16l(DBUF_BUF(db), (w))) +#define dbuf_putu16b(db, w) (buf_putu16b(DBUF_BUF(db), (w))) +#define dbuf_putu24(db, w) (buf_putu24(DBUF_BUF(db), (w))) +#define dbuf_putu24l(db, w) (buf_putu24l(DBUF_BUF(db), (w))) +#define dbuf_putu24b(db, w) (buf_putu24b(DBUF_BUF(db), (w))) +#define dbuf_putu32(db, w) (buf_putu32(DBUF_BUF(db), (w))) +#define dbuf_putu32l(db, w) (buf_putu32l(DBUF_BUF(db), (w))) +#define dbuf_putu32b(db, w) (buf_putu32b(DBUF_BUF(db), (w))) +#ifdef HAVE_UINT64 +# define dbuf_putu64(db, w) (buf_putu64(DBUF_BUF(db), (w))) +# define dbuf_putu64l(db, w) (buf_putu64l(DBUF_BUF(db), (w))) +# define dbuf_putu64b(db, w) (buf_putu64b(DBUF_BUF(db), (w))) +#endif -/* --- @buf_putk64{,l,b}@ --- * +/* --- @{,d}buf_putk64{,l,b}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @kludge64 w@ = word to write * * Returns: Zero if OK, or nonzero if there wasn't enough space @@ -323,10 +382,16 @@ DOUINTCONV(BUF_DECL_PUTU_) extern int buf_putk64(buf */*b*/, kludge64 /*w*/); extern int buf_putk64l(buf */*b*/, kludge64 /*w*/); extern int buf_putk64b(buf */*b*/, kludge64 /*w*/); +extern int dbuf_putk64(dbuf */*db*/, kludge64 /*w*/); +extern int dbuf_putk64l(dbuf */*db*/, kludge64 /*w*/); +extern int dbuf_putk64b(dbuf */*db*/, kludge64 /*w*/); +#define dbuf_putk64(db, w) (buf_putk64(DBUF_BUF(db), (w))) +#define dbuf_putk64l(db, w) (buf_putk64l(DBUF_BUF(db), (w))) +#define dbuf_putk64b(db, w) (buf_putk64b(DBUF_BUF(db), (w))) -/* --- @buf_getmem{8,{16,24,32,64}{,l,b},z} --- * +/* --- @{,d}buf_getmem{8,{16,24,32,64}{,l,b},z} --- * * - * Arguments: @buf *b@ = pointer to a buffer block + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @size_t *nn@ = where to put the length * * Returns: Pointer to the buffer data, or null. @@ -337,12 +402,27 @@ extern int buf_putk64b(buf */*b*/, kludge64 /*w*/); */ #define BUF_DECL_GETMEM_(n, W, w) \ - extern void *buf_getmem##w(buf */*b*/, size_t */*nn*/); + extern void *buf_getmem##w(buf */*b*/, size_t */*nn*/); \ + extern void *dbuf_getmem##w(dbuf */*db*/, size_t */*nn*/); BUF_DOSUFFIXES(BUF_DECL_GETMEM_) - -/* --- @buf_putmem{8,{16,24,32,64}{,l,b},z} --- * - * - * Arguments: @buf *b@ = pointer to a buffer block +#define dbuf_getmem8(db, nn) (buf_getmem8(DBUF_BUF(db), (nn))) +#define dbuf_getmem16(db, nn) (buf_getmem16(DBUF_BUF(db), (nn))) +#define dbuf_getmem16l(db, nn) (buf_getmem16l(DBUF_BUF(db), (nn))) +#define dbuf_getmem16b(db, nn) (buf_getmem16b(DBUF_BUF(db), (nn))) +#define dbuf_getmem24(db, nn) (buf_getmem24(DBUF_BUF(db), (nn))) +#define dbuf_getmem24l(db, nn) (buf_getmem24l(DBUF_BUF(db), (nn))) +#define dbuf_getmem24b(db, nn) (buf_getmem24b(DBUF_BUF(db), (nn))) +#define dbuf_getmem32(db, nn) (buf_getmem32(DBUF_BUF(db), (nn))) +#define dbuf_getmem32l(db, nn) (buf_getmem32l(DBUF_BUF(db), (nn))) +#define dbuf_getmem32b(db, nn) (buf_getmem32b(DBUF_BUF(db), (nn))) +#define dbuf_getmem64(db, nn) (buf_getmem64(DBUF_BUF(db), (nn))) +#define dbuf_getmem64l(db, nn) (buf_getmem64l(DBUF_BUF(db), (nn))) +#define dbuf_getmem64b(db, nn) (buf_getmem64b(DBUF_BUF(db), (nn))) +#define dbuf_getmemz(db, nn) (buf_getmemz(DBUF_BUF(db), (nn))) + +/* --- @{,d}buf_putmem{8,{16,24,32,64}{,l,b},z} --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const void *p@ = pointer to data to write * @size_t n@ = length to write * @@ -354,12 +434,27 @@ BUF_DOSUFFIXES(BUF_DECL_GETMEM_) */ #define BUF_DECL_PUTMEM_(n, W, w) \ - extern int buf_putmem##w(buf */*b*/, const void */*p*/, size_t /*nn*/); + extern int buf_putmem##w(buf */*b*/, const void */*p*/, size_t /*nn*/); \ + extern int dbuf_putmem##w(dbuf */*db*/, const void */*p*/, size_t /*nn*/); BUF_DOSUFFIXES(BUF_DECL_PUTMEM_) - -/* --- @buf_getbuf{8,{16,24,32,64}{,l,b},z} --- * - * - * Arguments: @buf *b@ = pointer to a buffer block +#define dbuf_putmem8(db, p, nn) (buf_putmem8(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem16(db, p, nn) (buf_putmem16(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem16l(db, p, nn) (buf_putmem16l(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem16b(db, p, nn) (buf_putmem16b(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem24(db, p, nn) (buf_putmem24(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem24l(db, p, nn) (buf_putmem24l(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem24b(db, p, nn) (buf_putmem24b(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem32(db, p, nn) (buf_putmem32(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem32l(db, p, nn) (buf_putmem32l(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem32b(db, p, nn) (buf_putmem32b(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem64(db, p, nn) (buf_putmem64(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem64l(db, p, nn) (buf_putmem64l(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmem64b(db, p, nn) (buf_putmem64b(DBUF_BUF(db), (p), (nn))) +#define dbuf_putmemz(db, p, nn) (buf_putmemz(DBUF_BUF(db), (p), (nn))) + +/* --- @{,d}buf_getbuf{8,{16,24,32,64}{,l,b},z} --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @buf *bb@ = where to put the result * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -369,12 +464,27 @@ BUF_DOSUFFIXES(BUF_DECL_PUTMEM_) */ #define BUF_DECL_GETBUF_(n, W, w) \ - extern int buf_getbuf##w(buf */*b*/, buf */*bb*/); + extern int buf_getbuf##w(buf */*b*/, buf */*bb*/); \ + extern int dbuf_getbuf##w(dbuf */*db*/, buf */*bb*/); BUF_DOSUFFIXES(BUF_DECL_GETBUF_) - -/* --- @buf_putbuf{8,{16,24,32,64}{,l,b},z} --- * - * - * Arguments: @buf *b@ = pointer to a buffer block +#define dbuf_getbuf8(db, bb) (buf_getbuf8(DBUF_BUF(db), (bb))) +#define dbuf_getbuf16(db, bb) (buf_getbuf16(DBUF_BUF(db), (bb))) +#define dbuf_getbuf16l(db, bb) (buf_getbuf16l(DBUF_BUF(db), (bb))) +#define dbuf_getbuf16b(db, bb) (buf_getbuf16b(DBUF_BUF(db), (bb))) +#define dbuf_getbuf24(db, bb) (buf_getbuf24(DBUF_BUF(db), (bb))) +#define dbuf_getbuf24l(db, bb) (buf_getbuf24l(DBUF_BUF(db), (bb))) +#define dbuf_getbuf24b(db, bb) (buf_getbuf24b(DBUF_BUF(db), (bb))) +#define dbuf_getbuf32(db, bb) (buf_getbuf32(DBUF_BUF(db), (bb))) +#define dbuf_getbuf32l(db, bb) (buf_getbuf32l(DBUF_BUF(db), (bb))) +#define dbuf_getbuf32b(db, bb) (buf_getbuf32b(DBUF_BUF(db), (bb))) +#define dbuf_getbuf64(db, bb) (buf_getbuf64(DBUF_BUF(db), (bb))) +#define dbuf_getbuf64l(db, bb) (buf_getbuf64l(DBUF_BUF(db), (bb))) +#define dbuf_getbuf64b(db, bb) (buf_getbuf64b(DBUF_BUF(db), (bb))) +#define dbuf_getbufz(db, bb) (buf_getbufz(DBUF_BUF(db), (bb))) + +/* --- @{,d}buf_putbuf{8,{16,24,32,64}{,l,b},z} --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @buf *bb@ = buffer to write * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -383,12 +493,27 @@ BUF_DOSUFFIXES(BUF_DECL_GETBUF_) */ #define BUF_DECL_PUTBUF_(n, W, w) \ - extern int buf_putbuf##w(buf */*b*/, buf */*bb*/); + extern int buf_putbuf##w(buf */*b*/, buf */*bb*/); \ + extern int dbuf_putbuf##w(dbuf */*db*/, buf */*bb*/); BUF_DOSUFFIXES(BUF_DECL_PUTBUF_) - -/* --- @buf_getdstr{8,{16,24,32,64}{,l,b},z} --- * - * - * Arguments: @buf *b@ = pointer to a buffer block +#define dbuf_putbuf8(db, bb) (buf_putbuf8(DBUF_BUF(db), (bb))) +#define dbuf_putbuf16(db, bb) (buf_putbuf16(DBUF_BUF(db), (bb))) +#define dbuf_putbuf16l(db, bb) (buf_putbuf16l(DBUF_BUF(db), (bb))) +#define dbuf_putbuf16b(db, bb) (buf_putbuf16b(DBUF_BUF(db), (bb))) +#define dbuf_putbuf24(db, bb) (buf_putbuf24(DBUF_BUF(db), (bb))) +#define dbuf_putbuf24l(db, bb) (buf_putbuf24l(DBUF_BUF(db), (bb))) +#define dbuf_putbuf24b(db, bb) (buf_putbuf24b(DBUF_BUF(db), (bb))) +#define dbuf_putbuf32(db, bb) (buf_putbuf32(DBUF_BUF(db), (bb))) +#define dbuf_putbuf32l(db, bb) (buf_putbuf32l(DBUF_BUF(db), (bb))) +#define dbuf_putbuf32b(db, bb) (buf_putbuf32b(DBUF_BUF(db), (bb))) +#define dbuf_putbuf64(db, bb) (buf_putbuf64(DBUF_BUF(db), (bb))) +#define dbuf_putbuf64l(db, bb) (buf_putbuf64l(DBUF_BUF(db), (bb))) +#define dbuf_putbuf64b(db, bb) (buf_putbuf64b(DBUF_BUF(db), (bb))) +#define dbuf_putbufz(db, bb) (buf_putbufz(DBUF_BUF(db), (bb))) + +/* --- @{,d}buf_getdstr{8,{16,24,32,64}{,l,b},z} --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @dstr *d@ = where to put the result * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -398,12 +523,27 @@ BUF_DOSUFFIXES(BUF_DECL_PUTBUF_) */ #define BUF_DECL_GETDSTR_(n, W, w) \ - extern int buf_getdstr##w(buf */*b*/, dstr */*d*/); + extern int buf_getdstr##w(buf */*b*/, dstr */*d*/); \ + extern int dbuf_getdstr##w(dbuf */*db*/, dstr */*d*/); BUF_DOSUFFIXES(BUF_DECL_GETDSTR_) - -/* --- @buf_putdstr{8,{16,24,32,64}{,l,b},z} --- * - * - * Arguments: @buf *b@ = pointer to a buffer block +#define dbuf_getdstr8(db, d) (buf_getdstr8(DBUF_BUF(db), (d))) +#define dbuf_getdstr16(db, d) (buf_getdstr16(DBUF_BUF(db), (d))) +#define dbuf_getdstr16l(db, d) (buf_getdstr16l(DBUF_BUF(db), (d))) +#define dbuf_getdstr16b(db, d) (buf_getdstr16b(DBUF_BUF(db), (d))) +#define dbuf_getdstr24(db, d) (buf_getdstr24(DBUF_BUF(db), (d))) +#define dbuf_getdstr24l(db, d) (buf_getdstr24l(DBUF_BUF(db), (d))) +#define dbuf_getdstr24b(db, d) (buf_getdstr24b(DBUF_BUF(db), (d))) +#define dbuf_getdstr32(db, d) (buf_getdstr32(DBUF_BUF(db), (d))) +#define dbuf_getdstr32l(db, d) (buf_getdstr32l(DBUF_BUF(db), (d))) +#define dbuf_getdstr32b(db, d) (buf_getdstr32b(DBUF_BUF(db), (d))) +#define dbuf_getdstr64(db, d) (buf_getdstr64(DBUF_BUF(db), (d))) +#define dbuf_getdstr64l(db, d) (buf_getdstr64l(DBUF_BUF(db), (d))) +#define dbuf_getdstr64b(db, d) (buf_getdstr64b(DBUF_BUF(db), (d))) +#define dbuf_getdstrz(db, d) (buf_getdstrz(DBUF_BUF(db), (d))) + +/* --- @{,d}buf_putdstr{8,{16,24,32,64}{,l,b},z} --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @dstr *d@ = string to write * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -412,12 +552,27 @@ BUF_DOSUFFIXES(BUF_DECL_GETDSTR_) */ #define BUF_DECL_PUTDSTR_(n, W, w) \ - extern int buf_putdstr##w(buf */*b*/, dstr */*d*/); + extern int buf_putdstr##w(buf */*b*/, dstr */*d*/); \ + extern int dbuf_putdstr##w(dbuf */*db*/, dstr */*d*/); BUF_DOSUFFIXES(BUF_DECL_PUTDSTR_) - -/* --- @buf_putstr{8,{16,24,32,64}{,l,b},z} --- * - * - * Arguments: @buf *b@ = pointer to a buffer block +#define dbuf_putdstr8(db, d) (buf_putdstr8(DBUF_BUF(db), (d))) +#define dbuf_putdstr16(db, d) (buf_putdstr16(DBUF_BUF(db), (d))) +#define dbuf_putdstr16l(db, d) (buf_putdstr16l(DBUF_BUF(db), (d))) +#define dbuf_putdstr16b(db, d) (buf_putdstr16b(DBUF_BUF(db), (d))) +#define dbuf_putdstr24(db, d) (buf_putdstr24(DBUF_BUF(db), (d))) +#define dbuf_putdstr24l(db, d) (buf_putdstr24l(DBUF_BUF(db), (d))) +#define dbuf_putdstr24b(db, d) (buf_putdstr24b(DBUF_BUF(db), (d))) +#define dbuf_putdstr32(db, d) (buf_putdstr32(DBUF_BUF(db), (d))) +#define dbuf_putdstr32l(db, d) (buf_putdstr32l(DBUF_BUF(db), (d))) +#define dbuf_putdstr32b(db, d) (buf_putdstr32b(DBUF_BUF(db), (d))) +#define dbuf_putdstr64(db, d) (buf_putdstr64(DBUF_BUF(db), (d))) +#define dbuf_putdstr64l(db, d) (buf_putdstr64l(DBUF_BUF(db), (d))) +#define dbuf_putdstr64b(db, d) (buf_putdstr64b(DBUF_BUF(db), (d))) +#define dbuf_putdstrz(db, d) (buf_putdstrz(DBUF_BUF(db), (d))) + +/* --- @{,d}buf_putstr{8,{16,24,32,64}{,l,b},z} --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const char *p@ = string to write * * Returns: Zero if it worked, nonzero if there wasn't enough space. @@ -426,12 +581,55 @@ BUF_DOSUFFIXES(BUF_DECL_PUTDSTR_) */ #define BUF_DECL_PUTSTR_(n, W, w) \ - extern int buf_putstr##w(buf */*b*/, const char */*p*/); + extern int buf_putstr##w(buf */*b*/, const char */*p*/); \ + extern int dbuf_putstr##w(dbuf */*db*/, const char */*p*/); BUF_DOSUFFIXES(BUF_DECL_PUTSTR_) +#define dbuf_putstr8(db, p) (buf_putstr8(DBUF_BUF(db), (p))) +#define dbuf_putstr16(db, p) (buf_putstr16(DBUF_BUF(db), (p))) +#define dbuf_putstr16l(db, p) (buf_putstr16l(DBUF_BUF(db), (p))) +#define dbuf_putstr16b(db, p) (buf_putstr16b(DBUF_BUF(db), (p))) +#define dbuf_putstr24(db, p) (buf_putstr24(DBUF_BUF(db), (p))) +#define dbuf_putstr24l(db, p) (buf_putstr24l(DBUF_BUF(db), (p))) +#define dbuf_putstr24b(db, p) (buf_putstr24b(DBUF_BUF(db), (p))) +#define dbuf_putstr32(db, p) (buf_putstr32(DBUF_BUF(db), (p))) +#define dbuf_putstr32l(db, p) (buf_putstr32l(DBUF_BUF(db), (p))) +#define dbuf_putstr32b(db, p) (buf_putstr32b(DBUF_BUF(db), (p))) +#define dbuf_putstr64(db, p) (buf_putstr64(DBUF_BUF(db), (p))) +#define dbuf_putstr64l(db, p) (buf_putstr64l(DBUF_BUF(db), (p))) +#define dbuf_putstr64b(db, p) (buf_putstr64b(DBUF_BUF(db), (p))) +#define dbuf_putstrz(db, p) (buf_putstrz(DBUF_BUF(db), (p))) + +/* --- @{,d}buf_getf64{,l,b} --- * + * + * Arguments: @buf *b@ = pointer to a bfufer block + * @double *x_out@ = where to put the result + * + * Returns: Zero on success, @-1@ on failure (and the buffer is broken). + * + * If the system supports NaNs, then any encoded NaN is returned + * as the value of @NAN@ in @@; otherwise, this function + * reports failure. + * + * In general, values are rounded to the nearest available + * value, in the way that the system usually rounds. If the + * system doesn't support infinities, then any encoded infinity + * is reported as the largest-possible-magnitude finite value + * instead. + */ + +extern int buf_getf64(buf */*b*/, double */*x_out*/); +extern int buf_getf64l(buf */*b*/, double */*x_out*/); +extern int buf_getf64b(buf */*b*/, double */*x_out*/); +extern int dbuf_getf64(dbuf */*db*/, double */*x_out*/); +extern int dbuf_getf64l(dbuf */*db*/, double */*x_out*/); +extern int dbuf_getf64b(dbuf */*db*/, double */*x_out*/); +#define dbuf_getf64(db, x_out) (buf_getf64(DBUF_BUF(db), (x_out))) +#define dbuf_getf64l(db, x_out) (buf_getf64l(DBUF_BUF(db), (x_out))) +#define dbuf_getf64b(db, x_out) (buf_getf64b(DBUF_BUF(db), (x_out))) -/* --- @buf_putf64{,b,l} --- * +/* --- @{,d}buf_putf64{,l,b} --- * * - * Arguments: @buf *b@ = a buffer to write to + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @double x@ = a number to write * * Returns: Zero on success, @-1@ on failure (and the buffer is broken). @@ -449,45 +647,63 @@ BUF_DOSUFFIXES(BUF_DECL_PUTSTR_) */ extern int buf_putf64(buf */*b*/, double /*x*/); -extern int buf_putf64b(buf */*b*/, double /*x*/); extern int buf_putf64l(buf */*b*/, double /*x*/); - -/* --- @buf_getf64{,b,l} --- * - * - * Arguments: @buf *b@ = a buffer to read from - * @double *x_out@ = where to put the result - * - * Returns: Zero on success, @-1@ on failure (and the buffer is broken). - * - * If the system supports NaNs, then any encoded NaN is returned - * as the value of @NAN@ in @@; otherwise, this function - * reports failure. - * - * In general, values are rounded to the nearest available - * value, in the way that the system usually rounds. If the - * system doesn't support infinities, then any encoded infinity - * is reported as the largest-possible-magnitude finite value - * instead. +extern int buf_putf64b(buf */*b*/, double /*x*/); +extern int dbuf_putf64(dbuf */*db*/, double /*x*/); +extern int dbuf_putf64l(dbuf */*db*/, double /*x*/); +extern int dbuf_putf64b(dbuf */*db*/, double /*x*/); +#define dbuf_putf64(db, x) (buf_putf64(DBUF_BUF(db), (x))) +#define dbuf_putf64l(db, x) (buf_putf64l(DBUF_BUF(db), (x))) +#define dbuf_putf64b(db, x) (buf_putf64b(DBUF_BUF(db), (x))) + +/* --- @{,D}BUF_ENCLOSETAG@ --- * + * + * Arguments: @tag@ = a control-structure macro tag + * @buf *b@ or @dbuf *db@ = pointer to a buffer block + * @size_t mk@ = temporary, used to stash starting offset + * @check@ = expression which is true if the length @_delta@ + * is representable + * @poke@ = function or macro called as @poke(octet *, size_t)@ + * to store the final size at the given address + * @size_t lensz@ = space to leave for the length + * + * Use: This is a statement head. It ensures that there is enough + * space in the buffer, saves the current output offset in @mk, + * and reserves @lensz@ bytes for a length prefix. It then + * executes the @body@, which should contribute zero or more + * further bytes to the buffer. Finally, it invokes @poke@ to + * store the length of the material written by @body@ in the + * space reserved. */ -extern int buf_getf64(buf */*b*/, double *x_/*out*/); -extern int buf_getf64b(buf */*b*/, double *x_/*out*/); -extern int buf_getf64l(buf */*b*/, double *x_/*out*/); - -#define BUF_ENCLOSETAG(tag, buf, mk, check, poke, lensz) \ - MC_BEFORE(tag##__save, \ - { (mk) = BLEN(buf); \ - if (!BENSURE(buf, lensz)) (buf)->p += (lensz); }) \ - MC_AFTER(tag##__poke, \ - { size_t _delta = BLEN(buf) - (mk) + (lensz); \ - assert(check); \ - if (BOK(buf)) poke((buf)->base + (mk), _delta); }) - -#define BUF_ENCLOSEZTAG(tag, buf) \ - MC_AFTER(tag##__zero, { buf_putbyte(buf, 0); }) - -#define BUF_ENCLOSENATIVETAG(tag, buf, mk, W) \ - BUF_ENCLOSETAG(tag, buf, mk, (_delta <= MASK##W), STORE##W, SZ_##W) +#define BUF_ENCLOSETAG(tag, b, mk, check, poke, lensz) \ + MC_BEFORE(tag##__save, { \ + (mk) = BLEN(b); \ + if (!BENSURE(b, lensz)) BSTEP(b, (lensz)); \ + }) \ + MC_AFTER(tag##__poke, { \ + size_t _delta = BLEN(b) - (mk) - (lensz); \ + assert(check); \ + if (BOK(b)) poke(BBASE(b) + (mk), _delta); \ + }) + +#define DBUF_ENCLOSETAG(tag, b, mk, check, poke, lensz) \ + BUF_ENCLOSETAG(tag, DBUF_BUF(b), (mk), (check), poke, (lensz)) + +/* --- @{,D}BUF_ENCLOSE{I,K,Z}TAG@ --- * + * + * Arguments: @tag@ = a control-structure macro tag + * @buf *b@ or @dbuf *db@ = pointer to a buffer block + * @size_t mk@ = temporary, used to stash starting offset + * @W@ = word-size and -order suffix + * + * Use: Specialized versions of @BUF_ENCLOSETAG@ above. + * + * @BUF_ENCLOSEZTAG@ just writes a terminating zero byte. + * @BUF_ENCLOSEITAG@ writes a word with the given size and + * byte ordering. @BUF_ENCLOSEKTAG@ does the same using the + * @kludge64@ machinery. + */ #define BUF_STORESZK64(p, sz) \ do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_((p), _k); } while (0) @@ -495,63 +711,105 @@ extern int buf_getf64l(buf */*b*/, double *x_/*out*/); do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_B_((p), _k); } while (0) #define BUF_STORESZK64_L(p, sz) \ do { kludge64 _k; ASSIGN64(_k, (sz)); STORE64_L_((p), _k); } while (0) -#define BUF_ENCLOSEK64TAG(tag, buf, mk, W) \ - BUF_ENCLOSE(tag, buf, mk, 1, BUF_STORESZK##W, 8) - -#define BUF_ENCLOSE8(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 8) -#define BUF_ENCLOSE16(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 16) -#define BUF_ENCLOSE16_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 16_B) -#define BUF_ENCLOSE16_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 16_L) -#define BUF_ENCLOSE24(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 24) -#define BUF_ENCLOSE24_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 24_B) -#define BUF_ENCLOSE24_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 24_L) -#define BUF_ENCLOSE32(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 32) -#define BUF_ENCLOSE32_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 32_B) -#define BUF_ENCLOSE32_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 32_L) + +#define BUF_ENCLOSEITAG(tag, b, mk, W) \ + BUF_ENCLOSETAG(tag, (b), (mk), (_delta <= MASK##W), STORE##W, SZ_##W) +#define BUF_ENCLOSEKTAG(tag, b, mk, W) \ + BUF_ENCLOSE(tag, (b), (mk), 1, BUF_STORESZK##W, 8) +#define BUF_ENCLOSEZTAG(tag, b) \ + MC_AFTER(tag##__zero, { buf_putbyte((b), 0); }) + +#define DBUF_ENCLOSEITAG(tag, b, mk, W) \ + BUF_ENCLOSEITAG(tag, DBUF_BUF(b), (mk), W) +#define DBUF_ENCLOSEKTAG(tag, b, mk, W) \ + BUF_ENCLOSEKTAG(tag, DBUF_BUF(b), (mk), W) +#define DBUF_ENCLOSEZTAG(tag, b) \ + BUF_ENCLOSEZTAG(tag, DBUF_BUF(b)) + +/* --- @{,D}BUF_ENCLOSE{8,{16,24,32,64}{,_L,_B},Z}@ --- * + * + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block + * @size_t mk@ = temporary, used to stash starting offset + * @W@ = word-size and -order suffix + * + * Use: User versions of @BUF_ENCLOSETAG@; see that macro for + * details. + * + * These are statement heads. They reserve space for a length + * prefix and execute the statement. When the statement + * completes, they patch the length of material written by the + * statement into the reserved space. + */ + +#define BUF_ENCLOSE8(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 8) +#define BUF_ENCLOSE16(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16) +#define BUF_ENCLOSE16_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_B) +#define BUF_ENCLOSE16_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 16_L) +#define BUF_ENCLOSE24(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24) +#define BUF_ENCLOSE24_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_B) +#define BUF_ENCLOSE24_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 24_L) +#define BUF_ENCLOSE32(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32) +#define BUF_ENCLOSE32_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_B) +#define BUF_ENCLOSE32_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 32_L) +#ifdef HAVE_UINT64 +# define BUF_ENCLOSE64(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64) +# define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_B) +# define BUF_ENCLOSE64_L(b, mk) BUF_ENCLOSEITAG(encl, (b), (mk), 64_L) +#else +# define BUF_ENCLOSE64(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64) +# define BUF_ENCLOSE64_B(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_B) +# define BUF_ENCLOSE64_L(b, mk) BUF_ENCLOSEKTAG(encl, (b), (mk), 64_L) +#endif +#define BUF_ENCLOSEZ(b) BUF_ENCLOSEZTAG(encl, (b)) + +#define DBUF_ENCLOSE8(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 8) +#define DBUF_ENCLOSE16(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16) +#define DBUF_ENCLOSE16_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_B) +#define DBUF_ENCLOSE16_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 16_L) +#define DBUF_ENCLOSE24(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24) +#define DBUF_ENCLOSE24_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_B) +#define DBUF_ENCLOSE24_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 24_L) +#define DBUF_ENCLOSE32(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32) +#define DBUF_ENCLOSE32_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32_B) +#define DBUF_ENCLOSE32_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 32_L) #ifdef HAVE_UINT64 -# define BUF_ENCLOSE64(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 64) -# define BUF_ENCLOSE64_B(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 64_B) -# define BUF_ENCLOSE64_L(buf, mk) BUF_ENCLOSENATIVETAG(encl, buf, mk, 64_L) +# define DBUF_ENCLOSE64(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64) +# define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64_B) +# define DBUF_ENCLOSE64_L(db, mk) DBUF_ENCLOSEITAG(encl, (db), (mk), 64_L) #else -# define BUF_ENCLOSE64(buf, mk) BUF_ENCLOSEK64TAG(encl, buf, mk, 64) -# define BUF_ENCLOSE64_B(buf, mk) BUF_ENCLOSEK64TAG(encl, buf, mk, 64_B) -# define BUF_ENCLOSE64_L(buf, mk) BUF_ENCLOSEK64TAG(encl, buf, mk, 64_L) +# define DBUF_ENCLOSE64(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64) +# define DBUF_ENCLOSE64_B(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_B) +# define DBUF_ENCLOSE64_L(db, mk) DBUF_ENCLOSEKTAG(encl, (db), (mk), 64_L) #endif -#define BUF_ENCLOSEZ(buf) BUF_ENCLOSEZTAG(encl, buf) +#define DBUF_ENCLOSEZ(db) DBUF_ENCLOSEZTAG(encl, (db)) -/* --- @buf_vputstrf@ --- * +/* --- @{,d}buf_putstrf@, @{,d}buf_vputstrf@ --- * * - * Arguments: @buf *b@ = pointer to a buffer + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const char *p@ = pointer to @printf@-style format string * @va_list *ap@ = argument handle * * Returns: The number of characters written to the string, or @-1@ on * failure. * - * Use: As for @buf_putstrf@, but may be used as a back-end to user- - * supplied functions with @printf@-style interfaces. - */ - -extern int buf_vputstrf(buf */*b*/, const char */*p*/, va_list */*ap*/); - -/* --- @buf_putstrf@ --- * - * - * Arguments: @buf *b@ = pointer to a buffer - * @const char *p@ = pointer to @printf@-style format string - * @...@ = argument handle - * - * Returns: The number of characters written to the string, or @-1@ on - * failure. - * * Use: Format a string to a buffer. The resulting output is not * null-terminated. */ -extern PRINTF_LIKE(2, 3) int buf_putstrf(buf */*b*/, const char */*p*/, ...); +extern PRINTF_LIKE(2, 3) + int buf_putstrf(buf */*b*/, const char */*p*/, ...); +extern PRINTF_LIKE(2, 3) + int dbuf_putstrf(dbuf */*db*/, const char */*p*/, ...); +#if __STDC__ >= 199901 +# define dbuf_putstrf(db, /*p*/...) (buf_putstr(DBUF_BUF(db), __VA_ARGS__)) +#endif +extern int buf_vputstrf(buf */*b*/, const char */*p*/, va_list */*ap*/); +extern int dbuf_vputstrf(dbuf */*db*/, const char */*p*/, va_list */*ap*/); +#define dbuf_vputstrf(db, p, ap) (buf_vputstrf(DBUF_BUF(db), (p), (ap))) -/* --- @buf_{,v}putstrf{8,{16,24,32,64}{,b,l},z}@ --- * +/* --- @{,d}buf_{,v}putstrf{8,{16,24,32,64}{,b,l},z}@ --- * * - * Arguments: @buf *b@ = pointer to a buffer + * Arguments: @buf *b@ or @dbuf *db@ = pointer to a buffer block * @const char *p@ = pointer to @printf@-style format string * @va_list *ap@ = argument handle * @@ -562,12 +820,59 @@ extern PRINTF_LIKE(2, 3) int buf_putstrf(buf */*b*/, const char */*p*/, ...); */ #define BUF_DECL_PUTSTRF_(n, W, w) \ + extern PRINTF_LIKE(2, 3) \ + int buf_putstrf##w(buf */*b*/, const char */*p*/, ...); \ + extern PRINTF_LIKE(2, 3) \ + int dbuf_putstrf##w(dbuf */*db*/, const char */*p*/, ...); \ extern int buf_vputstrf##w(buf */*b*/, \ const char */*p*/, va_list */*ap*/); \ - extern PRINTF_LIKE(2, 3) \ - int buf_putstrf##w(buf */*b*/, const char */*p*/, ...); + extern int dbuf_vputstrf##w(dbuf */*db*/, \ + const char */*p*/, va_list */*ap*/); BUF_DOSUFFIXES(BUF_DECL_PUTSTRF_) -#undef BUF_DECL_PUTSTRF_ +#if __STDC__ >= 199901 +# define dbuf_putstrf8(db, /*p*/...) \ + (buf_putstrf8(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf16(db, /*p*/...) \ + (buf_putstrf16(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf16l(db, /*p*/...) \ + (buf_putstrf16l(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf16b(db, /*p*/...) \ + (buf_putstrf16b(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf24(db, /*p*/...) \ + (buf_putstrf24(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf24l(db, /*p*/...) \ + (buf_putstrf24l(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf24b(db, /*p*/...) \ + (buf_putstrf24b(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf32(db, /*p*/...) \ + (buf_putstrf32(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf32l(db, /*p*/...) \ + (buf_putstrf32l(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf32b(db, /*p*/...) \ + (buf_putstrf32b(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf64(db, /*p*/...) \ + (buf_putstrf64(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf64l(db, /*p*/...) \ + (buf_putstrf64l(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrf64b(db, /*p*/...) \ + (buf_putstrf64b(DBUF_BUF(db), __VA_ARGS__)) +# define dbuf_putstrfz(db, /*p*/...) \ + (buf_putstrfz(DBUF_BUF(db), __VA_ARGS__)) +#endif +#define dbuf_vputstrf8(db, p, ap) (buf_vputstrf8(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf16(db, p, ap) (buf_vputstrf16(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf16l(db, p, ap) (buf_vputstrf16l(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf16b(db, p, ap) (buf_vputstrf16b(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf24(db, p, ap) (buf_vputstrf24(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf24l(db, p, ap) (buf_vputstrf24l(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf24b(db, p, ap) (buf_vputstrf24b(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf32(db, p, ap) (buf_vputstrf32(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf32l(db, p, ap) (buf_vputstrf32l(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf32b(db, p, ap) (buf_vputstrf32b(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf64(db, p, ap) (buf_vputstrf64(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf64l(db, p, ap) (buf_vputstrf64l(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrf64b(db, p, ap) (buf_vputstrf64b(DBUF_BUF(db), (p), (ap))) +#define dbuf_vputstrfz(db, p, ap) (buf_vputstrfz(DBUF_BUF(db), (p), (ap))) /*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/dstr.h b/struct/dstr.h index fedc48c..93c60e2 100644 --- a/struct/dstr.h +++ b/struct/dstr.h @@ -232,8 +232,7 @@ extern int dstr_vputf(dstr */*d*/, const char */*p*/, va_list */*ap*/); * itself is malicious. */ -extern int PRINTF_LIKE(2, 3) - dstr_putf(dstr */*d*/, const char */*p*/, ...); +extern PRINTF_LIKE(2, 3) int dstr_putf(dstr */*d*/, const char */*p*/, ...); /* --- @dstr_putd@ --- * * diff --git a/struct/t/dstr-putf-test.c b/struct/t/dstr-putf-test.c index 044da71..13fe155 100644 --- a/struct/t/dstr-putf-test.c +++ b/struct/t/dstr-putf-test.c @@ -18,7 +18,7 @@ static char strbuf[1024]; #define TESTGROUP(name) TVEC_TESTGROUP_TAG(grp, &tvstate, name) -static int PRINTF_LIKE(1, 2) format(const char *fmt, ...) +static PRINTF_LIKE(1, 2) int format(const char *fmt, ...) { va_list ap; int n; @@ -30,7 +30,7 @@ static int PRINTF_LIKE(1, 2) format(const char *fmt, ...) return (n); } -static void PRINTF_LIKE(1, 2) prepare(const char *fmt, ...) +static PRINTF_LIKE(1, 2) void prepare(const char *fmt, ...) { va_list ap; int n; diff --git a/sys/mdup.c b/sys/mdup.c index ee7816a..e4cb260 100644 --- a/sys/mdup.c +++ b/sys/mdup.c @@ -137,8 +137,9 @@ enum { #define D(x) x -static void PRINTF_LIKE(4, 5) IGNORABLE - dump(mdup_fdinfo *v, size_t n, mdup_fdinfo *dhead, const char *fmt, ...) +static PRINTF_LIKE(4, 5) IGNORABLE + void dump(mdup_fdinfo *v, size_t n, mdup_fdinfo *dhead, + const char *fmt, ...) { int i; mdup_fdinfo *f, *g; diff --git a/test/Makefile.am b/test/Makefile.am index 7990ab7..5a4b2ff 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -51,6 +51,7 @@ libtest_la_SOURCES += tvec-main.c libtest_la_SOURCES += tvec-bench.c libtest_la_SOURCES += tvec-remote.c +libtest_la_SOURCES += tvec-timeout.c check_PROGRAMS += t/tvec.t t_tvec_t_SOURCES = t/tvec-test.c diff --git a/test/bench.c b/test/bench.c index 9e65a6b..345944b 100644 --- a/test/bench.c +++ b/test/bench.c @@ -69,7 +69,7 @@ struct timer_ops { * Use: Maybe report a debugging message to standard error. */ -static void PRINTF_LIKE(1, 2) debug(const char *fmt, ...) +static PRINTF_LIKE(1, 2) void debug(const char *fmt, ...) { const char *p; va_list ap; diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c index a9ef269..ac316b6 100644 --- a/test/t/tvec-test.c +++ b/test/t/tvec-test.c @@ -27,8 +27,12 @@ /*----- Header files ------------------------------------------------------*/ +#include "tv.h" #include "tvec.h" +#include +#include + /*----- Register definitions ----------------------------------------------*/ static const struct tvec_iassoc ienum_assocs[] = { @@ -173,8 +177,7 @@ static void common_setup(struct tvec_state *tv, tctx->f = 0; } -static int common_set(struct tvec_state *tv, const char *name, - const struct tvec_env *env, void *ctx) +static int common_set(struct tvec_state *tv, const char *name, void *ctx) { struct test_context *tctx = ctx; union tvec_regval rv; @@ -442,7 +445,8 @@ static const struct tvec_env multi_serialize_testenv = { /*----- Crash test --------------------------------------------------------*/ -static void test_crash(const struct tvec_reg *in, struct tvec_reg *out, void *ctx) +static void test_crash(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) { out[RVOUT].v.u = in[RV].v.u; if (in[RSAB].v.i) abort(); @@ -458,6 +462,38 @@ static const struct tvec_regdef crash_regs[] = { TVEC_ENDREGS }; +/*----- Sleep test --------------------------------------------------------*/ + +static void test_sleep(const struct tvec_reg *in, struct tvec_reg *out, + void *ctx) +{ + struct timeval now, when, tv; + int rc; + + rc = gettimeofday(&now, 0); assert(!rc); + tv.tv_sec = in[RV].v.f; tv.tv_usec = 1e6*(in[RV].v.f - tv.tv_sec); + + TV_ADD(&when, &now, &tv); + for (;;) { + rc = select(0, 0, 0, 0, &tv); assert(!rc); + rc = gettimeofday(&now, 0); assert(!rc); + if (TV_CMP(&now, >=, &when)) break; + TV_SUB(&tv, &when, &now); + } + out[RVOUT].v.f = in[RV].v.f; +} + +static const struct tvec_timeoutenv sleep_subenv = + { TVEC_TIMEOUTINIT(ITIMER_REAL, 0.25) }; +static const struct tvec_remotefork sleep_testenv = + { TVEC_REMOTEFORK(&sleep_subenv._env, 0) }; + +static const struct tvec_regdef sleep_regs[] = { + { "time", RV, &tvty_float, 0, { &tvflt_nonneg } }, + { "z", RVOUT, &tvty_float, 0, { &tvflt_nonneg } }, + TVEC_ENDREGS +}; + /*----- Front end ---------------------------------------------------------*/ static const struct tvec_test tests[] = { @@ -473,7 +509,8 @@ static const struct tvec_test tests[] = { TYPEREGS(DEFSINGLE) #undef DEFSINGLE - { "crash", crash_regs, &crash_testenv._env, test_crash } , + { "crash", crash_regs, &crash_testenv._env, test_crash }, + { "sleep", sleep_regs, &sleep_testenv._env, test_sleep }, TVEC_ENDTESTS }; diff --git a/test/tests.at b/test/tests.at index 5c9c069..fbb62bb 100644 --- a/test/tests.at +++ b/test/tests.at @@ -68,14 +68,14 @@ AT_DATA([tv], $1 = $2 ]) check_template([BUILDDIR/t/tvec.t -fh tv], [2], -[tv:$3: $4 -tv:={N:\d+}: required register `$1' not set in test `copy-$1' +[tv:$3: ERROR: $4 +tv:={N:\d+}: ERROR: required register `$1' not set in test `copy-$1' copy-$1 skipped: no tests to run PASSED 0 tests in 0 groups (1 skipped) ERRORS found in input; tests may not have run correctly ], -[tvec.t: tv:$3: $4 -tvec.t: tv:={N:\d+}: required register `$1' not set in test `copy-$1' +[tvec.t: tv:$3: ERROR: $4 +tvec.t: tv:={N:\d+}: ERROR: required register `$1' not set in test `copy-$1' ])]) ###-------------------------------------------------------------------------- diff --git a/test/tvec-bench.c b/test/tvec-bench.c index 8c8cfdf..7086e14 100644 --- a/test/tvec-bench.c +++ b/test/tvec-bench.c @@ -143,7 +143,6 @@ fail_timer: * * 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. @@ -158,11 +157,10 @@ fail_timer: * environment, if there is one. */ -int tvec_benchset(struct tvec_state *tv, const char *var, - const struct tvec_env *env, void *ctx) +int tvec_benchset(struct tvec_state *tv, const char *var, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_benchenv *be = (const struct tvec_benchenv *)env; + const struct tvec_benchenv *be = bc->be; const struct tvec_env *subenv = be->env; union tvec_regval rv; static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 }; @@ -170,11 +168,11 @@ int tvec_benchset(struct tvec_state *tv, const char *var, { "@target", -1, &tvty_float, 0, { &fi } }; if (STRCMP(var, ==, "@target")) { + if (bc->f&TVBF_SETTRG) return (tvec_dupreg(tv, var)); if (tvty_float.parse(&rv, &rd, tv)) return (-1); - if (bc) bc->bst->target_s = rv.f; - return (1); + bc->bst->target_s = rv.f; bc->f |= TVBF_SETTRG; return (1); } else if (subenv && subenv->set) - return (subenv->set(tv, var, subenv, bc->subctx)); + return (subenv->set(tv, var, bc->subctx)); else return (0); } @@ -219,6 +217,7 @@ void tvec_benchafter(struct tvec_state *tv, void *ctx) /* Restore the benchmark state's old target. */ bc->bst->target_s = bc->dflt_target; + bc->f &= ~TVBF_SETTRG; /* Pass the call on to the subsidiary environment. */ if (subenv && subenv->after) subenv->after(tv, bc->subctx); @@ -240,11 +239,10 @@ void tvec_benchteardown(struct tvec_state *tv, void *ctx) const struct tvec_benchenv *be; const struct tvec_env *subenv; - if (!bc) return; be = bc->be; subenv = be->env; /* Tear down any subsidiary environment. */ - if (subenv && subenv->teardown && bc->subctx) + if (subenv && subenv->teardown) subenv->teardown(tv, bc->subctx); /* If the benchmark state was temporary, then dispose of it. */ diff --git a/test/tvec-core.c b/test/tvec-core.c index 36a54ed..f6d7918 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -37,9 +37,31 @@ /*----- Output ------------------------------------------------------------*/ +/* --- @tvec_strlevel@ --- * + * + * Arguments: @unsigned level@ = level code + * + * Returns: A human-readable description. + * + * Use: Converts a level code into something that you can print in a + * message. + */ + +const char *tvec_strlevel(unsigned level) +{ + switch (level) { +#define CASE(tag, name, val) \ + case TVLEV_##tag: return (name); + TVEC_LEVELS(CASE) +#undef CASE + default: return ("??"); + } +} + /* --- @tvec_report@, @tvec_report_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned level@ = severity level (@TVlEV_...@) * @const char *msg@, @va_list ap@ = error message * * Returns: --- @@ -94,12 +116,25 @@ void tvec_notice(struct tvec_state *tv, const char *msg, ...) /*----- Test processing ---------------------------------------------------*/ +/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list *ap@ = reason why skipped + * + * Returns: --- + * + * Use: Skip the current group. This should only be called from a + * test environment @setup@ function; a similar effect occurs if + * the @setup@ function fails. + */ + void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...) { va_list ap; va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap); } + void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap) { if (!(tv->f&TVSF_SKIP)) { @@ -108,17 +143,38 @@ void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap) } } +/* --- @set_outcome@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned out@ = the new outcome + * + * Returns: --- + * + * Use: Sets the outcome bits in the test state flags, and clears + * @TVSF_ACTIVE@. + */ + static void set_outcome(struct tvec_state *tv, unsigned out) -{ - tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK); - tv->f |= out << TVSF_OUTSHIFT; -} + { tv->f = (tv->f&~(TVSF_ACTIVE | TVSF_OUTMASK)) | (out << TVSF_OUTSHIFT); } + +/* --- @tvec_skip@, @tvec_skip_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *excuse@, @va_list *ap@ = reason why test skipped + * + * Returns: --- + * + * Use: Skip the current test. This should only be called from a + * test environment @run@ function; a similar effect occurs if + * the @before@ function fails. + */ void tvec_skip(struct tvec_state *tv, const char *excuse, ...) { va_list ap; va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap); } + void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap) { assert(tv->f&TVSF_ACTIVE); @@ -126,11 +182,29 @@ void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap) tv->output->ops->skip(tv->output, excuse, ap); } +/* --- @tvec_fail@, @tvec_fail_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *detail@, @va_list *ap@ = description of test + * + * Returns: --- + * + * Use: Report the current test as a failure. This function can be + * called multiple times for a single test, e.g., if the test + * environment's @run@ function invokes the test function + * repeatedly; but a single test that fails repeatedly still + * only counts as a single failure in the statistics. The + * @detail@ string and its format parameters can be used to + * distinguish which of several invocations failed; it can + * safely be left null if the test function is run only once. + */ + void tvec_fail(struct tvec_state *tv, const char *detail, ...) { va_list ap; va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap); } + void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap) { assert((tv->f&TVSF_ACTIVE) || @@ -138,11 +212,43 @@ void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap) set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap); } +/* --- @tvec_dumpreg@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned disp@ = the register disposition (@TVRD_...@) + * @const union tvec_regval *tv@ = register value, or null + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Dump a register value to the output. This is the lowest- + * level function for dumping registers, and calls the output + * formatter directly. + * + * Usually @tvec_mismatch@ is much more convenient. Low-level + * access is required for reporting `virtual' registers + * corresponding to test environment settings. + */ + void tvec_dumpreg(struct tvec_state *tv, unsigned disp, const union tvec_regval *r, const struct tvec_regdef *rd) { tv->output->ops->dumpreg(tv->output, disp, r, rd); } +/* --- @tvec_mismatch@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned f@ = flags (@TVMF_...@) + * + * Returns: --- + * + * Use: Dumps registers suitably to report a mismatch. The flag bits + * @TVMF_IN@ and @TVF_OUT@ select input-only and output + * registers. If both are reset then nothing happens. + * Suppressing the output registers may be useful, e.g., if the + * test function crashed rather than returning outputs. + */ + void tvec_mismatch(struct tvec_state *tv, unsigned f) { const struct tvec_regdef *rd; @@ -170,6 +276,20 @@ void tvec_mismatch(struct tvec_state *tv, unsigned f) /*----- Parsing -----------------------------------------------------------*/ +/* --- @tvec_syntax@, @tvec_syntax_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @int ch@ = the character found (in @fgetc@ format) + * @const char *expect@, @va_list ap@ = what was expected + * + * Returns: %$-1$%. + * + * Use: Report a syntax error quoting @ch@ and @expect@. If @ch@ is + * a newline, then back up so that it can be read again (e.g., + * by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also + * advance the line number). + */ + int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...) { va_list ap; @@ -177,6 +297,7 @@ int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...) va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap); return (-1); } + int tvec_syntax_v(struct tvec_state *tv, int ch, const char *expect, va_list *ap) { @@ -196,6 +317,31 @@ int tvec_syntax_v(struct tvec_state *tv, int ch, dstr_destroy(&d); return (-1); } +/* --- @tvec_dupreg@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *name@ = register or pseudoregister name + * + * Returns: %$-1$%. + * + * Use: Reports an error that the register or pseudoregister has been + * assigned already in the current test. + */ + +int tvec_dupreg(struct tvec_state *tv, const char *name) + { return (tvec_error(tv, "register `%s' is already set", name)); } + +/* --- @tvec_skipspc@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Advance over any whitespace characters other than newlines. + * This will stop at `;', end-of-file, or any other kind of + * non-whitespace; and it won't consume a newline. + */ + void tvec_skipspc(struct tvec_state *tv) { int ch; @@ -207,6 +353,24 @@ void tvec_skipspc(struct tvec_state *tv) } } +/* --- @tvec_flushtoeol@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned f@ = flags (@TVFF_...@) + * + * Returns: Zero on success, @-1@ on error. + * + * Use: Advance to the start of the next line, consuming the + * preceding newline. + * + * A syntax error is reported if no newline character is found, + * i.e., the file ends in mid-line. A syntax error is also + * reported if material other than whitespace or a comment is + * found before the end of the line end, and @TVFF_ALLOWANY@ is + * not set in @f@. The line number count is updated + * appropriately. + */ + int tvec_flushtoeol(struct tvec_state *tv, unsigned f) { int ch, rc = 0; @@ -227,6 +391,29 @@ 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 tvec_nexttoken(struct tvec_state *tv) { enum { TAIL, NEWLINE, INDENT, COMMENT }; @@ -261,6 +448,34 @@ int tvec_nexttoken(struct tvec_state *tv) } } +/* --- @tvec_readword@, @tvec_readword_v@ --- * + * + * 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. + */ + int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims, const char *expect, ...) { @@ -272,6 +487,7 @@ int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims, va_end(ap); return (rc); } + int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims, const char *expect, va_list *ap) { @@ -296,24 +512,21 @@ int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims, /*----- Main machinery ----------------------------------------------------*/ struct groupstate { - void *ctx; + void *ctx; /* test environment context */ }; #define GROUPSTATE_INIT { 0 } -void tvec_resetoutputs(struct tvec_state *tv) -{ - const struct tvec_regdef *rd; - struct tvec_reg *r; - - for (rd = tv->test->regs; rd->name; rd++) { - assert(rd->i < tv->nreg); - if (rd->i >= tv->nrout) continue; - r = TVEC_REG(tv, out, rd->i); - rd->ty->release(&r->v, rd); - rd->ty->init(&r->v, rd); - r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE; - } -} +/* --- @tvec_initregs@, @tvec_releaseregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Initialize or release, respectively, the registers required + * by the current test. All of the registers, both input and + * output, are effected. Initialized registers are not marked + * live. + */ void tvec_initregs(struct tvec_state *tv) { @@ -341,6 +554,51 @@ void tvec_releaseregs(struct tvec_state *tv) } } +/* --- @tvec_resetoutputs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Reset (releases and reinitializes) the output registers in + * the test state. This is mostly of use to test environment + * @run@ functions, between invocations of the test function. + * Output registers are marked live if and only if the + * corresponding input register is live. + */ + +void tvec_resetoutputs(struct tvec_state *tv) +{ + const struct tvec_regdef *rd; + struct tvec_reg *r; + + for (rd = tv->test->regs; rd->name; rd++) { + assert(rd->i < tv->nreg); + if (rd->i >= tv->nrout) continue; + r = TVEC_REG(tv, out, rd->i); + rd->ty->release(&r->v, rd); + rd->ty->init(&r->v, rd); + r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE; + } +} + +/* --- @tvec_checkregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, @-1@ on mismatch. + * + * Use: Compare the active output registers (according to the current + * test group definition) with the corresponding input register + * values. A mismatch occurs if the two values differ + * (according to the register type's @eq@ method), or if the + * input is live but the output is dead. + * + * This function only checks for a mismatch and returns the + * result; it takes no other action. In particular, it doesn't + * report a failure, or dump register values. + */ + int tvec_checkregs(struct tvec_state *tv) { const struct tvec_regdef *rd; @@ -356,17 +614,44 @@ int tvec_checkregs(struct tvec_state *tv) return (0); } +/* --- @tvec_check@, @tvec_check_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *detail@, @va_list *ap@ = description of test + * + * Returns: --- + * + * Use: Check the register values, reporting a failure and dumping + * the registers in the event of a mismatch. This just wraps up + * @tvec_checkregs@, @tvec_fail@ and @tvec_mismatch@ in the + * obvious way. + */ + void tvec_check(struct tvec_state *tv, const char *detail, ...) { va_list ap; va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap); } + void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap) { if (tvec_checkregs(tv)) { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } } +/* --- @open_test@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Note that we are now collecting data for a new test. The + * current line number is recorded in @test_lno@. The + * @TVSF_OPEN@ flag is set, and @TVSF_XFAIL@ is reset. + * + * If a test is already open, then do nothing. + */ + static void open_test(struct tvec_state *tv) { if (!(tv->f&TVSF_OPEN)) { @@ -375,12 +660,34 @@ static void open_test(struct tvec_state *tv) } } +/* --- @begin_test@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Note that we're about to try running a state. This is called + * before the test environment's @before@ function. Mark the + * test as active, clear the outcome, and inform the output + * driver. + */ + static void begin_test(struct tvec_state *tv) { tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->output->ops->btest(tv->output); } +/* --- @tvec_endtest@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End an ad-hoc test case, The statistics are updated and the + * outcome is reported to the output formatter. + */ + void tvec_endtest(struct tvec_state *tv) { unsigned out; @@ -394,6 +701,46 @@ void tvec_endtest(struct tvec_state *tv) tv->f &= ~TVSF_OPEN; } +/* --- @tvec_xfail@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Mark the current test as an `expected failure'. That is, the + * behaviour -- if everything works as expected -- is known to + * be incorrect, perhaps as a result of a longstanding bug, so + * calling it a `success' would be inappropriate. A failure, as + * reported by @tvec_fail@, means that the behaviour is not as + * expected -- either the long-standing bug has been fixed, or a + * new bug has been introduced -- so investigation is required. + * + * An expected failure doesn't cause the test group or the + * session as a whole to fail, but it isn't counted as a win + * either. + */ + +void tvec_xfail(struct tvec_state *tv) + { tv->f |= TVSF_XFAIL; } + +/* --- @check@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct groupstate *g@ = private test group state + * + * Returns: --- + * + * Use: Run the current test. + * + * This function is called once the data for a test has been + * collected. It's responsible for checking that all of the + * necessary registers have been assigned values. It marks the + * output registers as live if the corresponding inputs are + * live. It calls the environment's @before@, @run@, and + * @after@ functions if provided; if there is no @run@ function, + * it calls @tvec_check@ to verify the output values. + */ + static void check(struct tvec_state *tv, struct groupstate *g) { const struct tvec_test *t = tv->test; @@ -424,14 +771,27 @@ static void check(struct tvec_state *tv, struct groupstate *g) t->fn(tv->in, tv->out, g->ctx); tvec_check(tv, 0); } - if (env && env->after) env->after(tv, g->ctx); tvec_endtest(tv); } + if (env && env->after) env->after(tv, g->ctx); end: tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv); } +/* --- @begin_test_group@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct groupstate *g@ = private test group state + * + * Returns: --- + * + * Use: Begins a test group. Expects @tv->test@ to have been set + * already. Calls the output driver, initializes the registers, + * clears the @tv->curr@ counters, allocates the environment + * context and calls the environment @setup@ function. + */ + static void begin_test_group(struct tvec_state *tv, struct groupstate *g) { const struct tvec_test *t = tv->test; @@ -439,13 +799,26 @@ static void begin_test_group(struct tvec_state *tv, struct groupstate *g) unsigned i; tv->output->ops->bgroup(tv->output); - tv->f &= ~TVSF_SKIP; + tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE); tvec_initregs(tv); for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0; if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz); if (env && env->setup) env->setup(tv, env, 0, g->ctx); } +/* --- @report_group@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Reports the result of the test group to the output driver. + * + * If all of the tests have been skipped then report this as a + * group skip. Otherwise, determine and report the group + * outcome. + */ + static void report_group(struct tvec_state *tv) { unsigned i, out, nrun; @@ -462,6 +835,24 @@ static void report_group(struct tvec_state *tv) } } +/* --- @end_test_group@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct groupstate *g@ = private test group state + * + * Returns: --- + * + * Use: Handles the end of a test group. Called at the end of the + * input file or when a new test group header is found. + * + * If a test is open, call @check@ to see whether it worked. If + * the test group is not being skipped, report the group + * result. Call the test environment @teardown@ function. Free + * the environment context and release the registers. + * + * If there's no test group active, then nothing happens. + */ + static void end_test_group(struct tvec_state *tv, struct groupstate *g) { const struct tvec_test *t = tv->test; @@ -474,6 +865,20 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g) tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0; } +/* --- @tvec_read@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *infile@ = the name of the input file + * @FILE *fp@ = stream to read from + * + * Returns: Zero on success, @-1@ on error. + * + * Use: Read test vector data from @fp@ and exercise test functions. + * THe return code doesn't indicate test failures: it's only + * concerned with whether there were problems with the input + * file or with actually running the tests. + */ + enum { WIN, XFAIL, NOUT }; static const struct tvec_uassoc outcome_assoc[] = { { "success", WIN }, @@ -499,84 +904,162 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) union tvec_regval rv; int ch, ret, rc = 0; + /* Set the initial location. */ tv->infile = infile; tv->lno = 1; tv->fp = fp; for (;;) { + + /* Get the next character and dispatch. Note that we're always at the + * start of a line here. + */ ch = getc(tv->fp); switch (ch) { case EOF: + /* End of the file. Exit the loop. */ + goto end; case '[': + /* A test group header. */ + + /* End the current group, if there is one. */ end_test_group(tv, &g); + + /* Read the group name. There may be leading and trailing + * whitespace. + */ tvec_skipspc(tv); DRESET(&d); tvec_readword(tv, &d, "];", "group name"); tvec_skipspc(tv); ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'"); + + /* Find the matching test definition. */ for (test = tv->tests; test->name; test++) if (STRCMP(d.buf, ==, test->name)) goto found_test; - tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line; + + /* There wasn't one. Report the error. Muffle errors about the + * contents of this section because they won't be interesting. + */ + tvec_error(tv, "unknown test group `%s'", d.buf); + tv->f |= TVSF_MUFFLE; goto flush_line; + found_test: - tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv, &g); + /* Eat trailing whitespace and comments. */ + tvec_flushtoeol(tv, 0); + + /* Set up the new test group. */ + tv->test = test; begin_test_group(tv, &g); break; case '\n': + /* A newline, so this was a completely empty line. Advance the line + * counter, and run the current test. + */ + tv->lno++; if (tv->f&TVSF_OPEN) check(tv, &g); break; + case ';': + /* A semicolon. Skip the comment. */ + + tvec_flushtoeol(tv, TVFF_ALLOWANY); + break; + default: + /* Something else. */ + if (isspace(ch)) { - tvec_skipspc(tv); - ch = getc(tv->fp); + /* Whitespace. Skip and see what we find. */ + + tvec_skipspc(tv); ch = getc(tv->fp); + + /* If the file ends, then we're done. If we find a comment then we + * skip it. If there's some non-whitespace, then report an error. + * Otherwise the line was effectively blank, so run the test. + */ if (ch == EOF) goto end; else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY); else if (tvec_flushtoeol(tv, 0)) rc = -1; else check(tv, &g); - } else if (ch == ';') - tvec_flushtoeol(tv, TVFF_ALLOWANY); - else { + } else { + /* Some non-whitespace thing. */ + + /* Put the character back and read a word, which ought to be a + * register name. + */ ungetc(ch, tv->fp); DRESET(&d); if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line; + + /* Now there should be a separator. */ tvec_skipspc(tv); ch = getc(tv->fp); if (ch != '=' && ch != ':') { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; } tvec_skipspc(tv); - if (!tv->test) - { tvec_error(tv, "no current test"); goto flush_line; } + + /* If there's no test, then report an error. Set the muffle flag, + * because there's no point in complaining about every assignment + * in this block. + */ + if (!tv->test) { + if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test"); + tv->f |= TVSF_MUFFLE; goto flush_line; + } + + /* Open the test. This is syntactically a stanza of settings, so + * it's fair to report on missing register assignments. + */ + open_test(tv); + if (d.buf[0] == '@') { + /* A special register assignment. */ env = tv->test->env; + + /* See if it's one of the core settings. */ if (STRCMP(d.buf, ==, "@outcome")) { + + /* Parse the value. */ if (tvty_uenum.parse(&rv, &outcome_regdef, tv)) ret = -1; else { - if (rv.u == XFAIL) tv->f |= TVSF_XFAIL; + + /* Act on the result. */ + if (rv.u == XFAIL) tvec_xfail(tv); ret = 1; } - } else if (!env || !env->set) ret = 0; - else ret = env->set(tv, d.buf, env, g.ctx); + } + + /* If there's no environment, this is an unknown setting. */ + else if (!env || !env->set) ret = 0; + + /* Otherwise pass the setting on to the environment. */ + else ret = env->set(tv, d.buf, g.ctx); + + /* If it wasn't understood, report an error and flush. */ if (ret <= 0) { if (!ret) tvec_error(tv, "unknown special register `%s'", d.buf); goto flush_line; } - open_test(tv); } else { + /* A standard register. */ + + /* Find the definition. */ for (rd = tv->test->regs; rd->name; rd++) if (STRCMP(rd->name, ==, d.buf)) goto found_reg; tvec_error(tv, "unknown register `%s' for test `%s'", d.buf, tv->test->name); goto flush_line; + found_reg: - open_test(tv); - tvec_skipspc(tv); + /* Complain if the register is already set. */ r = TVEC_REG(tv, in, rd->i); - if (r->f&TVRF_LIVE) { - tvec_error(tv, "register `%s' already set", rd->name); - goto flush_line; - } + if (r->f&TVRF_LIVE) + { tvec_dupreg(tv, rd->name); goto flush_line; } + + /* Parse a value and mark the register as live. */ if (rd->ty->parse(&r->v, rd, tv)) goto flush_line; r->f |= TVRF_LIVE; } @@ -586,12 +1069,24 @@ int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) continue; flush_line: + /* This is a general parse-failure handler. Skip to the next line and + * remember that things didn't go so well. + */ tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1; } + + /* We reached the end. If that was actually an I/O error then report it. + */ if (ferror(tv->fp)) { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; } + end: + /* Process the final test, if there was one, and wrap up the final + * group. + */ end_test_group(tv, &g); + + /* Clean up. */ tv->infile = 0; tv->fp = 0; dstr_destroy(&d); return (rc); @@ -599,6 +1094,17 @@ end: /*----- Session lifecycle -------------------------------------------------*/ +/* --- @tvec_begin@ --- * + * + * Arguments: @struct tvec_state *tv_out@ = state structure to fill in + * @const struct tvec_config *config@ = test configuration + * @struct tvec_output *o@ = output driver + * + * Returns: --- + * + * Use: Initialize a state structure ready to do some testing. + */ + void tvec_begin(struct tvec_state *tv_out, const struct tvec_config *config, struct tvec_output *o) @@ -625,10 +1131,22 @@ void tvec_begin(struct tvec_state *tv_out, tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out); } +/* --- @tvec_end@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: A proposed exit code. + * + * Use: Conclude testing and suggests an exit code to be returned to + * the calling program. (The exit code comes from the output + * driver's @esession@ method.) + */ + int tvec_end(struct tvec_state *tv) { int rc = tv->output->ops->esession(tv->output); + if (tv->test) tvec_releaseregs(tv); tv->output->ops->destroy(tv->output); xfree(tv->in); xfree(tv->out); return (rc); @@ -636,6 +1154,25 @@ int tvec_end(struct tvec_state *tv) /*----- Serialization and deserialization ---------------------------------*/ +/* --- @tvec_serialize@ --- * + * + * Arguments: @const struct tvec_reg *rv@ = vector of registers + * @buf *b@ = buffer to write on + * @const struct tvec_regdef *regs@ = vector of register + * descriptions, terminated by an entry with a null + * @name@ slot + * @unsigned nr@ = number of entries in the @rv@ vector + * @size_t regsz@ = true size of each element of @rv@ + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Serialize a collection of register values. + * + * The serialized output is written to the buffer @b@. Failure + * can be caused by running out of buffer space, or a failing + * type handler. + */ + int tvec_serialize(const struct tvec_reg *rv, buf *b, const struct tvec_regdef *regs, unsigned nr, size_t regsz) @@ -660,6 +1197,30 @@ int tvec_serialize(const struct tvec_reg *rv, buf *b, return (0); } +/* --- @tvec_deserialize@ --- * + * + * Arguments: @struct tvec_reg *rv@ = vector of registers + * @buf *b@ = buffer to write on + * @const struct tvec_regdef *regs@ = vector of register + * descriptions, terminated by an entry with a null + * @name@ slot + * @unsigned nr@ = number of entries in the @rv@ vector + * @size_t regsz@ = true size of each element of @rv@ + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Deserialize a collection of register values. + * + * The size of the register vector @nr@ and the register + * definitions @regs@ must match those used when producing the + * serialization. For each serialized register value, + * deserialize and store the value into the appropriate register + * slot, and set the @TVRF_LIVE@ flag on the register. See + * @tvec_serialize@ for a description of the format. + * + * Failure results only from a failing register type handler. + */ + int tvec_deserialize(struct tvec_reg *rv, buf *b, const struct tvec_regdef *regs, unsigned nr, size_t regsz) @@ -690,12 +1251,42 @@ static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } }; static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p) { assert(!"fake test function"); } +/* --- @tvec_adhoc@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_test *t@ = space for a test definition + * + * Returns: --- + * + * Use: Begin ad-hoc testing, i.e., without reading a file of + * test-vector data. + * + * The structure at @t@ will be used to record information about + * the tests underway, which would normally come from a static + * test definition. The other functions in this section assume + * that @tvec_adhoc@ has been called. + */ + void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t) { t->name = ""; t->regs = &no_regs; t->env = 0; t->fn = fakefn; tv->tests = t; } +/* --- @tvec_begingroup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *name@ = name for this test group + * @const char *file@, @unsigned @lno@ = calling file and line + * + * Returns: --- + * + * Use: Begin an ad-hoc test group with the given name. The @file@ + * and @lno@ can be anything, but it's usually best if they + * refer to the source code performing the test: the macro + * @TVEC_BEGINGROUP@ does this automatically. + */ + void tvec_begingroup(struct tvec_state *tv, const char *name, const char *file, unsigned lno) { @@ -706,16 +1297,39 @@ void tvec_begingroup(struct tvec_state *tv, const char *name, begin_test_group(tv, 0); } +/* --- @tvec_endgroup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End an ad-hoc test group. The statistics are updated and the + * outcome is reported to the output formatter. + */ + void tvec_endgroup(struct tvec_state *tv) { if (!(tv->f&TVSF_SKIP)) report_group(tv); tv->test = 0; } +/* --- @tvec_begintest@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *file@, @unsigned @lno@ = calling file and line + * + * Returns: --- + * + * Use: Begin an ad-hoc test case. The @file@ and @lno@ can be + * anything, but it's usually best if they refer to the source + * code performing the test: the macro @TVEC_BEGINGROUP@ does + * this automatically. + */ + void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno) { tv->infile = file; tv->lno = tv->test_lno = lno; - begin_test(tv); tv->f |= TVSF_OPEN; + open_test(tv); begin_test(tv); } struct adhoc_claim { @@ -752,6 +1366,31 @@ static void adhoc_claim_teardown(struct tvec_state *tv, if (ck->f&ACF_FRESH) tvec_endtest(tv); } +/* --- @tvec_claim@, @tvec_claim_v@, @TVEC_CLAIM@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @int ok@ = a flag + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *msg@, @va_list *ap@ = message to report on + * failure + * + * Returns: The value @ok@. + * + * Use: Check that a claimed condition holds, as (part of) a test. + * If no test case is underway (i.e., if @TVSF_OPEN@ is reset in + * @tv->f@), then a new test case is begun and ended. The + * @file@ and @lno@ are passed to the output formatter to be + * reported in case of a failure. If @ok@ is nonzero, then + * nothing else happens; so, in particular, if @tvec_claim@ + * established a new test case, then the test case succeeds. If + * @ok@ is zero, then a failure is reported, quoting @msg@. + * + * The @TVEC_CLAIM@ macro is similar, only it (a) identifies the + * file and line number of the call site automatically, and (b) + * implicitly quotes the source text of the @ok@ condition in + * the failure message. + */ + int tvec_claim_v(struct tvec_state *tv, int ok, const char *file, unsigned lno, const char *msg, va_list *ap) diff --git a/test/tvec-output.c b/test/tvec-output.c index c0573f1..5210d11 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -151,8 +151,8 @@ struct layout { * file. Return immediately on error. \ */ \ \ - size_t n = limit - base; \ - if (fwrite(base, 1, n, lyt->fp) < n) return (-1); \ + size_t _n = limit - base; \ + if (_n && fwrite(base, 1, _n, lyt->fp) < _n) return (-1); \ } while (0) #define PUT_CHAR(ch) do { \ @@ -170,8 +170,8 @@ struct layout { #define PUT_SAVED do { \ /* Output the saved trailing blank material in the buffer. */ \ \ - size_t n = lyt->w.len; \ - if (n && fwrite(lyt->w.buf, 1, n, lyt->fp) < n) return (-1); \ + size_t _n = lyt->w.len; \ + if (_n && fwrite(lyt->w.buf, 1, _n, lyt->fp) < _n) return (-1); \ } while (0) #define PUT_PFXINB do { \ @@ -189,6 +189,31 @@ struct layout { DPUTM(&lyt->w, lyt->pfxtail, lyt->pfxlim - lyt->pfxtail); \ } while (0) +/* --- @set_layout_prefix@ --- * + * + * Arguments: @struct layout *lyt@ = layout state + * @const char *prefix@ = new prefix string or null + * + * Returns: --- + * + * Use: Change the configured prefix string. The change takes effect + * at the start of the next line (or the current line if it's + * empty or only whitespace so far). + */ + +static void set_layout_prefix(struct layout *lyt, const char *prefix) +{ + const char *q, *l; + + if (!prefix || !*prefix) + lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0; + else { + lyt->prefix = prefix; + l = lyt->pfxlim = prefix + strlen(prefix); + SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q; + } +} + /* --- @init_layout@ --- * * * Arguments: @struct layout *lyt@ = layout state to initialize @@ -202,21 +227,10 @@ struct layout { static void init_layout(struct layout *lyt, FILE *fp, const char *prefix) { - const char *q, *l; - - /* Basics. */ lyt->fp = fp; lyt->f = LYTF_NEWL; dstr_create(&lyt->w); - - /* Prefix portions. */ - if (!prefix || !*prefix) - lyt->prefix = lyt->pfxtail = lyt->pfxlim = 0; - else { - lyt->prefix = prefix; - l = lyt->pfxlim = prefix + strlen(prefix); - SPLIT_RANGE(q, prefix, l); lyt->pfxtail = q; - } + set_layout_prefix(lyt, prefix); } /* --- @destroy_layout@ --- * @@ -467,6 +481,9 @@ static const struct tvec_outops ..._ops = { #define HA_PLAIN 0 /* nothing special: terminal defaults */ #define HA_LOC (HFG(CYAN)) /* filename or line number */ #define HA_LOCSEP (HFG(BLUE)) /* location separator `:' */ +#define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* error messages */ +#define HA_NOTE (HFG(YELLOW)) /* notices */ +#define HA_UNKLEV (HFG(WHITE) | HBG(RED) | HAF_BOLD) /* unknown level */ #define HA_UNSET (HFG(YELLOW)) /* register not set */ #define HA_FOUND (HFG(RED)) /* incorrect output value */ #define HA_EXPECT (HFG(GREEN)) /* what the value should have been */ @@ -474,7 +491,6 @@ static const struct tvec_outops ..._ops = { #define HA_LOSE (HFG(RED) | HAF_BOLD) /* reporting failure */ #define HA_XFAIL (HFG(BLUE) | HAF_BOLD) /* reporting expected failure */ #define HA_SKIP (HFG(YELLOW)) /* reporting a skipped test/group */ -#define HA_ERR (HFG(MAGENTA) | HAF_BOLD) /* reporting an error */ /* Scoreboard indicators. */ #define HSB_WIN '.' /* test passed */ @@ -656,60 +672,6 @@ static void show_progress(struct human_output *h) } } -/* --- @report_location@ --- * - * - * Arguments: @struct human_output *h@ = output state - * @FILE *fp@ = stream to write the location on - * @const char *file@ = filename - * @unsigned lno@ = line number - * - * Returns: --- - * - * Use: Print the filename and line number to the output stream @fp@. - * Also, if appropriate, print interleaved highlighting control - * codes to our usual output stream. If @file@ is null then do - * nothing. - */ - -static void report_location(struct human_output *h, FILE *fp, - const char *file, unsigned lno) -{ - unsigned f = 0; -#define f_flush 1u - - /* We emit highlighting if @fp@ is our usual output stream, or the - * duplicate-errors flag is clear indicating that (we assume) they're - * secretly going to the same place anyway. If they're different streams, - * though, we have to be careful to keep the highlighting and the actual - * text synchronized. - */ - - if (!file) - /* nothing to do */; - else if (fp != h->lyt.fp && (h->f&HOF_DUPERR)) - fprintf(fp, "%s:%u: ", file, lno); - else { - if (fp != h->lyt.fp) f |= f_flush; - -#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0) - - setattr(h, HA_LOC); FLUSH(h->lyt.fp); - fputs(file, fp); FLUSH(fp); - setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp); - fputc(':', fp); FLUSH(fp); - setattr(h, HA_LOC); FLUSH(h->lyt.fp); - fprintf(fp, "%u", lno); FLUSH(fp); - setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp); - fputc(':', fp); FLUSH(fp); - setattr(h, HA_PLAIN); FLUSH(h->lyt.fp); - fputc(' ', fp); - -#undef FLUSH - } - -#undef f_flush -} - /* --- @human_writech@, @human_write@, @human_writef@ --- * * * Arguments: @void *go@ = output sink, secretly a @struct human_output@ @@ -809,7 +771,7 @@ static void report_unusual(struct human_output *h, * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct * human_output@ * - * Returns: Suggested exit status. + * Returns: Suggested exit code. * * Use: End a test session. * @@ -960,6 +922,60 @@ static void human_egroup(struct tvec_output *o) static void human_btest(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; show_progress(h); } +/* --- @report_location@ --- * + * + * Arguments: @struct human_output *h@ = output state + * @FILE *fp@ = stream to write the location on + * @const char *file@ = filename + * @unsigned lno@ = line number + * + * Returns: --- + * + * Use: Print the filename and line number to the output stream @fp@. + * Also, if appropriate, print interleaved highlighting control + * codes to our usual output stream. If @file@ is null then do + * nothing. + */ + +static void report_location(struct human_output *h, FILE *fp, + const char *file, unsigned lno) +{ + unsigned f = 0; +#define f_flush 1u + + /* We emit highlighting if @fp@ is our usual output stream, or the + * duplicate-errors flag is clear indicating that (we assume) they're + * secretly going to the same place anyway. If they're different streams, + * though, we have to be careful to keep the highlighting and the actual + * text synchronized. + */ + + if (!file) + /* nothing to do */; + else if (fp != h->lyt.fp && (h->f&HOF_DUPERR)) + fprintf(fp, "%s:%u: ", file, lno); + else { + if (fp != h->lyt.fp) f |= f_flush; + +#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0) + + setattr(h, HA_LOC); FLUSH(h->lyt.fp); + fputs(file, fp); FLUSH(fp); + setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp); + fputc(':', fp); FLUSH(fp); + setattr(h, HA_LOC); FLUSH(h->lyt.fp); + fprintf(fp, "%u", lno); FLUSH(fp); + setattr(h, HA_LOCSEP); FLUSH(h->lyt.fp); + fputc(':', fp); FLUSH(fp); + setattr(h, HA_PLAIN); FLUSH(h->lyt.fp); + fputc(' ', fp); + +#undef FLUSH + } + +#undef f_flush +} + /* --- @human_outcome@, @human_skip@, @human_fail@ --- * * * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct @@ -1116,7 +1132,7 @@ static void human_ebench(struct tvec_output *o, { struct human_output *h = (struct human_output *)o; - tvec_benchreport(&human_printops, h->lyt.fp, unit, tm); + tvec_benchreport(&human_printops, h, unit, tm); fputc('\n', h->lyt.fp); } @@ -1142,20 +1158,43 @@ static void human_report(struct tvec_output *o, unsigned level, { struct human_output *h = (struct human_output *)o; struct tvec_state *tv = h->tv; + const char *levstr; unsigned levattr; dstr d = DSTR_INIT; + unsigned f = 0; +#define f_flush 1u +#define f_progress 2u dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); - clear_progress(h); fflush(h->lyt.fp); + switch (level) { +#define CASE(tag, name, val) \ + case TVLEV_##tag: levstr = name; levattr = HA_##tag; break; + TVEC_LEVELS(CASE) + default: levstr = "??"; levattr = HA_UNKLEV; break; + } + + if (h->lyt.fp != stderr && !(h->f&HOF_DUPERR)) f |= f_flush; + +#define FLUSH do if (f&f_flush) fflush(h->lyt.fp); while (0) + + if (h->f^HOF_PROGRESS) + { clear_progress(h); fflush(h->lyt.fp); f |= f_progress; } fprintf(stderr, "%s: ", QUIS); report_location(h, stderr, tv->infile, tv->lno); - fwrite(d.buf, 1, d.len, stderr); + setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH; + fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr); + +#undef FLUSH if (h->f&HOF_DUPERR) { report_location(h, h->lyt.fp, tv->infile, tv->lno); + fprintf(h->lyt.fp, "%s: ", levstr); fwrite(d.buf, 1, d.len, h->lyt.fp); } - show_progress(h); + if (f&f_progress) show_progress(h); + +#undef f_flush +#undef f_progress } /* --- @human_destroy@ --- * @@ -1314,12 +1353,14 @@ static void tap_bsession(struct tvec_output *o, struct tvec_state *tv) * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct * tap_output@ * - * Returns: Suggested exit status. + * Returns: Suggested exit code. * * Use: End a test session. * * The TAP driver prints a final summary of the rest results - * and returns a suitable exit code. + * and returns a suitable exit code. If errors occurred, it + * instead prints a `Bail out!' line forcing the reader to + * report a failure. */ static int tap_esession(struct tvec_output *o) @@ -1496,6 +1537,7 @@ static void tap_dumpreg(struct tvec_output *o, struct tap_output *t = (struct tap_output *)o; const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); + set_layout_prefix(&t->lyt, " ## "); gprintf(&tap_printops, t, "%*s%s %s = ", 10 + t->maxlen - n, "", ds, rd->name); if (!rv) gprintf(&tap_printops, t, "#"); @@ -1572,6 +1614,7 @@ static void tap_ebench(struct tvec_output *o, struct tap_output *t = (struct tap_output *)o; struct tvec_state *tv = t->tv; + set_layout_prefix(&t->lyt, " ## "); gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident); tvec_benchreport(&tap_printops, t, unit, tm); layout_char(&t->lyt, '\n'); @@ -1589,8 +1632,11 @@ static void tap_ebench(struct tvec_output *o, * * Use: Report a message to the user. * - * The TAP driver converts error reports into TAP `Bail out!' - * errors. Less critical notices end up as comments. + * Messages are reported as comments, so that they can be + * accumulated by the reader. An error will cause a later + * bailout or, if we crash before then, a missing plan line, + * either of which will cause the reader to report a serious + * problem. */ static void tap_report(struct tvec_output *o, unsigned level, @@ -1598,16 +1644,14 @@ static void tap_report(struct tvec_output *o, unsigned level, { struct tap_output *t = (struct tap_output *)o; struct tvec_state *tv = t->tv; - const struct gprintf_ops *gops; void *go; - if (level >= TVLEV_ERR) { - fputs("Bail out! ", t->lyt.fp); - gops = &file_printops; go = t->lyt.fp; - } else { - gops = &tap_printops; go = t; - } - if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno); - gprintf(gops, go, msg, ap); gops->putch(go, '\n'); + if (tv->test) set_layout_prefix(&t->lyt, " ## "); + else set_layout_prefix(&t->lyt, "## "); + + if (tv->infile) gprintf(&tap_printops, t, "%s:%u: ", tv->infile, tv->lno); + gprintf(&tap_printops, t, "%s: ", tvec_strlevel(level)); + vgprintf(&tap_printops, t, msg, ap); + layout_char(&t->lyt, '\n'); } /* --- @tap_destroy@ --- * @@ -1665,7 +1709,7 @@ struct tvec_output *tvec_tapoutput(FILE *fp) struct tap_output *t; t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops; - init_layout(&t->lyt, fp, " ## "); + init_layout(&t->lyt, fp, 0); t->outbuf = 0; t->outsz = 0; return (&t->_o); } diff --git a/test/tvec-remote.c b/test/tvec-remote.c index 3dbb5d3..62dd150 100644 --- a/test/tvec-remote.c +++ b/test/tvec-remote.c @@ -49,6 +49,12 @@ #include "quis.h" #include "tvec.h" +/*----- Preliminaries -----------------------------------------------------*/ + +/* The control macros I'm using below provoke `dangling-else' warnings from + * compilers. Suppress them. I generally don't care. + */ + #if GCC_VERSION_P(7, 1) # pragma GCC diagnostic ignored "-Wdangling-else" #elif GCC_VERSION_P(4, 2) @@ -61,30 +67,90 @@ /*----- Basic I/O ---------------------------------------------------------*/ +/* --- @init_comms@ --- * + * + * Arguments: @struct tvec_remotecomms *rc@ = communication state + * + * Returns: --- + * + * Use: Initialize a communication state. This doesn't allocate any + * resurces: it just ensures that everything is set up so that + * subsequent operations -- in particular @release_comms@ -- + * behave sensibly. + */ + static void init_comms(struct tvec_remotecomms *rc) { - dbuf_create(&rc->bin); dbuf_create(&rc->bout); + rc->bin = 0; rc->binsz = 0; dbuf_create(&rc->bout); rc->infd = rc->outfd = -1; rc->f = 0; } +/* --- @close_comms@ --- * + * + * Arguments: @struct tvec_remotecomms *rc@ = communication state + * + * Returns: --- + * + * Use: Close the input and output descriptors. + * + * If the descriptors are already closed -- or were never opened + * -- then nothing happens. + */ + static void close_comms(struct tvec_remotecomms *rc) { if (rc->infd >= 0) { close(rc->infd); rc->infd = -1; } if (rc->outfd >= 0) { close(rc->outfd); rc->outfd = -1; } } +/* --- @release_comms@ --- * + * + * Arguments: @struct tvec_remotecomms *rc@ = communication state + * + * Returns: --- + * + * Use: Releases the resources -- most notably the input and output + * buffers -- held by the communication state. Also calls + * @close_comms@. + */ + static void release_comms(struct tvec_remotecomms *rc) - { close_comms(rc); dbuf_destroy(&rc->bin); dbuf_destroy(&rc->bout); } + { close_comms(rc); xfree(rc->bin); dbuf_destroy(&rc->bout); } + +/* --- @setup_comms@ --- * + * + * Arguments: @struct tvec_remotecomms *rc@ = communication state + * @int infd, outfd@ = input and output file descriptors + * + * Returns: --- + * + * Use: Use the given descriptors for communication. + * + * Clears the private flags. + */ static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd) { - rc->infd = infd; rc->outfd = outfd; rc->f &= ~0xffu; - dbuf_reset(&rc->bin); dbuf_reset(&rc->bout); + rc->infd = infd; rc->outfd = outfd; + rc->binoff = rc->binlen = 0; + rc->f &= ~0xffu; } -static int PRINTF_LIKE(3, 4) - ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc, - const char *msg, ...) +/* --- @ioerr@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * @const char *msg, ...@ = format string and arguments + * + * Returns: %$-1$%. + * + * Use: Reports the message as an error, closes communications and + * marks them as broken. + */ + +static PRINTF_LIKE(3, 4) + int ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc, + const char *msg, ...) { va_list ap; @@ -95,18 +161,24 @@ static int PRINTF_LIKE(3, 4) return (-1); } +/* --- @send_all@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * @const unsigned char *p@, @size_t sz@ = output buffer + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Send the output buffer over the communication state's output + * descriptor, even if it has to be written in multiple pieces. + */ + static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc, const unsigned char *p, size_t sz) { - void (*opipe)(int) = SIG_ERR; ssize_t n; int ret; - opipe = signal(SIGPIPE, SIG_IGN); - if (opipe == SIG_ERR) { - ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno)); - goto end; - } while (sz) { n = write(rc->outfd, p, sz); if (n > 0) @@ -119,131 +191,384 @@ static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc, } ret = 0; end: - if (opipe != SIG_ERR) signal(SIGPIPE, opipe); return (ret); } +/* --- @recv_all@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * @unsigned f@ = flags (@RCVF_...@) + * @unsigned char *p@, @size_t sz@ = input buffer + * @size_t min@ = minimum acceptable size to read + * @size_t *n_out@ = size read + * + * Returns: An @RECV_...@ code. + * + * Use: Receive data on the communication state's input descriptor to + * read at least @min@ bytes into the input buffer, even if it + * has to be done in multiple pieces. If more data is readily + * available, then up to @sz@ bytes will be read in total. + * + * If the descriptor immediately reports end-of-file, and + * @RCVF_ALLOWEOF@ is set in @f@, then return @RECV_EOF@. + * Otherwise, EOF is treated as an I/O error, resulting in a + * call to @ioerr@ and a return code of @RECV_FAIL@. If the + * read succeeded, then set @*n_out@ to the number of bytes read + * and return @RECV_OK@. + */ + #define RCVF_ALLOWEOF 1u + enum { RECV_FAIL = -1, RECV_OK = 0, RECV_EOF = 1 }; + static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc, - unsigned char *p, size_t sz, unsigned f) + unsigned f, unsigned char *p, size_t sz, + size_t min, size_t *n_out) { + size_t tot = 0; ssize_t n; - unsigned ff = 0; -#define f_any 1u while (sz) { n = read(rc->infd, p, sz); - if (n > 0) - { p += n; sz -= n; ff |= f_any; } - else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any)) + if (n > 0) { + p += n; sz -= n; tot += n; + if (tot >= min) break; + } else if (!n && !tot && (f&RCVF_ALLOWEOF)) return (RECV_EOF); else return (ioerr(tv, rc, "failed to receive: %s", n ? strerror(errno) : "unexpected end-of-file")); } - return (RECV_OK); + *n_out = tot; return (RECV_OK); #undef f_any } +/* --- @buferr@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * + * Returns: %$-$%. + * + * Use: Report a problem preparing the output buffer. + */ + +static int buferr(struct tvec_state *tv, struct tvec_remotecomms *rc) + { return (ioerr(tv, rc, "failed to build output packet")); } + +/* --- @malformed@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * + * Returns: %$-$%. + * + * Use: Report an I/O error that the incoming packet is malformed. + */ + +static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc) + { return (ioerr(tv, rc, "received malformed packet")); } + +/* --- @remote_send@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Send the accuulated contents of the output buffer @rc->bout@. + * + * The function arranges to convert @SIGPIPE@ into an error. + * + * If the output buffer is broken, report this as an I/O error. + */ + +#define SENDBUFSZ 4096 + static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc) { - kludge64 k; unsigned char lenbuf[8]; - const unsigned char *p; size_t sz; + void (*opipe)(int) = SIG_ERR; + int ret; + + /* Various preflight checks. */ + if (rc->f&TVRF_BROKEN) { ret = -1; goto end; } + if (DBBAD(&rc->bout)) { ret = buferr(tv, rc); goto end; } - if (rc->f&TVRF_BROKEN) return (-1); - if (BBAD(&rc->bout._b)) - return (ioerr(tv, rc, "failed to build output packet buffer")); + /* Arrange to trap broken-pipe errors. */ + opipe = signal(SIGPIPE, SIG_IGN); + if (opipe == SIG_ERR) { + ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno)); + goto end; + } - p = BBASE(&rc->bout._b); sz = BLEN(&rc->bout._b); - ASSIGN64(k, sz); STORE64_L_(lenbuf, k); - if (send_all(tv, rc, lenbuf, sizeof(lenbuf))) return (-1); - if (send_all(tv, rc, p, sz)) return (-1); + /* Transmit the packet. */ + if (send_all(tv, rc, DBBASE(&rc->bout), DBLEN(&rc->bout))) + { ret = -1; goto end; } - return (0); + /* Done. Put things back the way we found them. */ + ret = 0; +end: + DBRESET(&rc->bout); + if (opipe != SIG_ERR) signal(SIGPIPE, opipe); + return (ret); } +/* --- @receive_buffered@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * @unsigned f@ = flags (@RCVF_...@) + * @size_t want@ = data block size required + * + * Returns: An @RECV_...@ code. + * + * Use: Reads a block of data from the input descriptor into the + * input buffer. + * + * This is the main machinery for manipulating the input buffer. + * The buffer has three regions: + * + * * from the buffer start to @rc->binoff@ is `consumed'; + * * from @rc->binoff@ to @rc->binlen@ is `available'; and + * * from @rc->binlen@ to @rc->binsz@ is `free'. + * + * Data is read into the start of the `free' region, and the + * `available' region is extended to include it. Data in the + * `consumed' region is periodically discarded by moving the + * data from the `available' region to the start of the buffer + * and decreasing @rc->binoff@ and @rc->binlen@. + * + * This function ensures that the `available' region contains at + * least @want@ bytes, by (a) extending the buffer, if + * necessary, so that @rc->binsz >= rc->binoff + want@, and (b) + * reading fresh data from the input descriptor to extend the + * `available' region. + * + * If absolutely no data is available, and @RCVF_ALLOWEOF@ is + * set in @f@, then return @RECV_EOF@. On I/O errors, including + * a short read or end-of-file if @RCVF_ALLOWEOF@ is clear, + * return @RECV_FAIL@. On success, return @RECV_OK@. The + * amount of data read is indicated by updating the input buffer + * variables as described above. + */ + +#define RECVBUFSZ 4096u + +static int receive_buffered(struct tvec_state *tv, + struct tvec_remotecomms *rc, + unsigned f, size_t want) +{ + size_t sz; + int ret; + + /* If we can supply the caller's requirement from the buffer then do + * that. + */ + if (rc->binlen - rc->binoff >= want) return (RECV_OK); + + /* If the buffer is too small then we must grow it. */ + if (want > rc->binsz) { + sz = rc->binsz; if (!sz) sz = RECVBUFSZ; + while (sz < want) { assert(sz < (size_t)-1/2); sz *= 2; } + if (!rc->bin) rc->bin = xmalloc(sz); + else rc->bin = xrealloc(rc->bin, sz, rc->binsz); + rc->binsz = sz; + } + + /* Shunt the unused existing material to the start of the buffer. */ + memmove(rc->bin, rc->bin + rc->binoff, rc->binlen - rc->binoff); + rc->binlen -= rc->binoff; rc->binoff = 0; + + /* Satisfy the caller from the input stream, and try to fill up as much of + * the rest of the buffer as we can. + */ + ret = recv_all(tv, rc, rc->binlen ? 0 : f, + rc->bin + rc->binlen, rc->binsz - rc->binlen, + want - rc->binlen, &sz); + if (ret) return (ret); + + /* Note how much material we have and return. */ + rc->binlen += sz; return (RECV_OK); +} + +/* --- @remote_recv@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned f@ = flags (@RCVF_...@) + * @buf *b_out@ = buffer to establish around the packet contents + * + * Returns: An @RECV_...@ code. + * + * Use: Receive a packet into the input buffer @rc->bin@ and + * establish @*b_out@ to read from it. + */ + static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc, unsigned f, buf *b_out) { - kludge64 k, szmax; unsigned char lenbuf[8]; - unsigned char *p; - size_t sz; + kludge64 k, szmax; + size_t want; int ret; - if (rc->f&TVRF_BROKEN) return (RECV_FAIL); ASSIGN64(szmax, (size_t)-1); - ret = recv_all(tv, rc, lenbuf, sizeof(lenbuf), f); - if (ret) return (ret); - LOAD64_L_(k, lenbuf); + + /* Preflight checks. */ + if (rc->f&TVRF_BROKEN) return (RECV_FAIL); + + /* See if we can read the next packet length from what we already have. */ + ret = receive_buffered(tv, rc, f, 8); if (ret) return (ret); + LOAD64_L_(k, rc->bin + rc->binoff); rc->binoff += 8; if (CMP64(k, >, szmax)) return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range", (unsigned long)HI64(k), (unsigned long)LO64(k))); + want = GET64(size_t, k); - sz = GET64(size_t, k); dbuf_reset(&rc->bin); p = buf_get(&rc->bin._b, sz); - if (!p) return (ioerr(tv, rc, "failed to allocate receive buffer")); - if (recv_all(tv, rc, p, sz, 0)) return (RECV_FAIL); - buf_init(b_out, p, sz); return (RECV_OK); + /* Read the next packet payload. */ + ret = receive_buffered(tv, rc, 0, want); if (ret) return (ret); + buf_init(b_out, rc->bin + rc->binoff, want); rc->binoff += want; + return (RECV_OK); } -#define SENDPK(tv, rc, pk) \ - if ((rc)->f&TVRF_BROKEN) MC_GOELSE(body); else \ - MC_BEFORE(setpk, \ - { dbuf_reset(&(rc)->bout); \ - buf_putu16l(&(rc)->bout._b, (pk)); }) \ - MC_ALLOWELSE(body) \ - MC_AFTER(send, \ - { if (remote_send(tv, rc)) MC_GOELSE(body); }) \ +/* --- @QUEUEPK_TAG@, @QUEUEPK@ --- * + * + * Arguments: @tag@ = control structure tag + * @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotecomms *rc@ = communication state + * @unsigned fl@ = flags (@QF_...@) + * @unsigned pk@ = packet type + * + * Use: This is syntactically a statement head: the syntax is + * @QUEUEPK(tv, rc, f) body [else alt]@. The @body@ should + * write material to the output buffer @rc->bout@. The macro + * applies appropriate framing. If enough material has been + * collected, or if @QF_FORCE@ is set in @fl@, then + * @remote_send@ is invoked to transmit the buffered packets. + * If there is an error of any kind, then the @alt@ statement, + * if any, is executed. + */ -static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc) - { return (ioerr(tv, rc, "received malformed packet")); } +#define QF_FORCE 1u +#define QUEUEPK_TAG(tag, tv, rc, fl, pk) \ + if ((rc)->f&TVRF_BROKEN) MC_GOELSE(tag##__else); else \ + MC_ALLOWELSE(tag##__else) \ + MC_AFTER(tag##__send, { \ + if ((DBBAD(&(rc)->bout) && (buferr((tv), (rc)), 1)) || \ + ((((fl)&QF_FORCE) || DBLEN(&(rc)->bout) >= SENDBUFSZ) && \ + remote_send(tv, rc))) \ + MC_GOELSE(tag##__else); \ + }) \ + DBUF_ENCLOSEITAG(tag##__frame, &(rc)->bout, (rc)->t, 64_L) \ + MC_BEFORE(tag##__pkty, { \ + dbuf_putu16l(&(rc)->bout, (pk)); \ + }) + +#define QUEUEPK(tv, rc, fl, pk) QUEUEPK_TAG(queue, (tv), (rc), (fl), (pk)) /*----- Packet types ------------------------------------------------------*/ #define TVPF_ACK 0x0001u -#define TVPK_VER 0x0000u /* --> min, max: u16 */ - /* <-- ver: u16 */ +#define TVPK_VER 0x0000u /* --> min, max: u16 * + * <-- ver: u16 */ +#define TVPK_BGROUP 0x0002u /* --> name: str16 + * <-- --- */ +#define TVPK_TEST 0x0004u /* --> in: regs + * <-- --- */ +#define TVPK_EGROUP 0x0006u /* --> --- * + * <-- --- */ #define TVPK_REPORT 0x0100u /* <-- level: u16; msg: string */ #define TVPK_PROGRESS 0x0102u /* <-- st: str16 */ -#define TVPK_BGROUP 0x0200u /* --> name: str16 - * <-- --- */ -#define TVPK_TEST 0x0202u /* --> in: regs - * <-- --- */ -#define TVPK_EGROUP 0x0204u /* --> --- */ - -#define TVPK_SKIPGRP 0x0300u /* <-- excuse: str16 */ -#define TVPK_SKIP 0x0302u /* <-- excuse: str16 */ -#define TVPK_FAIL 0x0304u /* <-- flag: u8, detail: str16 */ -#define TVPK_DUMPREG 0x0306u /* <-- ri: u16; disp: u16; +#define TVPK_SKIPGRP 0x0104u /* <-- excuse: str16 */ +#define TVPK_SKIP 0x0106u /* <-- excuse: str16 */ +#define TVPK_FAIL 0x0108u /* <-- flag: u8, detail: str16 */ +#define TVPK_DUMPREG 0x010au /* <-- ri: u16; disp: u16; * flag: u8, rv: value */ -#define TVPK_BBENCH 0x0308u /* <-- ident: str32; unit: u16 */ -#define TVPK_EBENCH 0x030au /* <-- ident: str32; unit: u16; +#define TVPK_BBENCH 0x010cu /* <-- ident: str32; unit: u16 */ +#define TVPK_EBENCH 0x010eu /* <-- ident: str32; unit: u16; * flags: u16; n, t, cy: f64 */ /*----- Server ------------------------------------------------------------*/ +/* Forward declaration of output operations. */ static const struct tvec_outops remote_ops; -static struct tvec_state srvtv; -static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; -static struct tvec_output srvout = { &remote_ops }; +static struct tvec_state srvtv; /* server's test-vector state */ +static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; /* comms */ +static struct tvec_output srvout = { &remote_ops }; /* output state */ -int tvec_setprogress(const char *status) +/* --- @tvec_setprogress@, @tvec_setprogress_v@ --- * + * + * Arguments: @const char *status@ = progress status token format + * @va_list ap@ = argument tail + * + * Returns: --- + * + * Use: Reports the progress of a test execution to the client. + * + * The framework makes use of tokens beginning with %|%|%: + * + * * %|%IDLE|%: during the top-level server code; + * + * * %|%SETUP|%: during the enclosing environment's @before@ + * function; + * + * * %|%RUN|%: during the environment's @run@ function, or the + * test function; and + * + * * %|%DONE|%: during the enclosing environment's @after@ + * function. + * + * The intent is that a test can use the progress token to check + * that a function which is expected to crash does so at the + * correct point, so it's expected that more complex test + * functions and/or environments will set their own progress + * tokens to reflect what's going on. + */ + +int tvec_setprogress(const char *status, ...) +{ + va_list ap; + int rc; + + va_start(ap, status); rc = tvec_setprogress_v(status, &ap); va_end(ap); + return (rc); +} + +int tvec_setprogress_v(const char *status, va_list *ap) { - SENDPK(&srvtv, &srvrc, TVPK_PROGRESS) - buf_putstr16l(&srvrc.bout._b, status); + /* Force immediate output in case we crash before the buffer is output + * organically. + */ + QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_PROGRESS) + dbuf_vputstrf16l(&srvrc.bout, status, ap); else return (-1); return (0); } +/* --- @tvec_remoteserver@ --- * + * + * Arguments: @int infd@, @int outfd@ = input and output file descriptors + * @const struct tvec_config *config@ = test configuration + * + * Returns: Suggested exit code. + * + * Use: Run a test server, reading packets from @infd@ and writing + * responses and notifications to @outfd@, and invoking tests as + * described by @config@. + * + * This function is not particularly general purpose. It + * expects to `take over' the process, and makes use of private + * global variables. + */ + int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) { uint16 pk, u, v; @@ -252,14 +577,18 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) const struct tvec_test *t; void *p; size_t sz; const struct tvec_env *env = 0; - unsigned f = 0; -#define f_regslive 1u void *ctx = 0; int rc; + /* Initialize the communication machinery. */ setup_comms(&srvrc, infd, outfd); + + /* Begin a test session using our custom output driver. */ tvec_begin(&srvtv, config, &srvout); + /* Version negotiation. Expect a @TVPK_VER@ packet. At the moment, + * there's only version zero, so we return that. + */ if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; } if (buf_getu16l(&b, &pk)) goto bad; if (pk != TVPK_VER) { @@ -269,12 +598,33 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) goto end; } if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad; - SENDPK(&srvtv, &srvrc, TVPK_VER | TVPF_ACK) buf_putu16l(&srvrc.bout._b, 0); + QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_VER | TVPF_ACK) + dbuf_putu16l(&srvrc.bout, 0); else { rc = -1; goto end; } - tvec_setprogress("%IDLE"); - + /* Handle packets until the server closes the connection. + * + * The protocol looks much simpler from our point of view than from the + * client. + * + * * Receive @TVPK_VER@; respond with @TVPK_VER | TVPF_ACK@. + * + * * Receive zero or more @TVPK_BGROUP@. Open a test group, producing + * output packets, and eventually answer with @TVPK_BGROUP | TVPF_ACK@. + * + * -- Receive zero or more @TVPK_TEST@. Run a test, producing output + * packets, and eventually answer with @TVPK_TEST | TVPF_ACK@. + * + * -- Receive @TVPK_EGROUP@. Maybe produce output packets, and + * answer with @TVPK_EGROUP | TVPF_ACK@. + * + * * Read EOF. Stop. + */ for (;;) { + + /* Read a packet. End-of-file is expected here (and pretty much nowhere + * else). Otherwise, we expect to see @TVPK_BGROUP@. + */ rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b); if (rc == RECV_EOF) break; else if (rc == RECV_FAIL) goto end; @@ -283,8 +633,13 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) switch (pk) { case TVPK_BGROUP: + /* Start a group. */ + + /* Parse the packet payload. */ p = buf_getmem16l(&b, &sz); if (!p) goto bad; if (BLEFT(&b)) goto bad; + + /* Find the group given its name. */ for (t = srvtv.tests; t->name; t++) if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz)) goto found_group; @@ -293,6 +648,7 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) goto end; found_group: + /* Set up the test environment. */ srvtv.test = t; env = t->env; if (env && env->setup == tvec_remotesetup) env = ((struct tvec_remoteenv *)env)->r.env; @@ -300,36 +656,64 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) else ctx = xmalloc(env->ctxsz); if (env && env->setup) env->setup(&srvtv, env, 0, ctx); - SENDPK(&srvtv, &srvrc, TVPK_BGROUP | TVPF_ACK); + /* Initialize the registers. */ + tvec_initregs(&srvtv); + + /* Report that the group has been opened and that we're ready to run + * tests. + */ + QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_BGROUP | TVPF_ACK); else { rc = -1; goto end; } + /* Handle packets until we're told to end the group. */ for (;;) { + + /* Read a packet. We expect @TVPK_EGROUP@ or @TVPK_TEST@. */ if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; } if (buf_getu16l(&b, &pk)) goto bad; + switch (pk) { case TVPK_EGROUP: + /* End the group. */ + + /* Check the payload. */ if (BLEFT(&b)) goto bad; + + /* Leave the group loop. */ goto endgroup; case TVPK_TEST: - tvec_initregs(&srvtv); f |= f_regslive; + /* Run a test. */ + + /* Parse the packet payload. */ if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs, srvtv.nreg, srvtv.regsz)) goto bad; if (BLEFT(&b)) goto bad; + /* If we're not skipping the test group, then actually try to + * run the test. + */ if (!(srvtv.f&TVSF_SKIP)) { + + /* Prepare the output registers and reset the test outcome. + * (The environment may force a skip.) + */ + for (i = 0; i < srvtv.nrout; i++) + if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE) + TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE; srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK; - tvec_setprogress("%SETUP"); + + /* Invoke the environment @before@ function. */ + tvec_setprogress("%%SETUP"); if (env && env->before) env->before(&srvtv, ctx); + + /* Run the actual test. */ if (!(srvtv.f&TVSF_ACTIVE)) /* setup forced a skip */; else { - for (i = 0; i < srvtv.nrout; i++) - if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE) - TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE; - tvec_setprogress("%RUN"); + tvec_setprogress("%%RUN"); if (env && env->run) env->run(&srvtv, t->fn, ctx); else { @@ -337,46 +721,61 @@ int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) tvec_check(&srvtv, 0); } } - tvec_setprogress("%DONE"); + + /* Conclude the test. */ + tvec_setprogress("%%DONE"); if (env && env->after) env->after(&srvtv, ctx); tvec_endtest(&srvtv); } - tvec_releaseregs(&srvtv); f &= ~f_regslive; - SENDPK(&srvtv, &srvrc, TVPK_TEST | TVPF_ACK); + + /* Reset the input registers and report completion. */ + tvec_releaseregs(&srvtv); tvec_initregs(&srvtv); + QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_TEST | TVPF_ACK); else { rc = -1; goto end; } - tvec_setprogress("%IDLE"); break; default: + /* Some other kind of packet. Complain. */ + rc = ioerr(&srvtv, &srvrc, - "unexpected packet type 0x%04x", pk); + "unexpected packet type 0x%04x during test group", + pk); goto end; } } endgroup: + /* The test group completed. */ + + /* Tear down the environment and release other resources. */ if (env && env->teardown) env->teardown(&srvtv, ctx); - xfree(ctx); t = 0; env = 0; ctx = 0; + tvec_releaseregs(&srvtv); + xfree(ctx); srvtv.test = 0; env = 0; ctx = 0; + + /* Report completion. */ + QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_EGROUP | TVPF_ACK); + else { rc = -1; goto end; } break; default: - goto bad; + rc = ioerr(&srvtv, &srvrc, + "unexpected packet type 0x%04x at top level", pk); } } rc = 0; end: + /* Clean up and return. */ if (env && env->teardown) env->teardown(&srvtv, ctx); xfree(ctx); - if (f&f_regslive) tvec_releaseregs(&srvtv); - release_comms(&srvrc); + if (srvtv.test) tvec_releaseregs(&srvtv); + release_comms(&srvrc); tvec_end(&srvtv); return (rc ? 2 : 0); bad: + /* Miscellaneous malformed packet. */ rc = malformed(&srvtv, &srvrc); goto end; - -#undef f_regslive } /*----- Server output driver ----------------------------------------------*/ @@ -384,26 +783,26 @@ bad: static void remote_skipgroup(struct tvec_output *o, const char *excuse, va_list *ap) { - SENDPK(&srvtv, &srvrc, TVPK_SKIPGRP) - buf_vputstrf16l(&srvrc.bout._b, excuse, ap); + QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIPGRP) + dbuf_vputstrf16l(&srvrc.bout, excuse, ap); } static void remote_skip(struct tvec_output *o, const char *excuse, va_list *ap) { - SENDPK(&srvtv, &srvrc, TVPK_SKIP) - buf_vputstrf16l(&srvrc.bout._b, excuse, ap); + QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIP) + dbuf_vputstrf16l(&srvrc.bout, excuse, ap); } static void remote_fail(struct tvec_output *o, const char *detail, va_list *ap) { - SENDPK(&srvtv, &srvrc, TVPK_FAIL) + QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL) if (!detail) - buf_putbyte(&srvrc.bout._b, 0); + dbuf_putbyte(&srvrc.bout, 0); else { - buf_putbyte(&srvrc.bout._b, 1); - buf_vputstrf16l(&srvrc.bout._b, detail, ap); + dbuf_putbyte(&srvrc.bout, 1); + dbuf_vputstrf16l(&srvrc.bout, detail, ap); } } @@ -420,14 +819,14 @@ static void remote_dumpreg(struct tvec_output *o, assert(!"unexpected register definition"); found: - SENDPK(&srvtv, &srvrc, TVPK_DUMPREG) { - buf_putu16l(&srvrc.bout._b, r); - buf_putu16l(&srvrc.bout._b, disp); + QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) { + dbuf_putu16l(&srvrc.bout, r); + dbuf_putu16l(&srvrc.bout, disp); if (!rv) - buf_putbyte(&srvrc.bout._b, 0); + dbuf_putbyte(&srvrc.bout, 0); else { - buf_putbyte(&srvrc.bout._b, 1); - rd->ty->tobuf(&srvrc.bout._b, rv, rd); + dbuf_putbyte(&srvrc.bout, 1); + rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd); } } } @@ -435,9 +834,9 @@ found: static void remote_bbench(struct tvec_output *o, const char *ident, unsigned unit) { - SENDPK(&srvtv, &srvrc, TVPK_BBENCH) { - buf_putstr32l(&srvrc.bout._b, ident); - buf_putu16l(&srvrc.bout._b, unit); + QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) { + dbuf_putstr32l(&srvrc.bout, ident); + dbuf_putu16l(&srvrc.bout, unit); } } @@ -445,16 +844,16 @@ static void remote_ebench(struct tvec_output *o, const char *ident, unsigned unit, const struct bench_timing *t) { - SENDPK(&srvtv, &srvrc, TVPK_EBENCH) { - buf_putstr32l(&srvrc.bout._b, ident); - buf_putu16l(&srvrc.bout._b, unit); + QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) { + dbuf_putstr32l(&srvrc.bout, ident); + dbuf_putu16l(&srvrc.bout, unit); if (!t || !(t->f&BTF_ANY)) - buf_putu16l(&srvrc.bout._b, 0); + dbuf_putu16l(&srvrc.bout, 0); else { - buf_putu16l(&srvrc.bout._b, t->f); - buf_putf64l(&srvrc.bout._b, t->n); - if (t->f&BTF_TIMEOK) buf_putf64l(&srvrc.bout._b, t->t); - if (t->f&BTF_CYOK) buf_putf64l(&srvrc.bout._b, t->cy); + dbuf_putu16l(&srvrc.bout, t->f); + dbuf_putf64l(&srvrc.bout, t->n); + if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t); + if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy); } } } @@ -462,18 +861,11 @@ static void remote_ebench(struct tvec_output *o, static void remote_report(struct tvec_output *o, unsigned level, const char *msg, va_list *ap) { - const char *what; - - SENDPK(&srvtv, &srvrc, TVPK_REPORT) { - buf_putu16l(&srvrc.bout._b, level); - buf_vputstrf16l(&srvrc.bout._b, msg, ap); + QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) { + dbuf_putu16l(&srvrc.bout, level); + dbuf_vputstrf16l(&srvrc.bout, msg, ap); } else { - switch (level) { - case TVLEV_NOTE: what = "notice"; break; - case TVLEV_ERR: what = "ERROR"; break; - default: what = "(?level)"; break; - } - fprintf(stderr, "%s %s: ", QUIS, what); + fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level)); vfprintf(stderr, msg, *ap); fputc('\n', stderr); } @@ -726,6 +1118,7 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, for (;;) { rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end; if (buf_getu16l(b, &pk)) goto bad; + if (pk == end) break; switch (pk) { @@ -831,8 +1224,10 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, case TVPK_EBENCH: p = buf_getmem32l(b, &n); if (!p) goto bad; if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad; - if (u >= TVBU_LIMIT) - { rc = ioerr(tv, &r->rc, "unit code %u out of range", u); goto end; } + if (u >= TVBU_LIMIT) { + rc = ioerr(tv, &r->rc, "unit code %u out of range", u); + goto end; + } if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad; if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad; if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad; @@ -843,12 +1238,12 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; default: - if (pk == end) { rc = 0; goto end; } rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk); goto end; } } + rc = RECV_OK; end: DDESTROY(&d); xfree(reg); @@ -908,6 +1303,7 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, ssize_t n; int rc; + if (r->errfd < 0) { rc = 0; goto end; } if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE; else r->rc.f &= ~TVRF_MUFFLE; if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) { @@ -934,7 +1330,7 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, end: if (f&ERF_CLOSE) { lbuf_close(&r->errbuf); - close(r->errfd); + close(r->errfd); r->errfd = -1; } return (rc); } @@ -943,10 +1339,8 @@ end: static void disconnect_remote(struct tvec_state *tv, struct tvec_remotectx *r, unsigned f) { - if (r->kid < 0) return; if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM); close_comms(&r->rc); - if (r->kid > 0) kill(r->kid, SIGTERM); drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r); } @@ -966,9 +1360,9 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r) lbuf_init(&r->errbuf, report_errline, r); r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN; - SENDPK(tv, &r->rc, TVPK_VER) { - buf_putu16l(&r->rc.bout._b, 0); - buf_putu16l(&r->rc.bout._b, 0); + QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) { + dbuf_putu16l(&r->rc.bout, 0); + dbuf_putu16l(&r->rc.bout, 0); } else { rc = -1; goto end; } if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b)) @@ -980,8 +1374,8 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r) goto end; } - SENDPK(tv, &r->rc, TVPK_BGROUP) - buf_putstr16l(&r->rc.bout._b, tv->test->name); + QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP) + dbuf_putstr16l(&r->rc.bout, tv->test->name); else { rc = -1; goto end; } if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b)) { rc = -1; goto end; } @@ -1033,7 +1427,8 @@ static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r) static void reset_vars(struct tvec_remotectx *r) { - r->exwant = TVXST_RUN; r->rc.f = (r->rc.f&~TVRF_RCNMASK) | TVRCN_DEMAND; + r->exwant = TVXST_RUN; + r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) | TVRCN_DEMAND; DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE"); } @@ -1054,28 +1449,31 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env, reset_vars(r); } -int tvec_remoteset(struct tvec_state *tv, const char *var, - const struct tvec_env *env, void *ctx) +int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx) { struct tvec_remotectx *r = ctx; union tvec_regval rv; int rc; if (STRCMP(var, ==, "@exit")) { + if (r->rc.f&TVRF_SETEXIT) { rc = tvec_dupreg(tv, var); goto end; } if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; } - if (r) r->exwant = rv.u; - rc = 1; + r->exwant = rv.u; r->rc.f |= TVRF_SETEXIT; rc = 1; } else if (STRCMP(var, ==, "@progress")) { + if (r->rc.f&TVRF_SETPRG) { rc = tvec_dupreg(tv, var); goto end; } tvty_string.init(&rv, &progress_regdef); rc = tvty_string.parse(&rv, &progress_regdef, tv); - if (r && !rc) - { DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); } + if (!rc) { + DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); + r->rc.f |= TVRF_SETPRG; + } tvty_string.release(&rv, &progress_regdef); if (rc) { rc = -1; goto end; } rc = 1; } else if (STRCMP(var, ==, "@reconnect")) { + if (r->rc.f&TVRF_SETRCN) { rc = tvec_dupreg(tv, var); goto end; } if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; } - if (r) r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK); + r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK) | TVRF_SETRCN; rc = 1; } else rc = 0; @@ -1109,8 +1507,9 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) tvec_skip(tv, "no connection"); return; } - SENDPK(tv, &r->rc, TVPK_TEST) - tvec_serialize(tv->in, &r->rc.bout._b, + DRESET(&r->progress); DPUTS(&r->progress, "%IDLE"); + QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST) + tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout), tv->test->regs, tv->nreg, tv->regsz); else { rc = -1; goto end; } rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b); @@ -1168,11 +1567,15 @@ end: void tvec_remoteteardown(struct tvec_state *tv, void *ctx) { struct tvec_remotectx *r = ctx; + buf b; - if (r) { - disconnect_remote(tv, r, 0); release_comms(&r->rc); - DDESTROY(&r->prgwant); DDESTROY(&r->progress); + if (r->rc.outfd >= 0) { + QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP); + if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b)) + if (BLEFT(&b)) malformed(tv, &r->rc); } + disconnect_remote(tv, r, 0); release_comms(&r->rc); + DDESTROY(&r->prgwant); DDESTROY(&r->progress); } /*----- Connectors --------------------------------------------------------*/ @@ -1238,6 +1641,7 @@ int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out, if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; } if (!kid) { + if (tv->fp) fclose(tv->fp); config.tests = rf->f.tests ? rf->f.tests : tv->tests; config.nrout = tv->nrout; config.nreg = tv->nreg; config.regsz = tv->regsz; diff --git a/test/tvec-timeout.c b/test/tvec-timeout.c new file mode 100644 index 0000000..ca86b5c --- /dev/null +++ b/test/tvec-timeout.c @@ -0,0 +1,313 @@ +/* -*-c-*- + * + * Timeout extension for test vector framework + * + * (c) 2024 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software: you can redistribute it and/or modify it under + * the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * mLib is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib. If not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include +#include +#include + +#include "tvec.h" + +/*----- Main code ---------------------------------------------------------*/ + +static void reset(struct tvec_timeoutctx *tc) +{ + const struct tvec_timeoutenv *te = tc->te; + + tc->timer = te->timer; tc->t = te->t; tc->f &= ~TVTF_SETMASK; +} + +/* --- @tvec_timeoutsetup@ --- * + * + * 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: --- + * + * Use: Initialize a timeout environment context. + * + * The environment description should be a @struct + * tvec_timeoutenv@. + */ + +void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env, + void *pctx, void *ctx) +{ + struct tvec_timeoutctx *tc = ctx; + const struct tvec_timeoutenv *te = (struct tvec_timeoutenv *)env; + const struct tvec_env *subenv = te->env; + + tc->te = te; + + reset(tc); + + tc->subctx = 0; + if (subenv && subenv->ctxsz) tc->subctx = xmalloc(subenv->ctxsz); + if (subenv && subenv->setup) subenv->setup(tv, subenv, tc, tc->subctx); +} + +/* --- @tvec_timeoutset@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const char *var@ = variable name to set + * @void *ctx@ = context pointer + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Set a special variable. The following special variables are + * supported. + * + * * %|@timeout|% is the number of seconds (or other unit) to + * wait before giving up and killing the test process. The + * string may also include a keyword %|REAL|%, %|VIRTUAL|%, + * or %|PROF|% to select the timer. + * + * Unrecognized variables are passed to the subordinate + * environment, if there is one. + */ + +int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) +{ + struct tvec_timeoutctx *tc = ctx; + const struct tvec_timeoutenv *te = tc->te; + const struct tvec_env *subenv = te->env; + dstr d = DSTR_INIT; + double t = 0.0; unsigned tmr = 0; + const char *p; char *q; size_t pos; + int rc; + unsigned f = 0; +#define f_time 1u +#define f_timer 2u +#define f_all (f_time | f_timer) + + if (STRCMP(var, ==, "@timeout")) { + if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; } + + for (;;) { + DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break; + timeout_primed: + p = d.buf; + if (!(f&f_timer) && STRCMP(p, ==, "REAL")) + { tmr = ITIMER_REAL; f |= f_timer; } + else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL")) + { tmr = ITIMER_VIRTUAL; f |= f_timer; } + else if (!(f&f_timer) && STRCMP(p, ==, "PROF")) + { tmr = ITIMER_PROF; f |= f_timer; } + + else if (!(f&f_time)) { + if (*p == '+' || *p == '-') p++; + if (*p == '.') p++; + if (!ISDIGIT(*p)) { + tvec_syntax(tv, *d.buf, "floating-point number"); + rc = -1; goto end; + } + errno = 0; t = strtod(p, &q); f |= f_time; + if (errno) { + tvec_error(tv, "invalid floating-point number `%s': %s", + d.buf, strerror(errno)); + rc = -1; goto end; + } + + if (!*q) { + tvec_skipspc(tv); pos = d.len; + if (!tvec_readword(tv, &d, ";", 0)) pos++; + q = d.buf + pos; + } + + if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec")) + /* nothing to do */; + + else if (STRCMP(q, ==, "ds")) t *= 1e-1; + else if (STRCMP(q, ==, "cs")) t *= 1e-2; + else if (STRCMP(q, ==, "ms")) t *= 1e-3; + else if (STRCMP(q, ==, "us") || STRCMP(q, ==, "µs")) t *= 1e-6; + else if (STRCMP(q, ==, "ns")) t *= 1e-9; + else if (STRCMP(q, ==, "ps")) t *= 1e-12; + else if (STRCMP(q, ==, "fs")) t *= 1e-15; + else if (STRCMP(q, ==, "as")) t *= 1e-18; + else if (STRCMP(q, ==, "zs")) t *= 1e-21; + else if (STRCMP(q, ==, "ys")) t *= 1e-24; + + else if (STRCMP(q, ==, "das")) t *= 1e+1; + else if (STRCMP(q, ==, "hs")) t *= 1e+2; + else if (STRCMP(q, ==, "ks")) t *= 1e+3; + else if (STRCMP(q, ==, "Ms")) t *= 1e+6; + else if (STRCMP(q, ==, "Gs")) t *= 1e+9; + else if (STRCMP(q, ==, "Ts")) t *= 1e+12; + else if (STRCMP(q, ==, "Ps")) t *= 1e+15; + else if (STRCMP(q, ==, "Es")) t *= 1e+18; + else if (STRCMP(q, ==, "Zs")) t *= 1e+21; + else if (STRCMP(q, ==, "Ys")) t *= 1e+24; + + else if (STRCMP(q, ==, "m") || STRCMP(q, ==, "min")) t *= 60; + else if (STRCMP(q, ==, "h") || STRCMP(q, ==, "hr")) t *= 3600; + else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400; + else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600; + + else { + if (f&f_timer) + { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; } + pos = q - d.buf; d.len -= pos; + memmove(d.buf, q, d.len); + goto timeout_primed; + } + } + + if (!(~f&f_all)) break; + } + + if (!f) { + rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword"); + goto end; + } + rc = tvec_flushtoeol(tv, 0); if (rc) goto end; + if (f&f_time) tc->t = t; + if (f&f_timer) tc->timer = tmr; + tc->f |= TVTF_SETTMO; + + } else if (subenv && subenv->set) + rc = subenv->set(tv, var, tc->subctx); + else + rc = 0; + +end: + dstr_destroy(&d); + return (rc); + +#undef f_time +#undef f_timer +#undef f_all +} + +/* --- @tvec_timeoutbefore@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Invoke the subordinate environment's @before@ function to + * prepare for the test. + */ + +void tvec_timeoutbefore(struct tvec_state *tv, void *ctx) +{ + struct tvec_timeoutctx *tc = ctx; + const struct tvec_timeoutenv *te = tc->te; + const struct tvec_env *subenv = te->env; + + /* Just call the subsidiary environment. */ + if (subenv && subenv->before) subenv->before(tv, tc->subctx); +} + +/* --- @tvec_timeoutafter@ --- * + * + * 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 test. + */ + +void tvec_timeoutafter(struct tvec_state *tv, void *ctx) +{ + struct tvec_timeoutctx *tc = ctx; + const struct tvec_timeoutenv *te = tc->te; + const struct tvec_env *subenv = te->env; + + /* Reset variables. */ + reset(tc); + + /* Pass the call on to the subsidiary environment. */ + if (subenv && subenv->after) subenv->after(tv, tc->subctx); +} + +/* --- @tvec_timeoutteardown@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Tear down the timeoutmark environment. + */ + +void tvec_timeoutteardown(struct tvec_state *tv, void *ctx) +{ + struct tvec_timeoutctx *tc = ctx; + const struct tvec_timeoutenv *te = tc->te; + const struct tvec_env *subenv = te->env; + + /* Just call the subsidiary environment. */ + if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx); +} + +/* --- @tvec_timeoutrun@ --- * + * + * 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: Runs a test with a timeout attached. + */ + +void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +{ + struct tvec_timeoutctx *tc = ctx; + const struct tvec_timeoutenv *te = tc->te; + const struct tvec_env *subenv = te->env; + struct itimerval itv; + + itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = tc->t; + itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6; + + if (setitimer(tc->timer, &itv, 0)) + tvec_skip(tv, "failed to set interval timer: %s", strerror(errno)); + else { + if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx); + else fn(tv->in, tv->out, tc->subctx); + + itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; + if (setitimer(tc->timer, &itv, 0)) + tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno)); + } +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-types.c b/test/tvec-types.c index 3afa808..681409d 100644 --- a/test/tvec-types.c +++ b/test/tvec-types.c @@ -1562,6 +1562,10 @@ int tvec_claimeq_float(struct tvec_state *tv, file, lno, expr)); } +const struct tvec_floatinfo + tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 }, + tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 }; + /*----- Enumerations ------------------------------------------------------*/ #define init_ienum init_int diff --git a/test/tvec.h b/test/tvec.h index 123743e..38c462b 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -57,8 +57,8 @@ * -> @tvec_mismatch@ * -> output @dumpreg@ * -> type @dump@ - * -> env @after@ * -> output @etest@ + * -> env @after@ * finally * -> output @egroup@ * -> env @teardown@ @@ -177,16 +177,16 @@ enum { */ union tvec_regval { - /* The actual register value. This is what the type handler sees. - * Additional members can be added by setting `TVEC_REGSLOTS' before - * including this file. - * - * A register value can be /initialized/, which simply means that its - * contents represent a valid value according to its type -- the - * register can be compared, dumped, serialized, parsed into, etc. - * You can't do anything safely to an uninitialized register value - * other than initialize it. - */ + /* The actual register value. This is what the type handler sees. + * Additional members can be added by setting `TVEC_REGSLOTS' before + * including this file. + * + * A register value can be /initialized/, which simply means that its + * contents represent a valid value according to its type -- the register + * can be compared, dumped, serialized, parsed into, etc. You can't do + * anything safely to an uninitialized register value other than initialize + * it. + */ long i; /* signed integer */ unsigned long u; /* unsigned integer */ @@ -200,24 +200,22 @@ union tvec_regval { }; struct tvec_reg { - /* A register. - * - * Note that all of the registers listed as being used by a - * particular test group are initialized at all times[1] while that - * test group is being processed. (The other register slots don't - * even have types associated with them, so there's nothing useful we - * could do with them.) - * - * The `TVRF_LIVE' flag indicates that the register was assigned a - * value by the test vector file: it's the right thing to use to - * check whether an optional register is actually present. Even - * `dead' registers are still initialized, though. - * - * [1] This isn't quite true. Between individual tests, the - * registers are released and reinitialized in order to reset - * them to known values ready for the next test. But you won't - * see them at this point. - */ + /* A register. + * + * Note that all of the registers listed as being used by a particular test + * group are initialized at all times[1] while that test group is being + * processed. (The other register slots don't even have types associated + * with them, so there's nothing useful we could do with them.) + * + * The `TVRF_LIVE' flag indicates that the register was assigned a value by + * the test vector file: it's the right thing to use to check whether an + * optional register is actually present. Even `dead' registers are still + * initialized, though. + * + * [1] This isn't quite true. Between individual tests, the registers are + * released and reinitialized in order to reset them to known values + * ready for the next test. But you won't see them at this point. + */ unsigned f; /* flags */ #define TVRF_LIVE 1u /* used in current test */ @@ -225,13 +223,12 @@ struct tvec_reg { }; struct tvec_regdef { - /* A register definition. Register definitions list the registers - * which are used by a particular test group (see `struct tvec_test' - * below). - * - * A vector of register definitions is terminated by a definition - * whose `name' slot is null. - */ + /* A register definition. Register definitions list the registers which + * are used by a particular test group (see `struct tvec_test' below). + * + * A vector of register definitions is terminated by a definition whose + * `name' slot is null. + */ const char *name; /* register name (for input files) */ unsigned i; /* register index */ @@ -266,55 +263,55 @@ 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@. - */ + /* 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@. */ + /* 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@. - */ + /* 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. - */ + /* 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. - */ + /* 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. - */ + /* 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. - */ + /* 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. + */ }; /*----- Test descriptions -------------------------------------------------*/ @@ -346,12 +343,13 @@ typedef void tvec_envsetupfn(struct tvec_state */*tv*/, /* Initialize the context; called at the start of a test group; @pctx@ is * null for environments called by the core, but may be non-null for * subordinate environments. If setup fails, the function should call - * @tvec_skipgroup@ with a suitable excuse. The @set@ and @teardown@ entry - * points will still be called, but @before@, @run@, and @after@ will not. + * @tvec_skipgroup@ with a suitable excuse. The @set@, @after@, and + * @teardown@ entry points will still be called, but @before@ and @run@ + * will not. */ -typedef int tvec_envsetfn(struct tvec_state */*tv*/, const char */*var*/, - const struct tvec_env */*env*/, void */*ctx*/); +typedef int tvec_envsetfn(struct tvec_state */*tv*/, + const char */*var*/, void */*ctx*/); /* Called when the parser finds a %|@var|%' setting to parse and store the * value. If @setup@ failed, this is still called (so as to skip the * value), but @ctx@ is null. @@ -376,8 +374,10 @@ typedef void tvec_envrunfn(struct tvec_state */*tv*/, typedef void tvec_envafterfn(struct tvec_state */*tv*/, void */*ctx*/); /* Called after running or skipping a test. Typical actions involve - * resetting whatever things were established by @set@. This function is - * never called if the test group is skipped. + * resetting whatever things were established by @set@. This function + * %%\emph{is}%% called if the test group is skipped, so that the test + * environment can reset variables set by the @set@ entry point. It should + * check the @TVSF_SKIP@ flag if necessary. */ typedef void tvec_envteardownfn(struct tvec_state */*tv*/, void */*ctx*/); @@ -447,6 +447,7 @@ struct tvec_state { #define TVSF_OUTMASK 0x00f0u /* test outcome (@TVOUT_...@) */ #define TVSF_OUTSHIFT 4 /* shift applied to outcome */ #define TVSF_XFAIL 0x0100u /* test expected to fail */ +#define TVSF_MUFFLE 0x0200u /* muffle errors */ /* Registers. Available to execution environments. */ unsigned nrout, nreg; /* number of output/total registers */ @@ -502,106 +503,110 @@ 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. - */ + /* 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. */ + /* 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@. */ + /* 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. - */ + /* 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. - */ + /* 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. */ + /* 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. - */ + /* 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! - */ + /* The test case failed. + * + * The output driver should preferably report the filename (@infile@) and + * line number (@test_lno@, not @lno@) for the failing test. + * + * The @detail@ may be null or a format string describing detail about + * how the failing test was run which can't be determined from the + * registers; a @detail@ is usually provided when (and only when) the + * test environment potentially invokes the test function more than once. + * + * A single test case can fail multiple times! + */ void (*dumpreg)(struct tvec_output */*o*/, unsigned /*disp*/, const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); - /* Dump a register. The `disposition' @disp@ explains what condition - * the register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes. - * The register value is at @rv@, and its definition, including its - * type, at @rd@. Note that this function may be called on virtual - * registers which aren't in either of the register vectors or - * mentioned by the test description. It may also be called with - * @rv@ null, indicating that the register is not live. - */ + /* Dump a register. The `disposition' @disp@ explains what condition the + * register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes. The + * register value is at @rv@, and its definition, including its type, at + * @rd@. Note that this function may be called on virtual registers + * which aren't in either of the register vectors or mentioned by the + * test description. It may also be called with @rv@ null, indicating + * that the register is not live. + */ void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); - /* The test case concluded with the given @outcome@ (one of the - * @TVOUT_...@ codes. - */ + /* 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. - */ + /* 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. - */ + /* End a benchmark. The @ident@ and @unit@ arguments are as for + * @bbench@. If @tm@ is zero then the measurement failed; otherwise + * @tm->n@ counts total number of things (operations or bytes, as + * indicated by @unit@) processed in the indicated time. + */ void (*report)(struct tvec_output */*o*/, unsigned /*level*/, const char */*msg*/, va_list */*ap*/); - /* Report a message. The driver should ideally report the filename - * (@infile@) and line number (@lno@) prompting the error. - */ + /* Report a message. The driver should ideally report the filename + * (@infile@) and line number (@lno@) prompting the error. + */ void (*destroy)(struct tvec_output */*o*/); - /* Release any resources acquired by the driver. */ + /* Release any resources acquired by the driver. */ }; +#define TVEC_LEVELS(_) \ + _(NOTE, "notice", 4) \ + _(ERR, "ERROR", 8) + enum { - TVLEV_NOTE = 4, /* notice */ - TVLEV_ERR = 8 /* error */ +#define TVEC_DEFLEVEL(tag, name, val) TVLEV_##tag = val, + TVEC_LEVELS(TVEC_DEFLEVEL) +#undef TVEC_DEFLEVEL + TVLEV_LIMIT }; /*----- Session lifecycle -------------------------------------------------*/ @@ -640,7 +645,7 @@ extern int tvec_end(struct tvec_state */*tv*/); * @const char *infile@ = the name of the input file * @FILE *fp@ = stream to read from * - * Returns: Zero on success, @-1@ on error. + * 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 @@ -684,7 +689,7 @@ extern void tvec_parseargs(int /*argc*/, char */*argv*/[], * @const char *file@ = pathname of file to read * @const char *arg@ = argument to interpret * - * Returns: Zero on success, @-1@ on error. + * 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 `%|-|%', @@ -700,7 +705,7 @@ extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/); * Arguments: @struct tvec_state *tv@ = test vector state * @const char *dflt@ = defsault filename or null * - * Returns: Zero on success, @-1@ on error. + * 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 @@ -719,7 +724,7 @@ extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/); * @int *argpos_inout@ = current argument position (updated) * @const char *dflt@ = default filename or null * - * Returns: Zero on success, @-1@ on error. + * 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@; @@ -764,8 +769,9 @@ extern int tvec_main(int /*argc*/, char */*argv*/[], * the @setup@ function fails. */ -extern void PRINTF_LIKE(2, 3) - tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_skipgroup(struct tvec_state */*tv*/, + const char */*excuse*/, ...); extern void tvec_skipgroup_v(struct tvec_state */*tv*/, const char */*excuse*/, va_list */*ap*/); @@ -781,46 +787,11 @@ extern void tvec_skipgroup_v(struct tvec_state */*tv*/, * the @before@ function fails. */ -extern void PRINTF_LIKE(2, 3) - tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...); extern void tvec_skip_v(struct tvec_state */*tv*/, const char */*excuse*/, va_list */*ap*/); -/* --- @tvec_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*/); - -extern void tvec_initregs(struct tvec_state */*tv*/); -extern void tvec_releaseregs(struct tvec_state */*tv*/); - -/* --- @tvec_checkregs@ --- * - * - * Arguments: @struct tvec_state *tv@ = test-vector state - * - * Returns: Zero on success, @-1@ on mismatch. - * - * Use: Compare the active output registers (according to the current - * test group definition) with the corresponding input register - * values. A mismatch occurs if the two values differ - * (according to the register type's @eq@ method), or if the - * input is live but the output is dead. - * - * This function only checks for a mismatch and returns the - * result; it takes no other action. In particular, it doesn't - * report a failure, or dump register values. - */ - -extern int tvec_checkregs(struct tvec_state */*tv*/); - /* --- @tvec_fail@, @tvec_fail_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state @@ -838,8 +809,8 @@ extern int tvec_checkregs(struct tvec_state */*tv*/); * safely be left null if the test function is run only once. */ -extern void PRINTF_LIKE(2, 3) - tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_fail(struct tvec_state */*tv*/, const char */*detail*/, ...); extern void tvec_fail_v(struct tvec_state */*tv*/, const char */*detail*/, va_list */*ap*/); @@ -865,6 +836,55 @@ extern void tvec_dumpreg(struct tvec_state */*tv*/, unsigned /*disp*/, const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); +/* --- @tvec_initregs@, @tvec_releaseregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Initialize or release, respectively, the registers required + * by the current test. All of the registers, both input and + * output, are effected. Initialized registers are not marked + * live. + */ + +extern void tvec_initregs(struct tvec_state */*tv*/); +extern void tvec_releaseregs(struct tvec_state */*tv*/); + +/* --- @tvec_resetoutputs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Reset (releases and reinitializes) the output registers in + * the test state. This is mostly of use to test environment + * @run@ functions, between invocations of the test function. + * Output registers are marked live if and only if the + * corresponding input register is live. + */ + +extern void tvec_resetoutputs(struct tvec_state */*tv*/); + +/* --- @tvec_checkregs@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on mismatch. + * + * Use: Compare the active output registers (according to the current + * test group definition) with the corresponding input register + * values. A mismatch occurs if the two values differ + * (according to the register type's @eq@ method), or if the + * input is live but the output is dead. + * + * This function only checks for a mismatch and returns the + * result; it takes no other action. In particular, it doesn't + * report a failure, or dump register values. + */ + +extern int tvec_checkregs(struct tvec_state */*tv*/); + /* --- @tvec_mismatch@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state @@ -896,8 +916,8 @@ extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/); * obvious way. */ -extern void PRINTF_LIKE(2, 3) - tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_check(struct tvec_state */*tv*/, const char */*detail*/, ...); extern void tvec_check_v(struct tvec_state */*tv*/, const char */*detail*/, va_list */*ap*/); @@ -994,13 +1014,13 @@ extern void tvec_begintest(struct tvec_state */*tv*/, #define TVEC_BEGINTEST(tv) \ do tvec_begintest(tv, __FILE__, __LINE__); while (0) -/* --- *tvec_endtest@ --- * +/* --- @tvec_endtest@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * * Returns: --- * - * Use: End a ad-hoc test case, The statistics are updated and the + * Use: End an ad-hoc test case, The statistics are updated and the * outcome is reported to the output formatter. */ @@ -1053,10 +1073,10 @@ extern void tvec_endtest(struct tvec_state */*tv*/); * 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 PRINTF_LIKE(5, 6) + int tvec_claim(struct tvec_state */*tv*/, int /*ok*/, + const char */*file*/, unsigned /*lno*/, + const char */*msg*/, ...); extern int tvec_claim_v(struct tvec_state */*tv*/, int /*ok*/, const char */*file*/, unsigned /*lno*/, const char */*msg*/, va_list */*ap*/); @@ -1106,6 +1126,9 @@ struct tvec_benchctx { const struct tvec_benchenv *be; /* environment configuration */ struct bench_state *bst; /* benchmark state */ double dflt_target; /* default time in seconds */ + unsigned f; /* flags */ +#define TVBF_SETTRG 1u /* set `@target' */ +#define TVBF_SETMASK (TVBF_SETTRG)) /* mask of @TVBF_SET...@ */ void *subctx; /* subsidiary environment context */ }; @@ -1161,12 +1184,14 @@ extern void tvec_benchreport struct tvec_remotecomms { int infd, outfd; /* input and output descriptors */ - dbuf bin, bout; /* input and output buffers */ + dbuf bout; /* output buffer */ + unsigned char *bin; /* input buffer */ + size_t binoff, binlen, binsz; /* input offset, length, and size */ + size_t t; /* temporary offset */ unsigned f; /* flags */ #define TVRF_BROKEN 0x0001u /* communications have failed */ - /* bits 8--15 for upper layer */ }; -#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, DBUF_INIT, 0 } +#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, 0, 0, 0, 0, 0, 0 } struct tvec_remotectx { struct tvec_state *tv; /* test vector state */ @@ -1183,6 +1208,11 @@ struct tvec_remotectx { #define TVRCN_DEMAND 0x0100u /* connect on demand */ #define TVRCN_FORCE 0x0200u /* force reconnection */ #define TVRF_MUFFLE 0x0400u /* muffle child stderr */ +#define TVRF_SETEXIT 0x0800u /* set `@exit' */ +#define TVRF_SETPRG 0x1000u /* set `@progress' */ +#define TVRF_SETRCN 0x2000u /* set `@reconnect' */ +#define TVRF_SETMASK (TVRF_SETEXIT | TVRF_SETPRG | TVRF_SETRCN) + /* mask of @TVTF_SET...@ */ }; typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/, @@ -1241,21 +1271,69 @@ extern tvec_envteardownfn tvec_remoteteardown; tvec_remoteafter, \ tvec_remoteteardown } -extern int tvec_setprogress(const char */*status*/); +extern PRINTF_LIKE(1, 2) int tvec_setprogress(const char */*status*/, ...); +extern int tvec_setprogress_v(const char */*status*/, va_list */*ap*/); extern int tvec_remoteserver(int /*infd*/, int /*outfd*/, const struct tvec_config */*config*/); extern tvec_connectfn tvec_fork, tvec_exec; -#define TVEC_REMOTEFORK( subenv, tests) \ +#define TVEC_REMOTEFORK(subenv, tests) \ TVEC_REMOTEENV, { tvec_fork, subenv }, { tests } #define TVEC_REMOTEEXEC(subenv, args) \ TVEC_REMOTEENV, { tvec_exec, subenv }, { args } +/*----- Timeouts ----------------------------------------------------------*/ + +struct tvec_timeoutenv { + struct tvec_env _env; + unsigned timer; + double t; + const struct tvec_env *env; +}; + +struct tvec_timeoutctx { + const struct tvec_timeoutenv *te; + unsigned timer; + double t; + unsigned f; /* flags */ +#define TVTF_SETTMO 1u /* set `@timeout' */ +#define TVTF_SETMASK (TVTF_SETTMO) /* mask of @TVTF_SET...@ */ + void *subctx; +}; + +extern tvec_envsetupfn tvec_timeoutsetup; +extern tvec_envsetfn tvec_timeoutset; +extern tvec_envbeforefn tvec_timeoutbefore; +extern tvec_envrunfn tvec_timeoutrun; +extern tvec_envafterfn tvec_timeoutafter; +extern tvec_envteardownfn tvec_timeoutteardown; +#define TVEC_TIMEOUTENV \ + { sizeof(struct tvec_timeoutctx), \ + tvec_timeoutsetup, \ + tvec_timeoutset, \ + tvec_timeoutbefore, \ + tvec_timeoutrun, \ + tvec_timeoutafter, \ + tvec_timeoutteardown } +#define TVEC_TIMEOUTINIT(timer, t) TVEC_TIMEOUTENV, timer, t + /*----- Output functions --------------------------------------------------*/ +/* --- @tvec_strlevel@ --- * + * + * Arguments: @unsigned level@ = level code + * + * Returns: A human-readable description. + * + * Use: Converts a level code into something that you can print in a + * message. + */ + +extern const char *tvec_strlevel(unsigned /*level*/); + /* --- @tvec_report@, @tvec_report_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state @@ -1267,9 +1345,9 @@ extern tvec_connectfn tvec_fork, tvec_exec; * @TVLEV_ERR@ or higher force a nonzero exit code. */ -extern void PRINTF_LIKE(3, 4) - tvec_report(struct tvec_state */*tv*/, unsigned /*level*/, - const char */*msg*/, ...); +extern PRINTF_LIKE(3, 4) + void tvec_report(struct tvec_state */*tv*/, unsigned /*level*/, + const char */*msg*/, ...); extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/, const char */*msg*/, va_list */*ap*/); @@ -1278,7 +1356,7 @@ extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/, * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *msg@, @va_list ap@ = error message * - * Returns: The @tvec_error@ function returns @-1@ as a trivial + * Returns: The @tvec_error@ function returns %$-1$% as a trivial * convenience; @tvec_notice@ does not return a value. * * Use: Report an error or a notice. Errors are distinct from test @@ -1288,10 +1366,10 @@ extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/, * category. */ -extern int PRINTF_LIKE(2, 3) - tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); -extern void PRINTF_LIKE(2, 3) - tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); +extern PRINTF_LIKE(2, 3) + int tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); +extern PRINTF_LIKE(2, 3) + void tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); /* --- @tvec_humanoutput@ --- * * @@ -1377,7 +1455,7 @@ extern struct tvec_output *tvec_dfltout(FILE */*fp*/); * @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. + * Returns: Zero on success, %$-1$% on failure. * * Use: Serialize a collection of register values. * @@ -1400,7 +1478,7 @@ extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, * @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. + * Returns: Zero on success, %$-1$% on failure. * * Use: Deserialize a collection of register values. * @@ -1435,26 +1513,13 @@ extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, * 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@ + * 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., @@ -1462,18 +1527,44 @@ extern void tvec_skipspc(struct tvec_state */*tv*/); * advance the line number). */ -extern int PRINTF_LIKE(3, 4) - tvec_syntax(struct tvec_state */*tv*/, int /*ch*/, - const char */*expect*/, ...); +extern PRINTF_LIKE(3, 4) + int tvec_syntax(struct tvec_state */*tv*/, int /*ch*/, + const char */*expect*/, ...); extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/, const char */*expect*/, va_list */*ap*/); +/* --- @tvec_dupreg@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *name@ = register or pseudoregister name + * + * Returns: %$-1$%. + * + * Use: Reports an error that the register or pseudoregister has been + * assigned already in the current test. + */ + +extern int tvec_dupreg(struct tvec_state */*tv*/, const char */*name*/); + +/* --- @tvec_skipspc@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: Advance over any whitespace characters other than newlines. + * This will stop at `;', end-of-file, or any other kind of + * non-whitespace; and it won't consume a newline. + */ + +extern void tvec_skipspc(struct tvec_state */*tv*/); + /* --- @tvec_flushtoeol@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @unsigned f@ = flags (@TVFF_...@) * - * Returns: Zero on success, @-1@ on error. + * Returns: Zero on success, %$-1$% on error. * * Use: Advance to the start of the next line, consuming the * preceding newline. @@ -1493,7 +1584,7 @@ extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); * * Arguments: @struct tvec_state *tv@ = test-vector state * - * Returns: Zero if there is a next token which can be read; @-1@ if no + * 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 @@ -1507,21 +1598,21 @@ extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/); * * If this function returns zero, then the next character in the * file begins a suitable token which can be read and - * processed. If it returns @-1@ then there is no such token, + * processed. If it returns %$-1$% then there is no such token, * and the file position is left correctly. The line number * count is updated appropriately. */ extern int tvec_nexttoken(struct tvec_state */*tv*/); -/* --- @tvec_readword@ --- * +/* --- @tvec_readword@, @tvec_readword_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @dstr *d@ = string to append the word to * @const char *delims@ = additional delimiters to stop at * @const char *expect@, @va_list ap@ = what was expected * - * Returns: Zero on success, @-1@ on failure. + * Returns: Zero on success, %$-1$% on failure. * * Use: A `word' consists of characters other than whitespace, null * characters, and other than those listed in @delims@; @@ -1530,8 +1621,8 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/); * include `;' in @delims@. This is a common behaviour.) * * If there is no word beginning at the current file position, - * then return @-1@; furthermore, if @expect@ is not null, then - * report an appropriate error via @tvec_syntax@. + * then return %$-1$%; furthermore, if @expect@ is not null, + * then report an appropriate error via @tvec_syntax@. * * Otherwise, the word is accumulated in @d@ and zero is * returned; if @d@ was not empty at the start of the call, the @@ -1542,9 +1633,9 @@ extern int tvec_nexttoken(struct tvec_state */*tv*/); * terminated. */ -extern int PRINTF_LIKE(4, 5) - tvec_readword(struct tvec_state */*tv*/, dstr */*d*/, - const char */*delims*/, const char */*expect*/, ...); +extern PRINTF_LIKE(4, 5) + int 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*/); @@ -1755,6 +1846,8 @@ extern int tvec_claimeq_float(struct tvec_state */*tv*/, #define TVEC_CLAIMEQ_FLOAT(tv, f0, f1) \ (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1)) +extern const struct tvec_floatinfo tvflt_finite, tvflt_nonneg; + /*----- Enumerated types --------------------------------------------------*/ /* An enumeration describes a set of values of some underlying type, each of diff --git a/trace/trace.h b/trace/trace.h index a7edc37..0bb5622 100644 --- a/trace/trace.h +++ b/trace/trace.h @@ -61,8 +61,7 @@ typedef struct trace_opt { * Use: Reports a message to the trace output. */ -extern void PRINTF_LIKE(2, 3) - trace(unsigned /*l*/, const char */*f*/, ...); +extern PRINTF_LIKE(2, 3) void trace(unsigned /*l*/, const char */*f*/, ...); /* --- @trace_block@ --- * * diff --git a/ui/report.h b/ui/report.h index 4266cc2..1936877 100644 --- a/ui/report.h +++ b/ui/report.h @@ -50,8 +50,7 @@ * Use: Reports an error. */ -extern void PRINTF_LIKE(1, 2) - moan(const char *f, ...); +extern PRINTF_LIKE(1, 2) void moan(const char *f, ...); /* --- @die@ --- * * @@ -65,8 +64,7 @@ extern void PRINTF_LIKE(1, 2) * permanent. */ -extern void PRINTF_LIKE(2, 3) NORETURN - die(int status, const char *f, ...); +extern PRINTF_LIKE(2, 3) NORETURN void die(int status, const char *f, ...); /*----- That's all, folks -------------------------------------------------*/ diff --git a/utils/bits.h b/utils/bits.h index 4d1d61a..28c7dd4 100644 --- a/utils/bits.h +++ b/utils/bits.h @@ -36,7 +36,7 @@ #include #include -#if __STDC_VERSION__ >= 199900l +#if __STDC_VERSION__ >= 199901 # include #endif @@ -184,21 +184,21 @@ typedef unsigned char octet, uint8; /* --- List macros --- */ #ifdef HAVE_UINT64 -# define DOUINTCONV(_) \ - _(8, 8, 8) \ - _(16, 16, 16) _(16, 16_L, 16l) _(16, 16_B, 16b) \ - _(24, 24, 24) _(24, 24_L, 24l) _(24, 24_B, 24b) \ - _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b) \ +# define DOUINTCONV_64(_) \ _(64, 64, 64) _(64, 64_L, 64l) _(64, 64_B, 64b) -# define DOUINTSZ(_) _(8) _(16) _(24) _(32) _(64) +# define DOUINTSZ_64(_) _(64) #else -# define DOUINTCONV(_) \ +# define DOUINTCONV_64(_) +# define DOUINTSZ_64(_) +#endif + +#define DOUINTCONV(_) \ _(8, 8, 8) \ _(16, 16, 16) _(16, 16_L, 16l) _(16, 16_B, 16b) \ _(24, 24, 24) _(24, 24_L, 24l) _(24, 24_B, 24b) \ - _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b) -# define DOUINTSZ(_) _(8) _(16) _(24) _(32) -#endif + _(32, 32, 32) _(32, 32_L, 32l) _(32, 32_B, 32b) \ + DOUINTCONV_64(_) +#define DOUINTSZ(_) _(8) _(16) _(24) _(32) _DOUINTSZ_64(_) /* --- Type coercions --- */ diff --git a/utils/gprintf.h b/utils/gprintf.h index 0f06dd3..8c0f87c 100644 --- a/utils/gprintf.h +++ b/utils/gprintf.h @@ -45,8 +45,8 @@ struct gprintf_ops { int (*putch)(void */*out*/, int /*ch*/); int (*putm)(void */*out*/, const char */*p*/, size_t /*sz*/); - PRINTF_LIKE(3, 4) int (*nputf) - (void */*out*/, size_t /*maxsz*/, const char */*p*/, ...); + PRINTF_LIKE(3, 4) + int (*nputf)(void */*out*/, size_t /*maxsz*/, const char */*p*/, ...); }; extern const struct gprintf_ops file_printops; @@ -82,9 +82,9 @@ extern int vgprintf(const struct gprintf_ops */*ops*/, void */*out*/, * for @dstr_putf@, for example. */ -extern int PRINTF_LIKE(3, 4) - gprintf(const struct gprintf_ops */*ops*/, void */*out*/, - const char */*p*/, ...); +extern PRINTF_LIKE(3, 4) + int gprintf(const struct gprintf_ops */*ops*/, void */*out*/, + const char */*p*/, ...); /* --- @gprintf_memputf@ --- * * -- 2.11.0