From: Mark Wooding Date: Sat, 3 Jun 2023 09:31:39 +0000 (+0100) Subject: @@@ BROKEN wip X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/commitdiff_plain/3efcfd2df21aa11bd9d1ba5ea2f5f490fd4d5b84 @@@ BROKEN wip --- diff --git a/struct/t/dstr-putf-test.c b/struct/t/dstr-putf-test.c index 462371d..416b164 100644 --- a/struct/t/dstr-putf-test.c +++ b/struct/t/dstr-putf-test.c @@ -9,21 +9,14 @@ #include "dstr.h" #include "macros.h" +#include "report.h" +#include "tvec.h" -static int win = 0, lose = 0; +static struct tvec_state tvstate; static dstr d = DSTR_INIT; -static char buf[1024]; +static char strbuf[1024]; -static void check(const char *what, const char *want) -{ - if (STRCMP(want, ==, d.buf)) - win++; - else { - lose++; - fprintf(stderr, "test failed: %s\n expected: %s\n found: %s\n", - what, want, d.buf); - } -} +#define TESTGROUP(name) TVEC_TESTGROUP_TAG(grp, &tvstate, name) static void PRINTF_LIKE(1, 2) format(const char *fmt, ...) { @@ -41,54 +34,77 @@ static void PRINTF_LIKE(1, 2) prepare(const char *fmt, ...) va_start(ap, fmt); #ifdef HAVE_SNPRINTF - n = vsnprintf(buf, sizeof(buf), fmt, ap); + n = vsnprintf(strbuf, sizeof(strbuf), fmt, ap); #else - n = vsprintf(buf, fmt, ap); + n = vsprintf(strbuf, fmt, ap); #endif - assert(0 <= n && n < sizeof(buf)); + assert(0 <= n && n < sizeof(strbuf)); } -#define TEST1(fmtargs) do { \ +#define TEST_KNOWN(fmtargs, want) do { \ format fmtargs; \ - prepare fmtargs; \ - check(#fmtargs, buf); \ + tvec_claimeq_string(&tvstate, d.buf, d.len, want, strlen(want), \ + __FILE__, __LINE__, "format " #fmtargs); \ } while (0) -#define TEST2(fmtargs, want) do { \ +#define TEST_REF(fmtargs) do { \ format fmtargs; \ - check(#fmtargs, want); \ + prepare fmtargs; \ + tvec_claimeq_string(&tvstate, d.buf, d.len, strbuf, strlen(strbuf), \ + __FILE__, __LINE__, "format " #fmtargs); \ } while (0) #define LENGTHY \ "This is a rather longer string than the code is expecting: will it fit?" -int main(void) +int main(int argc, char *argv[]) { - TEST2(("Hello, world!"), "Hello, world!"); - TEST2(("just a ->%%<- sign"), "just a ->%<- sign"); - TEST2(("Testing, testing, %d, %d, %d.", 1, 2, 3), - "Testing, testing, 1, 2, 3."); - TEST2(("->%5d<-", 138), "-> 138<-"); - TEST2(("->%*d<-", 5, 138), "-> 138<-"); - TEST2(("->%-*d<-", 5, 138), "->138 <-"); - TEST2(("->%*d<-", -5, 138), "->138 <-"); - TEST2(("->%-*d<-", -5, 138), "->138 <-"); - TEST2(("->%.*s<-", 5, "truncate me"), "->trunc<-"); - TEST2(("->%.*s<-", -5, "don't truncate me"), "->don't truncate me<-"); - TEST2(("Truncation indirect: ->%.*s<-", 10, "a long string to be chopped"), - "Truncation indirect: ->a long str<-"); - TEST2(("%08lx:%s", 0x65604204ul, "tripe-ec"), "65604204:tripe-ec"); - TEST2(("%s", LENGTHY), LENGTHY); - - TEST1(("big float: ->%f<- and integer %d\n", DBL_MAX, 42)); - - TEST2(("Testing, testing, %3$d, %2$d, %1$d.", 3, 2, 1), - "Testing, testing, 1, 2, 3."); - TEST2(("Truncation indirect: ->%1$.*2$s<-", - "a long string to be chopped", 10), - "Truncation indirect: ->a long str<-"); - - if (!lose) printf("All tests successful.\n"); - else printf("FAILED %d of %d tests.\n", lose, win + lose); - return (!!lose); + struct tvec_test test; + int argpos; + + tvec_parseargs(argc, argv, &tvstate, &argpos, &tvec_adhocconfig); + if (argpos < argc) die(2, "no input files expected"); + tvec_adhoc(&tvstate, &test); + + TESTGROUP("basics") { + TEST_KNOWN(("Hello, world!"), "Hello, world!"); + TEST_KNOWN(("just a ->%%<- sign"), "just a ->%<- sign"); + } + + TESTGROUP("integers") { + TEST_KNOWN(("Testing, testing, %d, %d, %d.", 1, 2, 3), + "Testing, testing, 1, 2, 3."); + TEST_KNOWN(("->%5d<-", 138), "-> 138<-"); + TEST_KNOWN(("->%*d<-", 5, 138), "-> 138<-"); + TEST_KNOWN(("->%-*d<-", 5, 138), "->138 <-"); + TEST_KNOWN(("->%*d<-", -5, 138), "->138 <-"); + TEST_KNOWN(("->%-*d<-", -5, 138), "->138 <-"); + } + + TESTGROUP("strings") { + TEST_KNOWN(("->%.*s<-", 5, "truncate me"), "->trunc<-"); + TEST_KNOWN(("->%.*s<-", -5, "don't truncate me"), + "->don't truncate me<-"); + TEST_KNOWN(("Truncation indirect: ->%.*s<-", 10, + "a long string to be chopped"), + "Truncation indirect: ->a long str<-"); + } + + TESTGROUP("combinations") { + TEST_KNOWN(("%08lx:%s", 0x65604204ul, "tripe-ec"), "65604204:tripe-ec"); + TEST_REF(("big float: ->%f<- and integer %d\n", DBL_MAX, 42)); + } + + TESTGROUP("argument-order") { + TEST_KNOWN(("Testing, testing, %3$d, %2$d, %1$d.", 3, 2, 1), + "Testing, testing, 1, 2, 3."); + TEST_KNOWN(("Truncation indirect: ->%1$.*2$s<-", + "a long string to be chopped", 10), + "Truncation indirect: ->a long str<-"); + } + + TESTGROUP("misc") + TEST_KNOWN(("%s", LENGTHY), LENGTHY); + + return (tvec_end(&tvstate)); } diff --git a/struct/tests.at b/struct/tests.at index f54deb5..63d5fc4 100644 --- a/struct/tests.at +++ b/struct/tests.at @@ -50,8 +50,7 @@ AT_CLEANUP ## dstr AT_SETUP([struct: dstr-putf]) AT_KEYWORDS([struct dstr putf dstr_putf]) -AT_CHECK([BUILDDIR/t/dstr-putf.t], [0], [All tests successful. -]) +AT_CHECK([BUILDDIR/t/dstr-putf.t -fh], [0], [ignore]) AT_CLEANUP ## sym diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c index b4ef9a4..78f730f 100644 --- a/test/t/tvec-test.c +++ b/test/t/tvec-test.c @@ -70,7 +70,7 @@ static const struct tvec_floatinfo fenum_fltinfo = #define DEFENUM(tag, ty, slot) \ static const struct tvec_##slot##enuminfo slot##enum_info = \ - { { slot##enum_NAME, TVMISC_##tag }, slot##enum_assocs slot##enum_ARGS }; + { slot##enum_NAME, slot##enum_assocs slot##enum_ARGS }; #define ienum_NAME "order" #define ienum_ARGS , &tvrange_i16 #define uenum_NAME "fruit" @@ -122,10 +122,10 @@ static const struct tvec_urange range_32 = { 0, 31 }; _(float, RFP, float, p, 0) \ _(fltish, RFISH, float, p, &fltish_info) \ _(char, RCH, char, p, 0) \ - _(ienum, RIE, enum, p, &ienum_info) \ - _(uenum, RUE, enum, p, &uenum_info) \ - _(fenum, RFE, enum, p, &fenum_info) \ - _(penum, RPE, enum, p, &penum_info) \ + _(ienum, RIE, ienum, p, &ienum_info) \ + _(uenum, RUE, uenum, p, &uenum_info) \ + _(fenum, RFE, fenum, p, &fenum_info) \ + _(penum, RPE, penum, p, &penum_info) \ _(flags, RF, flags, p, &attr_info) \ _(string, RSTR, string, p, &range_32) \ _(bytes, RBY, bytes, p, &tvrange_byte) \ @@ -199,30 +199,25 @@ static void test_serialization if (BLEFT(&b._b)) { out[RRC].v.i = -3; goto end; } - if (in[RSAB].f&TVRF_LIVE) { - for (rd = tv->test->regs; rd->name; rd++) - if (STRCMP(in[RSAB].v.str.p, ==, rd->name)) { - rv = &out[rd->i].v; - if (rd->ty == &tvty_int || - (rd->ty == &tvty_enum && - ((const struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_INT)) - rv->i ^= 1; - else if (rd->ty == &tvty_uint || rd->ty == &tvty_flags || - (rd->ty == &tvty_enum && - ((const struct tvec_enuminfo *)rd->arg.p)->mv == - TVMISC_INT)) - rv->u ^= 1; - else if (rd->ty == &tvty_enum && - ((const struct tvec_enuminfo *)rd->arg.p)->mv == TVMISC_PTR) - rv->p = rv->p - ? 0 - : (/*unconst*/ void *) - ((const struct tvec_penuminfo *)rd->arg.p)->av[0].p; - else if (rd->ty == &tvty_string) - { if (rv->str.sz) rv->str.p[0] ^= 1; } - else if (rd->ty == &tvty_bytes) - { if (rv->bytes.sz) rv->bytes.p[0] ^= 1; } - } + if ((in[RSAB].f&TVRF_LIVE) && in[RSAB].v.i >= 0) { + rd = &tv->test->regs[in[RSAB].v.i]; rv = &out[in[RSAB].v.i].v; + if (rd->ty == &tvty_int || rd->ty == &tvty_ienum) + rv->i ^= 1; + else if (rd->ty == &tvty_uint || + rd->ty == &tvty_flags || rd->ty == &tvty_uenum) + rv->u ^= 1; + else if (rd->ty == &tvty_float || rd->ty == &tvty_fenum) { + if (rv->f == rv->f) rv->f = -rv->f; + else rv->f = 1.0; + } else if (rd->ty == &tvty_penum) + rv->p = rv->p + ? 0 + : (/*unconst*/ void *) + ((const struct tvec_penuminfo *)rd->arg.p)->av[0].p; + else if (rd->ty == &tvty_string) + { if (rv->str.sz) rv->str.p[0] ^= 1; } + else if (rd->ty == &tvty_bytes) + { if (rv->bytes.sz) rv->bytes.p[0] ^= 1; } } out[RRC].v.i = 0; @@ -230,6 +225,15 @@ end: dbuf_destroy(&b); } +static const struct tvec_iassoc type_assocs[] = { + { "none", -1 }, +#define DEFASSOC(name, i, ty, argslot, argval) { #name, i }, + TYPEREGS(DEFASSOC) +#undef DEFASSOC + { 0, 0 } +}; +static const struct tvec_ienuminfo type_enum = { "regty", type_assocs, 0 }; + static DSGINIT(const) struct tvec_regdef test_regs[] = { #define DEFREG(name, i, ty, argslot, argval) \ { #name, i, &tvty_##ty, TVRF_OPT, \ @@ -238,7 +242,7 @@ static DSGINIT(const) struct tvec_regdef test_regs[] = { #undef DEFREG { "rc", RRC, &tvty_int, TVRF_OPT, { &tvrange_int } }, { "serialized", RSER, &tvty_bytes, TVRF_OPT }, - { "sabotage", RSAB, &tvty_string, TVRF_OPT, { &tvrange_byte } }, + { "sabotage", RSAB, &tvty_ienum, TVRF_OPT, { &type_enum } }, { 0 } }; @@ -298,10 +302,10 @@ static int single_set(struct tvec_state *tv, const char *name, struct singlectx *s = ctx; union tvec_regval rv; static const struct tvec_regdef rd = - { "@show", -1, &tvty_enum, 0, { &tvenum_bool } }; + { "@show", -1, &tvty_ienum, 0, { &tvenum_bool } }; if (STRCMP(name, ==, "@show")) { - if (tvty_enum.parse(&rv, &rd, tv)) return (-1); + if (tvty_ienum.parse(&rv, &rd, tv)) return (-1); if (s) { if (rv.i) s->f |= SF_SHOW; else s->f &= ~SF_SHOW; diff --git a/test/tests.at b/test/tests.at index 30c898a..d823872 100644 --- a/test/tests.at +++ b/test/tests.at @@ -85,31 +85,48 @@ test_parse([int], [4], [4 ; = 0x04 = '\x04']) test_parse([int], [ 17; comment], [17 ; = 0x11 = '\x11']) test_parse([int], [0x234], [564 ; = 0x0234]) -test_parse([int], [033], [27 ; = 0x1b = '\e']) +test_parse([int], [0o33], [27 ; = 0x1b = '\e']) test_parse([int], [ +192], [192 ; = 0xc0 = '\xc0']) test_parse([int], [ -192], [-192 ; = -0xc0]) -test_parserr([int], [17 : badness], [3], - [syntax error: expected end-of-line but found `:']) -test_parserr([int], [17: badness], [3], - [syntax error: expected end-of-line but found `:']) - -test_parserr([int], [xyzzy], [3], - [syntax error: expected signed integer but found `x']) -test_parserr([int], [-splat], [3], - [syntax error: expected signed integer but found `s']) - -test_parserr([int], [0xq], [3], - [syntax error: expected end-of-line but found `x']) -test_parserr([int], [0x], [3], - [syntax error: expected end-of-line but found `x']) - -test_parserr([int], [], [3], - [syntax error: expected signed integer but found @%:@]) - -test_parserr([int], [123456], [3], - [integer 123456 out of range (must be in @<:@-32768 .. 32767@:>@)]) +test_parserr([int], [17 : badness], + [3], [syntax error: expected end-of-line but found `:']) +test_parserr([int], [17: badness], + [3], [syntax error: expected end-of-line but found `:']) + +test_parserr([int], [-_1], + [3], [invalid signed integer `-_1']) +test_parserr([int], [+1234_], + [3], [syntax error: expected end-of-line but found `_']) +test_parse([int], [-1234_5], [-12345 ; = -0x3039]) +test_parserr([int], [+0x_abc], + [3], [syntax error: expected end-of-line but found `x']) +test_parse([int], [-0xa_bc], [-2748 ; = -0x0abc]) +test_parserr([int], [-0xab__c], + [3], [syntax error: expected end-of-line but found `_']) +test_parserr([int], [+010r1234], + [3], [syntax error: expected end-of-line but found `r']) +test_parserr([int], [-1_0r1234], + [3], [syntax error: expected end-of-line but found `r']) + +test_parserr([int], [xyzzy], + [3], [invalid signed integer `xyzzy']) +test_parserr([int], [-splat], + [3], [invalid signed integer `-splat']) +test_parserr([int], [- 1], + [3], [invalid signed integer `-']) + +test_parserr([int], [0xq], + [3], [syntax error: expected end-of-line but found `x']) +test_parserr([int], [0x], + [3], [syntax error: expected end-of-line but found `x']) + +test_parserr([int], [], + [3], [syntax error: expected signed integer but found @%:@]) + +test_parserr([int], [123456], + [3], [integer 123456 out of range (must be in @<:@-32768 .. 32767@:>@)]) AT_CLEANUP @@ -119,38 +136,59 @@ AT_SETUP(tvec type-uint) test_parse([uint], [4], [4 ; = 0x04 = '\x04']) test_parse([uint], [ 17; comment], [17 ; = 0x11 = '\x11']) +test_parse([uint], [012345], [12345 ; = 0x3039]) test_parse([uint], [0x234], [564 ; = 0x0234]) -test_parse([uint], [033], [27 ; = 0x1b = '\e']) - -test_parserr([uint], [17 : badness], [3], - [syntax error: expected end-of-line but found `:']) -test_parserr([uint], [17: badness], [3], - [syntax error: expected end-of-line but found `:']) - -test_parserr([uint], [ +192], [3], - [syntax error: expected unsigned integer but found `+']) -test_parserr([uint], [ -192], [3], - [syntax error: expected unsigned integer but found `-']) - -test_parserr([uint], [xyzzy], [3], - [syntax error: expected unsigned integer but found `x']) - -test_parserr([uint], [0xq], [3], - [syntax error: expected end-of-line but found `x']) -test_parserr([uint], [0x], [3], - [syntax error: expected end-of-line but found `x']) - -test_parserr([uint], [], [3], - [syntax error: expected unsigned integer but found @%:@]) - -test_parserr([uint], [123456], [3], - [integer 123456 out of range (must be in @<:@0 .. 65535@:>@)]) +test_parse([uint], [0o33], [27 ; = 0x1b = '\e']) +test_parse([uint], [0b1011_1101], [189 ; = 0xbd = '\xbd']) +test_parse([uint], [12r123], [171 ; = 0xab = '\xab']) + +test_parserr([uint], [17 : badness], + [3], [syntax error: expected end-of-line but found `:']) +test_parserr([uint], [17: badness], + [3], [syntax error: expected end-of-line but found `:']) + +test_parserr([uint], [_1], + [3], [invalid unsigned integer `_1']) +test_parserr([uint], [1234_], + [3], [syntax error: expected end-of-line but found `_']) +test_parse([uint], [1234_5], [12345 ; = 0x3039]) +test_parserr([uint], [0x_abcd], + [3], [syntax error: expected end-of-line but found `x']) +test_parse([uint], [0xa_bcd], [43981 ; = 0xabcd]) +test_parserr([uint], [0xab__cd], + [3], [syntax error: expected end-of-line but found `_']) +test_parserr([uint], [010r1234], + [3], [syntax error: expected end-of-line but found `r']) +test_parserr([uint], [1_0r1234], + [3], [syntax error: expected end-of-line but found `r']) + +test_parserr([uint], [ +192], + [3], [invalid unsigned integer `+192']) +test_parserr([uint], [ -192], + [3], [invalid unsigned integer `-192']) + +test_parserr([uint], [xyzzy], + [3], [invalid unsigned integer `xyzzy']) + +test_parserr([uint], [0xq], + [3], [syntax error: expected end-of-line but found `x']) +test_parserr([uint], [0x], + [3], [syntax error: expected end-of-line but found `x']) + +test_parserr([uint], [], + [3], [syntax error: expected unsigned integer but found @%:@]) + +test_parserr([uint], [123456], + [3], [integer 123456 out of range (must be in @<:@0 .. 65535@:>@)]) AT_CLEANUP ###-------------------------------------------------------------------------- AT_SETUP([tvec type-float]) +test_parse([float], [0.0], [0]) +test_parse([float], [-0.0], [-0]) + test_parse([float], [1.234], [1.234]) AT_CLEANUP @@ -180,7 +218,7 @@ AT_DATA([tv], int = -2 uint = 7 -float = 6.28 +float = @%:@nan fltish = 0.1 char = x ienum = greater @@ -198,6 +236,7 @@ bytes = ad4f14f2 444066d0 6bc430b7 323ba122 f622919d e18b1fda b0ca9902 b9729d49 2c807ec5 99d5e980 b2eac9cc 53bf67d6 +@show = t ]) AT_CHECK([BUILDDIR/t/tvec.t -fh tv], [0], [ignore]) diff --git a/test/tvec-core.c b/test/tvec-core.c index 6aa49fa..03873dc 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -392,7 +392,7 @@ static void begin_test_group(struct tvec_state *tv, struct groupstate *g) } } -void tvec_reportgroup(struct tvec_state *tv) +static void report_group(struct tvec_state *tv) { unsigned i, out, nrun; @@ -404,7 +404,7 @@ void tvec_reportgroup(struct tvec_state *tv) else { if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE; else out = TVOUT_WIN; - tv->grps[out]++; tv->output->ops->egroup(tv->output, out); + tv->grps[out]++; tv->output->ops->egroup(tv->output); } } @@ -415,7 +415,7 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g) if (!t) return; if (tv->f&TVSF_OPEN) check(tv, g); - if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); + if (!(tv->f&TVSF_SKIP)) report_group(tv); env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx); release_registers(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0; } @@ -548,9 +548,7 @@ void tvec_begin(struct tvec_state *tv_out, tv_out->tests = config->tests; tv_out->test = 0; tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0; - o->tv = tv_out; tv_out->output = o; - - tv_out->output->ops->bsession(tv_out->output); + tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out); } int tvec_end(struct tvec_state *tv) @@ -636,7 +634,7 @@ void tvec_begingroup(struct tvec_state *tv, const char *name, void tvec_endgroup(struct tvec_state *tv) { - if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv); + if (!(tv->f&TVSF_SKIP)) report_group(tv); tv->test = 0; } @@ -680,19 +678,27 @@ static void adhoc_claim_teardown(struct tvec_state *tv, if (ck->f&ACF_FRESH) tvec_endtest(tv); } -int tvec_claim(struct tvec_state *tv, int ok, - const char *file, unsigned lno, const char *expr, ...) +int tvec_claim_v(struct tvec_state *tv, int ok, + const char *file, unsigned lno, + const char *msg, va_list *ap) { struct adhoc_claim ck; - va_list ap; adhoc_claim_setup(tv, &ck, 0, file, lno); - if (!ok) - { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); } + if (!ok) tvec_fail_v(tv, msg, ap); adhoc_claim_teardown(tv, &ck); return (ok); } +int tvec_claim(struct tvec_state *tv, int ok, + const char *file, unsigned lno, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); tvec_claim_v(tv, ok, file, lno, msg, &ap); va_end(ap); + return (ok); +} + int tvec_claimeq(struct tvec_state *tv, const struct tvec_regty *ty, const union tvec_misc *arg, const char *file, unsigned lno, const char *expr) diff --git a/test/tvec-output.c b/test/tvec-output.c index fbdb347..c4809bb 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -93,14 +93,12 @@ static int register_maxnamelen(const struct tvec_state *tv) /*----- Skeleton ----------------------------------------------------------*/ /* -static void ..._error(struct tvec_output *o, const char *msg, va_list *ap) -static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap) -static void ..._bsession(struct tvec_output *o) +static void ..._bsession(struct tvec_output *o, struct tvec_state *tv) static int ..._esession(struct tvec_output *o) static void ..._bgroup(struct tvec_output *o) -static void ..._egroup(struct tvec_output *o, unsigned outcome) static void ..._skipgroup(struct tvec_output *o, const char *excuse, va_list *ap) +static void ..._egroup(struct tvec_output *o) static void ..._btest(struct tvec_output *o) static void ..._skip(struct tvec_output *o, const char *excuse, va_list *ap) static void ..._fail(struct tvec_output *o, const char *detail, va_list *ap) @@ -112,14 +110,16 @@ static void ..._bbench(struct tvec_output *o, static void ..._ebench(struct tvec_output *o, const char *ident, unsigned unit, const struct tvec_timing *t) +static void ..._error(struct tvec_output *o, const char *msg, va_list *ap) +static void ..._notice(struct tvec_output *o, const char *msg, va_list *ap) static void ..._destroy(struct tvec_output *o) static const struct tvec_outops ..._ops = { - ..._error, ..._notice, ..._bsession, ..._esession, ..._bgroup, ..._egroup, ..._skip, ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest, ..._bbench, ..._ebench, + ..._error, ..._notice, ..._destroy }; */ @@ -151,6 +151,7 @@ static const struct tvec_outops ..._ops = { struct human_output { struct tvec_output _o; + struct tvec_state *tv; FILE *fp; dstr scoreboard; unsigned attr; @@ -207,7 +208,7 @@ static void clear_progress(struct human_output *h) size_t i, n; if (h->f&HOF_PROGRESS) { - n = strlen(h->_o.tv->test->name) + 2 + h->scoreboard.len; + n = strlen(h->tv->test->name) + 2 + h->scoreboard.len; for (i = 0; i < n; i++) fputs("\b \b", h->fp); h->f &= ~HOF_PROGRESS; } @@ -225,11 +226,11 @@ static void write_scoreboard_char(struct human_output *h, int ch) static void show_progress(struct human_output *h) { - struct tvec_state *tv = h->_o.tv; + struct tvec_state *tv = h->tv; const char *p, *l; if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) { - fprintf(h->fp, "%s: ", h->_o.tv->test->name); + fprintf(h->fp, "%s: ", tv->test->name); if (!(h->f&HOF_COLOUR)) dstr_write(&h->scoreboard, h->fp); else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++) @@ -260,27 +261,8 @@ static void report_location(struct human_output *h, FILE *fp, #undef FLUSH } -static void human_report(struct tvec_output *o, const char *msg, va_list *ap) -{ - struct human_output *h = (struct human_output *)o; - struct tvec_state *tv = h->_o.tv; - dstr d = DSTR_INIT; - - dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); - - clear_progress(h); fflush(h->fp); - fprintf(stderr, "%s: ", QUIS); - report_location(h, stderr, tv->infile, tv->lno); - fwrite(d.buf, 1, d.len, stderr); - - if (h->f&HOF_DUPERR) { - report_location(h, h->fp, tv->infile, tv->lno); - fwrite(d.buf, 1, d.len, h->fp); - } - show_progress(h); -} - -static void human_bsession(struct tvec_output *o) { ; } +static void human_bsession(struct tvec_output *o, struct tvec_state *tv) + { struct human_output *h = (struct human_output *)o; h->tv = tv; } static void report_skipped(struct human_output *h, unsigned n) { @@ -294,7 +276,7 @@ static void report_skipped(struct human_output *h, unsigned n) static int human_esession(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; - struct tvec_state *tv = h->_o.tv; + struct tvec_state *tv = h->tv; unsigned all_win = tv->all[TVOUT_WIN], grps_win = tv->grps[TVOUT_WIN], all_lose = tv->all[TVOUT_LOSE], grps_lose = tv->grps[TVOUT_LOSE], @@ -326,58 +308,51 @@ static int human_esession(struct tvec_output *o) fputs(" found in input; tests may not have run correctly\n", h->fp); } - return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0); + h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0); } static void human_bgroup(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; - h->maxlen = register_maxnamelen(h->_o.tv); + h->maxlen = register_maxnamelen(h->tv); dstr_reset(&h->scoreboard); show_progress(h); } -static void human_grpsumm(struct human_output *h, unsigned outcome) +static void human_skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) { - struct tvec_state *tv = h->_o.tv; - unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE], - skip = tv->curr[TVOUT_SKIP], run = win + lose; + struct human_output *h = (struct human_output *)o; - if (lose) { - assert(outcome == TVOUT_LOSE); - fprintf(h->fp, " %u/%u ", lose, run); - setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); - report_skipped(h, skip); + if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) { + h->f &= ~HOF_PROGRESS; + setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); } else { - assert(outcome == TVOUT_WIN); - fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0); - report_skipped(h, skip); + fprintf(h->fp, "%s: ", h->tv->test->name); + setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); } + if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); } fputc('\n', h->fp); } -static void human_egroup(struct tvec_output *o, unsigned outcome) +static void human_egroup(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; + struct tvec_state *tv = h->tv; + unsigned win = tv->curr[TVOUT_WIN], lose = tv->curr[TVOUT_LOSE], + skip = tv->curr[TVOUT_SKIP], run = win + lose; if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS; - else fprintf(h->fp, "%s:", h->_o.tv->test->name); - human_grpsumm(h, outcome); -} - -static void human_skipgroup(struct tvec_output *o, - const char *excuse, va_list *ap) -{ - struct human_output *h = (struct human_output *)o; + else fprintf(h->fp, "%s:", h->tv->test->name); - if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) { - h->f &= ~HOF_PROGRESS; - setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + if (lose) { + fprintf(h->fp, " %u/%u ", lose, run); + setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); + report_skipped(h, skip); } else { - fprintf(h->fp, "%s: ", h->_o.tv->test->name); - setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0); + report_skipped(h, skip); } - if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); } fputc('\n', h->fp); } @@ -388,7 +363,7 @@ static void human_skip(struct tvec_output *o, const char *excuse, va_list *ap) { struct human_output *h = (struct human_output *)o; - struct tvec_state *tv = h->_o.tv; + struct tvec_state *tv = h->tv; clear_progress(h); report_location(h, h->fp, tv->infile, tv->test_lno); @@ -402,7 +377,7 @@ static void human_fail(struct tvec_output *o, const char *detail, va_list *ap) { struct human_output *h = (struct human_output *)o; - struct tvec_state *tv = h->_o.tv; + struct tvec_state *tv = h->tv; clear_progress(h); report_location(h, h->fp, tv->infile, tv->test_lno); @@ -452,7 +427,7 @@ static void human_bbench(struct tvec_output *o, const char *ident, unsigned unit) { struct human_output *h = (struct human_output *)o; - struct tvec_state *tv = h->_o.tv; + struct tvec_state *tv = h->tv; clear_progress(h); fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp); @@ -466,6 +441,26 @@ static void human_ebench(struct tvec_output *o, tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp); } +static void human_report(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct human_output *h = (struct human_output *)o; + struct tvec_state *tv = h->tv; + dstr d = DSTR_INIT; + + dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); + + clear_progress(h); fflush(h->fp); + fprintf(stderr, "%s: ", QUIS); + report_location(h, stderr, tv->infile, tv->lno); + fwrite(d.buf, 1, d.len, stderr); + + if (h->f&HOF_DUPERR) { + report_location(h, h->fp, tv->infile, tv->lno); + fwrite(d.buf, 1, d.len, h->fp); + } + show_progress(h); +} + static void human_destroy(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; @@ -476,11 +471,11 @@ static void human_destroy(struct tvec_output *o) } static const struct tvec_outops human_ops = { - human_report, human_report, human_bsession, human_esession, - human_bgroup, human_egroup, human_skipgroup, + human_bgroup, human_skipgroup, human_egroup, human_btest, human_skip, human_fail, human_dumpreg, human_etest, human_bbench, human_ebench, + human_report, human_report, human_destroy }; @@ -521,6 +516,7 @@ struct tvec_output *tvec_humanoutput(FILE *fp) struct tap_output { struct tvec_output _o; + struct tvec_state *tv; FILE *fp; dstr d; int maxlen; @@ -528,26 +524,6 @@ struct tap_output { #define TOF_FRESHLINE 1u }; -static void tap_report(struct tap_output *t, const char *msg, va_list *ap) -{ - struct tvec_state *tv = t->_o.tv; - - if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno); - vfprintf(t->fp, msg, *ap); fputc('\n', t->fp); -} - -static void tap_error(struct tvec_output *o, const char *msg, va_list *ap) -{ - struct tap_output *t = (struct tap_output *)o; - fputs("Bail out! ", t->fp); tap_report(t, msg, ap); -} - -static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) -{ - struct tap_output *t = (struct tap_output *)o; - fputs("## ", t->fp); tap_report(t, msg, ap); -} - static int tap_writech(void *go, int ch) { struct tap_output *t = go; @@ -601,11 +577,17 @@ static int tap_nwritef(void *go, size_t maxsz, const char *p, ...) static const struct gprintf_ops tap_printops = { tap_writech, tap_writem, tap_nwritef }; -static void tap_bsession(struct tvec_output *o) { ; } +static void tap_bsession(struct tvec_output *o, struct tvec_state *tv) +{ + struct tap_output *t = (struct tap_output *)o; + + t->tv = tv; + fputs("TAP version 13\n", t->fp); +} static unsigned tap_grpix(struct tap_output *t) { - struct tvec_state *tv = t->_o.tv; + struct tvec_state *tv = t->tv; return (tv->grps[TVOUT_WIN] + tv->grps[TVOUT_LOSE] + @@ -615,7 +597,7 @@ static unsigned tap_grpix(struct tap_output *t) static int tap_esession(struct tvec_output *o) { struct tap_output *t = (struct tap_output *)o; - struct tvec_state *tv = t->_o.tv; + struct tvec_state *tv = t->tv; if (tv->f&TVSF_ERROR) { fputs("Bail out! " @@ -625,19 +607,29 @@ static int tap_esession(struct tvec_output *o) } fprintf(t->fp, "1..%u\n", tap_grpix(t)); - return (tv->all[TVOUT_LOSE] ? 1 : 0); + t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0); } static void tap_bgroup(struct tvec_output *o) { struct tap_output *t = (struct tap_output *)o; - t->maxlen = register_maxnamelen(t->_o.tv); + t->maxlen = register_maxnamelen(t->tv); +} + +static void tap_skipgroup(struct tvec_output *o, + const char *excuse, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + + fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name); + if (excuse) { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); } + fputc('\n', t->fp); } -static void tap_egroup(struct tvec_output *o, unsigned outcome) +static void tap_egroup(struct tvec_output *o) { struct tap_output *t = (struct tap_output *)o; - struct tvec_state *tv = t->_o.tv; + struct tvec_state *tv = t->tv; unsigned grpix = tap_grpix(t), win = tv->curr[TVOUT_WIN], @@ -645,35 +637,22 @@ static void tap_egroup(struct tvec_output *o, unsigned outcome) skip = tv->curr[TVOUT_SKIP]; if (lose) { - assert(outcome == TVOUT_LOSE); - fprintf(t->fp, "not ok %u %s: FAILED %u/%u", + fprintf(t->fp, "not ok %u - %s: FAILED %u/%u", grpix, tv->test->name, lose, win + lose); if (skip) fprintf(t->fp, " (skipped %u)", skip); } else { - assert(outcome == TVOUT_WIN); - fprintf(t->fp, "ok %u %s: passed %u", grpix, tv->test->name, win); + fprintf(t->fp, "ok %u - %s: passed %u", grpix, tv->test->name, win); if (skip) fprintf(t->fp, " (skipped %u)", skip); } fputc('\n', t->fp); } -static void tap_skipgroup(struct tvec_output *o, - const char *excuse, va_list *ap) -{ - struct tap_output *t = (struct tap_output *)o; - - fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->_o.tv->test->name); - if (excuse) - { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); } - fputc('\n', t->fp); -} - static void tap_btest(struct tvec_output *o) { ; } static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap) { struct tap_output *t = (struct tap_output *)o; - struct tvec_state *tv = t->_o.tv; + struct tvec_state *tv = t->tv; fprintf(t->fp, "## %s:%u: `%s' skipped", tv->infile, tv->test_lno, tv->test->name); @@ -684,7 +663,7 @@ static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap) static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap) { struct tap_output *t = (struct tap_output *)o; - struct tvec_state *tv = t->_o.tv; + struct tvec_state *tv = t->tv; fprintf(t->fp, "## %s:%u: `%s' FAILED", tv->infile, tv->test_lno, tv->test->name); @@ -720,13 +699,33 @@ static void tap_ebench(struct tvec_output *o, const struct bench_timing *tm) { struct tap_output *t = (struct tap_output *)o; - struct tvec_state *tv = t->_o.tv; + struct tvec_state *tv = t->tv; fprintf(t->fp, "## %s: %s: ", tv->test->name, ident); t->f &= ~TOF_FRESHLINE; tvec_benchreport(&tap_printops, t, unit, tm); fputc('\n', t->fp); } +static void tap_report(struct tap_output *t, const char *msg, va_list *ap) +{ + struct tvec_state *tv = t->tv; + + if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno); + vfprintf(t->fp, msg, *ap); fputc('\n', t->fp); +} + +static void tap_error(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + fputs("Bail out! ", t->fp); tap_report(t, msg, ap); +} + +static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) +{ + struct tap_output *t = (struct tap_output *)o; + fputs("## ", t->fp); tap_report(t, msg, ap); +} + static void tap_destroy(struct tvec_output *o) { struct tap_output *t = (struct tap_output *)o; @@ -737,11 +736,11 @@ static void tap_destroy(struct tvec_output *o) } static const struct tvec_outops tap_ops = { - tap_error, tap_notice, tap_bsession, tap_esession, - tap_bgroup, tap_egroup, tap_skipgroup, + tap_bgroup, tap_skipgroup, tap_egroup, tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest, tap_bbench, tap_ebench, + tap_error, tap_notice, tap_destroy }; diff --git a/test/tvec-types.c b/test/tvec-types.c index 8be8ae2..1984a1e 100644 --- a/test/tvec-types.c +++ b/test/tvec-types.c @@ -58,6 +58,16 @@ # define INFP(x) ((x) > DBL_MAX || (x) < DBL_MIN) #endif +#ifdef signbit +# define NEGP(x) signbit(x) +#else +# define NEGP(x) ((x) < 0.0) +#endif + +static void trivial_release(union tvec_regval *rv, + const struct tvec_regdef *rd) + { ; } + static int signed_to_buf(buf *b, long i) { kludge64 k; @@ -130,46 +140,129 @@ static int check_unsigned_range(unsigned long u, return (0); } +static int chtodig(int ch) +{ + if ('0' <= ch && ch <= '9') return (ch - '0'); + else if ('a' <= ch && ch <= 'z') return (ch - 'a' + 10); + else if ('A' <= ch && ch <= 'Z') return (ch - 'A' + 10); + else return (-1); +} + +static int parse_unsigned_integer(unsigned long *u_out, const char **q_out, + const char *p) +{ + unsigned long u; + int ch, d, r; + const char *q; + unsigned f = 0; +#define f_implicit 1u +#define f_digit 2u +#define f_uscore 4u + + if (p[0] != '0' || !p[1]) { + d = chtodig(*p); if (0 > d || d >= 10) return (-1); + r = 10; u = d; p++; f |= f_implicit | f_digit; + } else { + u = 0; d = chtodig(p[2]); + if (d < 0) { r = 10; f |= f_implicit | f_digit; p++; } + else if ((p[1] == 'x' || p[1] == 'X') && d < 16) { r = 16; p += 2; } + else if ((p[1] == 'o' || p[1] == 'O') && d < 8) { r = 8; p += 2; } + else if ((p[1] == 'b' || p[1] == 'B') && d < 2) { r = 2; p += 2; } + else { r = 10; f |= f_digit; p++; } + } + + q = p; + for (;;) { + ch = *p; + if (ch == '_') { + if (f&f_uscore) break; + p++; f = (f&~f_implicit) | f_uscore; + } + else if (ch == 'r' || ch == 'R') { + if (!(f&f_implicit) || !u || u >= 36) break; + d = chtodig(p[1]); if (0 > d || d >= u) break; + r = u; u = d; f = (f&~f_implicit) | f_digit; p += 2; q = p; + } else { + d = chtodig(ch); + if (d < 0 || d >= r) break; + if (u > ULONG_MAX/r) return (-1); + u *= r; if (u > ULONG_MAX - d) return (-1); + u += d; f = (f&~f_uscore) | f_digit; p++; q = p; + } + } + + if (!(f&f_digit)) return (-1); + *u_out = u; *q_out = q; return (0); + +#undef f_implicit +#undef f_digit +#undef f_uscore +} + +static int parse_signed_integer(long *i_out, const char **q_out, + const char *p) +{ + unsigned long u; + unsigned f = 0; +#define f_neg 1u + + if (*p == '+') p++; + else if (*p == '-') { f |= f_neg; p++; } + + if (parse_unsigned_integer(&u, q_out, p)) return (-1); + + if (!(f&f_neg)) { + if (u > LONG_MAX) return (-1); + *i_out = u; + } else { + if (u && u - 1 > -(LONG_MIN + 1)) return (-1); + *i_out = u ? -(long)(u - 1) - 1 : 0; + } + + return (0); + +#undef f_neg +} + static int parse_signed(long *i_out, const char *p, const struct tvec_irange *ir, struct tvec_state *tv) { - char *q; const char *pp; - int olderr; long i; + const char *q; - olderr = errno; errno = 0; - pp = p; if (*pp == '-' || *pp == '+') pp++; - if (!ISDIGIT(*pp)) - return (tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "signed integer")); - i = strtol(p, &q, 0); - if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line")); - if (errno) - return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno))); + if (parse_signed_integer(&i, &q, p)) + return (tvec_error(tv, "invalid signed integer `%s'", p)); + if (*q) return (tvec_syntax(tv, *q, "end-of-line")); if (check_signed_range(i, ir, tv)) return (-1); - errno = olderr; *i_out = i; - return (0); + *i_out = i; return (0); } static int parse_unsigned(unsigned long *u_out, const char *p, const struct tvec_urange *ur, struct tvec_state *tv) { - char *q; - int olderr; unsigned long u; + const char *q; - olderr = errno; errno = 0; - if (!ISDIGIT(*p)) return (tvec_syntax(tv, *p, "unsigned integer")); - u = strtoul(p, &q, 0); - if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line")); - if (errno) - return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno))); + if (parse_unsigned_integer(&u, &q, p)) + return (tvec_error(tv, "invalid unsigned integer `%s'", p)); + if (*q) return (tvec_syntax(tv, *q, "end-of-line")); if (check_unsigned_range(u, ur, tv)) return (-1); - errno = olderr; *u_out = u; - return (0); + *u_out = u; return (0); +} + +static void format_signed_hex(const struct gprintf_ops *gops, void *go, + long i) +{ + unsigned long u = i >= 0 ? i : -(unsigned long)i; + gprintf(gops, go, "%s0x%0*lx", i < 0 ? "-" : "", hex_width(u), u); } +static void format_unsigned_hex(const struct gprintf_ops *gops, void *go, + unsigned long u) + { gprintf(gops, go, "0x%0*lx", hex_width(u), u); } + static void format_floating(const struct gprintf_ops *gops, void *go, double x) { @@ -272,13 +365,11 @@ static int eqish_floating_p(double x, double y, switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) { case TVFF_EXACT: - return (x == y); + return (x == y && NEGP(x) == NEGP(y)); case TVFF_ABSDELTA: t = x - y; if (t < 0) t = -t; return (t < fi->delta); case TVFF_RELDELTA: - xx = x >= 0 ? x : -x; yy = y >= 0 ? y : -y; - if (xx < yy) { t = x; x = y; y = t; } - return (1.0 - y/x < fi->delta); + t = 1.0 - y/x; if (t < 0) t = -t; return (t < fi->delta); default: abort(); } @@ -303,7 +394,7 @@ static int parse_floating(double *x_out, const char *p, } else if (STRCMP(p, ==, "#inf") || STRCMP(p, ==, "#+inf") || STRCMP(p, ==, "+#inf")) { -#ifdef NAN +#ifdef INFINITY x = INFINITY; rc = 0; #else tvec_error(tv, "infinity not supported on this system"); @@ -311,7 +402,7 @@ static int parse_floating(double *x_out, const char *p, #endif } else if (STRCMP(p, ==, "#-inf") || STRCMP(p, ==, "-#inf")) { -#ifdef NAN +#ifdef INFINITY x = -INFINITY; rc = 0; #else tvec_error(tv, "infinity not supported on this system"); @@ -322,7 +413,7 @@ static int parse_floating(double *x_out, const char *p, if (*pp == '+' || *pp == '-') pp++; if (*pp == '.') pp++; if (!ISDIGIT(*pp)) { - tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "floating-point number"); + tvec_syntax(tv, *p ? *p : fgetc(tv->fp), "floating-point number"); rc = -1; goto end; } olderr = errno; errno = 0; @@ -464,8 +555,8 @@ end: } #define FCF_BRACE 1u -static void format_charesc(int ch, unsigned f, - const struct gprintf_ops *gops, void *go) +static void format_charesc(const struct gprintf_ops *gops, void *go, + int ch, unsigned f) { switch (ch) { case '\a': gprintf(gops, go, "\\a"); break; @@ -487,7 +578,7 @@ static void format_charesc(int ch, unsigned f, } } -static void format_char(int ch, const struct gprintf_ops *gops, void *go) +static void format_char(const struct gprintf_ops *gops, void *go, int ch) { if (ch == EOF) gprintf(gops, go, "#eof"); @@ -495,11 +586,25 @@ static void format_char(int ch, const struct gprintf_ops *gops, void *go) gprintf(gops, go, "'%c'", ch); else { gprintf(gops, go, "'"); - format_charesc(ch, 0, gops, go); + format_charesc(gops, go, ch, 0); gprintf(gops, go, "'"); } } +static void maybe_format_signed_char + (const struct gprintf_ops *gops, void *go, long i) +{ + if (i == EOF || (0 <= i && i < UCHAR_MAX)) + { gprintf(gops, go, " = "); format_char(gops, go, i); } +} + +static void maybe_format_unsigned_char + (const struct gprintf_ops *gops, void *go, unsigned long u) +{ + if (u < UCHAR_MAX) + { gprintf(gops, go, " = "); format_char(gops, go, u); } +} + enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 }; static int collect_bare(dstr *d, struct tvec_state *tv) @@ -656,9 +761,6 @@ static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd) static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd) { rv->u = 0; } -static void release_int(union tvec_regval *rv, const struct tvec_regdef *rd) - { ; } - static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1, const struct tvec_regdef *rd) { return (rv0->i == rv1->i); } @@ -725,16 +827,12 @@ static void dump_int(const union tvec_regval *rv, unsigned style, const struct gprintf_ops *gops, void *go) { - unsigned long u; gprintf(gops, go, "%ld", rv->i); if (!(style&TVSF_COMPACT)) { - if (rv->i >= 0) u = rv->i; - else u = -(unsigned long)rv->i; - gprintf(gops, go, " ; = %s0x%0*lx", - rv->i < 0 ? "-" : "", hex_width(u), u); - if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX)) - { gprintf(gops, go, " = "); format_char(rv->i, gops, go); } + gprintf(gops, go, " ; = "); + format_signed_hex(gops, go, rv->i); + maybe_format_signed_char(gops, go, rv->i); } } @@ -745,14 +843,14 @@ static void dump_uint(const union tvec_regval *rv, { gprintf(gops, go, "%lu", rv->u); if (!(style&TVSF_COMPACT)) { - gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u); - if (rv->u < UCHAR_MAX) - { gprintf(gops, go, " = "); format_char(rv->u, gops, go); } + gprintf(gops, go, " ; = "); + format_unsigned_hex(gops, go, rv->u); + maybe_format_unsigned_char(gops, go, rv->u); } } const struct tvec_regty tvty_int = { - init_int, release_int, eq_int, + init_int, trivial_release, eq_int, tobuf_int, frombuf_int, parse_int, dump_int }; @@ -767,7 +865,7 @@ const struct tvec_irange tvrange_i32 = { -2147483648, 2147483647 }; const struct tvec_regty tvty_uint = { - init_uint, release_int, eq_uint, + init_uint, trivial_release, eq_uint, tobuf_uint, frombuf_uint, parse_uint, dump_uint }; @@ -785,7 +883,7 @@ const struct tvec_urange int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1, const char *file, unsigned lno, const char *expr) { - tv->in[0].v.i = i0; tv->out[0].v.i = i1; + tv->out[0].v.i = i0; tv->in[0].v.i = i1; return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr)); } @@ -793,17 +891,14 @@ int tvec_claimeq_uint(struct tvec_state *tv, unsigned long u0, unsigned long u1, const char *file, unsigned lno, const char *expr) { - tv->in[0].v.u = u0; tv->out[0].v.u = u1; + tv->out[0].v.u = u0; tv->in[0].v.u = u1; return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr)); } -/*----- Main code ---------------------------------------------------------*/ +/*----- Floating-point type -----------------------------------------------*/ static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd) { rv->f = 0.0; } -static void release_float(union tvec_regval *rv, - const struct tvec_regdef *rd) - { ; } static int eq_float(const union tvec_regval *rv0, const union tvec_regval *rv1, @@ -842,11 +937,19 @@ static void dump_float(const union tvec_regval *rv, { format_floating(gops, go, rv->f); } const struct tvec_regty tvty_float = { - init_float, release_float, eq_float, + init_float, trivial_release, eq_float, tobuf_float, frombuf_float, parse_float, dump_float }; +int tvec_claimeq_float(struct tvec_state *tv, + double f0, double f1, + const char *file, unsigned lno, + const char *expr) +{ + return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0, + file, lno, expr)); +} int tvec_claimeqish_float(struct tvec_state *tv, double f0, double f1, unsigned f, double delta, const char *file, unsigned lno, @@ -856,277 +959,197 @@ int tvec_claimeqish_float(struct tvec_state *tv, union tvec_misc arg; fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi; - tv->in[0].v.f = f0; tv->in[1].v.f = f1; + tv->out[0].v.f = f0; tv->in[0].v.f = f1; return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr)); } -int tvec_claimeq_float(struct tvec_state *tv, - double f0, double f1, - const char *file, unsigned lno, - const char *expr) -{ - return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0, - file, lno, expr)); -} /*----- Enumerations ------------------------------------------------------*/ -static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd) -{ - const struct tvec_enuminfo *ei = rd->arg.p; - - switch (ei->mv) { -#define CASE(tag, ty, slot) \ - case TVMISC_##tag: rv->slot = 0; break; - TVEC_MISCSLOTS(CASE) -#undef CASE - default: abort(); - } -} +#define init_ienum init_int +#define init_uenum init_uint +#define init_fenum init_float +static void init_penum(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->p = 0; } -static int eq_enum(const union tvec_regval *rv0, - const union tvec_regval *rv1, - const struct tvec_regdef *rd) +#define eq_ienum eq_int +#define eq_uenum eq_uint +static int eq_fenum(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) { - const struct tvec_enuminfo *ei = rd->arg.p; - const struct tvec_fenuminfo *fei; - - switch (ei->mv) { -#define CASE(tag, ty, slot) \ - case TVMISC_##tag: PREP_##tag return (HANDLE_##tag); -#define PREP_INT -#define HANDLE_INT rv0->i == rv1->i -#define PREP_UINT -#define HANDLE_UINT rv0->u == rv1->u -#define PREP_FLT fei = (const struct tvec_fenuminfo *)ei; -#define HANDLE_FLT eqish_floating_p(rv0->f, rv1->f, fei->fi) -#define PREP_PTR -#define HANDLE_PTR rv0->p == rv1->p - TVEC_MISCSLOTS(CASE) -#undef CASE -#undef PREP_INT -#undef HANDLE_INT -#undef PREP_UINT -#undef HANDLE_UINT -#undef PREP_FLT -#undef HANDLE_FLT -#undef PREP_PTR -#undef HANDLE_PTR - default: abort(); - } + const struct tvec_fenuminfo *ei = rd->arg.p; + return (eqish_floating_p(rv0->f, rv1->f, ei->fi)); } +static int eq_penum(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) + { return (rv0->p == rv1->p); } -static int tobuf_enum(buf *b, const union tvec_regval *rv, - const struct tvec_regdef *rd) +#define tobuf_ienum tobuf_int +#define tobuf_uenum tobuf_uint +#define tobuf_fenum tobuf_float +static int tobuf_penum(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) { - const struct tvec_enuminfo *ei = rd->arg.p; - const struct tvec_penuminfo *pei; + const struct tvec_penuminfo *pei = rd->arg.p; const struct tvec_passoc *pa; long i; - switch (ei->mv) { -#define CASE(tag, ty, slot) \ - case TVMISC_##tag: HANDLE_##tag -#define HANDLE_INT return (signed_to_buf(b, rv->i)); -#define HANDLE_UINT return (unsigned_to_buf(b, rv->u)); -#define HANDLE_FLT return (buf_putf64l(b, rv->f)); -#define HANDLE_PTR \ - pei = (const struct tvec_penuminfo *)ei; \ - for (pa = pei->av, i = 0; pa->tag; pa++, i++) \ - if (pa->p == rv->p) goto found; \ - if (!rv->p) i = -1; \ - else return (-1); \ - found: \ - return (signed_to_buf(b, i)); - TVEC_MISCSLOTS(CASE) -#undef CASE -#undef HANDLE_INT -#undef HANDLE_UINT -#undef HANDLE_FLT -#undef HANDLE_PTR - default: abort(); - } - return (0); + for (pa = pei->av, i = 0; pa->tag; pa++, i++) + if (pa->p == rv->p) goto found; + if (!rv->p) i = -1; + else return (-1); +found: + return (signed_to_buf(b, i)); } -static int frombuf_enum(buf *b, union tvec_regval *rv, +#define frombuf_ienum frombuf_int +#define frombuf_uenum frombuf_uint +#define frombuf_fenum frombuf_float +static int frombuf_penum(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { - const struct tvec_enuminfo *ei = rd->arg.p; - const struct tvec_penuminfo *pei; + const struct tvec_penuminfo *pei = rd->arg.p; const struct tvec_passoc *pa; long i, n; - switch (ei->mv) { -#define CASE(tag, ty, slot) \ - case TVMISC_##tag: HANDLE_##tag -#define HANDLE_INT return (signed_from_buf(b, &rv->i)); -#define HANDLE_UINT return (unsigned_from_buf(b, &rv->u)); -#define HANDLE_FLT return (buf_getf64l(b, &rv->f)); -#define HANDLE_PTR \ - pei = (const struct tvec_penuminfo *)ei; \ - for (pa = pei->av, n = 0; pa->tag; pa++, n++); \ - if (signed_from_buf(b, &i)) return (-1); \ - if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p; \ - else if (i == -1) rv->p = 0; \ - else return (-1); \ - return (0); - TVEC_MISCSLOTS(CASE) -#undef CASE -#undef HANDLE_INT -#undef HANDLE_UINT -#undef HANDLE_FLT -#undef HANDLE_PTR - default: abort(); - } + for (pa = pei->av, n = 0; pa->tag; pa++, n++); + if (signed_from_buf(b, &i)) return (-1); + if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p; + else if (i == -1) rv->p = 0; + else return (-1); + return (0); } -static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv) -{ - const struct tvec_enuminfo *ei = rd->arg.p; -#define DECLS(tag, ty, slot) \ - const struct tvec_##slot##enuminfo *slot##ei; \ - const struct tvec_##slot##assoc *slot##a; - TVEC_MISCSLOTS(DECLS) -#undef DECLS - dstr d = DSTR_INIT; - int rc; +#define DEFPARSE_ENUM(tag_, ty, slot) \ + static int parse_##slot##enum(union tvec_regval *rv, \ + const struct tvec_regdef *rd, \ + struct tvec_state *tv) \ + { \ + const struct tvec_##slot##enuminfo *ei = rd->arg.p; \ + const struct tvec_##slot##assoc *a; \ + dstr d = DSTR_INIT; \ + int rc; \ + \ + if (tvec_readword(tv, &d, ";", "enumeration tag or " LITSTR_##tag_)) \ + { rc = -1; goto end; } \ + for (a = ei->av; a->tag; a++) \ + if (STRCMP(a->tag, ==, d.buf)) { FOUND_##tag_ goto done; } \ + MISSING_##tag_ \ + done: \ + if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } \ + rc = 0; \ + end: \ + dstr_destroy(&d); \ + return (rc); \ + } - if (tvec_readword(tv, &d, ";", "enumeration tag or literal integer")) - { rc = -1; goto end; } - switch (ei->mv) { - -#define CASE(tag_, ty, slot) \ - case TVMISC_##tag_: \ - slot##ei = (const struct tvec_##slot##enuminfo *)ei; \ - for (slot##a = slot##ei->av; slot##a->tag; slot##a++) \ - if (STRCMP(d.buf, ==, slot##a->tag)) \ - { rv->slot = FETCH_##tag_; goto done; } \ - HANDLE_##tag_ goto done; - -#define FETCH_INT (ia->i) -#define HANDLE_INT \ - if (parse_signed(&rv->i, d.buf, iei->ir, tv)) \ - { rc = -1; goto end; } - -#define FETCH_UINT (ua->u) -#define HANDLE_UINT \ - if (parse_unsigned(&rv->u, d.buf, uei->ur, tv)) \ - { rc = -1; goto end; } - -#define FETCH_FLT (fa->f) -#define HANDLE_FLT \ - if (parse_floating(&rv->f, d.buf, fei->fi, tv)) \ - { rc = -1; goto end; } - -#define FETCH_PTR ((/*unconst*/ void *)(pa->p)) -#define HANDLE_PTR \ - if (STRCMP(d.buf, ==, "#nil")) rv->p = 0; \ - else goto tagonly; - - TVEC_MISCSLOTS(CASE) - -#undef CASE -#undef FETCH_INT -#undef HANDLE_INT -#undef FETCH_UINT -#undef HANDLE_UINT -#undef FETCH_FLT -#undef HANDLE_FLT -#undef FETCH_PTR -#undef HANDLE_PTR - - tagonly: - tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf); - rc = -1; goto end; +#define LITSTR_INT "literal signed integer" +#define FOUND_INT rv->i = a->i; +#define MISSING_INT if (parse_signed(&rv->i, d.buf, ei->ir, tv)) \ + { rc = -1; goto end; } + +#define LITSTR_UINT "literal unsigned integer" +#define FOUND_UINT rv->u = a->u; +#define MISSING_UINT if (parse_unsigned(&rv->u, d.buf, ei->ur, tv)) \ + { rc = -1; goto end; } + +#define LITSTR_FLT "literal floating-point number, " \ + "`#-inf', `#+inf', or `#nan'" +#define FOUND_FLT rv->f = a->f; +#define MISSING_FLT if (parse_floating(&rv->f, d.buf, ei->fi, tv)) \ + { rc = -1; goto end; } + +#define LITSTR_PTR "`#nil'" +#define FOUND_PTR rv->p = (/*unconst*/ void *)a->p; +#define MISSING_PTR if (STRCMP(d.buf, ==, "#nil")) \ + rv->p = 0; \ + else { \ + tvec_error(tv, "unknown `%s' value `%s'", \ + ei->name, d.buf); \ + rc = -1; goto end; \ + } + +TVEC_MISCSLOTS(DEFPARSE_ENUM) + +#undef LITSTR_INT +#undef FOUND_INT +#undef MISSING_INT + +#undef LITSTR_UINT +#undef FOUND_UINT +#undef MISSING_UINT + +#undef LITSTR_FLT +#undef FOUND_FLT +#undef MISSING_FLT + +#undef LITSTR_PTR +#undef FOUND_PTR +#undef MISSING_PTR + +#undef DEFPARSE_ENUM + +#define DEFDUMP_ENUM(tag_, ty, slot) \ + static void dump_##slot##enum(const union tvec_regval *rv, \ + const struct tvec_regdef *rd, \ + unsigned style, \ + const struct gprintf_ops *gops, void *go) \ + { \ + const struct tvec_##slot##enuminfo *ei = rd->arg.p; \ + const struct tvec_##slot##assoc *a; \ + \ + for (a = ei->av; a->tag; a++) \ + if (rv->slot == a->slot) { \ + gprintf(gops, go, "%s", a->tag); \ + if (style&TVSF_COMPACT) return; \ + gprintf(gops, go, " ; = "); break; \ + } \ + \ + PRINTRAW_##tag_ \ } -done: - if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } - rc = 0; -end: - dstr_destroy(&d); - return (rc); -} +#define MAYBE_PRINT_EXTRA \ + if (style&TVSF_COMPACT) ; \ + else if (!a->tag) { gprintf(gops, go, " ; = "); goto _extra; } \ + else if (1) { gprintf(gops, go, " = "); goto _extra; } \ + else _extra: -static void dump_enum(const union tvec_regval *rv, - const struct tvec_regdef *rd, - unsigned style, - const struct gprintf_ops *gops, void *go) -{ - const struct tvec_enuminfo *ei = rd->arg.p; -#define DECLS(tag, ty, slot) \ - const struct tvec_##slot##enuminfo *slot##ei; \ - const struct tvec_##slot##assoc *slot##a; - TVEC_MISCSLOTS(DECLS) -#undef DECLS - const char *tag; - unsigned long u; - unsigned f = 0; -#define f_known 1u - - switch (ei->mv) { -#define CASE(tag_, ty, slot) \ - case TVMISC_##tag_: \ - slot##ei = (const struct tvec_##slot##enuminfo *)ei; \ - for (slot##a = slot##ei->av; slot##a->tag; slot##a++) \ - if (rv->slot == slot##a->slot) \ - { tag = slot##a->tag; goto found; } \ - break; - TVEC_MISCSLOTS(CASE) -#undef CASE - default: abort(); - } - goto print_raw; +#define PRINTRAW_INT gprintf(gops, go, "%ld", rv->i); \ + MAYBE_PRINT_EXTRA { \ + format_signed_hex(gops, go, rv->i); \ + maybe_format_signed_char(gops, go, rv->i); \ + } -found: - f |= f_known; - gprintf(gops, go, "%s", tag); - if (style&TVSF_COMPACT) return; - gprintf(gops, go, " ; = "); - -print_raw: - switch (ei->mv) { -#define CASE(tag, ty, slot) \ - case TVMISC_##tag: HANDLE_##tag break; -#define HANDLE_INT gprintf(gops, go, "%ld", rv->i); -#define HANDLE_UINT gprintf(gops, go, "%lu", rv->u); -#define HANDLE_FLT format_floating(gops, go, rv->f); -#define HANDLE_PTR if (!rv->p) gprintf(gops, go, "#nil"); \ +#define PRINTRAW_UINT gprintf(gops, go, "%lu", rv->u); \ + MAYBE_PRINT_EXTRA { \ + format_unsigned_hex(gops, go, rv->u); \ + maybe_format_unsigned_char(gops, go, rv->u); \ + } + +#define PRINTRAW_FLT format_floating(gops, go, rv->f); + +#define PRINTRAW_PTR if (!rv->p) gprintf(gops, go, "#nil"); \ else gprintf(gops, go, "#<%s %p>", ei->name, rv->p); - TVEC_MISCSLOTS(CASE) -#undef CASE -#undef HANDLE_INT -#undef HANDLE_UINT -#undef HANDLE_FLT -#undef HANDLE_PTR - } - if (style&TVSF_COMPACT) return; - switch (ei->mv) { - case TVMISC_INT: - if (!(f&f_known)) gprintf(gops, go, " ;"); - if (rv->i >= 0) u = rv->i; - else u = -(unsigned long)rv->i; - gprintf(gops, go, " = %s0x%0*lx", - rv->i < 0 ? "-" : "", hex_width(u), u); - if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX)) - { gprintf(gops, go, " = "); format_char(rv->i, gops, go); } - break; - case TVMISC_UINT: - if (!(f&f_known)) gprintf(gops, go, " ;"); - gprintf(gops, go, " = 0x%0*lx", hex_width(rv->u), rv->u); - if (rv->u < UCHAR_MAX) - { gprintf(gops, go, " = "); format_char(rv->u, gops, go); } - break; - } -} +TVEC_MISCSLOTS(DEFDUMP_ENUM) -const struct tvec_regty tvty_enum = { - init_enum, release_int, eq_enum, - tobuf_enum, frombuf_enum, - parse_enum, dump_enum -}; +#undef PRINTRAW_INT +#undef PRINTRAW_UINT +#undef PRINTRAW_FLT +#undef PRINTRAW_PTR + +#undef MAYBE_PRINT_EXTRA +#undef DEFDUMP_ENUM + +#define DEFTY_ENUM(tag, ty, slot) \ + const struct tvec_regty tvty_##slot##enum = { \ + init_##slot##enum, trivial_release, eq_##slot##enum, \ + tobuf_##slot##enum, frombuf_##slot##enum, \ + parse_##slot##enum, dump_##slot##enum \ + }; +TVEC_MISCSLOTS(DEFTY_ENUM) +#undef DEFTY_ENUM static const struct tvec_iassoc bool_assoc[] = { { "nil", 0 }, @@ -1146,7 +1169,7 @@ static const struct tvec_iassoc bool_assoc[] = { }; const struct tvec_ienuminfo tvenum_bool = - { { "bool", TVMISC_INT }, bool_assoc, &tvrange_int }; + { "bool", bool_assoc, &tvrange_int }; #define DEFCLAIM(tag, ty, slot) \ int tvec_claimeq_##slot##enum \ @@ -1156,11 +1179,11 @@ const struct tvec_ienuminfo tvenum_bool = { \ union tvec_misc arg; \ \ - assert(ei->_ei.mv == TVMISC_##tag); \ arg.p = ei; \ - tv->in[0].v.slot = GET_##tag(e0); \ - tv->out[0].v.slot = GET_##tag(e1); \ - return (tvec_claimeq(tv, &tvty_enum, &arg, file, lno, expr)); \ + tv->out[0].v.slot = GET_##tag(e0); \ + tv->in[0].v.slot = GET_##tag(e1); \ + return (tvec_claimeq(tv, &tvty_##slot##enum, &arg, \ + file, lno, expr)); \ } #define GET_INT(e) (e) #define GET_UINT(e) (e) @@ -1237,7 +1260,7 @@ static void dump_flags(const union tvec_regval *rv, } const struct tvec_regty tvty_flags = { - init_uint, release_int, eq_uint, + init_uint, trivial_release, eq_uint, tobuf_uint, frombuf_uint, parse_flags, dump_flags }; @@ -1249,7 +1272,7 @@ int tvec_claimeq_flags(struct tvec_state *tv, { union tvec_misc arg; - arg.p = fi; tv->in[0].v.u = f0; tv->out[0].v.u = f1; + arg.p = fi; tv->out[0].v.u = f0; tv->in[0].v.u = f1; return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr)); } @@ -1327,22 +1350,19 @@ static void dump_char(const union tvec_regval *rv, unsigned style, const struct gprintf_ops *gops, void *go) { - unsigned u; - if ((style&TVSF_COMPACT) && isprint(rv->i) && rv->i != '\'') gprintf(gops, go, "%c", (int)rv->i); else - format_char(rv->i, gops, go); + format_char(gops, go, rv->i); if (!(style&TVSF_COMPACT)) { - u = rv->i < 0 ? -rv->i : rv->i; - gprintf(gops, go, " ; = %d = %s0x%0*x", - (int)rv->i, rv->i < 0 ? "-" : "", hex_width(u), u); + gprintf(gops, go, " ; = %ld = ", rv->i); + format_signed_hex(gops, go, rv->i); } } const struct tvec_regty tvty_char = { - init_int, release_int, eq_int, + init_int, trivial_release, eq_int, tobuf_char, frombuf_char, parse_char, dump_char }; @@ -1350,7 +1370,7 @@ const struct tvec_regty tvty_char = { int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1, const char *file, unsigned lno, const char *expr) { - tv->in[0].v.i = c0; tv->out[0].v.i = c1; + tv->out[0].v.i = c0; tv->in[0].v.i = c1; return (tvec_claimeq(tv, &tvty_char, 0, file, lno, expr)); } @@ -1488,8 +1508,10 @@ quote: for (q = p; q < l; q++) if (!isprint(*q) || *q == '"') { if (p < q) gops->putm(go, (const char *)p, q - p); - if (*q == '\n' && !(style&TVSF_COMPACT))gprintf(gops, go, "\\n\"\t\""); - else format_charesc(*q, FCF_BRACE, gops, go); + if (*q == '\n' && !(style&TVSF_COMPACT)) + gprintf(gops, go, "\\n\"\t\""); + else + format_charesc(gops, go, *q, FCF_BRACE); } if (p < q) gops->putm(go, (const char *)p, q - p); gprintf(gops, go, "\""); @@ -1556,8 +1578,8 @@ int tvec_claimeq_string(struct tvec_state *tv, const char *p1, size_t sz1, const char *file, unsigned lno, const char *expr) { - tv->in[0].v.str.p = (/*unconst*/ char *)p0; tv->in[0].v.str.sz = sz0; - tv->out[0].v.str.p =(/*unconst*/ char *) p1; tv->out[0].v.str.sz = sz1; + tv->out[0].v.str.p = (/*unconst*/ char *)p0; tv->out[0].v.str.sz = sz0; + tv->in[0].v.str.p =(/*unconst*/ char *) p1; tv->in[0].v.str.sz = sz1; return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr)); } @@ -1565,10 +1587,10 @@ int tvec_claimeq_strz(struct tvec_state *tv, const char *p0, const char *p1, const char *file, unsigned lno, const char *expr) { - tv->in[0].v.str.p = (/*unconst*/ char *)p0; - tv->in[0].v.str.sz = strlen(p0); - tv->out[0].v.str.p = (/*unconst*/ char *)p1; - tv->out[0].v.str.sz = strlen(p1); + tv->out[0].v.str.p = (/*unconst*/ char *)p0; + tv->out[0].v.str.sz = strlen(p0); + tv->in[0].v.str.p = (/*unconst*/ char *)p1; + tv->in[0].v.str.sz = strlen(p1); return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr)); } @@ -1577,10 +1599,10 @@ int tvec_claimeq_bytes(struct tvec_state *tv, const void *p1, size_t sz1, const char *file, unsigned lno, const char *expr) { - tv->in[0].v.bytes.p = (/*unconst*/ void *)p0; - tv->in[0].v.bytes.sz = sz0; - tv->out[0].v.bytes.p = (/*unconst*/ void *)p1; - tv->out[0].v.bytes.sz = sz1; + tv->out[0].v.bytes.p = (/*unconst*/ void *)p0; + tv->out[0].v.bytes.sz = sz0; + tv->in[0].v.bytes.p = (/*unconst*/ void *)p1; + tv->in[0].v.bytes.sz = sz1; return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr)); } @@ -1613,8 +1635,7 @@ static int parse_buffer(union tvec_regval *rv, struct tvec_state *tv) { dstr d = DSTR_INIT; - char *q; const char *unit; - int olderr; + const char *q, *unit; size_t pos; unsigned long u, t; int rc; @@ -1622,10 +1643,7 @@ static int parse_buffer(union tvec_regval *rv, #define f_range 1u if (tvec_readword(tv, &d, ";", "buffer length")) { rc = -1; goto end; } - olderr = errno; errno = 0; - u = strtoul(d.buf, &q, 0); - if (errno) goto bad; - errno = olderr; + if (parse_unsigned_integer(&u, &q, d.buf)) goto bad; if (!*q) { tvec_skipspc(tv); pos = d.len; if (!tvec_readword(tv, &d, ";", 0)) pos++; diff --git a/test/tvec.h b/test/tvec.h index 740b6be..03c3ba4 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -260,8 +260,14 @@ extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, /*----- Test state --------------------------------------------------------*/ -/* Possible test outcomes. */ -enum { TVOUT_LOSE, TVOUT_SKIP, TVOUT_WIN, TVOUT_LIMIT }; +enum { + /* Possible test outcomes. */ + + TVOUT_LOSE, /* test failed */ + TVOUT_SKIP, /* test skipped */ + TVOUT_WIN, /* test passed */ + TVOUT_LIMIT /* (number of possible outcomes) */ +}; struct tvec_state { /* The primary state structure for the test vector machinery. */ @@ -271,8 +277,8 @@ struct tvec_state { #define TVSF_OPEN 2u /* test is open */ #define TVSF_ACTIVE 4u /* test is active */ #define TVSF_ERROR 8u /* an error occurred */ -#define TVSF_OUTMASK 0xf0 /* test outcome */ -#define TVSF_OUTSHIFT 4 +#define TVSF_OUTMASK 0xf0 /* test outcome (@TVOUT_...@) */ +#define TVSF_OUTSHIFT 4 /* shift applied to outcome */ /* Registers. Available to execution environments. */ unsigned nrout, nreg; /* number of output/total registers */ @@ -398,7 +404,7 @@ enum { /* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list ap@ = reason why group skipped + * @const char *excuse@, @va_list *ap@ = reason why skipped * * Returns: --- * @@ -415,7 +421,7 @@ extern void tvec_skipgroup_v(struct tvec_state */*tv*/, /* --- @tvec_skip@, @tvec_skip_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *excuse@, @va_list ap@ = reason why test skipped + * @const char *excuse@, @va_list *ap@ = reason why test skipped * * Returns: --- * @@ -464,7 +470,7 @@ extern int tvec_checkregs(struct tvec_state */*tv*/); /* --- @tvec_fail@, @tvec_fail_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *detail@, @va_list ap@ = description of test + * @const char *detail@, @va_list *ap@ = description of test * * Returns: --- * @@ -526,7 +532,7 @@ extern void tvec_mismatch(struct tvec_state */*tv*/, unsigned /*f*/); /* --- @tvec_check@, @tvec_check_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *detail@, @va_list ap@ = description of test + * @const char *detail@, @va_list *ap@ = description of test * * Returns: --- * @@ -727,194 +733,778 @@ extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/, const char */*delims*/, const char */*expect*/, va_list */*ap*/); +/*----- Ad-hoc testing ----------------------------------------------------*/ + +/* --- @tvec_adhoc@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_test *t@ = space for a test definition + * + * Returns: --- + * + * Use: Begin ad-hoc testing, i.e., without reading a file of + * test-vector data. + * + * The structure at @t@ will be used to record information about + * the tests underway, which would normally come from a static + * test definition. The other functions in this section assume + * that @tvec_adhoc@ has been called. + */ + +extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); + +/* --- @tvec_begingroup@, @TVEC_BEGINGROUP@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *name@ = name for this test group + * @const char *file@, @unsigned @lno@ = calling file and line + * + * Returns: --- + * + * Use: Begin an ad-hoc test group with the given name. The @file@ + * and @lno@ can be anything, but it's usually best if they + * refer to the source code performing the test: the macro + * @TVEC_BEGINGROUP@ does this automatically. + */ + +extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/, + const char */*file*/, unsigned /*lno*/); +#define TVEC_BEGINGROUP(tv, name) \ + do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0) + +/* --- @tvec_endgroup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End an ad-hoc test group. The statistics are updated and the + * outcome is reported to the output formatter. + */ + +extern void tvec_endgroup(struct tvec_state */*tv*/); + +/* --- @TVEC_TESTGROUP@, @TVEC_TESTGROUP_TAG@ --- * + * + * Arguments: @tag@ = label-disambiguation tag + * @const struct tvec_state *tv = test-vector state + * @const char *name@ = test-group name + * + * Returns: --- + * + * Use: Control-structure macro: @TVEC_TESTGROUP(tv, name) stmt@ + * establishes a test group with the given @name@ (attributing + * it to the source file and lie number), executes @stmt@, and + * ends the test group. If @stmt@ invokes @break@ then the test + * group is skipped. @TVEC_TESTGROUP_TAG@ is the same, with an + * additional @tag@ argument for use in higher-level macros. + */ + +#define TVEC_TESTGROUP_TAG(tag, tv, name) \ + MC_WRAP(tag##__around, \ + { TVEC_BEGINGROUP(tv, name); }, \ + { tvec_endgroup(tv); }, \ + { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \ + tvec_endgroup(tv); }) +#define TVEC_TESTGROUP(tv, name) TVEC_TESTGROUP_TAG(grp, tv, name) + +/* --- @tvec_begintest@, @TVEC_BEGINTEST@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *file@, @unsigned @lno@ = calling file and line + * + * Returns: --- + * + * Use: Begin an ad-hoc test case. The @file@ and @lno@ can be + * anything, but it's usually best if they refer to the source + * code performing the test: the macro @TVEC_BEGINGROUP@ does + * this automatically. + */ + +extern void tvec_begintest(struct tvec_state */*tv*/, + const char */*file*/, unsigned /*lno*/); +#define TVEC_BEGINTEST(tv) \ + do tvec_begintest(tv, __FILE__, __LINE__); while (0) + +/* --- *tvec_endtest@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * + * Returns: --- + * + * Use: End a ad-hoc test case, The statistics are updated and the + * outcome is reported to the output formatter. + */ + +extern void tvec_endtest(struct tvec_state */*tv*/); + +/* --- @TVEC_TEST@, @TVEC_TEST_TAG@ --- * + * + * Arguments: @tag@ = label-disambiguation tag + * @struct tvec_test *t@ = space for a test definition + * + * Returns: --- + * + * Use: Control-structure macro: @TVEC_TEST(tv) stmt@ begins a test + * case, executes @stmt@, and ends the test case. If @stmt@ + * invokes @break@ then the test case is skipped. + * @TVEC_TEST_TAG@ is the same, with an additional @tag@ argumet + * for use in higher-level macros. + */ + +#define TVEC_TEST_TAG(tag, tv) \ + MC_WRAP(tag##__around, \ + { TVEC_BEGINTEST(tv); }, \ + { tvec_endtest(tv); }, \ + { if ((tv)->f&TVSF_ACTIVE) tvec_skip((tv), 0); \ + tvec_endtest(tv); }) +#define TVEC_TEST(tv) TVEC_TEST_TAG(test, tv) + +/* --- @tvec_claim@, @tvec_claim_v@, @TVEC_CLAIM@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @int ok@ = a flag + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *msg@, @va_list *ap@ = message to report on + * failure + * + * Returns: The value @ok@. + * + * Use: Check that a claimed condition holds, as (part of) a test. + * If no test case is underway (i.e., if @TVSF_OPEN@ is reset in + * @tv->f@), then a new test case is begun and ended. The + * @file@ and @lno@ are passed to the output formatter to be + * reported in case of a failure. If @ok@ is nonzero, then + * nothing else happens; so, in particular, if @tvec_claim@ + * established a new test case, then the test case succeeds. If + * @ok@ is zero, then a failure is reported, quoting @msg@. + * + * The @TVEC_CLAIM@ macro is similar, only it (a) identifies the + * file and line number of the call site automatically, and (b) + * implicitly quotes the source text of the @ok@ condition in + * the failure message. + */ + +extern int PRINTF_LIKE(5, 6) + tvec_claim(struct tvec_state */*tv*/, int /*ok*/, + const char */*file*/, unsigned /*lno*/, + const char */*msg*/, ...); +extern int tvec_claim_v(struct tvec_state */*tv*/, int /*ok*/, + const char */*file*/, unsigned /*lno*/, + const char */*msg*/, va_list */*ap*/); +#define TVEC_CLAIM(tv, cond) \ + (tvec_claim(tv, !!(cond), __FILE__, __LINE__, "%s untrue", #cond)) + +/* --- @tvec_claimeq@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const struct tvec_regty *ty@ = register type + * @const union tvec_misc *arg@ = register type argument + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if the input and output values of register 0 are + * equal, zero if they differ. + * + * Use: Check that the input and output values of register 0 are + * equal (according to the register type @ty@). As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped. + * + * This function is not expected to be called directly, but + * through type-specific wrapper functions or macros such as + * @TVEC_CLAIMEQ_INT@. + */ + +extern int tvec_claimeq(struct tvec_state */*tv*/, + const struct tvec_regty */*ty*/, + const union tvec_misc */*arg*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); + /*----- Output formatting -------------------------------------------------*/ struct tvec_output { - const struct tvec_outops *ops; - struct tvec_state *tv; + /* An output formatter. */ + const struct tvec_outops *ops; /* pointer to operations */ }; -enum { TVBU_OP, TVBU_BYTE }; - -struct bench_timing; +/* Benchmarking details. */ +enum { + TVBU_OP, /* counting operations of some kind */ + TVBU_BYTE /* counting bytes (@RBUF >= 0@) */ +}; +struct bench_timing; /* forward declaration */ struct tvec_outops { - void (*error)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); - void (*notice)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); + /* Output operations. */ + + void (*bsession)(struct tvec_output */*o*/, struct tvec_state */*tv*/); + /* Begin a test session. The output driver will probably want to + * save @tv@, because this isn't provided to any other methods. + */ - void (*bsession)(struct tvec_output */*o*/); int (*esession)(struct tvec_output */*o*/); + /* End a session, and return the suggested exit code. */ void (*bgroup)(struct tvec_output */*o*/); - void (*egroup)(struct tvec_output */*o*/, unsigned /*outcome*/); + /* Begin a test group. The test group description is @tv->test@. */ + void (*skipgroup)(struct tvec_output */*o*/, const char */*excuse*/, va_list */*ap*/); + /* The group is being skipped; @excuse@ may be null or a format + * string explaining why. The @egroup@ method will not be called + * separately. + */ + + void (*egroup)(struct tvec_output */*o*/); + /* End a test group. At least one test was attempted or @skipgroup@ + * would have been called instead. If @tv->curr[TVOUT_LOSE]@ is + * nonzero then the test group as a whole failed; otherwise it + * passed. + */ void (*btest)(struct tvec_output */*o*/); + /* Begin a test case. */ + void (*skip)(struct tvec_output */*o*/, const char */*excuse*/, va_list */*ap*/); + /* The test case is being skipped; @excuse@ may be null or a format + * string explaining why. The @etest@ function will still be called + * (so this works differently from @skipgroup@ and @egroup@). A test + * case can be skipped at most once, and, if skipped, it cannot fail. + */ + void (*fail)(struct tvec_output */*o*/, const char */*detail*/, va_list */*ap*/); + /* The test case failed. + * + * The output driver should preferably report the filename (@infile@) + * and line number (@test_lno@, not @lno@) for the failing test. + * + * The @detail@ may be null or a format string describing detail + * about how the failing test was run which can't be determined from + * the registers; a @detail@ is usually provided when (and only when) + * the test environment potentially invokes the test function more + * than once. + * + * A single test case can fail multiple times! + */ + void (*dumpreg)(struct tvec_output */*o*/, unsigned /*disp*/, const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Dump a register. The `disposition' @disp@ explains what condition + * the register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes. + * The register value is at @rv@, and its definition, including its + * type, at @rd@. Note that this function may be called on virtual + * registers which aren't in either of the register vectors or + * mentioned by the test description. + */ + void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); + /* The test case concluded with the given @outcome@ (one of the + * @TVOUT_...@ codes. + */ void (*bbench)(struct tvec_output */*o*/, const char */*ident*/, unsigned /*unit*/); + /* Begin a benchmark. The @ident@ is a formatted string identifying + * the benchmark based on the values of the input registered marked + * @TVRF_ID@; the output driver is free to use this or come up with + * its own way to identify the test, e.g., by inspecting the register + * values for itself. The @unit@ is one of the @TVBU_...@ constants + * explaining what sort of thing is being measured. + */ + void (*ebench)(struct tvec_output */*o*/, const char */*ident*/, unsigned /*unit*/, const struct bench_timing */*tm*/); + /* End a benchmark. The @ident@ and @unit@ arguments are as for + * @bbench@. If @tm@ is zero then the measurement failed; otherwise + * @tm->n@ counts total number of things (operations or bytes, as + * indicated by @unit@) processed in the indicated time. + */ + + void (*error)(struct tvec_output */*o*/, + const char */*msg*/, va_list */*ap*/); + /* Report an error. The driver should ideally report the filename + * (@infile@) and line number (@lno@) prompting the error. + */ + + void (*notice)(struct tvec_output */*o*/, + const char */*msg*/, va_list */*ap*/); + /* Report a miscellaneous message. The driver should ideally report + * the filename (@infile@) and line number (@lno@) prompting the + * error. + */ void (*destroy)(struct tvec_output */*o*/); + /* Release any resources acquired by the driver. */ }; +/* --- @tvec_error@, @tvec_error_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *msg@, @va_list ap@ = error message + * + * Returns: @-1@. + * + * Use: Report an error. Errors are distinct from test failures, + * and indicate that a problem was encountered which compromised + * the activity of testing. + */ + extern int PRINTF_LIKE(2, 3) tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); extern int tvec_error_v(struct tvec_state */*tv*/, const char */*msg*/, va_list */*ap*/); +/* --- @tvec_notice@, @tvec_notice_v@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @const char *msg@, @va_list ap@ = message + * + * Returns: --- + * + * Use: Output a notice: essentially, some important information + * which doesn't fit into any of the existing categories. + */ + extern void PRINTF_LIKE(2, 3) tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); extern void tvec_notice_v(struct tvec_state */*tv*/, const char */*msg*/, va_list */*ap*/); +/* --- @tvec_humanoutput@ --- * + * + * Arguments: @FILE *fp@ = output file to write on + * + * Returns: An output formatter. + * + * Use: Return an output formatter which writes on @fp@ with the + * expectation that a human will be watching and interpreting + * the output. If @fp@ denotes a terminal, the display shows a + * `scoreboard' indicating the outcome of each test case + * attempted, and may in addition use colour and other + * highlighting. + */ + extern struct tvec_output *tvec_humanoutput(FILE */*fp*/); + +/* --- @tvec_tapoutput@ --- * + * + * Arguments: @FILE *fp@ = output file to write on + * + * Returns: An output formatter. + * + * Use: Return an output formatter which writes on @fp@ in `TAP' + * (`Test Anything Protocol') format. + * + * TAP comes from the Perl community, but has spread rather + * further. This driver produces TAP version 13. The driver + * produces a TAP `test point' -- i.e., a result reported as + * `ok' or `not ok' -- for each input test group. Failure + * reports and register dumps are produced as diagnostic + * messages before the final group result. (TAP permits + * structuerd YAML data after the test-point result, which could + * be used to report details, but (a) postponing the details + * until after the report is inconvenient, and (b) there is no + * standardization for the YAML anyway, so in practice it's no + * more useful than the unstructured diagnostics. + */ + extern struct tvec_output *tvec_tapoutput(FILE */*fp*/); + +/* --- @tvec_dfltoutput@ --- * + * + * Arguments: @FILE *fp@ = output file to write on + * + * Returns: An output formatter. + * + * Use: Selects and instantiates an output formatter suitable for + * writing on @fp@. The policy is subject to change, but + * currently the `human' output format is selected if @fp@ is + * interactive (i.e., if @isatty(fileno(fp))@ is true), and + * otherwise the `tap' format is used. + */ + extern struct tvec_output *tvec_dfltout(FILE */*fp*/); /*----- Register types ----------------------------------------------------*/ struct tvec_regty { + /* A register type. */ + void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Initialize the value in @*rv@. This will be called before any + * other function acting on the value, including @release@. + */ + void (*release)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Release any resources associated with the value in @*rv@. */ + int (*eq)(const union tvec_regval */*rv0*/, const union tvec_regval */*rv1*/, const struct tvec_regdef */*rd*/); + /* Return nonzero if @*rv0@ and @*rv1@ are equal values. + * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@ + * with the input register as @rv0@ and the output as @rv1@. + */ + int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Serialize the value @*rv@, writing the result to @b@. Return + * zero on success, or @-1@ on error. + */ + int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Deserialize a value from @b@, storing it in @*rv@. Return zero on + * success, or @-1@ on error. + */ + int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, struct tvec_state */*tv*/); + /* Parse a value from @tv->fp@, storing it in @*rv@. Return zero on + * success, or @-1@ on error, having reported one or more errors via + * @tvec_error@ or @tvec_syntax@. A successful return should leave + * the input position at the start of the next line; the caller will + * flush the remainder of the line itself. + */ + void (*dump)(const union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, unsigned /*style*/, const struct gprintf_ops */*gops*/, void */*go*/); #define TVSF_COMPACT 1u + /* Write a human-readable representation of the value @*rv@ using + * @gprintf@ on @gops@ and @go@. The @style@ is a collection of + * flags: if @TVSF_COMPACT@ is set, then output should be minimal, + * and must fit on a single line; otherwise, output may consist of + * multiple lines and may contain redundant information if that is + * likely to be useful to a human reader. + */ }; +/*----- Integer types: signed and unsigned ------------------------------- */ + +/* Integers may be input in decimal, hex, binary, or octal, following + * approximately usual conventions. + * + * * Signed integers may be preceded with a `+' or `-' sign. + * + * * Decimal integers are just a sequence of decimal digits `0' ... `9'. + * + * * Octal integers are a sequence of digits `0' ... `7', preceded by `0o' + * or `0O'. + * + * * Hexadecimal integers are a sequence of digits `0' ... `9', `a' + * ... `f', or `A' ... `F', preceded by `0x' or `0X'. + * + * * Radix-B integers are a sequence of digits `0' ... `9', `a' ... `f', or + * `A' ... `F', each with value less than B, preceded by `Br' or `BR', + * where 0 < B < 36 is expressed in decimal without any leading `0' or + * internal underscores `_'. + * + * * A digit sequence may contain internal underscore `_' separators, but + * not before or after all of the digits; and two consecutive `_' + * characters are not permitted. + */ + extern const struct tvec_regty tvty_int, tvty_uint; + +/* The @arg.p@ slot may be null or a pointer to @struct tvec_irange@ or + * @struct tvec_urange@ as appropriate. The bounds are inclusive; use, e.g., + * @LONG_MAX@ explicitly if one or the other bound is logically inapplicable. + */ struct tvec_irange { long min, max; }; struct tvec_urange { unsigned long min, max; }; +/* Bounds corresponding to common integer types. */ extern const struct tvec_irange tvrange_schar, tvrange_short, tvrange_int, tvrange_long, tvrange_sbyte, tvrange_i16, tvrange_i32; extern const struct tvec_urange tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size, tvrange_byte, tvrange_u16, tvrange_u32; -extern const struct tvec_frange - tvrange_float, tvrange_double; + +/* --- tvec_claimeq_int@, @TVEC_CLAIMEQ_INT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @long i0, i1@ = two signed integers + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @i0@ and @i1@ are equal, otherwise zero. + * + * Use: Check that values of @i0@ and @i1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @i0@ is printed as the output + * value and @i1@ is printed as the input reference. + * + * The @TVEC_CLAIM_INT@ macro is similar, only it (a) identifies + * the file and line number of the call site automatically, and + * (b) implicitly quotes the source text of the @i0@ and @i1@ + * arguments in the failure message. + */ extern int tvec_claimeq_int(struct tvec_state */*tv*/, long /*i0*/, long /*i1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); +#define TVEC_CLAIMEQ_INT(tv, i0, i1) \ + (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1)) + +/* --- @tvec_claimeq_uint@, @TVEC_CLAIMEQ_UINT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @unsigned long u0, u1@ = two unsigned integers + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @u0@ and @u1@ are equal, otherwise zero. + * + * Use: Check that values of @u0@ and @u1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @u0@ is printed as the output + * value and @u1@ is printed as the input reference. + * + * The @TVEC_CLAIM_UINT@ macro is similar, only it (a) + * identifies the file and line number of the call site + * automatically, and (b) implicitly quotes the source text of + * the @u0@ and @u1@ arguments in the failure message. + */ + extern int tvec_claimeq_uint(struct tvec_state */*tv*/, unsigned long /*u0*/, unsigned long /*u1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); -#define TVEC_CLAIMEQ_INT(tv, i0, i1) \ - (tvec_claimeq_int(tv, i0, i1, __FILE__, __LINE__, #i0 " /= " #i1)) #define TVEC_CLAIMEQ_UINT(tv, u0, u1) \ (tvec_claimeq_uint(tv, u0, u1, __FILE__, __LINE__, #u0 " /= " #u1)) +/*----- Floating-point type -----------------------------------------------*/ + +/* Floating-point are either NaN (`#nan', if supported by the platform); + * positive or negative infinity (`#inf', `+#inf', or, preferred, `#+inf', + * and `-#inf' or, preferred, `#-inf', if supported by the platform), or a + * number in strtod(3) syntax. + * + * The comparison rules for floating-point numbers are complex: see + * @tvec_claimeqish_float@ for details. + */ + extern const struct tvec_regty tvty_float; + struct tvec_floatinfo { - unsigned f; -#define TVFF_NOMIN 1u -#define TVFF_NOMAX 2u -#define TVFF_NANOK 4u -#define TVFF_EXACT 0u -#define TVFF_ABSDELTA 0x10 -#define TVFF_RELDELTA 0x20 -#define TVFF_EQMASK 0xf0 - double min, max; - double delta; + /* Details about acceptable floating-point values. */ + + unsigned f; /* flags (@TVFF_...@ bits) */ +#define TVFF_NOMIN 1u /* ignore @min@ (allow -inf) */ +#define TVFF_NOMAX 2u /* ignore @max@ (allow +inf) */ +#define TVFF_NANOK 4u /* permit NaN */ +#define TVFF_EQMASK 0xf0 /* how to compare */ +#define TVFF_EXACT 0x00 /* must equal exactly */ +#define TVFF_ABSDELTA 0x10 /* must be within @delta@ */ +#define TVFF_RELDELTA 0x20 /* diff < @delta@ fraction */ + double min, max; /* smallest/largest value allowed */ + double delta; /* maximum tolerable difference */ }; +/* --- @tvec_claimeqish_float@, @TVEC_CLAIMEQISH_FLOAT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @double f0, f1@ = two floating-point numbers + * @unsigned f@ = flags (@TVFF_...@) + * @double delta@ = maximum tolerable difference + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @f0@ and @u1@ are sufficiently close, otherwise + * zero. + * + * Use: Check that values of @f0@ and @f1@ are sufficiently close. + * As for @tvec_claim@ above, a test case is automatically begun + * and ended if none is already underway. If the values are + * too far apart, then @tvec_fail@ is called, quoting @expr@, + * and the mismatched values are dumped: @f0@ is printed as the + * output value and @f1@ is printed as the input reference. + * + * The details for the comparison are as follows. + * + * * A NaN value matches any other NaN, and nothing else. + * + * * An infinity matches another infinity of the same sign, + * and nothing else. + * + * * If @f&TVFF_EQMASK@ is @TVFF_EXACT@, then any + * representable number matches only itself: in particular, + * positive and negative zero are considered distinct. + * (This allows tests to check that they land on the correct + * side of branch cuts, for example.) + * + * * If @f&TVFF_EQMASK@ is @TVFF_ABSDELTA@, then %$x$% matches + * %$y$% when %$|x - y| < \delta$%. + * + * * If @f&TVFF_EQMASK@ is @TVFF_RELDELTA@, then %$x$% matches + * %$y$% when %$|1 - y/x| < \delta$%. (Note that this + * criterion is asymmetric + * + * The @TVEC_CLAIM_FLOAT@ macro is similar, only it (a) + * identifies the file and line number of the call site + * automatically, and (b) implicitly quotes the source text of + * the @f0@ and @f1@ arguments (and @delta@) in the failure + * message. + */ + extern int tvec_claimeqish_float(struct tvec_state */*tv*/, double /*f0*/, double /*f1*/, unsigned /*f*/, double /*delta*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); +#define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta) \ + (tvec_claimeqish_float(tv, f0, f1, f, delta, , __FILE__, __LINE__, \ + #f0 " /= " #f1 " (+/- " #delta ")")) + +/* --- @tvec_claimeq_float@, @TVEC_CLAIMEQ_FLOAT@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @double f0, f1@ = two floating-point numbers + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @f0@ and @u1@ are identical, otherwise zero. + * + * Use: Check that values of @f0@ and @f1@ are identical. The + * function is exactly equivalent to @tvec_claimeqish_float@ + * with @f == TVFF_EXACT@; the macro is similarly like + * @TVEC_CLAIMEQISH_FLOAT@ with @f == TVFF_EXACT@, except that + * it doesn't bother to quote a delta. + */ + extern int tvec_claimeq_float(struct tvec_state */*tv*/, double /*f0*/, double /*f1*/, const char */*file*/, unsigned /*lno*/, const char */*expr*/); -#define TVEC_CLAIMEQISH_FLOAT(tv, f0, f1, f, delta) \ - (tvec_claimeqish_float(tv, f0, f1, f, delta, , __FILE__, __LINE__, \ - #f0 " /= " #f1 " (+/- " #delta ")")) #define TVEC_CLAIMEQ_FLOAT(tv, f0, f1) \ (tvec_claimeq_float(tv, f0, f1, __FILE__, __LINE__, #f0 " /= " #f1)) -extern const struct tvec_regty tvty_enum; +/*----- Enumerated types --------------------------------------------------*/ + +/* There is a distinct enumerated type for each of the branches of + * @tvec_misc@. In the following, we write @t@ for the type code, which is + * @i@ for signed integer, @u@ for unsigned integer, @f@ for floating-point, + * and @p@ for pointer. + */ +#define DEFENUMTY(tag, ty, slot) \ + extern const struct tvec_regty tvty_##slot##enum; +TVEC_MISCSLOTS(DEFENUMTY) +#undef DEFENUMTY + +/* A @struct tvec_tassoc@ associates a string tag with a value. */ #define DEFASSOC(tag_, ty, slot) \ struct tvec_##slot##assoc { const char *tag; ty slot; }; TVEC_MISCSLOTS(DEFASSOC) #undef DEFASSOC -struct tvec_enuminfo { const char *name; unsigned mv; /* ... */ }; -struct tvec_ienuminfo { - struct tvec_enuminfo _ei; - const struct tvec_iassoc *av; - const struct tvec_irange *ir; -}; -struct tvec_uenuminfo { - struct tvec_enuminfo _ei; - const struct tvec_uassoc *av; - const struct tvec_urange *ur; -}; -struct tvec_fenuminfo { - struct tvec_enuminfo _ei; - const struct tvec_fassoc *av; - const struct tvec_floatinfo *fi; -}; -struct tvec_penuminfo { - struct tvec_enuminfo _ei; - const struct tvec_passoc *av; -}; +/* Information about an enumerated type. */ +#define DEFINFO(tag, ty, slot) \ + struct tvec_##slot##enuminfo { \ + const char *name; /* type name for diagnostics */ \ + const struct tvec_##slot##assoc *av; /* name/value mappings */ \ + EXTRA_##tag##_INFOSLOTS /* type-specific extra info */ \ + }; + +#define EXTRA_INT_INFOSLOTS \ + const struct tvec_irange *ir; /* allowed range of raw values */ + +#define EXTRA_UINT_INFOSLOTS \ + const struct tvec_urange *ur; /* allowed range of raw values */ + +#define EXTRA_FLT_INFOSLOTS \ + const struct tvec_floatinfo *fi; /* range and matching policy */ + +#define EXTRA_PTR_INFOSLOTS /* (nothing) */ +TVEC_MISCSLOTS(DEFINFO) + +#undef EXTRA_INT_INFOSLOTS +#undef EXTRA_UINT_INFOSLOTS +#undef EXTRA_FLT_INFOSLOTS +#undef EXTRA_PTR_INFOSLOTS + +#undef DEFINFO + +/* Standard enumerations. */ const struct tvec_ienuminfo tvenum_bool; +/* --- @tvec_claimeq_tenum@, @TVEC_CLAIMEQ_TENUM@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @ty t0, t1@ = two values + * @const char *file@, @unsigned @lno@ = calling file and line + * @const char *expr@ = the expression to quote on failure + * + * Returns: Nonzero if @t0@ and @t1@ are equal, otherwise zero. + * + * Use: Check that values of @t0@ and @t1@ are equal. As for + * @tvec_claim@ above, a test case is automatically begun and + * ended if none is already underway. If the values are + * unequal, then @tvec_fail@ is called, quoting @expr@, and the + * mismatched values are dumped: @u0@ is printed as the output + * value and @u1@ is printed as the input reference. + * + * The @TVEC_CLAIM_TENUM@ macro is similar, only it (a) + * identifies the file and line number of the call site + * automatically, and (b) implicitly quotes the source text of + * the @u0@ and @u1@ arguments in the failure message. + */ + #define DECLCLAIM(tag, ty, slot) \ extern int tvec_claimeq_##slot##enum \ (struct tvec_state */*tv*/, \ const struct tvec_##slot##enuminfo */*ei*/, \ - ty /*e0*/, ty /*e1*/, \ + ty /*t0*/, ty /*t1*/, \ const char */*file*/, unsigned /*lno*/, const char */*expr*/); TVEC_MISCSLOTS(DECLCLAIM) #undef DECLCLAIM -#define TVEC_CLAIMEQ_IENUM(tv, ei, e0, e1) \ - (tvec_claimeq_ienum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) -#define TVEC_CLAIMEQ_UENUM(tv, ei, e0, e1) \ - (tvec_claimeq_uenum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) -#define TVEC_CLAIMEQ_FENUM(tv, ei, e0, e1) \ - (tvec_claimeq_fenum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) -#define TVEC_CLAIMEQ_PENUM(tv, ei, e0, e1) \ - (tvec_claimeq_penum(tv, ei, e0, e1, \ - __FILE__, __LINE__, #e0 " /= " #e1)) +#define TVEC_CLAIMEQ_IENUM(tv, ei, i0, i1) \ + (tvec_claimeq_ienum(tv, ei, i0, i1, \ + __FILE__, __LINE__, #i0 " /= " #i1)) +#define TVEC_CLAIMEQ_UENUM(tv, ei, u0, u1) \ + (tvec_claimeq_uenum(tv, ei, u0, u1, \ + __FILE__, __LINE__, #u0 " /= " #u1)) +#define TVEC_CLAIMEQ_FENUM(tv, ei, f0, f1) \ + (tvec_claimeq_fenum(tv, ei, f0, f1, \ + __FILE__, __LINE__, #f0 " /= " #f1)) +#define TVEC_CLAIMEQ_PENUM(tv, ei, p0, p1) \ + (tvec_claimeq_penum(tv, ei, p0, p1, \ + __FILE__, __LINE__, #p0 " /= " #p1)) + +/*----- Flags type -------------------------------------------------------*/ extern const struct tvec_regty tvty_flags; -struct tvec_flag { const char *tag; unsigned long m, v; }; + +struct tvec_flag { + /* Definition of a single flag or bitfield value. + * + * Each named setting comes with a value @v@ and a mask @m@; the mask + * should cover all of the value bits, i.e., @(v&~m) == 0@. On input, + * exactly + */ + + const char *tag; /* name */ + unsigned long m, v; /* mask and value */ +}; + struct tvec_flaginfo { const char *name; const struct tvec_flag *fv; @@ -968,53 +1558,6 @@ extern const struct tvec_regty tvty_buffer; extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/); extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); -/*----- Ad-hoc testing ----------------------------------------------------*/ - -extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/); - -extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/, - const char */*file*/, unsigned /*lno*/); -extern void tvec_reportgroup(struct tvec_state */*tv*/); -extern void tvec_endgroup(struct tvec_state */*tv*/); - -#define TVEC_BEGINGROUP(tv, name) \ - do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0) - -#define TVEC_TESTGROUP(tag, tv, name) \ - MC_WRAP(tag##__around, \ - { TVEC_BEGINGROUP(tv, name); }, \ - { tvec_endgroup(tv); }, \ - { if (!((tv)->f&TVSF_SKIP)) tvec_skipgroup(tv, 0); \ - tvec_endgroup(tv); }) - -extern void tvec_begintest(struct tvec_state */*tv*/, - const char */*file*/, unsigned /*lno*/); -extern void tvec_endtest(struct tvec_state */*tv*/); - -#define TVEC_BEGINTEST(tv) \ - do tvec_begintest(tv, __FILE__, __LINE__); while (0) - -#define TVEC_TEST(tag, tv) \ - MC_WRAP(tag##__around, \ - { TVEC_BEGINTEST(tv); }, \ - { tvec_endtest(tv); }, \ - { if ((tv)->f&TVSF_ACTIVE) tvec_skipgroup((tv), 0); \ - tvec_endtest(tv); }) - -extern int PRINTF_LIKE(5, 6) - tvec_claim(struct tvec_state */*tv*/, int /*ok*/, - const char */*file*/, unsigned /*lno*/, - const char */*expr*/, ...); - -#define TVEC_CLAIM(tv, cond) \ - (tvec_claim(tv, !!(cond), __FILE__, __LINE__, #cond " untrue")) - -extern int tvec_claimeq(struct tvec_state */*tv*/, - const struct tvec_regty */*ty*/, - const union tvec_misc */*arg*/, - const char */*file*/, unsigned /*lno*/, - const char */*expr*/); - /*----- Benchmarking ------------------------------------------------------*/ struct tvec_bench { diff --git a/utils/t/control-test.c b/utils/t/control-test.c index cc88bfe..b2fd2c7 100644 --- a/utils/t/control-test.c +++ b/utils/t/control-test.c @@ -41,9 +41,9 @@ static struct tvec_state tvstate; static int step; #define TESTGROUP(name) \ - TVEC_TESTGROUP(tg, &tvstate, name) \ + TVEC_TESTGROUP_TAG(grp, &tvstate, name) \ MC_BEFORE(init, { step = 0; }) -#define TEST TVEC_TEST(test, &tvstate) +#define TEST TVEC_TEST_TAG(test, &tvstate) #define STEP(s) do { \ tvec_claim(&tvstate, s == step, __FILE__, __LINE__, \ "found %d /= expected %d", s, step); \ diff --git a/utils/tests.at b/utils/tests.at index d7cd6c3..8d1b0f0 100644 --- a/utils/tests.at +++ b/utils/tests.at @@ -31,13 +31,13 @@ AT_SETUP([utilities: bits]) AT_KEYWORDS([utils bits]) $PYTHON SRCDIR/t/bits-testgen.py 0xaca98e08 0x0b6e95fb - >bits.tests -AT_CHECK([BUILDDIR/t/bits.t bits.tests], [0], [ignore], [ignore]) +AT_CHECK([BUILDDIR/t/bits.t -fh bits.tests], [0], [ignore]) AT_CLEANUP ## control AT_SETUP([utilities: control]) AT_KEYWORDS([utils control]) -AT_CHECK([BUILDDIR/t/control.t], [0], [ignore], [ignore]) +AT_CHECK([BUILDDIR/t/control.t -fh], [0], [ignore]) AT_CLEANUP ## exc @@ -59,8 +59,8 @@ AT_CLEANUP ## versioncmp AT_SETUP([utilities: versioncmp]) AT_KEYWORDS([utils versioncmp]) -AT_CHECK([BUILDDIR/t/versioncmp.t SRCDIR/t/versioncmp.tests], - [0], [ignore], [ignore]) +AT_CHECK([BUILDDIR/t/versioncmp.t -fh SRCDIR/t/versioncmp.tests], + [0], [ignore]) AT_CLEANUP ###----- That's all, folks --------------------------------------------------