From c81c35dfd10050ffef85d57dc2ad73f52f38a3f2 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Fri, 8 Mar 2024 00:53:40 +0000 Subject: [PATCH] @@@ tvec doc wip --- hash/Makefile.am | 2 +- hash/t/{unihash-testgen.py => unihash-testgen} | 0 hash/tests.at | 2 +- struct/Makefile.am | 4 +- struct/t/{da-gtest.py => da-gtest} | 0 struct/t/dstr-putf-test.c | 47 +- struct/t/{sym-gtest.py => sym-gtest} | 0 struct/tests.at | 6 +- t/template-canonify | 2 +- test/bench.c | 21 +- test/t/tvec-test.c | 16 +- test/tests.at | 72 +- test/tvec-bench.c | 291 ++++---- test/tvec-core.c | 3 +- test/tvec-output.c | 32 - test/tvec-remote.c | 698 ++++++++++++++++-- test/tvec-timeout.c | 114 ++- test/tvec-types.c | 985 +++++++++++++++++++++---- test/tvec.h | 183 +++-- utils/Makefile.am | 2 +- utils/t/{bits-testgen.py => bits-testgen} | 0 utils/t/control-test.c | 2 +- utils/t/versioncmp-test.c | 6 +- utils/tests.at | 2 +- 24 files changed, 1969 insertions(+), 521 deletions(-) rename hash/t/{unihash-testgen.py => unihash-testgen} (100%) rename struct/t/{da-gtest.py => da-gtest} (100%) rename struct/t/{sym-gtest.py => sym-gtest} (100%) rename utils/t/{bits-testgen.py => bits-testgen} (100%) diff --git a/hash/Makefile.am b/hash/Makefile.am index 564be16..df6537a 100644 --- a/hash/Makefile.am +++ b/hash/Makefile.am @@ -80,7 +80,7 @@ $(precomp)/unihash-global.c: -o$@.new && mv $@.new $@ endif -EXTRA_DIST += t/unihash-testgen.py +EXTRA_DIST += t/unihash-testgen ## Test program. check_PROGRAMS += t/hash.t diff --git a/hash/t/unihash-testgen.py b/hash/t/unihash-testgen similarity index 100% rename from hash/t/unihash-testgen.py rename to hash/t/unihash-testgen diff --git a/hash/tests.at b/hash/tests.at index 45d4c13..5a2f1f7 100644 --- a/hash/tests.at +++ b/hash/tests.at @@ -39,7 +39,7 @@ AT_CLEANUP AT_SETUP([hash: unihash]) AT_KEYWORDS([hash unihash]) -$PYTHON SRCDIR/t/unihash-testgen.py >unihash.tests +$PYTHON SRCDIR/t/unihash-testgen >unihash.tests AT_CHECK([BUILDDIR/t/hash.t unihash.tests], [0], [ignore], [ignore]) AT_CLEANUP diff --git a/struct/Makefile.am b/struct/Makefile.am index 7996231..124b76e 100644 --- a/struct/Makefile.am +++ b/struct/Makefile.am @@ -56,7 +56,7 @@ t_darray_t_SOURCES = t/da-test.c t_darray_t_CPPFLAGS = $(TEST_CPPFLAGS) t_darray_t_LDFLAGS = -static -EXTRA_DIST += t/da-gtest.py +EXTRA_DIST += t/da-gtest ## Hash tables. pkginclude_HEADERS += hash.h @@ -73,7 +73,7 @@ t_sym_t_SOURCES = t/sym-test.c t_sym_t_CPPFLAGS = $(TEST_CPPFLAGS) t_sym_t_LDFLAGS = -static -EXTRA_DIST += t/sym-gtest.py +EXTRA_DIST += t/sym-gtest ## Atoms. pkginclude_HEADERS += atom.h diff --git a/struct/t/da-gtest.py b/struct/t/da-gtest similarity index 100% rename from struct/t/da-gtest.py rename to struct/t/da-gtest diff --git a/struct/t/dstr-putf-test.c b/struct/t/dstr-putf-test.c index 13fe155..eb67e60 100644 --- a/struct/t/dstr-putf-test.c +++ b/struct/t/dstr-putf-test.c @@ -1,3 +1,32 @@ +/* -*-c-*- + * + * Test driver for universal hashing + * + * (c) 2009 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + #include "config.h" #include @@ -12,10 +41,14 @@ #include "report.h" #include "tvec.h" +/*----- Static variables --------------------------------------------------*/ + static struct tvec_state tvstate; static dstr d = DSTR_INIT; static char strbuf[1024]; +/*----- Utilities ---------------------------------------------------------*/ + #define TESTGROUP(name) TVEC_TESTGROUP_TAG(grp, &tvstate, name) static PRINTF_LIKE(1, 2) int format(const char *fmt, ...) @@ -48,8 +81,8 @@ static PRINTF_LIKE(1, 2) void prepare(const char *fmt, ...) int n; n = format fmtargs; \ tvec_claimeq_int(&tvstate, n, strlen(want), \ __FILE__, __LINE__, "format " #fmtargs); \ - tvec_claimeq_string(&tvstate, d.buf, d.len, want, strlen(want), \ - __FILE__, __LINE__, "format " #fmtargs); \ + tvec_claimeq_text(&tvstate, d.buf, d.len, want, strlen(want), \ + __FILE__, __LINE__, "format " #fmtargs); \ } while (0) #define TEST_REF(fmtargs) do { \ @@ -57,13 +90,15 @@ static PRINTF_LIKE(1, 2) void prepare(const char *fmt, ...) prepare fmtargs; \ tvec_claimeq_int(&tvstate, n, strlen(strbuf), \ __FILE__, __LINE__, "format " #fmtargs); \ - tvec_claimeq_string(&tvstate, d.buf, d.len, strbuf, strlen(strbuf), \ - __FILE__, __LINE__, "format " #fmtargs); \ + tvec_claimeq_text(&tvstate, d.buf, d.len, strbuf, strlen(strbuf), \ + __FILE__, __LINE__, "format " #fmtargs); \ } while (0) -#define LENGTHY \ +#define LENGTHY \ "This is a rather longer string than the code is expecting: will it fit?" +/*----- Main program ------------------------------------------------------*/ + int main(int argc, char *argv[]) { struct tvec_test test; @@ -115,3 +150,5 @@ int main(int argc, char *argv[]) return (tvec_end(&tvstate)); } + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/struct/t/sym-gtest.py b/struct/t/sym-gtest similarity index 100% rename from struct/t/sym-gtest.py rename to struct/t/sym-gtest diff --git a/struct/tests.at b/struct/tests.at index 63d5fc4..6646355 100644 --- a/struct/tests.at +++ b/struct/tests.at @@ -32,7 +32,7 @@ AT_SETUP([struct: assoc]) AT_KEYWORDS([struct assoc]) AT_SKIP_IF([test "$PYTHON" = :]) for seed in 0x58677213 0xdfcc2ff4 ""; do - $PYTHON SRCDIR/t/sym-gtest.py $seed + $PYTHON SRCDIR/t/sym-gtest $seed AT_CHECK([BUILDDIR/t/assoc.t tm; struct bench_time t0, t1; - unsigned long n; + unsigned long n, nn; /* Make sure the state is calibrated. */ if (bench_calibrate(b)) return (-1); - /* Main adaptive measurement loop. */ + /* Main adaptive measurement loop. + * + * Suppose the timer loop %$n$% iterations in %$t$% seconds. Our ideal + * time is %$T$% seconds. If %$t \ge T/\sqrt{2}$%, we're happy. + * Otherwise, we need to scale up the iteration count. The obvious next + * choice is %$n' = n T/t$%. Alas, rounding is a problem: if + * %$T/t < 1 + 1/n$% then %$\floor{n T/t} = n$% and we will make no + * progress. We know that %$T/t > \sqrt{2}%, so this can only happen when + * %$1 + 1/n > \sqrt{2}$%, i.e., when %$n < \sqrt{2} + 1$%. On the other + * hand, if %$T/t < 1 + 1/n$% then %$t (n + 1)/n > T$%, so just trying + * again with %$n' = n + 1$% iterations will very likely work. + */ debug("measuring..."); n = 1; for (;;) { tm->ops->now(tm, &t0); fn(n, ctx); tm->ops->now(tm, &t1); @@ -661,8 +672,10 @@ int bench_measure(struct bench_timing *t_out, struct bench_state *b, if (!(t_out->f&BTF_TIMEOK)) return (-1); if (!(t_out->f&BTF_CYOK)) debug(" n = %10lu; t = %12g", n, t_out->t); else debug(" n = %10lu; t = %12g, cy = %10.0f", n, t_out->t, t_out->cy); - if (t_out->t >= 0.72*b->target_s) break; - n *= 1.44*b->target_s/t_out->t; + if (t_out->t >= 0.707*b->target_s) break; + nn = n*b->target_s/t_out->t; + if (nn > n) n = nn; + else n++; } /* Adjust according to the calibration. */ diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c index ac316b6..aaa4465 100644 --- a/test/t/tvec-test.c +++ b/test/t/tvec-test.c @@ -131,7 +131,7 @@ static const struct tvec_urange range_32 = { 0, 31 }; _(fenum, RFE, fenum, p, &fenum_info) \ _(penum, RPE, penum, p, &penum_info) \ _(flags, RF, flags, p, &attr_info) \ - _(string, RSTR, string, p, &range_32) \ + _(text, RTXT, text, p, &range_32) \ _(bytes, RBY, bytes, p, &tvrange_byte) \ _(buffer, RBUF, buffer, p, 0) @@ -221,18 +221,18 @@ static void test_copy_simple (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) { out->v = in->v; } -static void test_copy_string +static void test_copy_text (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) { - tvec_allocstring(&out->v, in->v.str.sz); - memcpy(out->v.str.p, in->v.str.p, in->v.str.sz); + tvec_alloctext(&out->v, in->v.text.sz); + memcpy(out->v.text.p, in->v.text.p, in->v.text.sz); } static void test_copy_bytes (const struct tvec_reg *in, struct tvec_reg *out, void *ctx) { - tvec_allocstring(&out->v, in->v.str.sz); - memcpy(out->v.str.p, in->v.str.p, in->v.str.sz); + tvec_allocbytes(&out->v, in->v.bytes.sz); + memcpy(out->v.bytes.p, in->v.bytes.p, in->v.bytes.sz); } #define test_copy_int test_copy_simple @@ -394,8 +394,8 @@ static void test_multi_serialize ? 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_text) + { if (rv->text.sz) rv->text.p[0] ^= 1; } else if (rd->ty == &tvty_bytes) { if (rv->bytes.sz) rv->bytes.p[0] ^= 1; } } diff --git a/test/tests.at b/test/tests.at index fbb62bb..4bea513 100644 --- a/test/tests.at +++ b/test/tests.at @@ -233,6 +233,7 @@ test_parse([char], [';'], [';' ; = 59 = 0x3b]) test_parse([char], [';';?], [';' ; = 59 = 0x3b]) test_parse([char], [';' ;?], [';' ; = 59 = 0x3b]) test_parse([char], [\"], ['"' ; = 34 = 0x22]) # " +test_parse([char], [@%:@], ['@%:@' ; = 35 = 0x23]) test_parse([char], ['@%:@'], ['@%:@' ; = 35 = 0x23]) test_parse([char], [\n], [@%:@newline ; = '\n' = 10 = 0x0a]) @@ -262,8 +263,6 @@ test_parserr([char], ['''], [3], [syntax error: expected character but found `'']) test_parserr([char], [;], [3], [syntax error: expected character but found `;']) -test_parserr([char], [@%:@], - [3], [unknown character name `@%:@']) test_parserr([char], [';], [3], [syntax error: expected `'' but found @%:@eol]) test_parserr([char], [\], @@ -282,41 +281,41 @@ test_parserr([char], [\{012], AT_CLEANUP ###-------------------------------------------------------------------------- -AT_SETUP([tvec type-string]) - -test_parse([string], [foo], [foo]) -test_parse([string], [foo bar], ["foo bar"]) -test_parse([string], [foo bar], ["foo bar"]) -test_parse([string], [foo "" bar], [foobar]) -test_parse([string], [ foo bar ], ["foo bar"]) -test_parse([string], [ foo @&t@ +AT_SETUP([tvec type-text]) + +test_parse([text], [foo], [foo]) +test_parse([text], [foo bar], ["foo bar"]) +test_parse([text], [foo bar], ["foo bar"]) +test_parse([text], [foo "" bar], [foobar]) +test_parse([text], [ foo bar ], ["foo bar"]) +test_parse([text], [ foo @&t@ bar ], ["foo bar"]) -test_parse([string], [foo @%:@nul bar], ["foo\{0}bar"]) +test_parse([text], [foo @%:@nul bar], ["foo\{0}bar"]) -test_parse([string], ["f" !repeat 2 { o } "bar"], [foobar]) -test_parse([string], ["{"!repeat 5{"abc"}"}"], ["{abcabcabcabcabc}"]) +test_parse([text], ["f" !repeat 2 { o } "bar"], [foobar]) +test_parse([text], ["{"!repeat 5{"abc"}"}"], ["{abcabcabcabcabc}"]) -test_parse([string], [!hex "f" 6f "o"], [foo]) +test_parse([text], [!hex "f" 6f "o"], [foo]) -test_parse([string], ["foo\n"], ["foo\n"]) +test_parse([text], ["foo\n"], ["foo\n"]) -test_parse([string], [foo\ +test_parse([text], [foo\ bar], [ "foo\n" "bar"]) -test_parse([string], ["foo\ +test_parse([text], ["foo\ bar"], [foobar]) -test_parse([string], ["foo" @%:@newline "bar" @%:@newline], [ +test_parse([text], ["foo" @%:@newline "bar" @%:@newline], [ "foo\n" "bar\n"]) -test_parserr([string], [], +test_parserr([text], [], [4], [syntax error: expected string but found @%:@eof]) -test_parse([string], [""], [""]) -test_parse([string], [''], [""]) +test_parse([text], [""], [""]) +test_parse([text], [''], [""]) -test_parse([string], ["f\x{6f}o"], [foo]) +test_parse([text], ["f\x{6f}o"], [foo]) AT_CLEANUP @@ -348,6 +347,8 @@ test_parse([bytes], [!base64 AAAA], [000000 ; ...]) test_parse([bytes], [!base64 AA], [00 ; .]) test_parse([bytes], [!base64 AAA], [0000 ; ..]) +test_parserr([text], [], + [4], [syntax error: expected string but found @%:@eof]) test_parserr([bytes], [0], [4], [invalid hex sequence end: Excess or nonzero padding bits]) test_parserr([bytes], [!base64 A], @@ -420,7 +421,30 @@ z = 1 check_template([BUILDDIR/t/tvec.t -fh tv], [0], [tv:11: `crash' skipped: no connection crash: ok (1 skipped) -PASSED 3 tests (1 skipped) in 1 group +PASSED ={N:\d+} tests (1 skipped) in 1 group +]) + +AT_CLEANUP + +###-------------------------------------------------------------------------- +AT_SETUP([tvec timeout]) + +AT_DATA([tv], +[;;; -*-conf-*- + +@<:@sleep@:>@ + +time = 0.5 +z = 0 +@progress = %RUN +@exit = killed | SIGALRM + +time = 0.125 +z = 1 +]) +check_template([BUILDDIR/t/tvec.t -fh tv], [0], +[sleep: ok +PASSED all ={N:\d+} tests in 1 group ]) AT_CLEANUP @@ -441,7 +465,7 @@ uenum = banana fenum = tau penum = alice flags = red-fg | white-bg | bright -string = "Hello, world!" +text = "Hello, world!" bytes = 2923be84 e16cd6ae 529049f1 f1bbe9eb b3a6db3c 870c3e99 245e0d1c 06b747de diff --git a/test/tvec-bench.c b/test/tvec-bench.c index 7086e14..c1d8cc0 100644 --- a/test/tvec-bench.c +++ b/test/tvec-bench.c @@ -81,6 +81,110 @@ static void normalize(double *x_inout, const char **unit_out, double scale) *x_inout = x; *unit_out = *u; } +/* --- @benchloop_...@ --- * + * + * Arguments: @unsigned long n@ = iteration count + * @void *ctx@ = benchmark running context + * + * Returns: --- + * + * Use: Run various kinds of benchmarking loops. + * + * * The @..._outer_...@ functions call the underlying + * function @n@ times in a loop; by contrast, the + * @..._inner_...@ functions set a register value to the + * chosen iteration count and expect the underlying function + * to perform the loop itself. + * + * * The @..._direct@ functions just call the underlying test + * function directly (though still through an `indirect + * jump' instruction); by contrast, the @..._indirect@ + * functions invoke a subsidiary environment's @run@ + * function, which adds additional overhead. + */ + +static void benchloop_outer_direct(unsigned long n, void *ctx) +{ + struct benchrun *r = ctx; + tvec_testfn *fn = r->fn; void *tctx = r->ctx; + const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out; + + while (n--) fn(in, out, tctx); +} + +static void benchloop_inner_direct(unsigned long n, void *ctx) + { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); } + +static void benchloop_outer_indirect(unsigned long n, void *ctx) +{ + struct benchrun *r = ctx; + struct tvec_state *tv = r->tv; + void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run; + tvec_testfn *fn = r->fn; void *tctx = r->ctx; + + while (n--) run(tv, fn, tctx); +} + +static void benchloop_inner_indirect(unsigned long n, void *ctx) + { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); } + +/*----- Output utilities --------------------------------------------------*/ + +/* --- @tvec_benchreport@ --- * + * + * Arguments: @const struct gprintf_ops *gops@ = print operations + * @void *go@ = print destination + * @unsigned unit@ = the unit being measured (~TVBU_...@) + * @const struct bench_timing *tm@ = the benchmark result + * + * Returns: --- + * + * Use: Formats a report about the benchmark performance. This + * function is intended to be called on by an output + * @ebench@ function. + */ + +void tvec_benchreport(const struct gprintf_ops *gops, void *go, + unsigned unit, const struct bench_timing *tm) +{ + double scale, x, n = tm->n; + const char *u, *what, *whats; + + if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; } + + assert(tm->f&BTF_TIMEOK); + + switch (unit) { + case TVBU_OP: + gprintf(gops, go, "%.0f iterations ", n); + what = "op"; whats = "ops"; scale = 1000; + break; + case TVBU_BYTE: + x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u); + what = whats = "B"; scale = 1024; + break; + default: + abort(); + } + + x = tm->t; normalize(&x, &u, 1000); + gprintf(gops, go, "in %.3f %ss", x, u); + if (tm->f&BTF_CYOK) { + x = tm->cy; normalize(&x, &u, 1000); + gprintf(gops, go, " (%.3f %scy)", x, u); + } + gprintf(gops, go, ": "); + + x = n/tm->t; normalize(&x, &u, scale); + gprintf(gops, go, "%.3f %s%s/s", x, u, whats); + x = tm->t/n; normalize(&x, &u, 1000); + gprintf(gops, go, ", %.3f %ss/%s", x, u, what); + if (tm->f&BTF_CYOK) { + x = tm->cy/n; normalize(&x, &u, 1000); + gprintf(gops, go, " (%.3f %scy/%s)", x, u, what); + } +} + /*----- Benchmark environment scaffolding ---------------------------------*/ /* --- @tvec_benchsetup@ --- * @@ -145,7 +249,8 @@ fail_timer: * @const char *var@ = variable name to set * @void *ctx@ = context pointer * - * Returns: Zero on success, @-1@ on failure. + * Returns: %$+1$% on success, %$0$% if the variable name was not + * recognized, or %$-1$% on any other error. * * Use: Set a special variable. The following special variables are * supported. @@ -198,109 +303,6 @@ void tvec_benchbefore(struct tvec_state *tv, void *ctx) if (subenv && subenv->before) subenv->before(tv, bc->subctx); } -/* --- @tvec_benchafter@ --- * - * - * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer - * - * Returns: --- - * - * Use: Invoke the subordinate environment's @after@ function to - * clean up after the benchmark. - */ - -void tvec_benchafter(struct tvec_state *tv, void *ctx) -{ - struct tvec_benchctx *bc = ctx; - const struct tvec_benchenv *be = bc->be; - const struct tvec_env *subenv = be->env; - - /* Restore the benchmark state's old target. */ - bc->bst->target_s = bc->dflt_target; - bc->f &= ~TVBF_SETTRG; - - /* Pass the call on to the subsidiary environment. */ - if (subenv && subenv->after) subenv->after(tv, bc->subctx); -} - -/* --- @tvec_benchteardown@ --- * - * - * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer - * - * Returns: --- - * - * Use: Tear down the benchmark environment. - */ - -void tvec_benchteardown(struct tvec_state *tv, void *ctx) -{ - struct tvec_benchctx *bc = ctx; - const struct tvec_benchenv *be; - const struct tvec_env *subenv; - - be = bc->be; subenv = be->env; - - /* Tear down any subsidiary environment. */ - if (subenv && subenv->teardown) - subenv->teardown(tv, bc->subctx); - - /* If the benchmark state was temporary, then dispose of it. */ - if (bc->bst) { - if (be->bst) bc->bst->target_s = bc->dflt_target; - else { bench_destroy(bc->bst); xfree(bc->bst); } - } -} - -/*----- Measurement machinery ---------------------------------------------*/ - -/* --- @benchloop_...@ --- * - * - * Arguments: @unsigned long n@ = iteration count - * @void *ctx@ = benchmark running context - * - * Returns: --- - * - * Use: Run various kinds of benchmarking loops. - * - * * The @..._outer_...@ functions call the underlying - * function @n@ times in a loop; by contrast, the - * @..._inner_...@ functions set a register value to the - * chosen iteration count and expect the underlying function - * to perform the loop itself. - * - * * The @..._direct@ functions just call the underlying test - * function directly (though still through an `indirect - * jump' instruction); by contrast, the @..._indirect@ - * functions invoke a subsidiary environment's @run@ - * function, which adds additional overhead. - */ - -static void benchloop_outer_direct(unsigned long n, void *ctx) -{ - struct benchrun *r = ctx; - tvec_testfn *fn = r->fn; void *tctx = r->ctx; - const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out; - - while (n--) fn(in, out, tctx); -} - -static void benchloop_inner_direct(unsigned long n, void *ctx) - { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); } - -static void benchloop_outer_indirect(unsigned long n, void *ctx) -{ - struct benchrun *r = ctx; - struct tvec_state *tv = r->tv; - void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run; - tvec_testfn *fn = r->fn; void *tctx = r->ctx; - - while (n--) run(tv, fn, tctx); -} - -static void benchloop_inner_indirect(unsigned long n, void *ctx) - { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); } - /* --- @tvec_benchrun@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state @@ -370,60 +372,57 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) #undef f_any } -/*----- Output utilities --------------------------------------------------*/ - -/* --- @tvec_benchreport@ --- * +/* --- @tvec_benchafter@ --- * * - * Arguments: @const struct gprintf_ops *gops@ = print operations - * @void *go@ = print destination - * @unsigned unit@ = the unit being measured (~TVBU_...@) - * @const struct bench_timing *tm@ = the benchmark result + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer * * Returns: --- * - * Use: Formats a report about the benchmark performance. This - * function is intended to be called on by an output - * @ebench@ function. + * Use: Invoke the subordinate environment's @after@ function to + * clean up after the benchmark. */ -void tvec_benchreport(const struct gprintf_ops *gops, void *go, - unsigned unit, const struct bench_timing *tm) +void tvec_benchafter(struct tvec_state *tv, void *ctx) { - double scale, x, n = tm->n; - const char *u, *what, *whats; + struct tvec_benchctx *bc = ctx; + const struct tvec_benchenv *be = bc->be; + const struct tvec_env *subenv = be->env; - if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; } + /* Restore the benchmark state's old target. */ + bc->bst->target_s = bc->dflt_target; + bc->f &= ~TVBF_SETTRG; - assert(tm->f&BTF_TIMEOK); + /* Pass the call on to the subsidiary environment. */ + if (subenv && subenv->after) subenv->after(tv, bc->subctx); +} - switch (unit) { - case TVBU_OP: - gprintf(gops, go, "%.0f iterations ", n); - what = "op"; whats = "ops"; scale = 1000; - break; - case TVBU_BYTE: - x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u); - what = whats = "B"; scale = 1024; - break; - default: - abort(); - } +/* --- @tvec_benchteardown@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Tear down the benchmark environment. + */ - x = tm->t; normalize(&x, &u, 1000); - gprintf(gops, go, "in %.3f %ss", x, u); - if (tm->f&BTF_CYOK) { - x = tm->cy; normalize(&x, &u, 1000); - gprintf(gops, go, " (%.3f %scy)", x, u); - } - gprintf(gops, go, ": "); +void tvec_benchteardown(struct tvec_state *tv, void *ctx) +{ + struct tvec_benchctx *bc = ctx; + const struct tvec_benchenv *be; + const struct tvec_env *subenv; - x = n/tm->t; normalize(&x, &u, scale); - gprintf(gops, go, "%.3f %s%s/s", x, u, whats); - x = tm->t/n; normalize(&x, &u, 1000); - gprintf(gops, go, ", %.3f %ss/%s", x, u, what); - if (tm->f&BTF_CYOK) { - x = tm->cy/n; normalize(&x, &u, 1000); - gprintf(gops, go, " (%.3f %scy/%s)", x, u, what); + be = bc->be; subenv = be->env; + + /* Tear down any subsidiary environment. */ + if (subenv && subenv->teardown) + subenv->teardown(tv, bc->subctx); + + /* If the benchmark state was temporary, then dispose of it. */ + if (bc->bst) { + if (be->bst) bc->bst->target_s = bc->dflt_target; + else { bench_destroy(bc->bst); xfree(bc->bst); } } } diff --git a/test/tvec-core.c b/test/tvec-core.c index f6d7918..65c74cf 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -744,7 +744,7 @@ void tvec_xfail(struct tvec_state *tv) static void check(struct tvec_state *tv, struct groupstate *g) { const struct tvec_test *t = tv->test; - const struct tvec_env *env; + const struct tvec_env *env = t->env; const struct tvec_regdef *rd; if (!(tv->f&TVSF_OPEN)) return; @@ -761,7 +761,6 @@ static void check(struct tvec_state *tv, struct groupstate *g) if (!(tv->f&TVSF_SKIP)) { begin_test(tv); - env = t->env; if (env && env->before) env->before(tv, g->ctx); if (!(tv->f&TVSF_ACTIVE)) /* setup forced a skip */; diff --git a/test/tvec-output.c b/test/tvec-output.c index 5210d11..8c9c214 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -416,38 +416,6 @@ static int layout_string(struct layout *lyt, const char *p, size_t sz) #undef PUT_CHAR #undef SAVE_PFXTAIL -/*----- Skeleton ----------------------------------------------------------*/ -/* -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 ..._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) -static void ..._dumpreg(struct tvec_output *o, unsigned disp, - union tvec_regval *rv, const struct tvec_regdef *rd) -static void ..._etest(struct tvec_output *o, unsigned outcome) -static void ..._bbench(struct tvec_output *o, - const char *ident, unsigned unit) -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 = { - ..._bsession, ..._esession, - ..._bgroup, ..._egroup, ..._skip, - ..._btest, ..._skip, ..._fail, ..._dumpreg, ..._etest, - ..._bbench, ..._ebench, - ..._error, ..._notice, - ..._destroy -}; -*/ /*----- Human-readable output ---------------------------------------------*/ /* Attributes for colour output. This should be done better, but @terminfo@ diff --git a/test/tvec-remote.c b/test/tvec-remote.c index 62dd150..052ce13 100644 --- a/test/tvec-remote.c +++ b/test/tvec-remote.c @@ -99,8 +99,13 @@ static void init_comms(struct tvec_remotecomms *rc) static void close_comms(struct tvec_remotecomms *rc) { - if (rc->infd >= 0) { close(rc->infd); rc->infd = -1; } - if (rc->outfd >= 0) { close(rc->outfd); rc->outfd = -1; } + if (rc->infd >= 0) { + if (rc->infd != rc->outfd) close(rc->infd); + rc->infd = -1; + } + if (rc->outfd >= 0) + { close(rc->outfd); rc->outfd = -1; } + rc->f |= TVRF_BROKEN; } /* --- @release_comms@ --- * @@ -203,7 +208,7 @@ end: * @size_t min@ = minimum acceptable size to read * @size_t *n_out@ = size read * - * Returns: An @RECV_...@ code. + * Returns: A @RECV_...@ code. * * Use: Receive data on the communication state's input descriptor to * read at least @min@ bytes into the input buffer, even if it @@ -239,7 +244,7 @@ static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc, p += n; sz -= n; tot += n; if (tot >= min) break; } else if (!n && !tot && (f&RCVF_ALLOWEOF)) - return (RECV_EOF); + { rc->f |= TVRF_BROKEN; return (RECV_EOF); } else return (ioerr(tv, rc, "failed to receive: %s", n ? strerror(errno) : "unexpected end-of-file")); @@ -326,7 +331,7 @@ end: * @unsigned f@ = flags (@RCVF_...@) * @size_t want@ = data block size required * - * Returns: An @RECV_...@ code. + * Returns: A @RECV_...@ code. * * Use: Reads a block of data from the input descriptor into the * input buffer. @@ -403,7 +408,7 @@ static int receive_buffered(struct tvec_state *tv, * @unsigned f@ = flags (@RCVF_...@) * @buf *b_out@ = buffer to establish around the packet contents * - * Returns: An @RECV_...@ code. + * Returns: A @RECV_...@ code. * * Use: Receive a packet into the input buffer @rc->bin@ and * establish @*b_out@ to read from it. @@ -780,6 +785,63 @@ bad: /*----- Server output driver ----------------------------------------------*/ +/* --- @remote_bsession@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @struct tvec_state *tv@ = the test state producing output + * + * Returns: --- + * + * Use: Begin a test session. + * + * The remote driver does nothing at all. + */ + +static void remote_bsession(struct tvec_output *o, struct tvec_state *tv) + { ; } + +/* --- @remote_esession@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * + * Returns: Suggested exit code. + * + * Use: End a test session. + * + * The remote driver returns a suitable exit code without + * printing anything. + */ + +static int remote_esession(struct tvec_output *o) + { return (srvtv.f&TVSF_ERROR ? 2 : 0); } + +/* --- @remote_bgroup@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * + * Returns: --- + * + * Use: Begin a test group. + * + * This is a stub which should never be called. + */ + +static void remote_bgroup(struct tvec_output *o) + { assert(!"remote_bgroup"); } + +/* --- @remote_skipgroup@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @const char *excuse@, @va_list *ap@ = reason for skipping the + * group, or null + * + * Returns: --- + * + * Use: Report that a test group is being skipped. + * + * The remote driver sends a @TVPK_SKIP@ packet to its client. + */ + static void remote_skipgroup(struct tvec_output *o, const char *excuse, va_list *ap) { @@ -787,6 +849,51 @@ static void remote_skipgroup(struct tvec_output *o, dbuf_vputstrf16l(&srvrc.bout, excuse, ap); } +/* --- @remote_egroup@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * + * Returns: --- + * + * Use: Report that a test group has finished. + * + * This is a stub which should never be called. + */ + +static void remote_egroup(struct tvec_output *o) + { assert(!"remote_egroup"); } + +/* --- @remote_btest@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * + * Returns: --- + * + * Use: Report that a test is starting. + * + * This is a stub which should never be called. + */ + +static void remote_btest(struct tvec_output *o) + { assert(!"remote_btest"); } + +/* --- @remote_skip@, @remote_fail@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @unsigned attr@ = attribute to apply to the outcome + * @const char *outcome@ = outcome string to report + * @const char *detail@, @va_list *ap@ = a detail message + * @const char *excuse@, @va_list *ap@ = reason for skipping the + * test + * + * Returns: --- + * + * Use: Report that a test has been skipped or failed. + * + * The remote driver sends a @TVPK_SKIP@ or @TVPK_FAIL@ packet + * to its client as appropriate. + */ + static void remote_skip(struct tvec_output *o, const char *excuse, va_list *ap) { @@ -806,6 +913,22 @@ static void remote_fail(struct tvec_output *o, } } +/* --- @remote_dumpreg@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @unsigned disp@ = register disposition + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Dump a register. + * + * The remote driver sends a @TVPK_DUMPREG@ packet to its + * client. This will only work if the register definition is + * one of those listed in the current test definition. + */ + static void remote_dumpreg(struct tvec_output *o, unsigned disp, const union tvec_regval *rv, const struct tvec_regdef *rd) @@ -831,6 +954,34 @@ found: } } +/* --- @remote_etest@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @unsigned outcome@ = the test outcome + * + * Returns: --- + * + * Use: Report that a test has finished. + * + * The remote driver does nothing at all. + */ + +static void remote_etest(struct tvec_output *o, unsigned outcome) + { ; } + +/* --- @remote_bbench@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @const char *ident@ = identifying register values + * @unsigned unit@ = measurement unit (@TVBU_...@) + * + * Returns: --- + * + * Use: Report that a benchmark has started. + * + * The remote driver sends a @TVPK_BBENCH@ packet to its client. + */ + static void remote_bbench(struct tvec_output *o, const char *ident, unsigned unit) { @@ -840,6 +991,20 @@ static void remote_bbench(struct tvec_output *o, } } +/* --- @remote_ebench@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @const char *ident@ = identifying register values + * @unsigned unit@ = measurement unit (@TVBU_...@) + * @const struct bench_timing *tm@ = measurement + * + * Returns: --- + * + * Use: Report a benchmark's results + * + * The remote driver sends a @TVPK_EBENCH@ packet to its client. + */ + static void remote_ebench(struct tvec_output *o, const char *ident, unsigned unit, const struct bench_timing *t) @@ -858,6 +1023,23 @@ static void remote_ebench(struct tvec_output *o, } } +/* --- @remote_report@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * @unsigned level@ = message level (@TVLEV_...@) + * @const char *msg@, @va_list *ap@ = format string and + * arguments + * + * Returns: --- + * + * Use: Report a message to the user. + * + * The remote driver sends a @TVPK_REPORT@ packet to its + * client. If its attempt to transmit the packet fails, then + * the message is written to the standard error stream instead, + * in the hope that this will help it be noticed. + */ + static void remote_report(struct tvec_output *o, unsigned level, const char *msg, va_list *ap) { @@ -871,21 +1053,19 @@ static void remote_report(struct tvec_output *o, unsigned level, } } -static void remote_bsession(struct tvec_output *o, struct tvec_state *tv) - { ; } -static int remote_esession(struct tvec_output *o) - { return (srvtv.f&TVSF_ERROR ? 2 : 0); } +/* --- @remote_destroy@ --- * + * + * Arguments: @struct tvec_output *o@ = output sink (ignored) + * + * Returns: --- + * + * Use: Release the resources held by the output driver. + * + * The remote driver does nothing at all. + */ + static void remote_destroy(struct tvec_output *o) { ; } -static void remote_etest(struct tvec_output *o, unsigned outcome) - { ; } - -static void remote_bgroup(struct tvec_output *o) - { assert(!"remote_bgroup"); } -static void remote_egroup(struct tvec_output *o) - { assert(!"remote_egroup"); } -static void remote_btest(struct tvec_output *o) - { assert(!"remote_btest"); } static const struct tvec_outops remote_ops = { remote_bsession, remote_esession, @@ -896,21 +1076,20 @@ static const struct tvec_outops remote_ops = { remote_destroy }; -/*----- Client ------------------------------------------------------------*/ - -#define TVXF_VALMASK 0x0fffu -#define TVXF_SIG 0x1000u -#define TVXF_CAUSEMASK 0xe000u -#define TVXST_RUN 0x0000u -#define TVXST_EXIT 0x2000u -#define TVXST_KILL 0x4000u -#define TVXST_CONT 0x6000u -#define TVXST_STOP 0x8000u -#define TVXST_DISCONN 0xa000u -#define TVXST_UNK 0xc000u -#define TVXST_ERR 0xe000u +/*----- Pseudoregister definitions ----------------------------------------*/ static const struct tvec_flag exit_flags[] = { + + /* Cause codes. */ + { "running", TVXF_CAUSEMASK, TVXST_RUN }, + { "exited", TVXF_CAUSEMASK, TVXST_EXIT }, + { "killed", TVXF_CAUSEMASK, TVXST_KILL }, + { "stopped", TVXF_CAUSEMASK, TVXST_STOP }, + { "continued", TVXF_CAUSEMASK, TVXST_CONT }, + { "disconnected", TVXF_CAUSEMASK, TVXST_DISCONN }, + { "unknown", TVXF_CAUSEMASK, TVXST_UNK }, + { "error", TVXF_CAUSEMASK, TVXST_ERR }, + /* ;;; The signal name table is very boring to type. To make life less ;;; awful, put the signal names in this list and evaluate the code to @@ -1060,17 +1239,9 @@ static const struct tvec_flag exit_flags[] = { #endif /***END***/ + /* This should be folded into the signal entries above. */ { "signal", TVXF_SIG, TVXF_SIG }, - { "running", TVXF_CAUSEMASK, TVXST_RUN }, - { "exited", TVXF_CAUSEMASK, TVXST_EXIT }, - { "killed", TVXF_CAUSEMASK, TVXST_KILL }, - { "stopped", TVXF_CAUSEMASK, TVXST_STOP }, - { "continued", TVXF_CAUSEMASK, TVXST_CONT }, - { "disconnected", TVXF_CAUSEMASK, TVXST_DISCONN }, - { "unknown", TVXF_CAUSEMASK, TVXST_UNK }, - { "error", TVXF_CAUSEMASK, TVXST_ERR }, - TVEC_ENDFLAGS }; @@ -1079,8 +1250,12 @@ static const struct tvec_flaginfo exit_flaginfo = static const struct tvec_regdef exit_regdef = { "@exit", 0, &tvty_flags, 0, { &exit_flaginfo } }; +/* Progress. */ + static const struct tvec_regdef progress_regdef = - { "@progress", 0, &tvty_string, 0 }; + { "@progress", 0, &tvty_text, 0 }; + +/* Reconnection. */ static const struct tvec_uassoc reconn_assocs[] = { { "on-demand", TVRCN_DEMAND }, @@ -1089,6 +1264,14 @@ static const struct tvec_uassoc reconn_assocs[] = { TVEC_ENDENUM }; +static const struct tvec_uenuminfo reconn_enuminfo = + { "remote-reconnection", reconn_assocs, &tvrange_uint }; +static const struct tvec_regdef reconn_regdef = + { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } }; + +/*----- Client ------------------------------------------------------------*/ + +/* Connection state. */ enum { CONN_BROKEN = -2, /* previously broken */ CONN_FAILED = -1, /* attempt freshly failed */ @@ -1096,10 +1279,35 @@ enum { CONN_FRESH = 1 /* freshly connected */ }; -static const struct tvec_uenuminfo reconn_enuminfo = - { "remote-reconnection", reconn_assocs, &tvrange_uint }; -static const struct tvec_regdef reconn_regdef = - { "@reconnect", 0, &tvty_uenum, 0, { &reconn_enuminfo } }; +/* --- @handle_packets@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * @unsigned f@ = receive flags (@RCVF_...@) + * @uint16 end@ = expected end packet type + * @buf *b_out@ = buffer in which to return end packet payload + * + * Returns: A @RECV_...@ code. + * + * Use: Handles notification packets from the server until a final + * termination packet is received. + * + * The client/server protocol consists of a number of flows, + * beginning with a request from the client, followed by a + * number of notifications from the server, and terminated by an + * acknowledgement to the original request indicating that the + * server has completed acting on the original request. + * + * This function handles the notifications issued by the server, + * returning when one of the following occurs: (a) a packet of + * type @end@ is received, in which case the function returns + * @RECV_OK@ and the remainder of the packet payload is left in + * @b_out@; (b) the flag @RCVF_ALLOWEOF@ was set in @f@ on entry + * and end-of-file is received at a packet boundary, in which + * case the function returns @RECV_EOF@; or (c) an I/O error + * occurs, in which case @ioerr@ is called and the function + * returns @RECV_FAIL@. + */ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, unsigned f, uint16 end, buf *b_out) @@ -1116,13 +1324,21 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, int rc; for (;;) { - rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end; + + /* Read the next packet. If we didn't receive one then end the loop. + * Otherwise, retrieve the packet type and check it against @end@: quit + * the loop if we get a match. + */ + rc = remote_recv(tv, &r->rc, f, b); if (rc) break; if (buf_getu16l(b, &pk)) goto bad; - if (pk == end) break; + if (pk == end) { rc = 0; break; } + /* Dispatch based on the packet type. */ switch (pk) { case TVPK_PROGRESS: + /* A progress report. Update the saved progress. */ + p = buf_getmem16l(b, &n); if (!p) goto bad; if (BLEFT(b)) goto bad; @@ -1130,6 +1346,8 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; case TVPK_REPORT: + /* A report. Recover the message and pass it along. */ + if (buf_getu16l(b, &u)) goto bad; p = buf_getmem16l(b, &n); if (!p) goto bad; if (BLEFT(b)) goto bad; @@ -1139,14 +1357,22 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; case TVPK_SKIPGRP: + /* A request to skip the group. Recover the excuse message and pass + * it along. + */ + p = buf_getmem16l(b, &n); if (!p) goto bad; - DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); if (BLEFT(b)) goto bad; + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); tvec_skipgroup(tv, "%s", d.buf); break; case TVPK_SKIP: + /* A request to skip the test. Recover the excuse message and pass + * it along, if it's not unreasonable. + */ + if (!(tv->f&TVSF_ACTIVE)) { rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name); goto end; @@ -1160,6 +1386,10 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; case TVPK_FAIL: + /* A report that the test failed. Recover the detail message, if + * any, and pass it along, if it's not unreasonable. + */ + if (!(tv->f&TVSF_ACTIVE) && ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) { rc = ioerr(tv, &r->rc, "test `%s' not active or failing", @@ -1181,6 +1411,9 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; case TVPK_DUMPREG: + /* A request to dump a register. */ + + /* Find the register definition. */ if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad; for (rd = tv->test->regs, i = 0; rd->name; rd++, i++) if (i == u) goto found_reg; @@ -1194,6 +1427,9 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, goto end; } + /* Read the flag. If there's no register value, then `dump' its + * absence. Otherwise retrieve the register value and dump it. + */ rc = buf_getbyte(b); if (rc < 0) goto bad; if (!rc) tvec_dumpreg(tv, v, 0, rd); @@ -1209,6 +1445,8 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; case TVPK_BBENCH: + /* A report that we're starting a benchmark. Pass this along. */ + p = buf_getmem32l(b, &n); if (!p) goto bad; if (buf_getu16l(b, &u)) goto bad; if (BLEFT(b)) goto bad; @@ -1222,6 +1460,8 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; case TVPK_EBENCH: + /* A report that a benchmark completed. Pass this along. */ + p = buf_getmem32l(b, &n); if (!p) goto bad; if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad; if (u >= TVBU_LIMIT) { @@ -1238,12 +1478,13 @@ static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, break; default: + /* Something else. This is unexpected. */ + rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk); goto end; } } - rc = RECV_OK; end: DDESTROY(&d); xfree(reg); @@ -1252,11 +1493,36 @@ bad: rc = malformed(tv, &r->rc); goto end; } +/* --- @reap_kid@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * + * Returns: --- + * + * Use: Determine the exit status of a broken connection, setting + * @r->exit@ appropriately. + * + * If @r->kid@ is negative, the exit status has already been + * set, and nothing further happens; this is not an error. + * + * If @r->kid@ is zero, then there is no real child process + * (e.g., because the remote connection is a network connection + * or similar), so @r->exit@ is set equal to @RVXST_DISCONN@. + * + * If @r->kid@ is positive, then it holds a child process id; + * the function waits for it to end and collects its exit status + * + * It is an error to call this function if the connection is not + * broken. + */ + static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r) { pid_t kid; int st; + assert(r->rc.f&TVRF_BROKEN); if (!r->kid) { r->exit = TVXST_DISCONN; r->kid = -1; } else if (r->kid > 0) { @@ -1285,6 +1551,20 @@ static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r) } } +/* --- @report_errline@ --- * + * + * Arguments: @char *p@ = pointer to the line + * @size_t n@ = length in characters + * @void *ctx@ = context, secretly a @struct tvec_remotectx@ + * + * Returns: --- + * + * Use: Print a line of stderr output from the child. If + * @TVRF_MUFFLE@ is set, then discard the line silently. + * + * This is an @lbuf_func@, invoked via @drain_errfd@. + */ + static void report_errline(char *p, size_t n, void *ctx) { struct tvec_remotectx *r = ctx; @@ -1294,6 +1574,29 @@ static void report_errline(char *p, size_t n, void *ctx) tvec_notice(tv, "child process stderr: %s", p); } +/* --- @drain_errfd@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * @unsigned f@ = receive flags (@ERF_...@) + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Collect material written by the child to its stderr stream + * and report it. + * + * If @f@ has @ERF_SILENT@ set, then discard the stderr material + * without reporting it. Otherwise it is reported as + * @TVLEV_NOTE@. + * + * if @f@ has @ERF_CLOSE@ set, then continue reading until + * end-of-file is received; also, report any final partial line, + * and close @r->errfd@. + * + * If @r->errfd@ is already closed, or never established, then + * do nothing and return successfully. + */ + #define ERF_SILENT 0x0001u #define ERF_CLOSE 0x0002u static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, @@ -1303,6 +1606,10 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, ssize_t n; int rc; + /* Preliminaries. Bail if there is no error stream to fetch. Arrange + * (rather clumsily) to muffle the output if we're supposed to be client. + * And set the nonblocking state on @errfd@ appropriately. + */ if (r->errfd < 0) { rc = 0; goto end; } if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE; else r->rc.f &= ~TVRF_MUFFLE; @@ -1312,6 +1619,7 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, goto end; } + /* Read pieces of error output and feed them into the line buffer. */ for (;;) { sz = lbuf_free(&r->errbuf, &p); n = read(r->errfd, p, sz); @@ -1326,6 +1634,8 @@ static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, } lbuf_flush(&r->errbuf, p, n); } + + /* Done. */ rc = 0; end: if (f&ERF_CLOSE) { @@ -1335,6 +1645,24 @@ end: return (rc); } +/* --- @disconnect_remote@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * @unsigned f@ = receive flags (@DCF_...@) + * + * Returns: --- + * + * Use: Disconnect and shut down all of the remote client state. + * + * If @f@ has @DCF_KILL@ set then send the child process (if + * any) @SIGTERM@ to make sure it shuts down in a timely manner. + * + * In detail: this function closes the @infd@ and @outfd@ + * descriptors, drains and closes @errfd@, and collects the exit + * status (if any). + */ + #define DCF_KILL 0x0100u static void disconnect_remote(struct tvec_state *tv, struct tvec_remotectx *r, unsigned f) @@ -1344,6 +1672,16 @@ static void disconnect_remote(struct tvec_state *tv, drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r); } +/* --- @connect_remote@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Connect to the test server. + */ + static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r) { const struct tvec_remoteenv *re = r->re; @@ -1352,19 +1690,26 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r) uint16 v; int infd = -1, outfd = -1, errfd = -1, rc; - DRESET(&r->progress); DPUTS(&r->progress, "%INIT"); + /* If we're already connected, then there's nothing to do. */ if (r->kid >= 0) { rc = 0; goto end; } + + /* Set the preliminary progress indication. */ + DRESET(&r->progress); DPUTS(&r->progress, "%INIT"); + + /* Call the connection function to establish descriptors. */ if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re)) { rc = -1; goto end; } + + /* Establish communications state. */ setup_comms(&r->rc, infd, outfd); r->kid = kid; r->errfd = errfd; lbuf_init(&r->errbuf, report_errline, r); r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN; + /* Do version negotiation. */ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_VER) { dbuf_putu16l(&r->rc.bout, 0); dbuf_putu16l(&r->rc.bout, 0); } else { rc = -1; goto end; } - if (handle_packets(tv, r, 0, TVPK_VER | TVPF_ACK, &b)) { rc = -1; goto end; } if (buf_getu16l(&b, &v)) goto bad; @@ -1373,14 +1718,18 @@ static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r) rc = ioerr(tv, &r->rc, "protocol version %u not supported", v); goto end; } + r->ver = v; + /* Begin the test group at the server. */ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_BGROUP) dbuf_putstr16l(&r->rc.bout, tv->test->name); else { rc = -1; goto end; } if (handle_packets(tv, r, 0, TVPK_BGROUP | TVPF_ACK, &b)) { rc = -1; goto end; } if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; } - r->ver = v; rc = 0; + + /* Done. */ + rc = 0; end: if (rc) disconnect_remote(tv, r, DCF_KILL); return (rc); @@ -1388,6 +1737,19 @@ bad: rc = malformed(tv, &r->rc); goto end; } +/* --- @check_comms@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * + * Returns: A @CONN_...@ code reflecting the current communication + * state. + * + * Use: Determine the current connection state. If the connection + * has recently broken (i.e., @TVRF_BROKEN@ is set in @r->rc.f@) + * since the last time we checked then disconnect. + */ + static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r) { if (r->kid < 0) @@ -1398,6 +1760,17 @@ static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r) return (CONN_ESTABLISHED); } +/* --- @try_reconnect@ --- * + * + * Arguments: @struct tvec_state *tv@ = test-vector state + * @struct tvec_remotectx *r@ = remote client context + * + * Returns: A @CONN_...@ code reflecting the new communication state. + * + * Use: Reconnects to the server according to the configured + * @TVRCN_...@ policy. + */ + static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r) { int rc; @@ -1425,6 +1798,17 @@ static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r) return (rc); } +/*----- Remote environment ------------------------------------------------*/ + +/* --- @reset_vars@ --- * + * + * Arguments: @struct tvec_remotectx *r@ = remote client context + * + * Returns: --- + * + * Use: Reset the pseudoregisters set through @tvec_remoteset@. + */ + static void reset_vars(struct tvec_remotectx *r) { r->exwant = TVXST_RUN; @@ -1432,6 +1816,22 @@ static void reset_vars(struct tvec_remotectx *r) DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE"); } +/* --- @tvec_remotesetup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const struct tvec_env *env@ = environment description + * @void *pctx@ = parent context (ignored) + * @void *ctx@ = context pointer to initialize + * + * Returns: --- + * + * Use: Initialize a timeout environment context. + * + * The environment description should be a @struct + * tvec_remoteenv@ subclass suitable for use by the @connect@ + * function. + */ + void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env, void *pctx, void *ctx) { @@ -1449,6 +1849,29 @@ void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env, reset_vars(r); } +/* --- @tvec_remoteset@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const char *var@ = variable name to set + * @void *ctx@ = context pointer + * + * Returns: %$+1$% on success, %$0$% if the variable name was not + * recognized, or %$-1$% on any other error. + * + * Use: Set a special variable. The following special variables are + * supported. + * + * * %|@exit|% is the expected exit status; see @TVXF_...@ and + * @TVXST_...@. + * + * * %|progress|% is the expected progress token when the test + * completes. On successful completion, this will be + * %|%DONE|%; it's %|%RUN|% on entry to the test function, + * but that can call @tvec_setprogress@ to change it. + * + * * %|reconnect|% is a reconnection policy; see @TVRCN_...@. + */ + int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx) { struct tvec_remotectx *r = ctx; @@ -1461,13 +1884,13 @@ int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx) r->exwant = rv.u; r->rc.f |= TVRF_SETEXIT; rc = 1; } else if (STRCMP(var, ==, "@progress")) { if (r->rc.f&TVRF_SETPRG) { rc = tvec_dupreg(tv, var); goto end; } - tvty_string.init(&rv, &progress_regdef); - rc = tvty_string.parse(&rv, &progress_regdef, tv); + tvty_text.init(&rv, &progress_regdef); + rc = tvty_text.parse(&rv, &progress_regdef, tv); if (!rc) { - DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); + DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.text.p, rv.text.sz); r->rc.f |= TVRF_SETPRG; } - tvty_string.release(&rv, &progress_regdef); + tvty_text.release(&rv, &progress_regdef); if (rc) { rc = -1; goto end; } rc = 1; } else if (STRCMP(var, ==, "@reconnect")) { @@ -1482,12 +1905,16 @@ end: return (rc); } -void tvec_remoteafter(struct tvec_state *tv, void *ctx) -{ - struct tvec_remotectx *r = ctx; - - reset_vars(r); -} +/* --- @tvec_remoterun@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @tvec_testfn *fn@ = test function to run + * @void *ctx@ = context pointer for the test function + * + * Returns: --- + * + * Use: Run a test on a remote server. + */ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { @@ -1500,6 +1927,7 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) buf b; int rc; + /* Reconnect to the server according to policy. */ switch (try_reconnect(tv, r)) { case CONN_FAILED: tvec_skip(tv, "failed to connect to test backend"); return; @@ -1507,29 +1935,61 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) tvec_skip(tv, "no connection"); return; } + /* Set initial progress state. */ DRESET(&r->progress); DPUTS(&r->progress, "%IDLE"); + + /* Send the command to the server and handle output. */ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_TEST) tvec_serialize(tv->in, DBUF_BUF(&r->rc.bout), tv->test->regs, tv->nreg, tv->regsz); - else { rc = -1; goto end; } + else { goto fail; } rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b); + + /* Deal with the outcome. */ switch (rc) { + case RECV_FAIL: - goto end; + /* Some kind of error. Abandon ship. */ + + fail: + tvec_skip(tv, "remote test runner communications failed"); + disconnect_remote(tv, r, 0); + break; + case RECV_EOF: + /* End-of-file at a packet boundary. The server crashed trying to run + * our test. Collect the exit status and continue. + */ reap_kid(tv, r); /* fall through */ + case RECV_OK: + /* Successful completion (or EOF). */ + + /* Notice if the exit status isn't right. */ if (r->exit != r->exwant) f |= f_exit; + + /* Notice if the progress token isn't right. */ if (r->progress.len != r->prgwant.len || MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len)) f |= f_progress; + + /* If we found something wrong but the test is passing so far, then + * report the failure and dump the input registers. + */ if (f && (tv->f&TVSF_ACTIVE)) { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); } + + /* If the test failed, then report the exit and progress states + * relative to their expectations. + */ if (!(tv->f&TVSF_ACTIVE) && (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) { + + /* Note here that the test failed. */ f |= f_fail; + /* Report exit status. */ rv.u = r->exit; tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH, &rv, &exit_regdef); @@ -1538,32 +1998,56 @@ void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef); } - rv.str.p = r->progress.buf; rv.str.sz = r->progress.len; + /* Report progress token. */ + rv.text.p = r->progress.buf; rv.text.sz = r->progress.len; tvec_dumpreg(tv, f&f_progress ? TVRD_FOUND : TVRD_MATCH, &rv, &progress_regdef); if (f&f_progress) { - rv.str.p = r->prgwant.buf; rv.str.sz = r->prgwant.len; + rv.text.p = r->prgwant.buf; rv.text.sz = r->prgwant.len; tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef); } } + /* If we received end-of-file, then close the connection. Suppress + * error output if the test passed: it was presumably expected. + */ if (rc == RECV_EOF) disconnect_remote(tv, r, f ? 0 : ERF_SILENT); break; } -end: - if (rc) { - if ((tv->f&TVSF_ACTIVE) && f) - tvec_skip(tv, "remote test runner communications failed"); - disconnect_remote(tv, r, 0); - } - #undef f_exit #undef f_progress #undef f_fail } +/* --- @tvec_remoteafter@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Reset variables to their default values. + */ + +void tvec_remoteafter(struct tvec_state *tv, void *ctx) +{ + struct tvec_remotectx *r = ctx; + + reset_vars(r); +} + +/* --- @tvec_remoteteardown@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Tear down the remote environment. + */ + void tvec_remoteteardown(struct tvec_state *tv, void *ctx) { struct tvec_remotectx *r = ctx; @@ -1580,6 +2064,27 @@ void tvec_remoteteardown(struct tvec_state *tv, void *ctx) /*----- Connectors --------------------------------------------------------*/ +/* --- @fork_common@ --- * + * + * Arguments: @pid_t *kid_out@ = where to put child process-id + * @int *infd_out, *outfd_out, *errfd_out@ = where to put file + * descriptors + * @struct tvec_state *tv@ = test vector state + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Common @fork@ machinery for the connectors. Create a + * subprocess. On successful return, in the subprocess, + * @*kid_out@ is zero, and the error descriptor replaces the + * standard-error descriptor; in the parent, @*kid_out@ is the + * child process-id, and @*errfd_out@ is a descriptor on which + * the child's standard-error output can be read; in both + * @*infd_out@ and @*outfd_out@ are descriptors for input and + * output respectively -- they're opposite ends of pipes, but + * obviously they're crossed over so that the parent's output + * matches the child's input and %%\emph{vice versa}%%. + */ + static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out, struct tvec_state *tv) { @@ -1587,6 +2092,7 @@ static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out, pid_t kid = -1; int rc; + /* Try to create the pipes. */ if (pipe(p0) || pipe(p1) || pipe(pe) || fdflags(p0[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC) || fdflags(p1[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC) || @@ -1595,8 +2101,12 @@ static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out, rc = -1; goto end; } + /* Flush all of the stream buffers so that we don't get duplicated + * output. + */ fflush(0); + /* Try to set up the child process. */ kid = fork(); if (kid < 0) { tvec_error(tv, "fork failed: %s", strerror(errno)); @@ -1604,23 +2114,30 @@ static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out, } if (!kid) { + /* Child process. */ + *kid_out = 0; *infd_out = p0[0]; p0[0] = -1; *outfd_out = p1[1]; p1[1] = -1; if (pe[1] != STDERR_FILENO && dup2(pe[1], STDERR_FILENO) < 0) { fprintf(stderr, "failed to establish child stderr: %s", strerror(errno)); - exit(127); + _exit(127); } } else { + /* Parent process. */ + *kid_out = kid; kid = -1; *infd_out = p1[0]; p1[0] = -1; *outfd_out = p0[1]; p0[1] = -1; *errfd_out = pe[0]; pe[0] = -1; } + /* All done. */ rc = 0; + end: + /* Clean up. So much of this... */ if (p0[0] >= 0) close(p0[0]); if (p0[1] >= 0) close(p0[1]); if (p1[0] >= 0) close(p1[0]); @@ -1630,6 +2147,21 @@ end: return (rc); } +/* --- @tvec_fork@ --- * + * + * Arguments: @pid_t *kid_out@ = where to put child process-id + * @int *infd_out, *outfd_out, *errfd_out@ = where to put file + * descriptors + * @struct tvec_state *tv@ = test vector state + * @const struct tvec_remoteenv@ = the remote environment + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Starts a remote server running in a fork of the main + * process. This is useful for testing functions which might -- + * or are even intended to -- crash. + */ + int tvec_fork(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out, struct tvec_state *tv, const struct tvec_remoteenv *env) { @@ -1654,6 +2186,22 @@ end: return (rc); } +/* --- @tvec_exec@ --- * + * + * Arguments: @pid_t *kid_out@ = where to put child process-id + * @int *infd_out, *outfd_out, *errfd_out@ = where to put file + * descriptors + * @struct tvec_state *tv@ = test vector state + * @const struct tvec_remoteenv@ = the remote environment + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Starts a remote server by running some program. The command + * given in the environment description will probably some hairy + * shell rune allowing for configuration via files or + * environment variables. + */ + int tvec_exec(pid_t *kid_out, int *infd_out, int *outfd_out, int *errfd_out, struct tvec_state *tv, const struct tvec_remoteenv *env) { diff --git a/test/tvec-timeout.c b/test/tvec-timeout.c index ca86b5c..d9a614b 100644 --- a/test/tvec-timeout.c +++ b/test/tvec-timeout.c @@ -83,7 +83,8 @@ void tvec_timeoutsetup(struct tvec_state *tv, const struct tvec_env *env, * @const char *var@ = variable name to set * @void *ctx@ = context pointer * - * Returns: Zero on success, @-1@ on failure. + * Returns: %$+1$% on success, %$0$% if the variable name was not + * recognized, or %$-1$% on any other error. * * Use: Set a special variable. The following special variables are * supported. @@ -112,12 +113,22 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) #define f_all (f_time | f_timer) if (STRCMP(var, ==, "@timeout")) { + /* Parse a timeout specification. */ + + /* If we've already set one then report an error. */ if (tc->f&TVTF_SETTMO) { rc = tvec_dupreg(tv, var); goto end; } for (;;) { + /* Continue until we run out of things. */ + + /* Read the next word. */ DRESET(&d); if (tvec_readword(tv, &d, ";", 0)) break; timeout_primed: + + /* Start parsing the word. */ p = d.buf; + + /* Check for timer tokens. */ if (!(f&f_timer) && STRCMP(p, ==, "REAL")) { tmr = ITIMER_REAL; f |= f_timer; } else if (!(f&f_timer) && STRCMP(p, ==, "VIRTUAL")) @@ -125,26 +136,42 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) else if (!(f&f_timer) && STRCMP(p, ==, "PROF")) { tmr = ITIMER_PROF; f |= f_timer; } + /* Otherwise, check for durations. */ else if (!(f&f_time)) { + + /* Skip leading stuff that isn't digits. This is a hedge against + * @strtod@ interpreting something unhelpful like a NaN. + */ if (*p == '+' || *p == '-') p++; if (*p == '.') p++; if (!ISDIGIT(*p)) { tvec_syntax(tv, *d.buf, "floating-point number"); rc = -1; goto end; } + + /* Parse the number and check that it's reasonable. */ errno = 0; t = strtod(p, &q); f |= f_time; if (errno) { tvec_error(tv, "invalid floating-point number `%s': %s", d.buf, strerror(errno)); rc = -1; goto end; } + if (t < 0) { + tvec_error(tv, "invalid duration `%s': %s", + d.buf, strerror(errno)); + rc = -1; goto end; + } + /* We're now on the lookout for units. If there's nothing here then + * fetch the next word. + */ if (!*q) { tvec_skipspc(tv); pos = d.len; if (!tvec_readword(tv, &d, ";", 0)) pos++; q = d.buf + pos; } + /* Match various units. */ if (!*q || STRCMP(q, ==, "s") || STRCMP(q, ==, "sec")) /* nothing to do */; @@ -175,32 +202,45 @@ int tvec_timeoutset(struct tvec_state *tv, const char *var, void *ctx) else if (STRCMP(q, ==, "d") || STRCMP(q, ==, "dy")) t *= 86400; else if (STRCMP(q, ==, "y") || STRCMP(q, ==, "yr")) t *= 31557600; + /* Not a unit specification after all. If we've already selected a + * timer, then this is just junk so report the error. Otherwise, we + * snarfed the next token too early, so move it to the start of the + * buffer and go round again. + */ else { if (f&f_timer) { rc = tvec_syntax(tv, *q, "end-of-line"); goto end; } pos = q - d.buf; d.len -= pos; - memmove(d.buf, q, d.len); + memmove(d.buf, q, d.len + 1); goto timeout_primed; } } + /* If we've read all that we need to, then stop. */ if (!(~f&f_all)) break; } + /* If we didn't get anything, that's a problem. */ if (!f) { rc = tvec_syntax(tv, fgetc(tv->fp), "timeout or timer keyword"); goto end; } + + /* Make sure there's nothing else on the line. */ rc = tvec_flushtoeol(tv, 0); if (rc) goto end; if (f&f_time) tc->t = t; if (f&f_timer) tc->timer = tmr; tc->f |= TVTF_SETTMO; + rc = 1; } else if (subenv && subenv->set) + /* Not one of ours: pass it on to the sub-environment. */ rc = subenv->set(tv, var, tc->subctx); else + /* No subenvironment. Report the error. */ rc = 0; + /* Done. */ end: dstr_destroy(&d); return (rc); @@ -231,83 +271,83 @@ void tvec_timeoutbefore(struct tvec_state *tv, void *ctx) if (subenv && subenv->before) subenv->before(tv, tc->subctx); } -/* --- @tvec_timeoutafter@ --- * +/* --- @tvec_timeoutrun@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer + * @tvec_testfn *fn@ = test function to run + * @void *ctx@ = context pointer for the test function * * Returns: --- * - * Use: Invoke the subordinate environment's @after@ function to - * clean up after the test. + * Use: Runs a test with a timeout attached. */ -void tvec_timeoutafter(struct tvec_state *tv, void *ctx) +void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { struct tvec_timeoutctx *tc = ctx; const struct tvec_timeoutenv *te = tc->te; const struct tvec_env *subenv = te->env; + struct itimerval itv; - /* Reset variables. */ - reset(tc); + itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = tc->t; + itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6; - /* Pass the call on to the subsidiary environment. */ - if (subenv && subenv->after) subenv->after(tv, tc->subctx); + if (setitimer(tc->timer, &itv, 0)) + tvec_skip(tv, "failed to set interval timer: %s", strerror(errno)); + else { + if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx); + else fn(tv->in, tv->out, tc->subctx); + + itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; + if (setitimer(tc->timer, &itv, 0)) + tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno)); + } } -/* --- @tvec_timeoutteardown@ --- * +/* --- @tvec_timeoutafter@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state * @void *ctx@ = context pointer * * Returns: --- * - * Use: Tear down the timeoutmark environment. + * Use: Invoke the subordinate environment's @after@ function to + * clean up after the test. */ -void tvec_timeoutteardown(struct tvec_state *tv, void *ctx) +void tvec_timeoutafter(struct tvec_state *tv, void *ctx) { struct tvec_timeoutctx *tc = ctx; const struct tvec_timeoutenv *te = tc->te; const struct tvec_env *subenv = te->env; - /* Just call the subsidiary environment. */ - if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx); + /* Reset variables. */ + reset(tc); + + /* Pass the call on to the subsidiary environment. */ + if (subenv && subenv->after) subenv->after(tv, tc->subctx); } -/* --- @tvec_timeoutrun@ --- * +/* --- @tvec_timeoutteardown@ --- * * * Arguments: @struct tvec_state *tv@ = test vector state - * @tvec_testfn *fn@ = test function to run - * @void *ctx@ = context pointer for the test function + * @void *ctx@ = context pointer * * Returns: --- * - * Use: Runs a test with a timeout attached. + * Use: Tear down the timeoutmark environment. */ -void tvec_timeoutrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +void tvec_timeoutteardown(struct tvec_state *tv, void *ctx) { struct tvec_timeoutctx *tc = ctx; const struct tvec_timeoutenv *te = tc->te; const struct tvec_env *subenv = te->env; - struct itimerval itv; - - itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; - itv.it_value.tv_sec = tc->t; - itv.it_value.tv_usec = (tc->t - itv.it_value.tv_sec)*1e6; - - if (setitimer(tc->timer, &itv, 0)) - tvec_skip(tv, "failed to set interval timer: %s", strerror(errno)); - else { - if (subenv && subenv->run) subenv->run(tv, fn, tc->subctx); - else fn(tv->in, tv->out, tc->subctx); - itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; - itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 0; - if (setitimer(tc->timer, &itv, 0)) - tvec_error(tv, "failed to disarm interval timer: %s", strerror(errno)); - } + /* Just call the subsidiary environment. */ + if (subenv && subenv->teardown) subenv->teardown(tv, tc->subctx); } /*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec-types.c b/test/tvec-types.c index 681409d..acb848c 100644 --- a/test/tvec-types.c +++ b/test/tvec-types.c @@ -1241,36 +1241,36 @@ end: return (rc); } -/*----- Skeleton ----------------------------------------------------------*/ -/* -static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd) -static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd) -static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1, - const struct tvec_regdef *rd) -static int tobuf_...(buf *b, const union tvec_regval *rv, - const struct tvec_regdef *rd) -static int frombuf_...(buf *b, union tvec_regval *rv, - const struct tvec_regdef *rd) -static int parse_...(union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv) -static void dump_...(const union tvec_regval *rv, - const struct tvec_regdef *rd, - struct tvec_state *tv, unsigned style) - -const struct tvec_regty tvty_... = { - init_..., release_..., eq_..., - tobuf_..., frombuf_..., - parse_..., dump_... -}; -*/ /*----- Signed and unsigned integer types ---------------------------------*/ +/* --- @init_int@, @init_uint@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Initialize a register value. + * + * Integer values are initialized to zero. + */ + static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd) { rv->i = 0; } static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd) { rv->u = 0; } +/* --- @eq_int@, @eq_uint@ --- * + * + * Arguments: @const union tvec_regval *rv0, *rv1@ = register values + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Nonzero if the values are equal, zero if unequal + * + * Use: Compare register values for equality. + */ + static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1, const struct tvec_regdef *rd) { return (rv0->i == rv1->i); } @@ -1280,6 +1280,20 @@ static int eq_uint(const union tvec_regval *rv0, const struct tvec_regdef *rd) { return (rv0->u == rv1->u); } +/* --- @tobuf_int@, @tobuf_uint@ --- * + * + * Arguments: @buf *b@ = buffer + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Serialize a register value to a buffer. + * + * Integer values are serialized as little-endian 64-bit signed + * or unsigned integers. + */ + static int tobuf_int(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { return (signed_to_buf(b, rv->i)); } @@ -1288,6 +1302,20 @@ static int tobuf_uint(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { return (unsigned_to_buf(b, rv->u)); } +/* --- @frombuf_int@, @frombuf_uint@ --- * + * + * Arguments: @buf *b@ = buffer + * @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Deserialize a register value from a buffer. + * + * Integer values are serialized as 64-bit signed or unsigned + * integers. + */ + static int frombuf_int(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { return (signed_from_buf(b, &rv->i)); } @@ -1296,18 +1324,51 @@ static int frombuf_uint(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { return (unsigned_from_buf(b, &rv->u)); } +/* --- @parse_int@, @parse_uint@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * 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. + */ + static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd, struct tvec_state *tv) { dstr d = DSTR_INIT; int rc; - if (tvec_readword(tv, &d, ";", "signed integer")) - { rc = -1; goto end; } - if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) - { rc = -1; goto end; } - if (tvec_flushtoeol(tv, 0)) - { rc = -1; goto end; } + if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; } + if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; } + if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } rc = 0; end: dstr_destroy(&d); @@ -1320,18 +1381,31 @@ static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd, dstr d = DSTR_INIT; int rc; - if (tvec_readword(tv, &d, ";", "unsigned integer")) - { rc = -1; goto end; } - if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) - { rc = -1; goto end; } - if (tvec_flushtoeol(tv, 0)) - { rc = -1; goto end; } + if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; } + if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; } + if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } rc = 0; end: dstr_destroy(&d); return (rc); } +/* --- @dump_int@, @dump_uint@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * Integer values are dumped in decimal and, unless compact + * output is requested, hex, and maybe a character, as a + * comment. + */ + static void dump_int(const union tvec_regval *rv, const struct tvec_regdef *rd, unsigned style, @@ -1359,12 +1433,19 @@ static void dump_uint(const union tvec_regval *rv, } } +/* Integer type definitions. */ const struct tvec_regty tvty_int = { init_int, trivial_release, eq_int, tobuf_int, frombuf_int, parse_int, dump_int }; +const struct tvec_regty tvty_uint = { + init_uint, trivial_release, eq_uint, + tobuf_uint, frombuf_uint, + parse_uint, dump_uint +}; +/* Predefined integer ranges. */ const struct tvec_irange tvrange_schar = { SCHAR_MIN, SCHAR_MAX }, tvrange_short = { SHRT_MIN, SHRT_MAX }, @@ -1373,13 +1454,6 @@ const struct tvec_irange tvrange_sbyte = { -128, 127 }, tvrange_i16 = { -32768, +32767 }, tvrange_i32 = { -2147483648, 2147483647 }; - -const struct tvec_regty tvty_uint = { - init_uint, trivial_release, eq_uint, - tobuf_uint, frombuf_uint, - parse_uint, dump_uint -}; - const struct tvec_urange tvrange_uchar = { 0, UCHAR_MAX }, tvrange_ushort = { 0, USHRT_MAX }, @@ -1441,21 +1515,93 @@ int tvec_claimeq_uint(struct tvec_state *tv, /*----- Floating-point type -----------------------------------------------*/ +/* --- @float_int@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Initialize a register value. + * + * Floating-point values are initialized to zero. + */ + static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd) { rv->f = 0.0; } +/* --- @eq_float@ --- * + * + * Arguments: @const union tvec_regval *rv0, *rv1@ = register values + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Nonzero if the values are equal, zero if unequal + * + * Use: Compare register values for equality. + * + * Floating-point values may be considered equal if their + * absolute or relative difference is sufficiently small, as + * described in the register definition. + */ + static int eq_float(const union tvec_regval *rv0, const union tvec_regval *rv1, const struct tvec_regdef *rd) { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); } +/* --- @tobuf_float@ --- * + * + * Arguments: @buf *b@ = buffer + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Serialize a register value to a buffer. + * + * Floating-point values are serialized as little-endian + * IEEE 754 Binary64. + */ + static int tobuf_float(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { return (buf_putf64l(b, rv->f)); } + +/* --- @frombuf_float@ --- * + * + * Arguments: @buf *b@ = buffer + * @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Deserialize a register value from a buffer. + * + * Floating-point values are serialized as little-endian + * IEEE 754 Binary64. + */ + static int frombuf_float(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { return (buf_getf64l(b, &rv->f)); } +/* --- @parse_float@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * Floating-point values are either NaN (%|#nan|%, if supported + * by the platform); positive or negative infinity (%|#inf|%, + * %|+#inf|%, or %|#+inf|% (preferring the last), and %|-#inf|% + * or %|#-inf|% (preferring the latter), if supported by the + * platform); or a number in strtod(3) syntax. + */ + static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd, struct tvec_state *tv) { @@ -1464,28 +1610,49 @@ static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd, if (tvec_readword(tv, &d, ";", "floating-point number")) { rc = -1; goto end; } - if (parse_floating(&rv->f, d.buf, rd->arg.p, tv)) - { rc = -1; goto end; } - if (tvec_flushtoeol(tv, 0)) - { rc = -1; goto end; } + if (parse_floating(&rv->f, d.buf, rd->arg.p, tv)) { rc = -1; goto end; } + if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } rc = 0; end: dstr_destroy(&d); return (rc); } +/* --- @dump_float@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * Floating-point values are dumped in decimal or as a special + * token beginning with `%|#|%'. Some effort is taken to ensure + * that the output is sufficient to uniquely identify the + * original value, but, honestly, C makes this really hard. + */ + static void dump_float(const union tvec_regval *rv, const struct tvec_regdef *rd, unsigned style, const struct gprintf_ops *gops, void *go) { format_floating(gops, go, rv->f); } +/* Floating-point type definition. */ const struct tvec_regty tvty_float = { init_float, trivial_release, eq_float, tobuf_float, frombuf_float, parse_float, dump_float }; +/* Predefined floating-point ranges. */ +const struct tvec_floatinfo + tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 }, + tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 }; + /* --- @tvec_claimeqish_float@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state @@ -1562,20 +1729,48 @@ int tvec_claimeq_float(struct tvec_state *tv, file, lno, expr)); } -const struct tvec_floatinfo - tvflt_finite = { TVFF_EXACT, -DBL_MAX, DBL_MAX, 0.0 }, - tvflt_nonneg = { TVFF_EXACT, 0, DBL_MAX, 0.0 }; - /*----- Enumerations ------------------------------------------------------*/ +/* --- @init_tenum@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Initialize a register value. + * + * Integer and floating-point enumeration values are initialized + * as their underlying representations. Pointer enumerations + * are initialized to %|#nil|%. + */ + #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; } +/* --- @eq_tenum@ --- * + * + * Arguments: @const union tvec_regval *rv0, *rv1@ = register values + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Nonzero if the values are equal, zero if unequal + * + * Use: Compare register values for equality. + * + * Integer and floating-point enumeration values are compared as + * their underlying representations; in particular, floating- + * point enumerations may compare equal if their absolute or + * relative difference is sufficiently small. Pointer + * enumerations are compared as pointers. + */ + #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) @@ -1583,14 +1778,33 @@ static int eq_fenum(const union tvec_regval *rv0, 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); } +/* --- @tobuf_tenum@ --- * + * + * Arguments: @buf *b@ = buffer + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Serialize a register value to a buffer. + * + * Integer and floating-point enumeration values are serialized + * as their underlying representations. Pointer enumerations + * are serialized as the signed integer index into the + * association table; %|#nil|% serializes as %$-1$%, and + * unrecognized pointers cause failure. + */ + #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) { @@ -1606,6 +1820,23 @@ found: return (signed_to_buf(b, i)); } +/* --- @frombuf_tenum@ --- * + * + * Arguments: @buf *b@ = buffer + * @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Deserialize a register value from a buffer. + * + * Integer and floating-point enumeration values are serialized + * as their underlying representations. Pointer enumerations + * are serialized as the signed integer index into the + * association table; %|#nil|% serializes as %$-1$%; out-of- + * range indices cause failure. + */ + #define frombuf_ienum frombuf_int #define frombuf_uenum frombuf_uint #define frombuf_fenum frombuf_float @@ -1624,6 +1855,23 @@ static int frombuf_penum(buf *b, union tvec_regval *rv, return (0); } +/* --- @parse_tenum@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * An enumerated value may be given by name or as a literal + * value. For enumerations based on numeric types, the literal + * values can be written in the same syntax as the underlying + * values. For enumerations based on pointers, the only + * permitted literal is %|#nil|%, which denotes a null pointer. + */ + #define DEFPARSE_ENUM(tag_, ty, slot) \ static int parse_##slot##enum(union tvec_regval *rv, \ const struct tvec_regdef *rd, \ @@ -1693,6 +1941,27 @@ TVEC_MISCSLOTS(DEFPARSE_ENUM) #undef DEFPARSE_ENUM +/* --- @dump_tenum@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * Enumeration values are dumped as their symbolic names, if + * possible, with the underlying values provided as a comment + * unless compact output is requested, as for the underlying + * representation. A null pointer is printed as %|#nil|%; + * non-null pointers are printed as %|#|%, with the + * enumeration TYPE and the raw pointer PTR printed with the + * system's %|%p|% format specifier. + */ + + #define DEFDUMP_ENUM(tag_, ty, slot) \ static void dump_##slot##enum(const union tvec_regval *rv, \ const struct tvec_regdef *rd, \ @@ -1713,7 +1982,7 @@ TVEC_MISCSLOTS(DEFPARSE_ENUM) } #define MAYBE_PRINT_EXTRA \ - if (style&TVSF_COMPACT) ; \ + if (style&TVSF_COMPACT) /* nothing to do */; \ else if (!a->tag) { gprintf(gops, go, " ; = "); goto _extra; } \ else if (1) { gprintf(gops, go, " = "); goto _extra; } \ else _extra: @@ -1745,6 +2014,7 @@ TVEC_MISCSLOTS(DEFDUMP_ENUM) #undef MAYBE_PRINT_EXTRA #undef DEFDUMP_ENUM +/* Enumeration type definitions. */ #define DEFTY_ENUM(tag, ty, slot) \ const struct tvec_regty tvty_##slot##enum = { \ init_##slot##enum, trivial_release, eq_##slot##enum, \ @@ -1754,6 +2024,7 @@ TVEC_MISCSLOTS(DEFDUMP_ENUM) TVEC_MISCSLOTS(DEFTY_ENUM) #undef DEFTY_ENUM +/* Predefined enumeration types. */ static const struct tvec_iassoc bool_assoc[] = { { "nil", 0 }, { "false", 0 }, @@ -1838,6 +2109,27 @@ TVEC_MISCSLOTS(DEFCLAIM) /*----- Flag types --------------------------------------------------------*/ +/* Flag types are initialized, compared, and serialized as unsigned + * integers. + */ + +/* --- @parse_flags@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * The input syntax is a sequence of items separated by `|' + * signs. Each item may be the symbolic name of a field value, + * or a literal unsigned integer. The masks associated with the + * given symbolic names must be disjoint. The resulting + * numerical value is simply the bitwise OR of the given values. + */ + static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd, struct tvec_state *tv) { @@ -1848,10 +2140,13 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd, int ch, rc; for (;;) { + + /* Read the next item. */ DRESET(&d); if (tvec_readword(tv, &d, "|;", "flag name or integer")) { rc = -1; goto end; } + /* Try to find a matching entry in the table. */ for (f = fi->fv; f->tag; f++) if (STRCMP(f->tag, ==, d.buf)) { if (m&f->m) @@ -1860,23 +2155,54 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd, { m |= f->m; v |= f->v; goto next; } } + /* Otherwise, try to parse it as a raw integer. */ if (parse_unsigned(&t, d.buf, fi->range, tv)) { rc = -1; goto end; } v |= t; + next: + /* Advance to the next token. If it's a separator then consume it, and + * go round again. Otherwise we stop here. + */ if (tvec_nexttoken(tv)) break; ch = getc(tv->fp); if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; } - if (tvec_nexttoken(tv)) + if (tvec_nexttoken(tv)) { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; } } - rv->u = v; - rc = 0; + + /* Done. */ + rv->u = v; rc = 0; end: dstr_destroy(&d); return (rc); } +/* --- @dump_flags@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * The table of symbolic names and their associated values and + * masks is repeatedly scanned, in order, to find disjoint + * matches -- i.e., entries whose value matches the target value + * in the bit positions indicated by the mask, and whose mask + * doesn't overlap with any previously found matches; the names + * are then output, separated by `|'. Any remaining nonzero + * bits not covered by any of the matching masks are output as a + * single literal integer, in hex. + * + * Unless compact output is requested, or no symbolic names were + * found, the raw numeric value is also printed in hex, as a + * comment. + */ + static void dump_flags(const union tvec_regval *rv, const struct tvec_regdef *rd, unsigned style, @@ -1884,7 +2210,7 @@ static void dump_flags(const union tvec_regval *rv, { const struct tvec_flaginfo *fi = rd->arg.p; const struct tvec_flag *f; - unsigned long m = ~(unsigned long)0, v = rv->u; + unsigned long m = ~0ul, v = rv->u; const char *sep; for (f = fi->fv, sep = ""; f->tag; f++) @@ -1895,10 +2221,11 @@ static void dump_flags(const union tvec_regval *rv, if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m); - if (!(style&TVSF_COMPACT)) + if (m != ~0ul && !(style&TVSF_COMPACT)) gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u); } +/* Flags type definition. */ const struct tvec_regty tvty_flags = { init_uint, trivial_release, eq_uint, tobuf_uint, frombuf_uint, @@ -1936,16 +2263,47 @@ int tvec_claimeq_flags(struct tvec_state *tv, /*----- Characters --------------------------------------------------------*/ +/* Character values are initialized and compared as signed integers. */ + +/* --- @tobuf_char@ --- * + * + * Arguments: @buf *b@ = buffer + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Serialize a register value to a buffer. + * + * Character values are serialized as little-endian 32-bit + * unsigned integers, with %|EOF|% serialized as all-bits-set. + */ + static int tobuf_char(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { uint32 u; + if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i; else if (rv->i == EOF) u = MASK32; else return (-1); return (buf_putu32l(b, u)); } +/* --- @frombuf_char@ --- * + * + * Arguments: @buf *b@ = buffer + * @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Deserialize a register value from a buffer. + * + * Character values are serialized as little-endian 32-bit + * unsigned integers, with %|EOF|% serialized as all-bits-set. + */ + static int frombuf_char(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { @@ -1958,6 +2316,75 @@ static int frombuf_char(buf *b, union tvec_regval *rv, return (0); } +/* --- @parse_char@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * A character value can be given by symbolic name, with a + * leading `%|#|%'; or a character or `%|\|%'-escape sequence, + * optionally in single quotes. + * + * The following escape sequences and character names are + * recognized. + * + * * `%|#eof|%' is the special end-of-file marker. + * + * * `%|#nul|%' is the NUL character, sometimes used to + * terminate strings. + * + * * `%|bell|%', `%|bel|%', `%|ding|%', or `%|\a|%' is the BEL + * character used to ring the terminal bell (or do some other + * thing to attract the user's attention). + * + * * %|#backspace|%, %|#bs|%, or %|\b|% is the backspace + * character, used to move the cursor backwords by one cell. + * + * * %|#escape|% %|#esc|%, or%|\e|% is the escape character, + * used to introduce special terminal commands. + * + * * %|#formfeed|%, %|#ff|%, or %|\f|% is the formfeed + * character, used to separate pages of text. + * + * * %|#newline|%, %|#linefeed|%, %|#lf|%, %|#nl|%, or %|\n|% is + * the newline character, used to terminate lines of text or + * advance the cursor to the next line (perhaps without + * returning it to the start of the line). + * + * * %|#return|%, %|#carriage-return|%, %|#cr|%, or %|\r|% is + * the carriage-return character, used to return the cursor to + * the start of the line. + * + * * %|#tab|%, %|#horizontal-tab|%, %|#ht|%, or %|\t|% is the + * tab character, used to advance the cursor to the next tab + * stop on the current line. + * + * * %|#vertical-tab|%, %|#vt|%, %|\v|% is the vertical tab + * character. + * + * * %|#space|%, %|#spc|% is the space character. + * + * * %|#delete|%, %|#del|% is the delete character, used to + * erase the most recent character. + * + * * %|\'|% is the single-quote character. + * + * * %|\\|% is the backslash character. + * + * * %|\"|% is the double-quote character. + * + * * %|\NNN|% or %|\{NNN}|% is the character with code NNN in + * octal. The NNN may be up to three digits long. + * + * * %|\xNN|% or %|\x{NN}|% is the character with code NNN in + * hexadecimal. + */ + static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd, struct tvec_state *tv) { @@ -1966,40 +2393,77 @@ static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd, unsigned f = 0; #define f_quote 1u + /* Inspect the character to see what we're up against. */ ch = getc(tv->fp); + if (ch == '#') { + /* It looks like a special token. Push the `%|#|%' back and fetch the + * whole word. If there's just the `%|#|%' after all, then treat it as + * literal. + */ + ungetc(ch, tv->fp); if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; } - if (read_charname(&ch, d.buf, RCF_EOFOK)) { - rc = tvec_error(tv, "unknown character name `%s'", d.buf); - goto end; + if (STRCMP(d.buf, !=, "#")) { + if (read_charname(&ch, d.buf, RCF_EOFOK)) { + rc = tvec_error(tv, "unknown character name `%s'", d.buf); + goto end; + } + if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } + rv->i = ch; rc = 0; goto end; } - if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } - rv->i = ch; rc = 0; goto end; } + /* If this is a single quote then we expect to see a matching one later, + * and we should process backslash escapes. Get the next character and see + * what happens. + */ if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); } + + /* Main character dispatch. */ switch (ch) { + case ';': + /* Unquoted, semicolon begins a comment. */ if (!(f&f_quote)) { rc = tvec_syntax(tv, ch, "character"); goto end; } - goto plain; + else goto plain; + case '\n': - if (f&f_quote) - { f &= ~f_quote; ungetc(ch, tv->fp); ch = '\''; goto plain; } + /* A newline. If we saw a single quote, then treat that as literal. + * Otherwise this is an error. + */ + if (!(f&f_quote)) goto nochar; + else { f &= ~f_quote; ungetc(ch, tv->fp); ch = '\''; goto plain; } + case EOF: - if (f&f_quote) { f &= ~f_quote; ch = '\''; goto plain; } - /* fall through */ - case '\'': + /* End-of-file. Similar to newline, but with slightly different + * effects on the parse state. + */ + if (!(f&f_quote)) goto nochar; + else { f &= ~f_quote; ch = '\''; goto plain; } + + case '\'': nochar: + /* A single quote. This must be the second of a pair, and there should + * have been a character or escape sequence between them. + */ rc = tvec_syntax(tv, ch, "character"); goto end; + case '\\': + /* A backslash. Read a character escape. */ if (read_charesc(&ch, tv)) return (-1); + default: plain: + /* Anything else. Treat as literal. */ rv->i = ch; break; } + + /* If we saw an opening quote, then expect the closing quote. */ if (f&f_quote) { ch = getc(tv->fp); if (ch != '\'') { rc = tvec_syntax(tv, ch, "`''"); goto end; } } + + /* Done. */ if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } rc = 0; end: @@ -2009,6 +2473,25 @@ end: #undef f_quote } +/* --- @dump_char@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * Character values are dumped as their symbolic names, if any, + * or as a character or escape sequence within single quotes + * (which may be omitted in compact style). If compact output + * is not requested, then the single-quoted representation (for + * characters dumped as symbolic names) and integer code in + * decimal and hex are printed as a comment. + */ + static void dump_char(const union tvec_regval *rv, const struct tvec_regdef *rd, unsigned style, @@ -2018,6 +2501,7 @@ static void dump_char(const union tvec_regval *rv, unsigned f = 0; #define f_semi 1u + /* Print a character name if we can find one. */ p = find_charname(rv->i, (style&TVSF_COMPACT) ? CTF_SHORT : CTF_PREFER); if (p) { gprintf(gops, go, "%s", p); @@ -2025,6 +2509,9 @@ static void dump_char(const union tvec_regval *rv, else { gprintf(gops, go, " ;"); f |= f_semi; } } + /* If the character isn't @EOF@ then print it as a single-quoted thing. + * In compact style, see if we can omit the quotes. + */ if (rv->i >= 0) { if (f&f_semi) gprintf(gops, go, " = "); switch (rv->i) { @@ -2038,6 +2525,7 @@ static void dump_char(const union tvec_regval *rv, } } + /* And the character code as an integer. */ if (!(style&TVSF_COMPACT)) { if (!(f&f_semi)) gprintf(gops, go, " ;"); gprintf(gops, go, " = %ld = ", rv->i); @@ -2047,6 +2535,7 @@ static void dump_char(const union tvec_regval *rv, #undef f_semi } +/* Character type definition. */ const struct tvec_regty tvty_char = { init_int, trivial_release, eq_int, tobuf_char, frombuf_char, @@ -2079,27 +2568,62 @@ int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1, /*----- Text and byte strings ---------------------------------------------*/ -static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd) - { rv->str.p = 0; rv->str.sz = 0; } +/* --- @init_text@, @init_bytes@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Initialize a register value. + * + * Text and binary string values are initialized with a null + * pointer and zero length. + */ + +static void init_text(union tvec_regval *rv, const struct tvec_regdef *rd) + { rv->text.p = 0; rv->text.sz = 0; } static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd) { rv->bytes.p = 0; rv->bytes.sz = 0; } -static void release_string(union tvec_regval *rv, - const struct tvec_regdef *rd) - { xfree(rv->str.p); } +/* --- @release_string@, @release_bytes@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: --- + * + * Use: Release resources held by a register value. + * + * Text and binary string buffers are freed. + */ + +static void release_text(union tvec_regval *rv, + const struct tvec_regdef *rd) + { xfree(rv->text.p); } static void release_bytes(union tvec_regval *rv, const struct tvec_regdef *rd) { xfree(rv->bytes.p); } -static int eq_string(const union tvec_regval *rv0, - const union tvec_regval *rv1, - const struct tvec_regdef *rd) +/* --- @eq_text@, @eq_bytes@ --- * + * + * Arguments: @const union tvec_regval *rv0, *rv1@ = register values + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Nonzero if the values are equal, zero if unequal + * + * Use: Compare register values for equality. + */ + +static int eq_text(const union tvec_regval *rv0, + const union tvec_regval *rv1, + const struct tvec_regdef *rd) { - return (rv0->str.sz == rv1->str.sz && - (!rv0->bytes.sz || - MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz))); + return (rv0->text.sz == rv1->text.sz && + (!rv0->text.sz || + MEMCMP(rv0->text.p, ==, rv1->text.p, rv1->text.sz))); } static int eq_bytes(const union tvec_regval *rv0, @@ -2111,22 +2635,52 @@ static int eq_bytes(const union tvec_regval *rv0, MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz))); } -static int tobuf_string(buf *b, const union tvec_regval *rv, - const struct tvec_regdef *rd) - { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); } +/* --- @tobuf_text@, @tobuf_bytes@ --- * + * + * Arguments: @buf *b@ = buffer + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Serialize a register value to a buffer. + * + * Text and binary string values are serialized as a little- + * endian 64-bit length %$n$% in bytes followed by %$n$% bytes + * of string data. + */ + +static int tobuf_text(buf *b, const union tvec_regval *rv, + const struct tvec_regdef *rd) + { return (buf_putmem64l(b, rv->text.p, rv->text.sz)); } static int tobuf_bytes(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) - { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); } + { return (buf_putmem64l(b, rv->bytes.p, rv->bytes.sz)); } -static int frombuf_string(buf *b, union tvec_regval *rv, - const struct tvec_regdef *rd) +/* --- @frombuf_text@, @frombuf_bytes@ --- * + * + * Arguments: @buf *b@ = buffer + * @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Deserialize a register value from a buffer. + * + * Text and binary string values are serialized as a little- + * endian 64-bit length %$n$% in bytes followed by %$n$% bytes + * of string data. + */ + +static int frombuf_text(buf *b, union tvec_regval *rv, + const struct tvec_regdef *rd) { const void *p; size_t sz; - p = buf_getmem32l(b, &sz); if (!p) return (-1); - tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz); rv->str.p[sz] = 0; + p = buf_getmem64l(b, &sz); if (!p) return (-1); + tvec_alloctext(rv, sz); memcpy(rv->text.p, p, sz); rv->text.p[sz] = 0; return (0); } @@ -2136,11 +2690,23 @@ static int frombuf_bytes(buf *b, union tvec_regval *rv, const void *p; size_t sz; - p = buf_getmem32l(b, &sz); if (!p) return (-1); + p = buf_getmem64l(b, &sz); if (!p) return (-1); tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz); return (0); } +/* --- @check_string_length@ --- * + * + * Arguments: @size_t sz@ = found string length + * @const struct tvec_urange *ur@ = acceptable range + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Checks that @sz@ is within the bounds described by @ur@, + * reporting an error if not. + */ + static int check_string_length(size_t sz, const struct tvec_urange *ur, struct tvec_state *tv) { @@ -2151,15 +2717,58 @@ static int check_string_length(size_t sz, const struct tvec_urange *ur, return (0); } -static int parse_string(union tvec_regval *rv, const struct tvec_regdef *rd, - struct tvec_state *tv) +/* --- @parse_text@, @parse_bytes@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * The input format for both kinds of strings is basically the + * same: a `compound string', consisting of + * + * * single-quoted strings, which are interpreted entirely + * literally, but can't contain single quotes or newlines; + * + * * double-quoted strings, in which `%|\|%'-escapes are + * interpreted as for characters; + * + * * character names, marked by an initial `%|#|%' sign; + * + * * special tokens marked by an initial `%|!|%' sign; or + * + * * barewords interpreted according to the current coding + * scheme. + * + * The special tokens are + * + * * `%|!bare|%', which causes subsequent sequences of + * barewords to be treated as plain text; + * + * * `%|!hex|%', `%|!base32|%', `%|!base64|%', which cause + * subsequent barewords to be decoded in the requested + * manner. + * + * * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%', + * which includes %$n$% copies of the (compound) string. + * + * The only difference between text and binary strings is that + * the initial coding scheme is %|bare|% for text strings and + * %|hex|% for binary strings. + */ + +static int parse_text(union tvec_regval *rv, const struct tvec_regdef *rd, + struct tvec_state *tv) { - void *p = rv->str.p; + void *p = rv->text.p; - if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, 0, tv)) + if (read_compound_string(&p, &rv->text.sz, TVCODE_BARE, 0, tv)) return (-1); - rv->str.p = p; - if (check_string_length(rv->str.sz, rd->arg.p, tv)) return (-1); + rv->text.p = p; + if (check_string_length(rv->text.sz, rd->arg.p, tv)) return (-1); return (0); } @@ -2175,19 +2784,45 @@ static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd, return (0); } -static void dump_string(const union tvec_regval *rv, - const struct tvec_regdef *rd, - unsigned style, - const struct gprintf_ops *gops, void *go) +/* --- @dump_text@, @dump_bytes@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * Text string values are dumped as plain text, in double quotes + * if necessary, and using backslash escape sequences for + * nonprintable characters. Unless compact output is requested, + * strings consisting of multiple lines are dumped with each + * line of the string on a separate output line. + * + * Binary string values are dumped in hexadecimal. In compact + * style, the output simply consists of a single block of hex + * digits. Otherwise, the dump is a display consisting of + * groups of hex digits, with comments showing the offset (if + * the string is long enough) and the corresponding plain text. + * + * Empty strings are dumped as %|""|%. + */ + +static void dump_text(const union tvec_regval *rv, + const struct tvec_regdef *rd, + unsigned style, + const struct gprintf_ops *gops, void *go) { const unsigned char *p, *q, *l; unsigned f = 0; #define f_nonword 1u #define f_newline 2u - if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; } + if (!rv->text.sz) { gprintf(gops, go, "\"\""); return; } - p = (const unsigned char *)rv->str.p; l = p + rv->str.sz; + p = (const unsigned char *)rv->text.p; l = p + rv->text.sz; switch (*p) { case '!': case '#': case ';': case '"': case '\'': case '(': case '{': case '[': case ']': case '}': case ')': @@ -2199,7 +2834,7 @@ static void dump_string(const union tvec_regval *rv, if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; } else if (f&f_nonword) goto quote; - gops->putm(go, (const char *)p, rv->str.sz); + gops->putm(go, (const char *)p, rv->text.sz); return; quote: @@ -2263,19 +2898,19 @@ static void dump_bytes(const union tvec_regval *rv, } } -const struct tvec_regty tvty_string = { - init_string, release_string, eq_string, - tobuf_string, frombuf_string, - parse_string, dump_string +/* Text and byte string type definitions. */ +const struct tvec_regty tvty_text = { + init_text, release_text, eq_text, + tobuf_text, frombuf_text, + parse_text, dump_text }; - const struct tvec_regty tvty_bytes = { init_bytes, release_bytes, eq_bytes, tobuf_bytes, frombuf_bytes, parse_bytes, dump_bytes }; -/* --- @tvec_claimeq_string@ --- * +/* --- @tvec_claimeq_text@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *p0@, @size_t sz0@ = first string with length @@ -2294,17 +2929,17 @@ const struct tvec_regty tvty_bytes = { * value and @p1@ is printed as the input reference. */ -int tvec_claimeq_string(struct tvec_state *tv, - const char *p0, size_t sz0, - const char *p1, size_t sz1, - const char *file, unsigned lno, const char *expr) +int tvec_claimeq_text(struct tvec_state *tv, + const char *p0, size_t sz0, + const char *p1, size_t sz1, + const char *file, unsigned lno, const char *expr) { - 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)); + tv->out[0].v.text.p = (/*unconst*/ char *)p0; tv->out[0].v.text.sz = sz0; + tv->in[0].v.text.p =(/*unconst*/ char *) p1; tv->in[0].v.text.sz = sz1; + return (tvec_claimeq(tv, &tvty_text, 0, file, lno, expr)); } -/* --- @tvec_claimeq_strz@ --- * +/* --- @tvec_claimeq_textz@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *p0, *p1@ = two strings to compare @@ -2320,15 +2955,15 @@ int tvec_claimeq_string(struct tvec_state *tv, * explicitly. */ -int tvec_claimeq_strz(struct tvec_state *tv, - const char *p0, const char *p1, - const char *file, unsigned lno, const char *expr) +int tvec_claimeq_textz(struct tvec_state *tv, + const char *p0, const char *p1, + const char *file, unsigned lno, const char *expr) { - 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)); + tv->out[0].v.text.p = (/*unconst*/ char *)p0; + tv->out[0].v.text.sz = strlen(p0); + tv->in[0].v.text.p = (/*unconst*/ char *)p1; + tv->in[0].v.text.sz = strlen(p1); + return (tvec_claimeq(tv, &tvty_text, 0, file, lno, expr)); } /* --- @tvec_claimeq_bytes@ --- * @@ -2362,7 +2997,7 @@ int tvec_claimeq_bytes(struct tvec_state *tv, return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr)); } -/* --- @tvec_allocstring@, @tvec_allocbytes@ --- * +/* --- @tvec_alloctext@, @tvec_allocbytes@ --- * * * Arguments: @union tvec_regval *rv@ = register value * @size_t sz@ = required size @@ -2378,15 +3013,15 @@ int tvec_claimeq_bytes(struct tvec_state *tv, * the old buffer contents are simply discarded if reallocation * is necessary. Instead, use a @dbuf@ or @dstr@. * - * The @tvec_allocstring@ function sneakily allocates an extra + * The @tvec_alloctext@ function sneakily allocates an extra * byte for a terminating zero. The @tvec_allocbytes@ function * doesn't do this. */ -void tvec_allocstring(union tvec_regval *rv, size_t sz) +void tvec_alloctext(union tvec_regval *rv, size_t sz) { - if (rv->str.sz <= sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz + 1); } - rv->str.sz = sz; + if (rv->text.sz <= sz) { xfree(rv->text.p); rv->text.p = xmalloc(sz + 1); } + rv->text.sz = sz; } void tvec_allocbytes(union tvec_regval *rv, size_t sz) @@ -2397,15 +3032,73 @@ void tvec_allocbytes(union tvec_regval *rv, size_t sz) /*----- Buffer type -------------------------------------------------------*/ +/* Buffers are initialized and released as binary strings. */ + +/* --- @eq_buffer@ --- * + * + * Arguments: @const union tvec_regval *rv0, *rv1@ = register values + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Nonzero if the values are equal, zero if unequal + * + * Use: Compare register values for equality. + * + * Buffer values are equal if and only if their sizes are equal; + * their contents are %%\emph{not}%% compared. + */ + static int eq_buffer(const union tvec_regval *rv0, const union tvec_regval *rv1, const struct tvec_regdef *rd) { return (rv0->bytes.sz == rv1->bytes.sz); } +/* --- @tobuf_buffer@ --- * + * + * Arguments: @buf *b@ = buffer + * @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Serialize a register value to a buffer. + * + * Buffer values are serialized as just their lengths, as + * unsigned integers. + */ + static int tobuf_buffer(buf *b, const union tvec_regval *rv, const struct tvec_regdef *rd) { return (unsigned_to_buf(b, rv->bytes.sz)); } +/* --- @allocate_buffer@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @size_t sz@ = size to allocate + * + * Returns: --- + * + * Use: Allocate @sz@ bytes to the buffer and fill the space with a + * distinctive pattern. + */ + +static void allocate_buffer(union tvec_regval *rv, size_t sz) + { tvec_allocbytes(rv, sz); memset(rv->bytes.p, '?', sz); } + +/* --- @frombuf_buffer@ --- * + * + * Arguments: @buf *b@ = buffer + * @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * + * Returns: Zero on success, %$-1$% on failure. + * + * Use: Deserialize a register value from a buffer. + * + * Buffer values are serialized as just their lengths, as + * unsigned integers. The buffer is allocated on + * deserialization and filled with a distinctive pattern. + */ + static int frombuf_buffer(buf *b, union tvec_regval *rv, const struct tvec_regdef *rd) { @@ -2413,10 +3106,29 @@ static int frombuf_buffer(buf *b, union tvec_regval *rv, if (unsigned_from_buf(b, &u)) return (-1); if (u > (size_t)-1) return (-1); - tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u); + allocate_buffer(rv, u); return (0); } +/* --- @parse_buffer@ --- * + * + * Arguments: @union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @struct tvec_state *tv@ = test-vector state + * + * Returns: Zero on success, %$-1$% on error. + * + * Use: Parse a register value from an input file. + * + * The input format for a buffer value consists of an unsigned + * integer followed by an optional unit specifier consisting of + * an SI unit prefix and (optionally) the letter `B'. Unit + * prefixes denote %%\emph{binary}%% multipliers, not decimal. + * + * The buffer is allocated and filled with a distinctive + * pattern. + */ + static const char units[] = "kMGTPEZY"; static int parse_buffer(union tvec_regval *rv, @@ -2453,7 +3165,7 @@ static int parse_buffer(union tvec_regval *rv, if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; } if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; } - tvec_allocbytes(rv, u); memset(rv->bytes.p, '?', u); + allocate_buffer(rv, u); rc = 0; end: DDESTROY(&d); return (rc); @@ -2469,6 +3181,22 @@ rangerr: #undef f_range } +/* --- @dump_buffer@ --- * + * + * Arguments: @const union tvec_regval *rv@ = register value + * @const struct tvec_regdef *rd@ = register definition + * @unsigned style@ = output style (@TVSF_...@) + * @const struct gprintf_ops *gops@, @void *gp@ = format output + * + * Returns: --- + * + * Use: Dump a register value to the format output. + * + * Buffer values are dumped as their size with an appropriate + * unit specifier. A unit prefix is only used if the size is an + * exact multiple of the relevant power of two. + */ + static void dump_buffer(const union tvec_regval *rv, const struct tvec_regdef *rd, unsigned style, @@ -2485,6 +3213,7 @@ static void dump_buffer(const union tvec_regval *rv, } } +/* Buffer type definition. */ const struct tvec_regty tvty_buffer = { init_bytes, release_bytes, eq_buffer, tobuf_buffer, frombuf_buffer, diff --git a/test/tvec.h b/test/tvec.h index 38c462b..5d37c3f 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -193,7 +193,7 @@ union tvec_regval { void *p; /* pointer */ double f; /* floating point */ struct { unsigned char *p; size_t sz; } bytes; /* binary string of bytes */ - struct { char *p; size_t sz; } str; /* text string */ + struct { char *p; size_t sz; } text; /* text string */ #ifdef TVEC_REGSLOTS TVEC_REGSLOTS #endif @@ -351,8 +351,9 @@ typedef void tvec_envsetupfn(struct tvec_state */*tv*/, typedef int tvec_envsetfn(struct tvec_state */*tv*/, const char */*var*/, void */*ctx*/); /* Called when the parser finds a %|@var|%' setting to parse and store the - * value. If @setup@ failed, this is still called (so as to skip the - * value), but @ctx@ is null. + * value. Return %$+1$% on success, %$0$% if the variable name was not + * recognized, or %$-1$% on any other error (which should have been + * reported via @tvec_error@). */ typedef void tvec_envbeforefn(struct tvec_state */*tv*/, void */*ctx*/); @@ -1219,6 +1220,19 @@ typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/, int */*outfd_out*/, int */*errfd_out*/, struct tvec_state */*tv*/, const struct tvec_remoteenv */*env*/); + /* A connection function. On entry, @tv@ holds the test-vector state, and + * @env@ is the test group's remote environment structure, which will + * typically really be some subclass of @struct tvec_remoteenv@ containing + * additional parameters for establishing the child process. + * + * On successful completion, the function stores input and output + * descriptors (which need not be distinct) in @*infd_out@ and + * @*outfd_out@, and returns zero; if it creates a child process, it should + * additionally store the child's process-id in @*kid_out@ and store in + * @*errfd_out@ a descriptor from which the child's error output can be + * read. On error, the function should report an appropriate message via + * @tvec_error@ and return %$-1$%. + */ struct tvec_remoteenv_slots { tvec_connectfn *connect; /* connection function */ @@ -1257,6 +1271,30 @@ union tvec_remoteenv_subclass_kludge { struct tvec_remoteexec exec; }; +/* Exit status. + * + * We don't use the conventional encoding returned by the @wait@(2) family of + * system calls because it's too hard for our flags type to decode. Instead, + * we use our own encoding. + * + * The exit code or signal number ends up in the `value' field in the low 12 + * bits; bit 12 is set if the value field holds a signal, and it if holds an + * exit code. Bits 13--15 hold a code which describes the status of a child + * process or connection. + */ +#define TVXF_VALMASK 0x0fffu /* value (exit code or signal) */ +#define TVXF_SIG 0x1000u /* value is signal, not exit code */ +#define TVXF_CAUSEMASK 0xe000u /* mask for cause bits */ +#define TVXST_RUN 0x0000u /* still running */ +#define TVXST_EXIT 0x2000u /* child exited */ +#define TVXST_KILL 0x4000u /* child killed by signal */ +#define TVXST_CONT 0x6000u /* child continued (?) */ +#define TVXST_STOP 0x8000u /* child stopped (?) */ +#define TVXST_DISCONN 0xa000u /* disconnected */ +#define TVXST_UNK 0xc000u /* unknown */ +#define TVXST_ERR 0xe000u /* local error prevented diagnosis */ + +/* Remote environment. */ extern tvec_envsetupfn tvec_remotesetup; extern tvec_envsetfn tvec_remoteset; extern tvec_envrunfn tvec_remoterun; @@ -1271,9 +1309,54 @@ extern tvec_envteardownfn tvec_remoteteardown; tvec_remoteafter, \ tvec_remoteteardown } +/* --- @tvec_setprogress@, @tvec_setprogress_v@ --- * + * + * Arguments: @const char *status@ = progress status token format + * @va_list ap@ = argument tail + * + * Returns: --- + * + * Use: Reports the progress of a test execution to the client. + * + * The framework makes use of tokens beginning with %|%|%: + * + * * %|%IDLE|%: during the top-level server code; + * + * * %|%SETUP|%: during the enclosing environment's @before@ + * function; + * + * * %|%RUN|%: during the environment's @run@ function, or the + * test function; and + * + * * %|%DONE|%: during the enclosing environment's @after@ + * function. + * + * The intent is that a test can use the progress token to check + * that a function which is expected to crash does so at the + * correct point, so it's expected that more complex test + * functions and/or environments will set their own progress + * tokens to reflect what's going on. + */ + extern PRINTF_LIKE(1, 2) int tvec_setprogress(const char */*status*/, ...); extern int tvec_setprogress_v(const char */*status*/, va_list */*ap*/); +/* --- @tvec_remoteserver@ --- * + * + * Arguments: @int infd@, @int outfd@ = input and output file descriptors + * @const struct tvec_config *config@ = test configuration + * + * Returns: Suggested exit code. + * + * Use: Run a test server, reading packets from @infd@ and writing + * responses and notifications to @outfd@, and invoking tests as + * described by @config@. + * + * This function is not particularly general purpose. It + * expects to `take over' the process, and makes use of private + * global variables. + */ + extern int tvec_remoteserver(int /*infd*/, int /*outfd*/, const struct tvec_config */*config*/); @@ -1289,15 +1372,15 @@ extern tvec_connectfn tvec_fork, tvec_exec; struct tvec_timeoutenv { struct tvec_env _env; - unsigned timer; - double t; - const struct tvec_env *env; + unsigned timer; /* the timer (@ITIMER_...@) */ + double t; /* time to wait (in seconds) */ + const struct tvec_env *env; /* subsidiary environment */ }; struct tvec_timeoutctx { - const struct tvec_timeoutenv *te; - unsigned timer; - double t; + const struct tvec_timeoutenv *te; /* saved environment description */ + unsigned timer; /* timer code (as overridden) */ + double t; /* time to wait (as overridden) */ unsigned f; /* flags */ #define TVTF_SETTMO 1u /* set `@timeout' */ #define TVTF_SETMASK (TVTF_SETTMO) /* mask of @TVTF_SET...@ */ @@ -1742,10 +1825,11 @@ extern int tvec_claimeq_uint(struct tvec_state */*tv*/, /*----- 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. +/* Floating-point values are either NaN (%|#nan|%, if supported by the + * platform); positive or negative infinity (%|#inf|%, %|+#inf|%, or + * %|#+inf|% (preferring the last), and %|-#inf|% or %|#-inf|% (preferring + * the latter), 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. @@ -1863,7 +1947,7 @@ extern const struct tvec_floatinfo tvflt_finite, tvflt_nonneg; * On input, an enumerated value may be given by name or as a literal value. * For enumerations based on numeric types, the literal values can be written * in the same syntax as the underlying values. For enumerations based on - * pointers, the only permitted literal is `#nil', which denotes a null + * pointers, the only permitted literal is %|#nil|%, which denotes a null * pointer. On output, names are preferred (with the underlying value given * in a comment). */ @@ -1964,18 +2048,19 @@ TVEC_MISCSLOTS(DECLCLAIM) * fields; more precisely, each name is associated with a value and a * covering bitmask. * - * The input syntax is a sequence of items separated by `|' signs. Each item - * may be the symbolic name of a field value, or a literal unsigned integer. - * The masks associated with the given symbolic names must be disjoint. The - * resulting numerical value is simply the bitwise OR of the given values. + * The input syntax is a sequence of items separated by `%|||%' signs. Each + * item may be the symbolic name of a field value, or a literal unsigned + * integer. The masks associated with the given symbolic names must be + * disjoint. The resulting numerical value is simply the bitwise OR of the + * given values. * * On output, the table of symbolic names and their associated values and * masks is repeatedly scanned, in order, to find disjoint matches -- i.e., * entries whose value matches the target value in the bit positions * indicated by the mask, and whose mask doesn't overlap with any previously - * found matches; the names are then output, separated by `|'. Any remaining - * nonzero bits not covered by any of the matching masks are output as a - * single literal integer, in hex. + * found matches; the names are then output, separated by `%|||%'. Any + * remaining nonzero bits not covered by any of the matching masks are output + * as a single literal integer, in hex. */ extern const struct tvec_regty tvty_flags; @@ -2038,8 +2123,9 @@ extern int tvec_claimeq_flags(struct tvec_state */*tv*/, /* A character value holds a character, as read by @fgetc@. The special * @EOF@ value can also be represented. * - * On input, a character value can be given by name, with a leading `%|#|%'; - * or a character or `%|\|%'-escape sequence, optionally in single quotes. + * On input, a character value can be given by symbolic name, with a leading + * `%|#|%'; or a character or `%|\|%'-escape sequence, optionally in single + * quotes. * * The following escape sequences and character names are recognized. * @@ -2156,6 +2242,9 @@ extern int tvec_claimeq_char(struct tvec_state */*tv*/, * * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%', which includes * %$n$% copies of the (compound) string. * + * The only difference between text and binary strings is that the initial + * coding scheme is %|bare|% for text strings and %|hex|% for binary strings. + * * Either kind of string can contain internal nul characters. A trailing nul * is appended -- beyond the stated input length -- to input strings as a * convenience to test functions. Test functions may include such a nul @@ -2165,9 +2254,9 @@ extern int tvec_claimeq_char(struct tvec_state */*tv*/, * string (in bytes) will be checked against the permitted range. */ -extern const struct tvec_regty tvty_string, tvty_bytes; +extern const struct tvec_regty tvty_text, tvty_bytes; -/* --- @tvec_claimeq_string@, @TVEC_CLAIMEQ_STRING@ --- * +/* --- @tvec_claimeq_text@, @TVEC_CLAIMEQ_TEXT@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *p0@, @size_t sz0@ = first string with length @@ -2185,22 +2274,22 @@ extern const struct tvec_regty tvty_string, tvty_bytes; * mismatched values are dumped: @p0@ is printed as the output * value and @p1@ is printed as the input reference. * - * The @TVEC_CLAIM_STRING@ macro is similar, only it (a) + * The @TVEC_CLAIM_TEXT@ macro is similar, only it (a) * identifies the file and line number of the call site * automatically, and (b) implicitly quotes the source text of * the @ch0@ and @ch1@ arguments in the failure message. */ -extern int tvec_claimeq_string(struct tvec_state */*tv*/, - const char */*p0*/, size_t /*sz0*/, - const char */*p1*/, size_t /*sz1*/, - const char */*file*/, unsigned /*lno*/, - const char */*expr*/); -#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \ - (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ - #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) +extern int tvec_claimeq_text(struct tvec_state */*tv*/, + const char */*p0*/, size_t /*sz0*/, + const char */*p1*/, size_t /*sz1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_TEXT(tv, p0, sz0, p1, sz1) \ + (tvec_claimeq_text(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ + #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) -/* --- @tvec_claimeq_strz@, @TVEC_CLAIMEQ_STRZ@ --- * +/* --- @tvec_claimeq_textz@, @TVEC_CLAIMEQ_TEXTZ@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *p0, *p1@ = two strings to compare @@ -2213,16 +2302,15 @@ extern int tvec_claimeq_string(struct tvec_state */*tv*/, * Use: Check that strings at @p0@ and @p1@ are equal, as for * @tvec_claimeq_string@, except that the strings are assumed * null-terminated, so their lengths don't need to be supplied - * explicitly. The macro is similarly like - * @TVEC_CLAIMEQ_STRING@. + * explicitly. The macro is similarly like @TVEC_CLAIMEQ_TEXT@. */ -extern int tvec_claimeq_strz(struct tvec_state */*tv*/, - const char */*p0*/, const char */*p1*/, - const char */*file*/, unsigned /*lno*/, - const char */*expr*/); -#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \ - (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1)) +extern int tvec_claimeq_textz(struct tvec_state */*tv*/, + const char */*p0*/, const char */*p1*/, + const char */*file*/, unsigned /*lno*/, + const char */*expr*/); +#define TVEC_CLAIMEQ_TEXTZ(tv, p0, p1) \ + (tvec_claimeq_textz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1)) /* --- @tvec_claimeq_bytes@, @TVEC_CLAIMEQ_BYTES@ --- * * @@ -2257,7 +2345,7 @@ extern int tvec_claimeq_bytes(struct tvec_state */*tv*/, (tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \ #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]")) -/* --- @tvec_allocstring@, @tvec_allocbytes@ --- * +/* --- @tvec_alloctext@, @tvec_allocbytes@ --- * * * Arguments: @union tvec_regval *rv@ = register value * @size_t sz@ = required size @@ -2273,12 +2361,12 @@ extern int tvec_claimeq_bytes(struct tvec_state */*tv*/, * the old buffer contents are simply discarded if reallocation * is necessary. Instead, use a @dbuf@ or @dstr@. * - * The @tvec_allocstring@ function sneakily allocates an extra + * The @tvec_alloctext@ function sneakily allocates an extra * byte for a terminating zero. The @tvec_allocbytes@ function * doesn't do this. */ -extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/); +extern void tvec_alloctext(union tvec_regval */*rv*/, size_t /*sz*/); extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); /*----- Buffer type -------------------------------------------------------*/ @@ -2291,6 +2379,9 @@ extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/); * with a unit `kB', `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or * without the `B') denoting a power of 1024. Units are used on output only * when the size would be expressed exactly. + * + * No @claimeq@ functions or macros are provided for buffers because they + * don't seem very useful. */ extern const struct tvec_regty tvty_buffer; diff --git a/utils/Makefile.am b/utils/Makefile.am index ffbe920..240a046 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -52,7 +52,7 @@ check_PROGRAMS += t/bits.t t_bits_t_SOURCES = t/bits-test.c t_bits_t_CPPFLAGS = $(TEST_CPPFLAGS) t_bits_t_LDFLAGS = -static -EXTRA_DIST += t/bits-testgen.py +EXTRA_DIST += t/bits-testgen ## Control flow. pkginclude_HEADERS += control.h diff --git a/utils/t/bits-testgen.py b/utils/t/bits-testgen similarity index 100% rename from utils/t/bits-testgen.py rename to utils/t/bits-testgen diff --git a/utils/t/control-test.c b/utils/t/control-test.c index b2fd2c7..44c4d2f 100644 --- a/utils/t/control-test.c +++ b/utils/t/control-test.c @@ -183,7 +183,7 @@ int main(int argc, char *argv[]) FOR_FIZZBUZZ(fb, 19, 32) TEST if (TVEC_CLAIM(&tvstate, ref[i])) - { TVEC_CLAIMEQ_STRZ(&tvstate, fb, ref[i]); i++; } + { TVEC_CLAIMEQ_TEXTZ(&tvstate, fb, ref[i]); i++; } TVEC_CLAIM(&tvstate, !ref[i]); } diff --git a/utils/t/versioncmp-test.c b/utils/t/versioncmp-test.c index 396325a..fb462d0 100644 --- a/utils/t/versioncmp-test.c +++ b/utils/t/versioncmp-test.c @@ -35,7 +35,7 @@ enum { static void test_versioncmp(const struct tvec_reg *in, struct tvec_reg *out, void *ctx) - { out[RRC].v.i = versioncmp(in[RV0].v.str.p, in[RV1].v.str.p); } + { out[RRC].v.i = versioncmp(in[RV0].v.text.p, in[RV1].v.text.p); } static void swap_test(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { @@ -50,8 +50,8 @@ static void swap_test(struct tvec_state *tv, tvec_testfn *fn, void *ctx) static const struct tvec_env swap_testenv = { 0, 0, 0, 0, swap_test, 0, 0 }; static const struct tvec_regdef versioncmp_regs[] = { - { "v0", RV0, &tvty_string, 0 }, - { "v1", RV1, &tvty_string, 0 }, + { "v0", RV0, &tvty_text, 0 }, + { "v1", RV1, &tvty_text, 0 }, { "rc", RRC, &tvty_ienum, 0, { &tvenum_cmp } }, TVEC_ENDREGS }; diff --git a/utils/tests.at b/utils/tests.at index 8d1b0f0..32b8111 100644 --- a/utils/tests.at +++ b/utils/tests.at @@ -30,7 +30,7 @@ ## bits AT_SETUP([utilities: bits]) AT_KEYWORDS([utils bits]) -$PYTHON SRCDIR/t/bits-testgen.py 0xaca98e08 0x0b6e95fb - >bits.tests +$PYTHON SRCDIR/t/bits-testgen 0xaca98e08 0x0b6e95fb - >bits.tests AT_CHECK([BUILDDIR/t/bits.t -fh bits.tests], [0], [ignore]) AT_CLEANUP -- 2.11.0