From c91413e6acbc8d157ff52ceb8cd78cee97403584 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Wed, 21 Feb 2024 02:39:21 +0000 Subject: [PATCH] @@@ remote works? --- hash/t/hash-test.c | 10 +- struct/buf.c | 6 +- struct/buf.h | 4 +- test/Makefile.am | 4 +- test/bench.h | 10 +- test/t/tvec-test.c | 32 +- test/tests.at | 39 +- test/tvec-bench.c | 68 ++- test/tvec-core.c | 87 ++-- test/tvec-output.c | 52 ++- test/tvec-remote.c | 1251 ++++++++++++++++++++++++++++++++++++++++++++++------ test/tvec.h | 787 +++++++++++++++++---------------- utils/bits.h | 8 +- utils/compiler.h | 2 +- utils/control.h | 2 +- utils/macros.h | 6 +- 16 files changed, 1728 insertions(+), 640 deletions(-) diff --git a/hash/t/hash-test.c b/hash/t/hash-test.c index 1718a98..1bb21e9 100644 --- a/hash/t/hash-test.c +++ b/hash/t/hash-test.c @@ -81,9 +81,9 @@ static void test_unihash(const struct tvec_reg *in, struct tvec_reg *out, static void bench_unihash(const struct tvec_reg *in, struct tvec_reg *out, void *ctx) { unihash_hash(ctx, 0, in[RM].v.bytes.p, in[RM].v.bytes.sz); } -static int setup_unihash(struct tvec_state *tv, - const struct tvec_env *env, void *pctx, void *ctx) - { unihash_setkey(ctx, 0); return (0); } +static void setup_unihash(struct tvec_state *tv, + const struct tvec_env *env, void *pctx, void *ctx) + { unihash_setkey(ctx, 0); } static const struct tvec_env unihash_benchenv = { sizeof(unihash_info), setup_unihash, 0, 0 }; @@ -123,9 +123,9 @@ static const struct tvec_regdef bench_regs[] = { TVEC_ENDREGS }; -static const struct tvec_bench crc32_bench = +static const struct tvec_benchenv crc32_bench = { TVEC_BENCHINIT, 1, -1, RM, 0 }; -static const struct tvec_bench unihash_bench = +static const struct tvec_benchenv unihash_bench = { TVEC_BENCHINIT, 1, -1, RM, &unihash_benchenv }; static const struct tvec_test tests[] = { diff --git a/struct/buf.c b/struct/buf.c index 00163bf..5d1fc27 100644 --- a/struct/buf.c +++ b/struct/buf.c @@ -53,7 +53,7 @@ void buf_init(buf *b, void *p, size_t sz) b->f = 0; } -/* --- @dbuf_init@ --- * +/* --- @dbuf_create@ --- * * * Arguments: @dbuf *db@ = pointer to a dynamic buffer block * @@ -63,7 +63,7 @@ void buf_init(buf *b, void *p, size_t sz) * and ready for writing. */ -void dbuf_init(dbuf *db) +void dbuf_create(dbuf *db) { db->_b.base = db->_b.p = db->_b.limit = 0; db->_b.f = BF_ALLOC | BF_WRITE; db->a = &arena_stdlib; db->sz = 0; @@ -96,7 +96,7 @@ void dbuf_reset(dbuf *db) void dbuf_destroy(dbuf *db) { if (db->_b.base) x_free(db->a, db->_b.base); - dbuf_init(db); + dbuf_create(db); } /* --- @buf_break@ --- * diff --git a/struct/buf.h b/struct/buf.h index 389d038..6193cd3 100644 --- a/struct/buf.h +++ b/struct/buf.h @@ -137,7 +137,7 @@ extern const struct gprintf_ops buf_printops; extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/); -/* --- @dbuf_init@ --- * +/* --- @dbuf_create@ --- * * * Arguments: @dbuf *db@ = pointer to a dynamic buffer block * @@ -147,7 +147,7 @@ extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/); * and ready for writing. */ -extern void dbuf_init(dbuf */*db*/); +extern void dbuf_create(dbuf */*db*/); /* --- @dbuf_reset@ --- * * diff --git a/test/Makefile.am b/test/Makefile.am index 2f7a041..7990ab7 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -43,13 +43,15 @@ LIBMANS += testrig.3 ## New `tvec' testing framework. pkginclude_HEADERS += tvec.h -libtest_la_SOURCES += tvec-bench.c libtest_la_SOURCES += tvec-core.c libtest_la_SOURCES += tvec-output.c libtest_la_SOURCES += tvec-types.c libtest_la_SOURCES += tvec-main.c #LIBMANS += tvec.3 +libtest_la_SOURCES += tvec-bench.c +libtest_la_SOURCES += tvec-remote.c + check_PROGRAMS += t/tvec.t t_tvec_t_SOURCES = t/tvec-test.c t_tvec_t_CPPFLAGS = $(TEST_CPPFLAGS) diff --git a/test/bench.h b/test/bench.h index e7736c4..c0627e2 100644 --- a/test/bench.h +++ b/test/bench.h @@ -45,12 +45,12 @@ struct bench_time { #define BTF_TIMEOK 1u /* @s@ ad @ns@ slots are value */ #define BTF_CYOK 2u /* @cy@ slot is valid */ #define BTF_ANY (BTF_TIMEOK | BTF_CYOK) /* some part is useful */ - kludge64 s; uint32 ns; /* real time in seconds and nanos */ - kludge64 cy; /* count of CPU cycles */ + kludge64 s; uint32 ns; /* real time, seconds and nanos */ + kludge64 cy; /* count of CPU cycles */ }; struct bench_timing { - unsigned f; /* flags (as in @struct bench_time@) */ + unsigned f; /* flags (@BTF_...@) */ double n, t, cy; /* count, time, and cycles */ }; @@ -67,12 +67,12 @@ struct bench_timerops { struct bench_state { struct bench_timer *tm; /* a timer */ double target_s; /* target time to run benchmarks */ - unsigned f; /* flags (@BTF_...@) for calibrations */ + unsigned f; /* calibration flags (@BTF_...@) */ struct { double m, c; } clk, cy; /* calculated overheads */ }; typedef void bench_fn(unsigned long /*n*/, void */*ctx*/); -/* Run the benchmark @n@ times, given a context pointer @ctx@. */ + /* Run the benchmark @n@ times, given a context pointer @ctx@. */ /*----- Functions provided ------------------------------------------------*/ diff --git a/test/t/tvec-test.c b/test/t/tvec-test.c index 6dd0ef0..a9ef269 100644 --- a/test/t/tvec-test.c +++ b/test/t/tvec-test.c @@ -164,14 +164,13 @@ struct test_context { #define SF_SHOW 1u }; -static int common_setup(struct tvec_state *tv, +static void common_setup(struct tvec_state *tv, const struct tvec_env *env, void *pctx, void *ctx) { struct test_context *tctx = ctx; tctx->tv = tv; tctx->f = 0; - return (0); } static int common_set(struct tvec_state *tv, const char *name, @@ -320,16 +319,15 @@ static void test_single_deserialize TYPEREGS(SERREG) #undef SERREG -static int before_single_serialize(struct tvec_state *tv, void *ctx) +static void before_single_serialize(struct tvec_state *tv, void *ctx) { if (!(tv->in[RRC].f&TVRF_LIVE)) { tv->in[RRC].v.i = 0; tv->in[RRC].f |= TVRF_LIVE; tv->out[RRC].f |= TVRF_LIVE; } - return (0); } -static int before_single_deserialize(struct tvec_state *tv, void *ctx) +static void before_single_deserialize(struct tvec_state *tv, void *ctx) { if (!(tv->in[RRC].f&TVRF_LIVE)) { tv->in[RRC].v.i = 0; tv->in[RRC].f |= TVRF_LIVE; @@ -339,7 +337,6 @@ static int before_single_deserialize(struct tvec_state *tv, void *ctx) tv->in[RLEFT].v.u = 0; tv->in[RLEFT].f |= TVRF_LIVE; tv->out[RLEFT].f |= TVRF_LIVE; } - return (0); } static const struct tvec_env single_serialize_testenv = { @@ -428,13 +425,12 @@ static DSGINIT(const) struct tvec_regdef multi_serialize_regs[] = { TVEC_ENDREGS }; -static int before_multi_serialize(struct tvec_state *tv, void *ctx) +static void before_multi_serialize(struct tvec_state *tv, void *ctx) { if (!(tv->in[RRC].f&TVRF_LIVE)) { tv->in[RRC].v.i = 0; tv->in[RRC].f |= TVRF_LIVE; tv->out[RRC].f |= TVRF_LIVE; } - return (0); } static const struct tvec_env multi_serialize_testenv = { @@ -444,6 +440,24 @@ static const struct tvec_env multi_serialize_testenv = { 0 }; +/*----- Crash test --------------------------------------------------------*/ + +static void test_crash(const struct tvec_reg *in, struct tvec_reg *out, void *ctx) +{ + out[RVOUT].v.u = in[RV].v.u; + if (in[RSAB].v.i) abort(); +} + +static const struct tvec_remotefork crash_testenv = + { TVEC_REMOTEFORK(0, 0) }; + +static const struct tvec_regdef crash_regs[] = { + { "crash", RSAB, &tvty_ienum, 0, { &tvenum_bool } }, + { "x", RV, &tvty_uint, 0, { &tvrange_uint } }, + { "z", RVOUT, &tvty_uint, 0, { &tvrange_uint } }, + TVEC_ENDREGS +}; + /*----- Front end ---------------------------------------------------------*/ static const struct tvec_test tests[] = { @@ -459,6 +473,8 @@ static const struct tvec_test tests[] = { TYPEREGS(DEFSINGLE) #undef DEFSINGLE + { "crash", crash_regs, &crash_testenv._env, test_crash } , + TVEC_ENDTESTS }; diff --git a/test/tests.at b/test/tests.at index 74f40fa..fa4b1d8 100644 --- a/test/tests.at +++ b/test/tests.at @@ -55,7 +55,7 @@ $1 = $2 @show = t ]) check_template([BUILDDIR/t/tvec.t -fh tv], [0], -[left_pad([matched $1], [17]) = $3 +[left_pad([matched $1], [21]) = $3 copy-$1: ok PASSED all 1 test in 1 group ])]) @@ -389,6 +389,43 @@ test_parserr([buffer], [16 EB], [3], [buffer length `16 EB' out of range]) AT_CLEANUP ###-------------------------------------------------------------------------- +AT_SETUP([tvec remote]) + +AT_DATA([tv], +[;;; -*-conf-*- + +@<:@crash@:>@ + +crash = t +x = 1 +z = 0 +@progress = %RUN +@exit = killed | SIGABRT + +crash = nil +x = 0 +z = 0 +@reconnect = skip + +crash = nil +x = 1 +z = 1 + +crash = nil +x = 1 +z = 1 +@progress = %DONE +@exit = running +]) +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 +]) + +AT_CLEANUP + +###-------------------------------------------------------------------------- AT_SETUP([tvec serialize]) AT_DATA([tv], diff --git a/test/tvec-bench.c b/test/tvec-bench.c index b56fcbe..8c8cfdf 100644 --- a/test/tvec-bench.c +++ b/test/tvec-bench.c @@ -90,12 +90,12 @@ static void normalize(double *x_inout, const char **unit_out, double scale) * @void *pctx@ = parent context (ignored) * @void *ctx@ = context pointer to initialize * - * Returns: Zero on success, @-1@ on failure. + * Returns: --- * * Use: Initialize a benchmarking environment context. * * The environment description must really be a @struct - * tvec_bench@. If the @bst@ slot is null, then a temporary + * tvec_benchenv@. If the @bst@ slot is null, then a temporary * benchmark state is allocated for the current test group and * released at the end. Otherwise, it must be the address of a * pointer to a benchmark state: if the pointer is null, then a @@ -104,38 +104,37 @@ static void normalize(double *x_inout, const char **unit_out, double scale) * existing valid benchmark state. */ -int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, - void *pctx, void *ctx) +void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, + void *pctx, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_bench *b = (const struct tvec_bench *)env; - const struct tvec_env *subenv = b->env; + const struct tvec_benchenv *be = (const struct tvec_benchenv *)env; + const struct tvec_env *subenv = be->env; struct bench_timer *bt; /* Basic initialization. */ - bc->b = b; bc->bst = 0; bc->subctx = 0; + bc->be = be; bc->bst = 0; bc->subctx = 0; /* Set up the benchmarking state if it hasn't been done before. */ - if (!b->bst || !*b->bst) { + if (!be->bst || !*be->bst) { bt = bench_createtimer(); if (!bt) goto fail_timer; bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt); - if (b->bst) *b->bst = bc->bst; - } else if (!(*b->bst)->tm) + if (be->bst) *be->bst = bc->bst; + } else if (!(*be->bst)->tm) goto fail_timer; else - bc->bst = *b->bst; + bc->bst = *be->bst; /* Set the default target time. */ bc->dflt_target = bc->bst->target_s; /* Initialize the subordinate environment. */ if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz); - if (subenv && subenv->setup && subenv->setup(tv, subenv, bc, bc->subctx)) - { xfree(bc->subctx); bc->subctx = 0; return (-1); } + if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx); /* All done. */ end: - return (0); + return; fail_timer: tvec_skipgroup(tv, "failed to create timer"); goto end; } @@ -163,8 +162,8 @@ int tvec_benchset(struct tvec_state *tv, const char *var, const struct tvec_env *env, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_bench *b = (const struct tvec_bench *)env; - const struct tvec_env *subenv = b->env; + const struct tvec_benchenv *be = (const struct tvec_benchenv *)env; + const struct tvec_env *subenv = be->env; union tvec_regval rv; static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 }; static const struct tvec_regdef rd = @@ -185,21 +184,20 @@ int tvec_benchset(struct tvec_state *tv, const char *var, * Arguments: @struct tvec_state *tv@ = test vector state * @void *ctx@ = context pointer * - * Returns: Zero on success, @-1@ on failure. + * Returns: --- * * Use: Invoke the subordinate environment's @before@ function to * prepare for the benchmark. */ -int tvec_benchbefore(struct tvec_state *tv, void *ctx) +void tvec_benchbefore(struct tvec_state *tv, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_bench *b = bc->b; - const struct tvec_env *subenv = b->env; + const struct tvec_benchenv *be = bc->be; + const struct tvec_env *subenv = be->env; /* Just call the subsidiary environment. */ - if (subenv && subenv->before) return (subenv->before(tv, bc->subctx)); - else return (0); + if (subenv && subenv->before) subenv->before(tv, bc->subctx); } /* --- @tvec_benchafter@ --- * @@ -216,8 +214,8 @@ int tvec_benchbefore(struct tvec_state *tv, void *ctx) void tvec_benchafter(struct tvec_state *tv, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_bench *b = bc->b; - const struct tvec_env *subenv = b->env; + 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; @@ -239,11 +237,11 @@ void tvec_benchafter(struct tvec_state *tv, void *ctx) void tvec_benchteardown(struct tvec_state *tv, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_bench *b; + const struct tvec_benchenv *be; const struct tvec_env *subenv; if (!bc) return; - b = bc->b; subenv = b->env; + be = bc->be; subenv = be->env; /* Tear down any subsidiary environment. */ if (subenv && subenv->teardown && bc->subctx) @@ -251,7 +249,7 @@ void tvec_benchteardown(struct tvec_state *tv, void *ctx) /* If the benchmark state was temporary, then dispose of it. */ if (bc->bst) { - if (b->bst) bc->bst->target_s = bc->dflt_target; + if (be->bst) bc->bst->target_s = bc->dflt_target; else { bench_destroy(bc->bst); xfree(bc->bst); } } } @@ -314,15 +312,13 @@ static void benchloop_inner_indirect(unsigned long n, void *ctx) * Returns: --- * * Use: Measures and reports the performance of a test function. - * - * */ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { struct tvec_benchctx *bc = ctx; - const struct tvec_bench *b = bc->b; - const struct tvec_env *subenv = b->env; + const struct tvec_benchenv *be = bc->be; + const struct tvec_env *subenv = be->env; const struct tvec_regdef *rd; struct tvec_output *o = tv->output; struct bench_timing tm; @@ -339,8 +335,8 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) r.in = tv->in; r.out = tv->out; r.fn = fn; /* Decide on the run function to select. */ - if (b->riter >= 0) { - r.n = &TVEC_REG(tv, in, b->riter)->v.u; + if (be->riter >= 0) { + r.n = &TVEC_REG(tv, in, be->riter)->v.u; loopfn = subenv && subenv->run ? benchloop_inner_indirect : benchloop_inner_direct; } else { @@ -350,9 +346,9 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) } /* Decide on the kind of unit and the base count. */ - base = b->niter; - if (b->rbuf < 0) unit = TVBU_OP; - else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; } + base = be->niter; + if (be->rbuf < 0) unit = TVBU_OP; + else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; } /* Construct a description of the test using the identifier registers. */ for (rd = tv->test->regs; rd->name; rd++) diff --git a/test/tvec-core.c b/test/tvec-core.c index 92f31d3..51834f4 100644 --- a/test/tvec-core.c +++ b/test/tvec-core.c @@ -37,49 +37,60 @@ /*----- Output ------------------------------------------------------------*/ -/* --- @tvec_error@, @tvec_error_v@ --- * +/* --- @tvec_report@, @tvec_report_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *msg@, @va_list ap@ = error message * - * Returns: @-1@. + * Returns: --- * - * Use: Report an error. Errors are distinct from test failures, - * and indicate that a problem was encountered which compromised - * the activity of testing. + * Use: Report an message with a given severity. Messages with level + * @TVLEV_ERR@ or higher force a nonzero exit code. */ -int tvec_error(struct tvec_state *tv, const char *msg, ...) +void tvec_report(struct tvec_state *tv, unsigned level, const char *msg, ...) { va_list ap; - va_start(ap, msg); tvec_error_v(tv, msg, &ap); va_end(ap); - return (-1); + va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap); } -int tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap) + +void tvec_report_v(struct tvec_state *tv, unsigned level, + const char *msg, va_list *ap) { - tv->output->ops->error(tv->output, msg, ap); - tv->f |= TVSF_ERROR; return (-1); + tv->output->ops->report(tv->output, level, msg, ap); + if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR; } -/* --- @tvec_notice@, @tvec_notice_v@ --- * +/* --- @tvec_error@, @tvec_notice@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *msg@, @va_list ap@ = message + * @const char *msg@, @va_list ap@ = error message * - * Returns: --- + * Returns: The @tvec_error@ function returns @-1@ as a trivial + * convenience; @tvec_notice@ does not return a value. * - * Use: Output a notice: essentially, some important information - * which doesn't fit into any of the existing categories. + * Use: Report an error or a notice. Errors are distinct from test + * failures, and indicate that a problem was encountered which + * compromised the activity of testing. Notices are important + * information which doesn't fit into any other obvious + * category. */ +int tvec_error(struct tvec_state *tv, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); tvec_report_v(tv, TVLEV_ERR, msg, &ap); va_end(ap); + return (-1); +} + void tvec_notice(struct tvec_state *tv, const char *msg, ...) { va_list ap; - va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap); + + va_start(ap, msg); tvec_report_v(tv, TVLEV_NOTE, msg, &ap); va_end(ap); } -void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap) - { tv->output->ops->notice(tv->output, msg, ap); } /*----- Test processing ---------------------------------------------------*/ @@ -91,8 +102,10 @@ void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...) } void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap) { - tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++; - tv->output->ops->skipgroup(tv->output, excuse, ap); + if (!(tv->f&TVSF_SKIP)) { + tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++; + tv->output->ops->skipgroup(tv->output, excuse, ap); + } } static void set_outcome(struct tvec_state *tv, unsigned out) @@ -302,7 +315,7 @@ void tvec_resetoutputs(struct tvec_state *tv) } } -static void init_registers(struct tvec_state *tv) +void tvec_initregs(struct tvec_state *tv) { const struct tvec_regdef *rd; struct tvec_reg *r; @@ -315,7 +328,7 @@ static void init_registers(struct tvec_state *tv) } } -static void release_registers(struct tvec_state *tv) +void tvec_releaseregs(struct tvec_state *tv) { const struct tvec_regdef *rd; struct tvec_reg *r; @@ -392,18 +405,21 @@ 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)) - tvec_skip(tv, "test setup failed"); + if (env && env->before) env->before(tv, g->ctx); + if (!(tv->f&TVSF_ACTIVE)) + /* setup forced a skip */; + else if (env && env->run) + env->run(tv, t->fn, g->ctx); else { - if (env && env->run) env->run(tv, t->fn, g->ctx); - else { t->fn(tv->in, tv->out, g->ctx); tvec_check(tv, 0); } + t->fn(tv->in, tv->out, g->ctx); + tvec_check(tv, 0); } if (env && env->after) env->after(tv, g->ctx); tvec_endtest(tv); } end: - tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv); + tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv); } static void begin_test_group(struct tvec_state *tv, struct groupstate *g) @@ -414,13 +430,10 @@ static void begin_test_group(struct tvec_state *tv, struct groupstate *g) tv->output->ops->bgroup(tv->output); tv->f &= ~TVSF_SKIP; - init_registers(tv); + tvec_initregs(tv); for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0; if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz); - if (env && env->setup && env->setup(tv, env, 0, g->ctx)) { - tvec_skipgroup(tv, "setup failed"); - xfree(g->ctx); g->ctx = 0; - } + if (env && env->setup) env->setup(tv, env, 0, g->ctx); } static void report_group(struct tvec_state *tv) @@ -448,7 +461,7 @@ static void end_test_group(struct tvec_state *tv, struct groupstate *g) if (tv->f&TVSF_OPEN) check(tv, g); if (!(tv->f&TVSF_SKIP)) report_group(tv); env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx); - release_registers(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0; + tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0; } int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp) @@ -611,7 +624,7 @@ int tvec_serialize(const struct tvec_reg *rv, buf *b, for (rd = regs, i = 0; rd->name; rd++, i++) { if (rd->i >= nr) continue; r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue; - bitmap = BBASE(b) + bitoff; bitmap[rd->i/8] |= 1 << rd->i%8; + bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8; if (rd->ty->tobuf(b, &r->v, rd)) return (-1); } return (0); @@ -632,7 +645,7 @@ int tvec_deserialize(struct tvec_reg *rv, buf *b, bitmap = buf_get(b, bitsz); if (!bitmap) return (-1); for (rd = regs, i = 0; rd->name; rd++, i++) { if (rd->i >= nr) continue; - if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue; + if (!(bitmap[i/8]&(1 << i%8))) continue; r = TVEC_GREG(rv, rd->i, regsz); if (rd->ty->frombuf(b, &r->v, rd)) return (-1); r->f |= TVRF_LIVE; @@ -750,7 +763,7 @@ int tvec_claimeq(struct tvec_state *tv, adhoc_claim_setup(tv, &ck, regs, file, lno); ok = ty->eq(&tv->in[0].v, &tv->out[0].v, ®s[0]); if (!ok) - { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } + { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); } adhoc_claim_teardown(tv, &ck); return (ok); } diff --git a/test/tvec-output.c b/test/tvec-output.c index 9ca4f29..e92ea69 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -110,7 +110,7 @@ static int getenv_boolean(const char *var, int dflt) static int register_maxnamelen(const struct tvec_state *tv) { const struct tvec_regdef *rd; - int maxlen = 6, n; + int maxlen = 10, n; for (rd = tv->test->regs; rd->name; rd++) { n = strlen(rd->name); if (n > maxlen) maxlen = n; } @@ -216,7 +216,6 @@ static void init_fmt(struct format *fmt, FILE *fp, const char *prefix) fmt->prefix = prefix; l = fmt->pfxlim = prefix + strlen(prefix); SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q; - DPUTM(&fmt->w, q, l - q); } } @@ -343,11 +342,11 @@ static int format_string(struct format *fmt, const char *p, size_t sz) * need to write that. Otherwise, there's only blank stuff, which we * accumulate in the buffer. * - * If we're at the start of a line here, then + * If we're at the start of a line here, then */ if (r > p) { - if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; } + if (fmt->f&FMTF_NEWL) { PUT_PREFIX; fmt->f &= ~FMTF_NEWL; } PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w); } SAVE_TAIL; @@ -357,8 +356,11 @@ static int format_string(struct format *fmt, const char *p, size_t sz) /* There is at least one more segment, so we know that there'll be a line * to output. */ - if (fmt->f&FMTF_NEWL) PUT_PFXINB; - if (r > p) { PUT_SAVED; PUT_NONBLANK; } + if (r > p) { + if (fmt->f&FMTF_NEWL) PUT_PREFIX; + PUT_SAVED; PUT_NONBLANK; + } else if (fmt->f&FMTF_NEWL) + PUT_PFXINB; PUT_NEWLINE; DRESET(&fmt->w); SPLIT_SEGMENT; @@ -367,7 +369,9 @@ static int format_string(struct format *fmt, const char *p, size_t sz) * newline, so we write the initial prefix and drop the trailing blanks. */ while (q) { - PUT_PREFIX; PUT_NONBLANK; PUT_NEWLINE; + if (r > p) { PUT_PREFIX; PUT_NONBLANK; } + else PUT_PFXINB; + PUT_NEWLINE; SPLIT_SEGMENT; } @@ -660,6 +664,7 @@ static void human_skipgroup(struct tvec_output *o, if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) { h->f &= ~HOF_PROGRESS; + putc(' ', h->fmt.fp); setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); } else { fprintf(h->fmt.fp, "%s: ", h->tv->test->name); @@ -780,7 +785,8 @@ static void human_ebench(struct tvec_output *o, fputc('\n', h->fmt.fp); } -static void human_report(struct tvec_output *o, const char *msg, va_list *ap) +static void human_report(struct tvec_output *o, unsigned level, + const char *msg, va_list *ap) { struct human_output *h = (struct human_output *)o; struct tvec_state *tv = h->tv; @@ -814,7 +820,7 @@ static const struct tvec_outops human_ops = { human_bgroup, human_skipgroup, human_egroup, human_btest, human_skip, human_fail, human_dumpreg, human_etest, human_bbench, human_ebench, - human_report, human_report, + human_report, human_destroy }; @@ -1007,31 +1013,23 @@ static void tap_ebench(struct tvec_output *o, format_char(&t->fmt, '\n'); } -static void tap_report(struct tap_output *t, - const struct gprintf_ops *gops, void *go, +static void tap_report(struct tvec_output *o, unsigned level, const char *msg, va_list *ap) { + struct tap_output *t = (struct tap_output *)o; struct tvec_state *tv = t->tv; + const struct gprintf_ops *gops; void *go; + if (level >= TVLEV_ERR) { + fputs("Bail out! ", t->fmt.fp); + gops = &file_printops; go = t->fmt.fp; + } else { + gops = &tap_printops; go = t; + } if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno); gprintf(gops, go, msg, ap); gops->putch(go, '\n'); } -static void tap_error(struct tvec_output *o, const char *msg, va_list *ap) -{ - struct tap_output *t = (struct tap_output *)o; - - fputs("Bail out! ", t->fmt.fp); - tap_report(t, &file_printops, t->fmt.fp, msg, ap); -} - -static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) -{ - struct tap_output *t = (struct tap_output *)o; - - tap_report(t, &tap_printops, t, msg, ap); -} - static void tap_destroy(struct tvec_output *o) { struct tap_output *t = (struct tap_output *)o; @@ -1046,7 +1044,7 @@ static const struct tvec_outops tap_ops = { tap_bgroup, tap_skipgroup, tap_egroup, tap_btest, tap_skip, tap_fail, tap_dumpreg, tap_etest, tap_bbench, tap_ebench, - tap_error, tap_notice, + tap_report, tap_destroy }; diff --git a/test/tvec-remote.c b/test/tvec-remote.c index a00f046..3dbb5d3 100644 --- a/test/tvec-remote.c +++ b/test/tvec-remote.c @@ -28,71 +28,108 @@ /*----- Header files ------------------------------------------------------*/ #include +#include #include #include #include #include #include -#include +#include #include #include #include "alloc.h" +#include "bench.h" #include "buf.h" +#include "compiler.h" +#include "fdflags.h" +#include "lbuf.h" +#include "mdup.h" +#include "quis.h" #include "tvec.h" -/*----- Data structures ---------------------------------------------------*/ +#if GCC_VERSION_P(7, 1) +# pragma GCC diagnostic ignored "-Wdangling-else" +#elif GCC_VERSION_P(4, 2) +# pragma GCC diagnostic ignored "-Wparentheses" +#endif -struct tvec_remote { - int infd, outfd; - dbuf bin, bout; - unsigned f; -#define TVRF_BROKEN 1u -}; +#if CLANG_VERSION_P(3, 1) +# pragma clang diagnostic ignored "-Wdangling-else" +#endif -struct tvec_remotectx { - struct tvec_remote r; - pid_t kid; -}; +/*----- Basic I/O ---------------------------------------------------------*/ -struct remote_output { - struct tvec_output _o; - struct tvec_remote r; -}; +static void init_comms(struct tvec_remotecomms *rc) +{ + dbuf_create(&rc->bin); dbuf_create(&rc->bout); + rc->infd = rc->outfd = -1; rc->f = 0; +} -/*----- Basic I/O ---------------------------------------------------------*/ +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; } +} + +static void release_comms(struct tvec_remotecomms *rc) + { close_comms(rc); dbuf_destroy(&rc->bin); dbuf_destroy(&rc->bout); } + +static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd) +{ + rc->infd = infd; rc->outfd = outfd; rc->f &= ~0xffu; + dbuf_reset(&rc->bin); dbuf_reset(&rc->bout); +} static int PRINTF_LIKE(3, 4) - ioerr(struct tvec_state *tv, struct tvec_remote *r, const char *msg, ...) + ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc, + const char *msg, ...) { va_list ap; va_start(ap, msg); - r->f |= TVRF_BROKEN; - tvec_write(tv, msg, &ap); + close_comms(rc); rc->f |= TVRF_BROKEN; + tvec_report_v(tv, TVLEV_ERR, msg, &ap); va_end(ap); return (-1); } -static int send_all(struct tvec_state *tv, struct tvec_remote *r, +static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc, const unsigned char *p, size_t sz) { + void (*opipe)(int) = SIG_ERR; ssize_t n; + int ret; + opipe = signal(SIGPIPE, SIG_IGN); + if (opipe == SIG_ERR) { + ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno)); + goto end; + } while (sz) { - n = write(r->outfd, p, sz); + n = write(rc->outfd, p, sz); if (n > 0) { p += n; sz -= n; } - else - return (ioerr(tv, r, "failed to send: %s", - n ? strerror(errno) : "empty write")); + else { + ret = ioerr(tv, rc, "failed to send: %s", + n ? strerror(errno) : "empty write"); + goto end; + } } - return (0); + ret = 0; +end: + if (opipe != SIG_ERR) signal(SIGPIPE, opipe); + return (ret); } #define RCVF_ALLOWEOF 1u -static int recv_all(struct tvec_state *tv, struct tvec_remote *r, +enum { + RECV_FAIL = -1, + RECV_OK = 0, + RECV_EOF = 1 +}; +static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc, unsigned char *p, size_t sz, unsigned f) { ssize_t n; @@ -100,196 +137,1146 @@ static int recv_all(struct tvec_state *tv, struct tvec_remote *r, #define f_any 1u while (sz) { - n = read(r->infd, p, sz); + n = read(rc->infd, p, sz); if (n > 0) { p += n; sz -= n; ff |= f_any; } else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any)) - return (1); + return (RECV_EOF); else - return (ioerr(tv, r, "failed to receive: %s", + return (ioerr(tv, rc, "failed to receive: %s", n ? strerror(errno) : "unexpected end-of-file")); } - return (0); + return (RECV_OK); #undef f_any } -int tvec_send(struct tvec_state *tv, struct tvec_remote *r) +static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc) { kludge64 k; unsigned char lenbuf[8]; - const char *p; size_t sz; + const unsigned char *p; size_t sz; - if (r->f&TVRF_BROKEN) return (-1); - if (BBAD(&r->bout.b)) - return (ioerr(tv, r, "failed to build output packet buffer"); + if (rc->f&TVRF_BROKEN) return (-1); + if (BBAD(&rc->bout._b)) + return (ioerr(tv, rc, "failed to build output packet buffer")); - p = BBASE(r->bout.b); sz = BLEN(&r->bout.b); + p = BBASE(&rc->bout._b); sz = BLEN(&rc->bout._b); ASSIGN64(k, sz); STORE64_L_(lenbuf, k); - if (send_all(tv, r, lenbuf, sizeof(lenbuf))) return (-1); - if (send_all(tv, r, p, sz)) return (-1); + if (send_all(tv, rc, lenbuf, sizeof(lenbuf))) return (-1); + if (send_all(tv, rc, p, sz)) return (-1); return (0); } -int tvec_recv(struct tvec_state *tv, struct tvec_remote *r, buf *b_out) +static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc, + unsigned f, buf *b_out) { kludge64 k, szmax; unsigned char lenbuf[8]; unsigned char *p; size_t sz; - int rc; + int ret; - if (r->f&TVRF_BROKEN) return (-1); - ASSIGN64(k, (size_t)-1); - rc = recv_all(tv, r, lenbuf, sizeof(lenbuf), RCVF_ALLOWEOF); - if (rc) return (rc); + if (rc->f&TVRF_BROKEN) return (RECV_FAIL); + ASSIGN64(szmax, (size_t)-1); + ret = recv_all(tv, rc, lenbuf, sizeof(lenbuf), f); + if (ret) return (ret); LOAD64_L_(k, lenbuf); if (CMP64(k, >, szmax)) - return (ioerr(tv, r, "packet size 0x%08lx%08lx out of range", + return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range", (unsigned long)HI64(k), (unsigned long)LO64(k))); - sz = GET64(size_t, k); buf_reset(&r->bin); p = buf_get(&r->bin.b, sz); - if (!p) return (ioerr(tv, r, "failed to allocate receive buffer")); - if (recv_all(tv, r, p, sz, 0)) return (-1); - buf_init(b_out, p, sz); return (0); + sz = GET64(size_t, k); dbuf_reset(&rc->bin); p = buf_get(&rc->bin._b, sz); + if (!p) return (ioerr(tv, rc, "failed to allocate receive buffer")); + if (recv_all(tv, rc, p, sz, 0)) return (RECV_FAIL); + buf_init(b_out, p, sz); return (RECV_OK); } -/*----- Data formatting primitives ----------------------------------------*/ +#define SENDPK(tv, rc, pk) \ + if ((rc)->f&TVRF_BROKEN) MC_GOELSE(body); else \ + MC_BEFORE(setpk, \ + { dbuf_reset(&(rc)->bout); \ + buf_putu16l(&(rc)->bout._b, (pk)); }) \ + MC_ALLOWELSE(body) \ + MC_AFTER(send, \ + { if (remote_send(tv, rc)) MC_GOELSE(body); }) \ +static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc) + { return (ioerr(tv, rc, "received malformed packet")); } /*----- Packet types ------------------------------------------------------*/ -#define TVPK_ERROR 0x0001 /* msg: string */ -#define TVPK_NOTICE 0x0002 /* msg: string */ -#define TVPK_STATUS 0x0003 /* st: char */ +#define TVPF_ACK 0x0001u -#define TVPK_BGROUP 0x0101 /* name: string */ -#define TVPK_TEST 0x0102 /* in: regs */ -#define TVPK_EGROUP 0x0103 /* --- */ +#define TVPK_VER 0x0000u /* --> min, max: u16 */ + /* <-- ver: u16 */ -#define TVPK_SKIPGRP 0x0201 /* excuse: string */ -#define TVPK_SKIP 0x0202 /* excuse: string */ -#define TVPK_FAIL 0x0203 /* detail: string */ -#define TVPK_MISMATCH 0x0204 /* in, out: regs */ -#define TVPK_BBENCH 0x0205 /* in: regs */ -#define TVPK_EBENCH 0x0206 /* flags: u16; n: u64; t, cy: float */ +#define TVPK_REPORT 0x0100u /* <-- level: u16; msg: string */ +#define TVPK_PROGRESS 0x0102u /* <-- st: str16 */ -/*----- The output driver -------------------------------------------------*/ +#define TVPK_BGROUP 0x0200u /* --> name: str16 + * <-- --- */ +#define TVPK_TEST 0x0202u /* --> in: regs + * <-- --- */ +#define TVPK_EGROUP 0x0204u /* --> --- */ -#define SENDPK(ro, pk) \ - if ((ro)->r.f&TVRF_BROKEN) /* do nothing */; else \ - MC_BEFORE(setpk, \ - { buf_reset(&(ro)->r.bout); \ - buf_putu16l(&(ro)->r.bout.b, (pk)); }) \ - MC_AFTER(send, \ - { tvec_send(&ro->_o.tv, &ro->r); }) +#define TVPK_SKIPGRP 0x0300u /* <-- excuse: str16 */ +#define TVPK_SKIP 0x0302u /* <-- excuse: str16 */ +#define TVPK_FAIL 0x0304u /* <-- flag: u8, detail: str16 */ +#define TVPK_DUMPREG 0x0306u /* <-- ri: u16; disp: u16; + * flag: u8, rv: value */ +#define TVPK_BBENCH 0x0308u /* <-- ident: str32; unit: u16 */ +#define TVPK_EBENCH 0x030au /* <-- ident: str32; unit: u16; + * flags: u16; n, t, cy: f64 */ -static int sendstr(struct tvec_output *o, unsigned pk, - const char *p, va_list *ap) -{ - struct remote_output *ro = (struct remote_output *)o; +/*----- Server ------------------------------------------------------------*/ + +static const struct tvec_outops remote_ops; + +static struct tvec_state srvtv; +static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; +static struct tvec_output srvout = { &remote_ops }; - SENDPK(ro, pk) buf_vputstrf16l(&ro->r.bout.b, msg, ap); - return (ro->r.f&TVRF_BROKEN ? -1 : 0); +int tvec_setprogress(const char *status) +{ + SENDPK(&srvtv, &srvrc, TVPK_PROGRESS) + buf_putstr16l(&srvrc.bout._b, status); + else return (-1); + return (0); } -static void report(struct tvec_output *o, unsigned pk, const char *what, - const char *msg, va_list *ap) +int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config) { - if (sendstr(o, pk, msg, ap)) { - fprintf(stderr, "%s %s: ", QUIS, what); - vfprintf(stderr, msg, *ap); - fputc('\n', stderr); + uint16 pk, u, v; + unsigned i; + buf b; + const struct tvec_test *t; + void *p; size_t sz; + const struct tvec_env *env = 0; + unsigned f = 0; +#define f_regslive 1u + void *ctx = 0; + int rc; + + setup_comms(&srvrc, infd, outfd); + tvec_begin(&srvtv, config, &srvout); + + if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; } + if (buf_getu16l(&b, &pk)) goto bad; + if (pk != TVPK_VER) { + rc = ioerr(&srvtv, &srvrc, + "unexpected packet type 0x%04x instead of client version", + pk); + goto end; } -} + if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad; + SENDPK(&srvtv, &srvrc, TVPK_VER | TVPF_ACK) buf_putu16l(&srvrc.bout._b, 0); + else { rc = -1; goto end; } -static void remote_error(struct tvec_output *o, const char *msg, va_list *ap) - { report(o, TVPK_ERROR, "ERROR", msg, ap); } + tvec_setprogress("%IDLE"); -static void remote_notice(struct tvec_output *o, - const char *msg, va_list *ap) - { report(o, TVPK_NOTICE, "notice", msg, ap); } + for (;;) { + rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b); + if (rc == RECV_EOF) break; + else if (rc == RECV_FAIL) goto end; + if (buf_getu16l(&b, &pk)) goto bad; -static void remote_setstatus(struct tvec_ouptut *o, int st) -{ - struct remote_output *ro = (struct remote_output *)o; - SENDPK(ro, TVPK_STATUS) buf_putbyte(&ro->r.bout.b, st); + switch (pk) { + + case TVPK_BGROUP: + p = buf_getmem16l(&b, &sz); if (!p) goto bad; + if (BLEFT(&b)) goto bad; + for (t = srvtv.tests; t->name; t++) + if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz)) + goto found_group; + rc = ioerr(&srvtv, &srvrc, "unknown test group `%.*s'", + (int)sz, (char *)p); + goto end; + + found_group: + srvtv.test = t; env = t->env; + if (env && env->setup == tvec_remotesetup) + env = ((struct tvec_remoteenv *)env)->r.env; + if (!env || !env->ctxsz) ctx = 0; + else ctx = xmalloc(env->ctxsz); + if (env && env->setup) env->setup(&srvtv, env, 0, ctx); + + SENDPK(&srvtv, &srvrc, TVPK_BGROUP | TVPF_ACK); + else { rc = -1; goto end; } + + for (;;) { + if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; } + if (buf_getu16l(&b, &pk)) goto bad; + switch (pk) { + + case TVPK_EGROUP: + if (BLEFT(&b)) goto bad; + goto endgroup; + + case TVPK_TEST: + tvec_initregs(&srvtv); f |= f_regslive; + if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs, + srvtv.nreg, srvtv.regsz)) + goto bad; + if (BLEFT(&b)) goto bad; + + if (!(srvtv.f&TVSF_SKIP)) { + srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK; + tvec_setprogress("%SETUP"); + if (env && env->before) env->before(&srvtv, ctx); + if (!(srvtv.f&TVSF_ACTIVE)) + /* setup forced a skip */; + else { + for (i = 0; i < srvtv.nrout; i++) + if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE) + TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE; + tvec_setprogress("%RUN"); + if (env && env->run) + env->run(&srvtv, t->fn, ctx); + else { + t->fn(srvtv.in, srvtv.out, ctx); + tvec_check(&srvtv, 0); + } + } + tvec_setprogress("%DONE"); + if (env && env->after) env->after(&srvtv, ctx); + tvec_endtest(&srvtv); + } + tvec_releaseregs(&srvtv); f &= ~f_regslive; + SENDPK(&srvtv, &srvrc, TVPK_TEST | TVPF_ACK); + else { rc = -1; goto end; } + tvec_setprogress("%IDLE"); + break; + + default: + rc = ioerr(&srvtv, &srvrc, + "unexpected packet type 0x%04x", pk); + goto end; + + } + } + + endgroup: + if (env && env->teardown) env->teardown(&srvtv, ctx); + xfree(ctx); t = 0; env = 0; ctx = 0; + break; + + default: + goto bad; + } + } + rc = 0; + +end: + if (env && env->teardown) env->teardown(&srvtv, ctx); + xfree(ctx); + if (f&f_regslive) tvec_releaseregs(&srvtv); + release_comms(&srvrc); + return (rc ? 2 : 0); + +bad: + rc = malformed(&srvtv, &srvrc); goto end; + +#undef f_regslive } +/*----- Server output driver ----------------------------------------------*/ + static void remote_skipgroup(struct tvec_output *o, const char *excuse, va_list *ap) - { sendstr(o, TVPK_SKIPGRP, excuse, ap); } +{ + SENDPK(&srvtv, &srvrc, TVPK_SKIPGRP) + buf_vputstrf16l(&srvrc.bout._b, excuse, ap); +} static void remote_skip(struct tvec_output *o, const char *excuse, va_list *ap) - { sendstr(o, TVPK_SKIP, excuse, ap); } +{ + SENDPK(&srvtv, &srvrc, TVPK_SKIP) + buf_vputstrf16l(&srvrc.bout._b, excuse, ap); +} static void remote_fail(struct tvec_output *o, const char *detail, va_list *ap) - { sendstr(o, TVPK_FAIL, detail, ap); } +{ + SENDPK(&srvtv, &srvrc, TVPK_FAIL) + if (!detail) + buf_putbyte(&srvrc.bout._b, 0); + else { + buf_putbyte(&srvrc.bout._b, 1); + buf_vputstrf16l(&srvrc.bout._b, detail, ap); + } +} -static void remote_mismatch(struct tvec_output *o) +static void remote_dumpreg(struct tvec_output *o, + unsigned disp, const union tvec_regval *rv, + const struct tvec_regdef *rd) { - struct remote_output *ro = (struct remote_output *)o; - struct tvec_state *rv = ro->_o.tv; + const struct tvec_regdef *reg; + unsigned r; - SENDPK(ro, TVPK_MISMATCH) { - tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz); - tvec_serialize(tv, &ro->r.bout.b, tv->out, tv->nrout, tv->regsz); + /* Find the register definition. */ + for (reg = srvtv.test->regs, r = 0; reg->name; reg++, r++) + if (reg == rd) goto found; + assert(!"unexpected register definition"); + +found: + SENDPK(&srvtv, &srvrc, TVPK_DUMPREG) { + buf_putu16l(&srvrc.bout._b, r); + buf_putu16l(&srvrc.bout._b, disp); + if (!rv) + buf_putbyte(&srvrc.bout._b, 0); + else { + buf_putbyte(&srvrc.bout._b, 1); + rd->ty->tobuf(&srvrc.bout._b, rv, rd); + } } } -static void remote_bbench(struct tvec_output *o) +static void remote_bbench(struct tvec_output *o, + const char *ident, unsigned unit) { - struct remote_output *ro = (struct remote_output *)o; - struct tvec_state *rv = ro->_o.tv; - - SENDPK(ro, TVPK_BBENCH) - tvec_serialize(tv, &ro->r.bout.b, tv->in, tv->nreg, tv->regsz); + SENDPK(&srvtv, &srvrc, TVPK_BBENCH) { + buf_putstr32l(&srvrc.bout._b, ident); + buf_putu16l(&srvrc.bout._b, unit); + } } static void remote_ebench(struct tvec_output *o, + const char *ident, unsigned unit, const struct bench_timing *t) { - struct remote_output *ro = (struct remote_output *)o; - kludge64 k; + SENDPK(&srvtv, &srvrc, TVPK_EBENCH) { + buf_putstr32l(&srvrc.bout._b, ident); + buf_putu16l(&srvrc.bout._b, unit); + if (!t || !(t->f&BTF_ANY)) + buf_putu16l(&srvrc.bout._b, 0); + else { + buf_putu16l(&srvrc.bout._b, t->f); + buf_putf64l(&srvrc.bout._b, t->n); + if (t->f&BTF_TIMEOK) buf_putf64l(&srvrc.bout._b, t->t); + if (t->f&BTF_CYOK) buf_putf64l(&srvrc.bout._b, t->cy); + } + } +} + +static void remote_report(struct tvec_output *o, unsigned level, + const char *msg, va_list *ap) +{ + const char *what; - SENDPK(ro, TVPK_EBENCH) { - buf_putu16l(&ro->r.bout.b, t->f); - ASSIGN64(k, t->n); buf_putk64l(&ro->r.bout.b, k); - if (t->f&BTF_TIMEOK) buf_putf64l(&ro->r.bout.b, t->t); - if (t->f&BTF_CYOK) buf_putf64l(&ro->r.bout.b, t->cy); + SENDPK(&srvtv, &srvrc, TVPK_REPORT) { + buf_putu16l(&srvrc.bout._b, level); + buf_vputstrf16l(&srvrc.bout._b, msg, ap); + } else { + switch (level) { + case TVLEV_NOTE: what = "notice"; break; + case TVLEV_ERR: what = "ERROR"; break; + default: what = "(?level)"; break; + } + fprintf(stderr, "%s %s: ", QUIS, what); + vfprintf(stderr, msg, *ap); + fputc('\n', stderr); } } -static void remote_write(struct tvec_output *o, const char *p, size_t sz) - { assert(!"remote_write"); } -static void remote_bsession(struct tvec_output *o) - { assert(!"remote_bsession"); } +static void remote_bsession(struct tvec_output *o, struct tvec_state *tv) + { ; } static int remote_esession(struct tvec_output *o) - { assert(!"remote_esession"); return (-1); } + { return (srvtv.f&TVSF_ERROR ? 2 : 0); } +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 void remote_egroup(struct tvec_output *o, unsigned outcome) - { assert(!"remote_egroup"); } -static void remote_etest(struct tvec_output *o, unsigned outcome) - { assert(!"remote_etest"); } - -static void remote_destroy(struct tvec_output *o) -{ -} static const struct tvec_outops remote_ops = { - remote_error, remote_notice, remote_setstatus, remote_write, remote_bsession, remote_esession, - remote_bgroup, remote_egroup, remote_skip, - remote_btest, remote_skip, remote_fail, remote_mismatch, remote_etest, + remote_bgroup, remote_skipgroup, remote_egroup, + remote_btest, remote_skip, remote_fail, remote_dumpreg, remote_etest, remote_bbench, remote_ebench, + remote_report, 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 + +static const struct tvec_flag exit_flags[] = { + /* + ;;; 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 + ;;; get Emacs to regenerate it. + + (let ((signals '(HUP INT QUIT ILL TRAP ABRT IOT EMT FPE KILL BUS SEGV SYS + PIPE ALRM TERM URG STOP TSTP CONT CHLD CLD TTIN TTOU + POLL IO TIN XCPU XFSZ VTALRM PROF WINCH USR1 USR2 + STKFLT INFO PWR THR LWP LIBRT LOST))) + (save-excursion + (goto-char (point-min)) + (search-forward (concat "***" "BEGIN siglist" "***")) + (beginning-of-line 2) + (delete-region (point) + (progn + (search-forward "***END***") + (beginning-of-line) + (point))) + (dolist (sig signals) + (insert (format "#ifdef SIG%s\n { \"SIG%s\", TVXF_VALMASK | TVXF_SIG, SIG%s | TVXF_SIG },\n#endif\n" + sig sig sig))))) + */ + + /***BEGIN siglist***/ +#ifdef SIGHUP + { "SIGHUP", TVXF_VALMASK | TVXF_SIG, SIGHUP | TVXF_SIG }, +#endif +#ifdef SIGINT + { "SIGINT", TVXF_VALMASK | TVXF_SIG, SIGINT | TVXF_SIG }, +#endif +#ifdef SIGQUIT + { "SIGQUIT", TVXF_VALMASK | TVXF_SIG, SIGQUIT | TVXF_SIG }, +#endif +#ifdef SIGILL + { "SIGILL", TVXF_VALMASK | TVXF_SIG, SIGILL | TVXF_SIG }, +#endif +#ifdef SIGTRAP + { "SIGTRAP", TVXF_VALMASK | TVXF_SIG, SIGTRAP | TVXF_SIG }, +#endif +#ifdef SIGABRT + { "SIGABRT", TVXF_VALMASK | TVXF_SIG, SIGABRT | TVXF_SIG }, +#endif +#ifdef SIGIOT + { "SIGIOT", TVXF_VALMASK | TVXF_SIG, SIGIOT | TVXF_SIG }, +#endif +#ifdef SIGEMT + { "SIGEMT", TVXF_VALMASK | TVXF_SIG, SIGEMT | TVXF_SIG }, +#endif +#ifdef SIGFPE + { "SIGFPE", TVXF_VALMASK | TVXF_SIG, SIGFPE | TVXF_SIG }, +#endif +#ifdef SIGKILL + { "SIGKILL", TVXF_VALMASK | TVXF_SIG, SIGKILL | TVXF_SIG }, +#endif +#ifdef SIGBUS + { "SIGBUS", TVXF_VALMASK | TVXF_SIG, SIGBUS | TVXF_SIG }, +#endif +#ifdef SIGSEGV + { "SIGSEGV", TVXF_VALMASK | TVXF_SIG, SIGSEGV | TVXF_SIG }, +#endif +#ifdef SIGSYS + { "SIGSYS", TVXF_VALMASK | TVXF_SIG, SIGSYS | TVXF_SIG }, +#endif +#ifdef SIGPIPE + { "SIGPIPE", TVXF_VALMASK | TVXF_SIG, SIGPIPE | TVXF_SIG }, +#endif +#ifdef SIGALRM + { "SIGALRM", TVXF_VALMASK | TVXF_SIG, SIGALRM | TVXF_SIG }, +#endif +#ifdef SIGTERM + { "SIGTERM", TVXF_VALMASK | TVXF_SIG, SIGTERM | TVXF_SIG }, +#endif +#ifdef SIGURG + { "SIGURG", TVXF_VALMASK | TVXF_SIG, SIGURG | TVXF_SIG }, +#endif +#ifdef SIGSTOP + { "SIGSTOP", TVXF_VALMASK | TVXF_SIG, SIGSTOP | TVXF_SIG }, +#endif +#ifdef SIGTSTP + { "SIGTSTP", TVXF_VALMASK | TVXF_SIG, SIGTSTP | TVXF_SIG }, +#endif +#ifdef SIGCONT + { "SIGCONT", TVXF_VALMASK | TVXF_SIG, SIGCONT | TVXF_SIG }, +#endif +#ifdef SIGCHLD + { "SIGCHLD", TVXF_VALMASK | TVXF_SIG, SIGCHLD | TVXF_SIG }, +#endif +#ifdef SIGCLD + { "SIGCLD", TVXF_VALMASK | TVXF_SIG, SIGCLD | TVXF_SIG }, +#endif +#ifdef SIGTTIN + { "SIGTTIN", TVXF_VALMASK | TVXF_SIG, SIGTTIN | TVXF_SIG }, +#endif +#ifdef SIGTTOU + { "SIGTTOU", TVXF_VALMASK | TVXF_SIG, SIGTTOU | TVXF_SIG }, +#endif +#ifdef SIGPOLL + { "SIGPOLL", TVXF_VALMASK | TVXF_SIG, SIGPOLL | TVXF_SIG }, +#endif +#ifdef SIGIO + { "SIGIO", TVXF_VALMASK | TVXF_SIG, SIGIO | TVXF_SIG }, +#endif +#ifdef SIGTIN + { "SIGTIN", TVXF_VALMASK | TVXF_SIG, SIGTIN | TVXF_SIG }, +#endif +#ifdef SIGXCPU + { "SIGXCPU", TVXF_VALMASK | TVXF_SIG, SIGXCPU | TVXF_SIG }, +#endif +#ifdef SIGXFSZ + { "SIGXFSZ", TVXF_VALMASK | TVXF_SIG, SIGXFSZ | TVXF_SIG }, +#endif +#ifdef SIGVTALRM + { "SIGVTALRM", TVXF_VALMASK | TVXF_SIG, SIGVTALRM | TVXF_SIG }, +#endif +#ifdef SIGPROF + { "SIGPROF", TVXF_VALMASK | TVXF_SIG, SIGPROF | TVXF_SIG }, +#endif +#ifdef SIGWINCH + { "SIGWINCH", TVXF_VALMASK | TVXF_SIG, SIGWINCH | TVXF_SIG }, +#endif +#ifdef SIGUSR1 + { "SIGUSR1", TVXF_VALMASK | TVXF_SIG, SIGUSR1 | TVXF_SIG }, +#endif +#ifdef SIGUSR2 + { "SIGUSR2", TVXF_VALMASK | TVXF_SIG, SIGUSR2 | TVXF_SIG }, +#endif +#ifdef SIGSTKFLT + { "SIGSTKFLT", TVXF_VALMASK | TVXF_SIG, SIGSTKFLT | TVXF_SIG }, +#endif +#ifdef SIGINFO + { "SIGINFO", TVXF_VALMASK | TVXF_SIG, SIGINFO | TVXF_SIG }, +#endif +#ifdef SIGPWR + { "SIGPWR", TVXF_VALMASK | TVXF_SIG, SIGPWR | TVXF_SIG }, +#endif +#ifdef SIGTHR + { "SIGTHR", TVXF_VALMASK | TVXF_SIG, SIGTHR | TVXF_SIG }, +#endif +#ifdef SIGLWP + { "SIGLWP", TVXF_VALMASK | TVXF_SIG, SIGLWP | TVXF_SIG }, +#endif +#ifdef SIGLIBRT + { "SIGLIBRT", TVXF_VALMASK | TVXF_SIG, SIGLIBRT | TVXF_SIG }, +#endif +#ifdef SIGLOST + { "SIGLOST", TVXF_VALMASK | TVXF_SIG, SIGLOST | TVXF_SIG }, +#endif + /***END***/ + + { "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 +}; + +static const struct tvec_flaginfo exit_flaginfo = + { "exit-status", exit_flags, &tvrange_uint }; +static const struct tvec_regdef exit_regdef = + { "@exit", 0, &tvty_flags, 0, { &exit_flaginfo } }; + +static const struct tvec_regdef progress_regdef = + { "@progress", 0, &tvty_string, 0 }; + +static const struct tvec_uassoc reconn_assocs[] = { + { "on-demand", TVRCN_DEMAND }, + { "force", TVRCN_FORCE }, + { "skip", TVRCN_SKIP }, + TVEC_ENDENUM +}; + +enum { + CONN_BROKEN = -2, /* previously broken */ + CONN_FAILED = -1, /* attempt freshly failed */ + CONN_ESTABLISHED = 0, /* previously established */ + 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 } }; + +static int handle_packets(struct tvec_state *tv, struct tvec_remotectx *r, + unsigned f, uint16 end, buf *b_out) +{ + struct tvec_output *o = tv->output; + uint16 pk, u, v; + const char *p; size_t n; + dstr d = DSTR_INIT; + buf *b = b_out; + const struct tvec_regdef *rd; + struct bench_timing bt; + struct tvec_reg *reg = 0; + unsigned i; + int rc; + + for (;;) { + rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end; + if (buf_getu16l(b, &pk)) goto bad; + + switch (pk) { + + case TVPK_PROGRESS: + p = buf_getmem16l(b, &n); if (!p) goto bad; + if (BLEFT(b)) goto bad; + + DRESET(&r->progress); DPUTM(&r->progress, p, n); DPUTZ(&r->progress); + break; + + case TVPK_REPORT: + if (buf_getu16l(b, &u)) goto bad; + p = buf_getmem16l(b, &n); if (!p) goto bad; + if (BLEFT(b)) goto bad; + + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); + tvec_report(tv, u, "%s", d.buf); + break; + + case TVPK_SKIPGRP: + p = buf_getmem16l(b, &n); if (!p) goto bad; + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); + if (BLEFT(b)) goto bad; + + tvec_skipgroup(tv, "%s", d.buf); + break; + + case TVPK_SKIP: + if (!(tv->f&TVSF_ACTIVE)) { + rc = ioerr(tv, &r->rc, "test `%s' not active", tv->test->name); + goto end; + } + + p = buf_getmem16l(b, &n); if (!p) goto bad; + if (BLEFT(b)) goto bad; + + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); + tvec_skip(tv, "%s", d.buf); + break; + + case TVPK_FAIL: + if (!(tv->f&TVSF_ACTIVE) && + ((tv->f&TVSF_OUTMASK) != (TVOUT_LOSE << TVSF_OUTSHIFT))) { + rc = ioerr(tv, &r->rc, "test `%s' not active or failing", + tv->test->name); + goto end; + } + + rc = buf_getbyte(b); if (rc < 0) goto bad; + if (rc) { p = buf_getmem16l(b, &n); if (!p) goto bad; } + else p = 0; + if (BLEFT(b)) goto bad; -/*----- Main code ---------------------------------------------------------*/ + if (!p) + tvec_fail(tv, 0); + else { + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); + tvec_fail(tv, "%s", d.buf); + } + break; + case TVPK_DUMPREG: + 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; + rc = ioerr(tv, &r->rc, + "register definition %u out of range for test `%s'", + u, tv->test->name); + goto end; + found_reg: + if (v >= TVRD_LIMIT) { + rc = ioerr(tv, &r->rc, "register disposition %u out of range", v); + goto end; + } + rc = buf_getbyte(b); if (rc < 0) goto bad; + if (!rc) + tvec_dumpreg(tv, v, 0, rd); + else { + if (!reg) reg = xmalloc(tv->regsz); + rd->ty->init(®->v, rd); + rc = rd->ty->frombuf(b, ®->v, rd); + if (!rc) tvec_dumpreg(tv, v, ®->v, rd); + rd->ty->release(®->v, rd); + if (rc) goto bad; + } + if (BLEFT(b)) goto bad; + break; + + case TVPK_BBENCH: + p = buf_getmem32l(b, &n); if (!p) goto bad; + if (buf_getu16l(b, &u)) goto bad; + if (BLEFT(b)) goto bad; + if (u >= TVBU_LIMIT) { + rc = ioerr(tv, &r->rc, "unit code %u out of range", u); + goto end; + } + + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); + o->ops->bbench(o, d.buf, u); + break; + + case TVPK_EBENCH: + p = buf_getmem32l(b, &n); if (!p) goto bad; + if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad; + if (u >= TVBU_LIMIT) + { rc = ioerr(tv, &r->rc, "unit code %u out of range", u); goto end; } + if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad; + if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad; + if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad; + if (BLEFT(b)) goto bad; + + DRESET(&d); DPUTM(&d, p, n); DPUTZ(&d); + o->ops->ebench(o, d.buf, u, v&BTF_ANY ? &bt : 0); + break; + + default: + if (pk == end) { rc = 0; goto end; } + rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk); + goto end; + } + } + +end: + DDESTROY(&d); + xfree(reg); + return (rc); +bad: + rc = malformed(tv, &r->rc); goto end; +} + +static void reap_kid(struct tvec_state *tv, struct tvec_remotectx *r) +{ + pid_t kid; + int st; + + if (!r->kid) + { r->exit = TVXST_DISCONN; r->kid = -1; } + else if (r->kid > 0) { + kid = waitpid(r->kid, &st, 0); + if (kid < 0) { + tvec_notice(tv, "failed to wait for remote child: %s", + strerror(errno)); + r->exit = TVXST_ERR; + } else if (!kid) { + tvec_notice(tv, "remote child vanished without a trace"); + r->exit = TVXST_ERR; + } else if (WIFCONTINUED(st)) + r->exit = TVXST_CONT; + else if (WIFSIGNALED(st)) + r->exit = TVXST_KILL | TVXF_SIG | WTERMSIG(st); + else if (WIFSTOPPED(st)) + r->exit = TVXST_STOP | TVXF_SIG | WSTOPSIG(st); + else if (WIFEXITED(st)) + r->exit = TVXST_EXIT | WEXITSTATUS(st); + else { + tvec_notice(tv, "remote child died with unknown status 0x%04x", + (unsigned)st); + r->exit = TVXST_UNK; + } + r->kid = -1; + } +} + +static void report_errline(char *p, size_t n, void *ctx) +{ + struct tvec_remotectx *r = ctx; + struct tvec_state *tv = r->tv; + + if (p && !(r->rc.f&TVRF_MUFFLE)) + tvec_notice(tv, "child process stderr: %s", p); +} + +#define ERF_SILENT 0x0001u +#define ERF_CLOSE 0x0002u +static int drain_errfd(struct tvec_state *tv, struct tvec_remotectx *r, + unsigned f) +{ + char *p; size_t sz; + ssize_t n; + int rc; + + if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE; + else r->rc.f &= ~TVRF_MUFFLE; + if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) { + rc = ioerr(tv, &r->rc, "failed to %s error non-blocking flag", + f&ERF_CLOSE ? "clear" : "set"); + goto end; + } + + for (;;) { + sz = lbuf_free(&r->errbuf, &p); + n = read(r->errfd, p, sz); + if (!n) break; + if (n < 0) { + if (errno == EINTR) continue; + if (!(f&ERF_CLOSE) && (errno == EWOULDBLOCK || errno == EAGAIN)) + break; + rc = ioerr(tv, &r->rc, "failed to read child stderr: %s", + strerror(errno)); + goto end; + } + lbuf_flush(&r->errbuf, p, n); + } + rc = 0; +end: + if (f&ERF_CLOSE) { + lbuf_close(&r->errbuf); + close(r->errfd); + } + return (rc); +} + +#define DCF_KILL 0x0100u +static void disconnect_remote(struct tvec_state *tv, + struct tvec_remotectx *r, unsigned f) +{ + if (r->kid < 0) return; + if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM); + close_comms(&r->rc); + if (r->kid > 0) kill(r->kid, SIGTERM); + drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r); +} + +static int connect_remote(struct tvec_state *tv, struct tvec_remotectx *r) +{ + const struct tvec_remoteenv *re = r->re; + pid_t kid = 0; + buf b; + uint16 v; + int infd = -1, outfd = -1, errfd = -1, rc; + + DRESET(&r->progress); DPUTS(&r->progress, "%INIT"); + if (r->kid >= 0) { rc = 0; goto end; } + if (re->r.connect(&kid, &infd, &outfd, &errfd, tv, re)) + { rc = -1; goto end; } + 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; + + SENDPK(tv, &r->rc, TVPK_VER) { + buf_putu16l(&r->rc.bout._b, 0); + buf_putu16l(&r->rc.bout._b, 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; + if (BLEFT(&b)) { rc = malformed(tv, &r->rc); goto end; } + if (v) { + rc = ioerr(tv, &r->rc, "protocol version %u not supported", v); + goto end; + } + + SENDPK(tv, &r->rc, TVPK_BGROUP) + buf_putstr16l(&r->rc.bout._b, 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; +end: + if (rc) disconnect_remote(tv, r, DCF_KILL); + return (rc); +bad: + rc = malformed(tv, &r->rc); goto end; +} + +static int check_comms(struct tvec_state *tv, struct tvec_remotectx *r) +{ + if (r->kid < 0) + return (CONN_BROKEN); + else if (r->rc.f&TVRF_BROKEN) + { disconnect_remote(tv, r, DCF_KILL); return (CONN_FAILED); } + else + return (CONN_ESTABLISHED); +} + +static int try_reconnect(struct tvec_state *tv, struct tvec_remotectx *r) +{ + int rc; + + switch (r->rc.f&TVRF_RCNMASK) { + case TVRCN_DEMAND: + rc = check_comms(tv, r); + if (rc < CONN_ESTABLISHED) { + close_comms(&r->rc); + if (connect_remote(tv, r)) rc = CONN_FAILED; + else rc = CONN_FRESH; + } + break; + case TVRCN_FORCE: + disconnect_remote(tv, r, DCF_KILL); + if (connect_remote(tv, r)) rc = CONN_FAILED; + else rc = CONN_FRESH; + break; + case TVRCN_SKIP: + rc = check_comms(tv, r); + break; + default: + abort(); + } + return (rc); +} + +static void reset_vars(struct tvec_remotectx *r) +{ + r->exwant = TVXST_RUN; r->rc.f = (r->rc.f&~TVRF_RCNMASK) | TVRCN_DEMAND; + DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE"); +} + +void tvec_remotesetup(struct tvec_state *tv, const struct tvec_env *env, + void *pctx, void *ctx) +{ + struct tvec_remotectx *r = ctx; + const struct tvec_remoteenv *re = (const struct tvec_remoteenv *)env; + + assert(!re->r.env || tv->test->env == &re->_env); + + r->tv = tv; + init_comms(&r->rc); + r->re = re; r->kid = -1; + DCREATE(&r->prgwant); DCREATE(&r->progress); + if (connect_remote(tv, r)) + tvec_skipgroup(tv, "failed to connect to test backend"); + reset_vars(r); +} + +int tvec_remoteset(struct tvec_state *tv, const char *var, + const struct tvec_env *env, void *ctx) +{ + struct tvec_remotectx *r = ctx; + union tvec_regval rv; + int rc; + + if (STRCMP(var, ==, "@exit")) { + if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; } + if (r) r->exwant = rv.u; + rc = 1; + } else if (STRCMP(var, ==, "@progress")) { + tvty_string.init(&rv, &progress_regdef); + rc = tvty_string.parse(&rv, &progress_regdef, tv); + if (r && !rc) + { DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); } + tvty_string.release(&rv, &progress_regdef); + if (rc) { rc = -1; goto end; } + rc = 1; + } else if (STRCMP(var, ==, "@reconnect")) { + if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; } + if (r) r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK); + rc = 1; + } else + rc = 0; + +end: + return (rc); +} + +void tvec_remoteafter(struct tvec_state *tv, void *ctx) +{ + struct tvec_remotectx *r = ctx; + + reset_vars(r); +} + +void tvec_remoterun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) +{ + struct tvec_remotectx *r = ctx; + union tvec_regval rv; + unsigned f = 0; +#define f_exit 1u +#define f_progress 2u +#define f_fail 4u + buf b; + int rc; + + switch (try_reconnect(tv, r)) { + case CONN_FAILED: + tvec_skip(tv, "failed to connect to test backend"); return; + case CONN_BROKEN: + tvec_skip(tv, "no connection"); return; + } + + SENDPK(tv, &r->rc, TVPK_TEST) + tvec_serialize(tv->in, &r->rc.bout._b, + tv->test->regs, tv->nreg, tv->regsz); + else { rc = -1; goto end; } + rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b); + switch (rc) { + case RECV_FAIL: + goto end; + case RECV_EOF: + reap_kid(tv, r); + /* fall through */ + case RECV_OK: + if (r->exit != r->exwant) f |= f_exit; + if (r->progress.len != r->prgwant.len || + MEMCMP(r->progress.buf, !=, r->prgwant.buf, r->progress.len)) + f |= f_progress; + if (f && (tv->f&TVSF_ACTIVE)) + { tvec_fail(tv, 0); tvec_mismatch(tv, TVMF_IN); } + if (!(tv->f&TVSF_ACTIVE) && + (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT)) { + f |= f_fail; + + rv.u = r->exit; + tvec_dumpreg(tv, f&f_exit ? TVRD_FOUND : TVRD_MATCH, + &rv, &exit_regdef); + if (f&f_exit) { + rv.u = r->exwant; + tvec_dumpreg(tv, TVRD_EXPECT, &rv, &exit_regdef); + } + + rv.str.p = r->progress.buf; rv.str.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; + tvec_dumpreg(tv, TVRD_EXPECT, &rv, &progress_regdef); + } + } + + 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 +} + +void tvec_remoteteardown(struct tvec_state *tv, void *ctx) +{ + struct tvec_remotectx *r = ctx; + + if (r) { + disconnect_remote(tv, r, 0); release_comms(&r->rc); + DDESTROY(&r->prgwant); DDESTROY(&r->progress); + } +} + +/*----- Connectors --------------------------------------------------------*/ + +static int fork_common(pid_t *kid_out, int *infd_out, int *outfd_out, + int *errfd_out, struct tvec_state *tv) +{ + int p0[2] = { -1, -1 }, p1[2] = { -1, -1 }, pe[2] = { -1, -1 }; + pid_t kid = -1; + int rc; + + 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) || + fdflags(pe[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC)) { + tvec_error(tv, "pipe failed: %s", strerror(errno)); + rc = -1; goto end; + } + + fflush(0); + + kid = fork(); + if (kid < 0) { + tvec_error(tv, "fork failed: %s", strerror(errno)); + rc = -1; goto end; + } + + if (!kid) { + *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); + } + } else { + *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; + } + + rc = 0; +end: + if (p0[0] >= 0) close(p0[0]); + if (p0[1] >= 0) close(p0[1]); + if (p1[0] >= 0) close(p1[0]); + if (p1[1] >= 0) close(p1[1]); + if (pe[0] >= 0) close(pe[0]); + if (pe[1] >= 0) close(pe[1]); + return (rc); +} + +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) +{ + struct tvec_config config; + const struct tvec_remotefork *rf = (const struct tvec_remotefork *)env; + pid_t kid = -1; + int infd = -1, outfd = -1, errfd = -1; + int rc; + + if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; } + if (!kid) { + config.tests = rf->f.tests ? rf->f.tests : tv->tests; + config.nrout = tv->nrout; config.nreg = tv->nreg; + config.regsz = tv->regsz; + _exit(tvec_remoteserver(infd, outfd, &config)); + } + + *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd; + rc = 0; +end: + return (rc); +} + +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) +{ + const struct tvec_remoteexec *rx = (const struct tvec_remoteexec *)env; + pid_t kid = -1; + int infd = -1, outfd = -1, errfd = -1; + mdup_fd v[2]; + int rc; + + if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; } + if (!kid) { + v[0].cur = infd; v[0].want = STDIN_FILENO; + v[1].cur = outfd; v[1].want = STDOUT_FILENO; + if (mdup(v, 2)) { + fprintf(stderr, "failed to establish standard file descriptors: %s", + strerror(errno)); + exit(127); + } + execvp(rx->x.args[0], (/*uncosnt*/ char *const *)rx->x.args); + fprintf(stderr, "failed to invoke test runner: %s", strerror(errno)); + exit(127); + } + + *kid_out = kid; *infd_out = infd; *outfd_out = outfd; *errfd_out = errfd; + rc = 0; +end: + return (rc); +} /*----- That's all, folks -------------------------------------------------*/ diff --git a/test/tvec.h b/test/tvec.h index 5ea18ca..d18411c 100644 --- a/test/tvec.h +++ b/test/tvec.h @@ -32,6 +32,74 @@ extern "C" { #endif +/* Here's the overall flow for a testing session. + * + * @tvec_begin@ + * -> output @bsession@ + * @tvec_read@ + * -> output @bgroup@ + * -> env @setup@ + * one or more tests + * -> type @init@ (input and output) + * -> type @parse@ (input) + * -> output @btest@ + * -> env @before@ + * -> @tvec_skipgroup@ + * -> output @skipgroup@ + * -> env @run@ + * -> @tvec_skip@ + * -> output @skip@ + * -> test @fn@ + * -> @tvec_checkregs@ + * -> type @eq@ + * -> @tvec_fail@ + * -> output @fail@ + * -> @tvec_mismatch@ + * -> output @dumpreg@ + * -> type @dump@ + * -> env @after@ + * -> output @etest@ + * finally + * -> output @egroup@ + * -> env @teardown@ + * + * @tvec_adhoc@ + * @tvec_begingroup@ + * -> output @bgroup@ + * -> env @setup@ + * @tvec_begintest@ + * -> output @btest@ + * @tvec_skip@ + * -> output @skip@ + * @tvec_claimeq@ + * -> @tvec_fail@ + * -> output @fail@ + * -> @tvec_mismatch@ + * -> output @dumpreg@ + * -> type @dump@ + * @tvec_endtest@ + * -> output @etest@ + * or @tvec_skipgroup@ + * -> output @skipgroup@ + * @tvec_endgroup@ + * -> output @egroup@ + * + * @tvec_end@ + * -> output @esession@ + * -> output @destroy@ + * + * @tvec_benchrun@ + * -> type @dump@ (compact style) + * -> output @bbench@ + * -> subenv @run@ + * -> test @fn@ + * -> output @ebench@ + * -> @tvec_benchreport@ + * + * The output functions @error@ and @notice@ can be called at arbitrary + * times. + */ + /*----- Header files ------------------------------------------------------*/ #include @@ -55,6 +123,10 @@ # include "gprintf.h" #endif +#ifndef MLIB_LBUF_H +# include "lbuf.h" +#endif + #ifndef MLIB_MACROS_H # include "macros.h" #endif @@ -186,6 +258,172 @@ struct tvec_regdef { #define TVEC_GREG(vec, i, regsz) \ ((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz))) +/*----- Register types ----------------------------------------------------*/ + +struct tvec_state; /* forward declaration */ + +struct tvec_regty { + /* A register type. */ + + void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); + /* Initialize the value in @*rv@. This will be called before any + * other function acting on the value, including @release@. + */ + + void (*release)(union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Release any resources associated with the value in @*rv@. */ + + int (*eq)(const union tvec_regval */*rv0*/, + const union tvec_regval */*rv1*/, + const struct tvec_regdef */*rd*/); + /* Return nonzero if @*rv0@ and @*rv1@ are equal values. + * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@ + * with the input register as @rv0@ and the output as @rv1@. + */ + + int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Serialize the value @*rv@, writing the result to @b@. Return + * zero on success, or @-1@ on error. + */ + + int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/); + /* Deserialize a value from @b@, storing it in @*rv@. Return zero on + * success, or @-1@ on error. + */ + + int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, + struct tvec_state */*tv*/); + /* Parse a value from @tv->fp@, storing it in @*rv@. Return zero on + * success, or @-1@ on error, having reported one or more errors via + * @tvec_error@ or @tvec_syntax@. A successful return should leave + * the input position at the start of the next line; the caller will + * flush the remainder of the line itself. + */ + + void (*dump)(const union tvec_regval */*rv*/, + const struct tvec_regdef */*rd*/, + unsigned /*style*/, + const struct gprintf_ops */*gops*/, void */*go*/); +#define TVSF_COMPACT 1u + /* Write a human-readable representation of the value @*rv@ using + * @gprintf@ on @gops@ and @go@. The @style@ is a collection of + * flags: if @TVSF_COMPACT@ is set, then output should be minimal, + * and must fit on a single line; otherwise, output may consist of + * multiple lines and may contain redundant information if that is + * likely to be useful to a human reader. + */ +}; + +/*----- Test descriptions -------------------------------------------------*/ + +typedef void tvec_testfn(const struct tvec_reg */*in*/, + struct tvec_reg */*out*/, + void */*ctx*/); + /* A test function. It should read inputs from @in@ and write outputs to + * @out@. The @TVRF_LIVE@ is set on inputs which are actually present, and + * on outputs which are wanted to test. A test function can set additional + * `gratuitous outputs' by setting @TVRF_LIVE@ on them; clearing + * @TVRF_LIVE@ on a wanted output causes a mismatch. + * + * A test function may be called zero or more times by the environment. In + * particular, it may be called multiple times, though usually by prior + * arrangement with the environment. + * + * The @ctx@ is supplied by the environment's @run@ function (see below). + * The default environment calls the test function once, with a null + * @ctx@. There is no expectation that the environment's context has + * anything to do with the test function's context. + */ + +struct tvec_env; + +typedef void tvec_envsetupfn(struct tvec_state */*tv*/, + const struct tvec_env */*env*/, + void */*pctx*/, void */*ctx*/); + /* Initialize the context; called at the start of a test group; @pctx@ is + * null for environments called by the core, but may be non-null for + * subordinate environments. If setup fails, the function should call + * @tvec_skipgroup@ with a suitable excuse. The @set@ and @teardown@ entry + * points will still be called, but @before@, @run@, and @after@ will not. + */ + +typedef int tvec_envsetfn(struct tvec_state */*tv*/, const char */*var*/, + const struct tvec_env */*env*/, 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. + */ + +typedef void tvec_envbeforefn(struct tvec_state */*tv*/, void */*ctx*/); + /* Called prior to running a test. This is the right place to act on any + * `%|@var|%' settings. If preparation fails, the function should call + * @tvec_skip@ with a suitable excuse. This function is never called if + * the test group is skipped. + */ + +typedef void tvec_envrunfn(struct tvec_state */*tv*/, + tvec_testfn */*fn*/, void */*ctx*/); + /* Run the test. It should either call @tvec_skip@, or run @fn@ one or + * more times. In the latter case, it is responsible for checking the + * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will + * check the register values against the supplied test vector, while + * @tvec_check@ does pretty much everything necessary. This function is + * never called if the test group is skipped. + */ + +typedef void tvec_envafterfn(struct tvec_state */*tv*/, void */*ctx*/); + /* Called after running or skipping a test. Typical actions involve + * resetting whatever things were established by @set@. This function is + * never called if the test group is skipped. + */ + +typedef void tvec_envteardownfn(struct tvec_state */*tv*/, void */*ctx*/); + /* Tear down the environment: called at the end of a test group. */ + + +struct tvec_env { + /* A test environment sets things up for and arranges to run the test. + * + * The caller is responsible for allocating storage for the environment's + * context, based on the @ctxsz@ slot, and freeing it later; this space is + * passed in as the @ctx@ parameter to the remaining functions; if @ctxsz@ + * is zero then @ctx@ is null. + */ + + size_t ctxsz; /* environment context size */ + + tvec_envsetupfn *setup; /* setup for group */ + tvec_envsetfn *set; /* set variable */ + tvec_envbeforefn *before; /* prepare for test */ + tvec_envrunfn *run; /* run test function */ + tvec_envafterfn *after; /* clean up after test */ + tvec_envteardownfn *teardown; /* tear down after group */ +}; + +struct tvec_test { + /* A test description. */ + + const char *name; /* name of the test */ + const struct tvec_regdef *regs; /* descriptions of the registers */ + const struct tvec_env *env; /* environment to run test in */ + tvec_testfn *fn; /* test function */ +}; +#define TVEC_ENDTESTS { 0, 0, 0, 0 } + +enum { + /* Register output dispositions. */ + + TVRD_INPUT, /* input-only register */ + TVRD_OUTPUT, /* output-only (input is dead) */ + TVRD_MATCH, /* matching (equal) registers */ + TVRD_FOUND, /* mismatching output register */ + TVRD_EXPECT, /* mismatching input register */ + TVRD_LIMIT /* (number of dispositions) */ +}; + /*----- Test state --------------------------------------------------------*/ enum { @@ -253,7 +491,8 @@ struct tvec_output { /* Benchmarking details. */ enum { TVBU_OP, /* counting operations of some kind */ - TVBU_BYTE /* counting bytes (@rbuf >= 0@) */ + TVBU_BYTE, /* counting bytes (@rbuf >= 0@) */ + TVBU_LIMIT /* (number of units) */ }; struct bench_timing; /* forward declaration */ @@ -320,7 +559,8 @@ struct tvec_outops { * The register value is at @rv@, and its definition, including its * type, at @rd@. Note that this function may be called on virtual * registers which aren't in either of the register vectors or - * mentioned by the test description. + * mentioned by the test description. It may also be called with + * @rv@ null, indicating that the register is not live. */ void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/); @@ -347,245 +587,23 @@ struct tvec_outops { * indicated by @unit@) processed in the indicated time. */ - void (*error)(struct tvec_output */*o*/, - const char */*msg*/, va_list */*ap*/); - /* Report an error. The driver should ideally report the filename - * (@infile@) and line number (@lno@) prompting the error. - */ - - void (*notice)(struct tvec_output */*o*/, + void (*report)(struct tvec_output */*o*/, unsigned /*level*/, const char */*msg*/, va_list */*ap*/); - /* Report a miscellaneous message. The driver should ideally report - * the filename (@infile@) and line number (@lno@) prompting the - * error. + /* Report a message. The driver should ideally report the filename + * (@infile@) and line number (@lno@) prompting the error. */ void (*destroy)(struct tvec_output */*o*/); /* Release any resources acquired by the driver. */ }; -/*----- Test descriptions -------------------------------------------------*/ - -typedef void tvec_testfn(const struct tvec_reg */*in*/, - struct tvec_reg */*out*/, - void */*ctx*/); - /* A test function. It should read inputs from @in@ and write outputs to - * @out@. The @TVRF_LIVE@ is set on inputs which are actually present, and - * on outputs which are wanted to test. A test function can set additional - * `gratuitous outputs' by setting @TVRF_LIVE@ on them; clearing - * @TVRF_LIVE@ on a wanted output causes a mismatch. - * - * A test function may be called zero or more times by the environment. In - * particular, it may be called multiple times, though usually by prior - * arrangement with the environment. - * - * The @ctx@ is supplied by the environment's @run@ function (see below). - * The default environment calls the test function once, with a null - * @ctx@. There is no expectation that the environment's context has - * anything to do with the test function's context. - */ - -struct tvec_env { - /* A test environment sets things up for and arranges to run the test. - * - * The caller is responsible for allocating storage for the environment's - * context, based on the @ctxsz@ slot, and freeing it later; this space is - * passed in as the @ctx@ parameter to the remaining functions; if @ctxsz@ - * is zero then @ctx@ is null. - */ - - size_t ctxsz; /* environment context size */ - - int (*setup)(struct tvec_state */*tv*/, const struct tvec_env */*env*/, - void */*pctx*/, void */*ctx*/); - /* Initialize the context; called at the start of a test group; @pctx@ is - * null for environments called by the core, but may be non-null for - * subordinate environments. Return zero on success, or @-1@ on failure. - * If setup fails, the context is freed, and the test group is skipped. - */ - - int (*set)(struct tvec_state */*tv*/, const char */*var*/, - const struct tvec_env */*env*/, 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. - */ - - int (*before)(struct tvec_state */*tv*/, void */*ctx*/); - /* Called prior to running a test. This is the right place to act on any - * `%|@var|%' settings. Return zero on success or @-1@ on failure (which - * causes the test to be skipped). This function is never called if the - * test group is skipped. - */ - - void (*run)(struct tvec_state */*tv*/, tvec_testfn */*fn*/, void */*ctx*/); - /* Run the test. It should either call @tvec_skip@, or run @fn@ one or - * more times. In the latter case, it is responsible for checking the - * outputs, and calling @tvec_fail@ as necessary; @tvec_checkregs@ will - * check the register values against the supplied test vector, while - * @tvec_check@ does pretty much everything necessary. This function is - * never called if the test group is skipped. - */ - - void (*after)(struct tvec_state */*tv*/, void */*ctx*/); - /* Called after running or skipping a test. Typical actions involve - * resetting whatever things were established by @set@. This function is - * never called if the test group is skipped. - */ - - void (*teardown)(struct tvec_state */*tv*/, void */*ctx*/); - /* Tear down the environment: called at the end of a test group. If the - * setup failed, then this function is still called, with a null @ctx@. - */ -}; - -struct tvec_test { - /* A test description. */ - - const char *name; /* name of the test */ - const struct tvec_regdef *regs; /* descriptions of the registers */ - const struct tvec_env *env; /* environment to run test in */ - tvec_testfn *fn; /* test function */ -}; -#define TVEC_ENDTESTS { 0, 0, 0, 0 } - enum { - /* Register output dispositions. */ - - TVRD_INPUT, /* input-only register */ - TVRD_OUTPUT, /* output-only (input is dead) */ - TVRD_MATCH, /* matching (equal) registers */ - TVRD_FOUND, /* mismatching output register */ - TVRD_EXPECT /* mismatching input register */ -}; - -/*----- Register types ----------------------------------------------------*/ - -struct tvec_regty { - /* A register type. */ - - void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/); - /* Initialize the value in @*rv@. This will be called before any - * other function acting on the value, including @release@. - */ - - void (*release)(union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - /* Release any resources associated with the value in @*rv@. */ - - int (*eq)(const union tvec_regval */*rv0*/, - const union tvec_regval */*rv1*/, - const struct tvec_regdef */*rd*/); - /* Return nonzero if @*rv0@ and @*rv1@ are equal values. - * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@ - * with the input register as @rv0@ and the output as @rv1@. - */ - - int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - /* Serialize the value @*rv@, writing the result to @b@. Return - * zero on success, or @-1@ on error. - */ - - int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/); - /* Deserialize a value from @b@, storing it in @*rv@. Return zero on - * success, or @-1@ on error. - */ - - int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/, - struct tvec_state */*tv*/); - /* Parse a value from @tv->fp@, storing it in @*rv@. Return zero on - * success, or @-1@ on error, having reported one or more errors via - * @tvec_error@ or @tvec_syntax@. A successful return should leave - * the input position at the start of the next line; the caller will - * flush the remainder of the line itself. - */ - - void (*dump)(const union tvec_regval */*rv*/, - const struct tvec_regdef */*rd*/, - unsigned /*style*/, - const struct gprintf_ops */*gops*/, void */*go*/); -#define TVSF_COMPACT 1u - /* Write a human-readable representation of the value @*rv@ using - * @gprintf@ on @gops@ and @go@. The @style@ is a collection of - * flags: if @TVSF_COMPACT@ is set, then output should be minimal, - * and must fit on a single line; otherwise, output may consist of - * multiple lines and may contain redundant information if that is - * likely to be useful to a human reader. - */ + TVLEV_NOTE = 4, /* notice */ + TVLEV_ERR = 8 /* error */ }; /*----- Session lifecycle -------------------------------------------------*/ -/* Here's the overall flow for a testing session. - * - * @tvec_begin@ - * -> output @bsession@ - * @tvec_read@ - * -> output @bgroup@ - * -> env @setup@ - * one or more tests - * -> type @init@ (input and output) - * -> type @parse@ (input) - * -> output @btest@ - * -> env @before@ - * -> env @run@ - * -> @tvec_skip@ - * -> output @skip@ - * -> test @fn@ - * -> @tvec_checkregs@ - * -> type @eq@ - * -> @tvec_fail@ - * -> output @fail@ - * -> @tvec_mismatch@ - * -> output @dumpreg@ - * -> type @dump@ - * -> env @after@ - * -> output @etest@ - * or - * -> output @skipgroup@ - * finally - * -> output @egroup@ - * -> env @teardown@ - * - * @tvec_adhoc@ - * @tvec_begingroup@ - * -> output @bgroup@ - * -> env @setup@ - * @tvec_begintest@ - * -> output @btest@ - * @tvec_skip@ - * -> output @skip@ - * @tvec_claimeq@ - * -> @tvec_fail@ - * -> output @fail@ - * -> @tvec_mismatch@ - * -> output @dumpreg@ - * -> type @dump@ - * @tvec_endtest@ - * -> output @etest@ - * or @tvec_skipgroup@ - * -> output @skipgroup@ - * @tvec_endgroup@ - * -> output @egroup@ - * - * @tvec_end@ - * -> output @esession@ - * -> output @destroy@ - * - * @tvec_benchrun@ - * -> type @dump@ (compact style) - * -> output @bbench@ - * -> subenv @run@ - * -> test @fn@ - * -> output @ebench@ - * -> @tvec_benchreport@ - * - * The output functions @error@ and @notice@ can be called at arbitrary - * times. - */ - /* --- @tvec_begin@ --- * * * Arguments: @struct tvec_state *tv_out@ = state structure to fill in @@ -779,6 +797,9 @@ extern void tvec_skip_v(struct tvec_state */*tv*/, extern void tvec_resetoutputs(struct tvec_state */*tv*/); +extern void tvec_initregs(struct tvec_state */*tv*/); +extern void tvec_releaseregs(struct tvec_state */*tv*/); + /* --- @tvec_checkregs@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state @@ -824,7 +845,7 @@ extern void tvec_fail_v(struct tvec_state */*tv*/, * * Arguments: @struct tvec_state *tv@ = test-vector state * @unsigned disp@ = the register disposition (@TVRD_...@) - * @const union tvec_regval *tv@ = register value + * @const union tvec_regval *tv@ = register value, or null * @const struct tvec_regdef *rd@ = register definition * * Returns: --- @@ -1071,132 +1092,50 @@ extern int tvec_claimeq(struct tvec_state */*tv*/, /*----- Benchmarking ------------------------------------------------------*/ -struct tvec_bench { +struct tvec_benchenv { struct tvec_env _env; /* benchmarking is an environment */ struct bench_state **bst; /* benchmark state anchor or null */ unsigned long niter; /* iterations done per unit */ int riter, rbuf; /* iterations and buffer registers */ const struct tvec_env *env; /* subordinate environment */ }; -#define TVEC_BENCHENV \ - { sizeof(struct tvec_benchctx), \ - tvec_benchsetup, \ - tvec_benchset, \ - tvec_benchbefore, \ - tvec_benchrun, \ - tvec_benchafter, \ - tvec_benchteardown } -#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate struct tvec_benchctx { - const struct tvec_bench *b; - struct bench_state *bst; - double dflt_target; - void *subctx; + const struct tvec_benchenv *be; /* environment configuration */ + struct bench_state *bst; /* benchmark state */ + double dflt_target; /* default time in seconds */ + void *subctx; /* subsidiary environment context */ }; extern struct bench_state *tvec_benchstate; -/* --- @tvec_benchsetup@ --- * - * - * 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: Zero on success, @-1@ on failure. - * - * Use: Initialize a benchmarking environment context. - * - * The environment description must really be a @struct - * tvec_bench@. If the @bst@ slot is null, then a temporary - * benchmark state is allocated for the current test group and - * released at the end. Otherwise, it must be the address of a - * pointer to a benchmark state: if the pointer is null, then a - * fresh state is allocated and initialized and the pointer is - * updated; otherwise, the pointer is assumed to refer to an - * existing valid benchmark state. - */ - -extern int tvec_benchsetup(struct tvec_state */*tv*/, - const struct tvec_env */*env*/, - void */*pctx*/, void */*ctx*/); - -/* --- @tvec_benchset@ --- * - * - * Arguments: @struct tvec_state *tv@ = test vector state - * @const char *var@ = variable name to set - * @const struct tvec_env *env@ = environment description - * @void *ctx@ = context pointer - * - * Returns: Zero on success, @-1@ on failure. - * - * Use: Set a special variable. The following special variables are - * supported. - * - * * %|@target|% is the (approximate) number of seconds to run - * the benchmark. - * - * Unrecognized variables are passed to the subordinate - * environment, if there is one. - */ - -extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/, - const struct tvec_env */*env*/, void */*ctx*/); - -/* --- @tvec_benchbefore@ --- * - * - * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer - * - * Returns: Zero on success, @-1@ on failure. - * - * Use: Invoke the subordinate environment's @before@ function to - * prepare for the benchmark. - */ - -extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/); - -/* --- @tvec_benchrun@ --- * - * - * 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: Measures and reports the performance of a test function. - * - * - */ - -extern void tvec_benchrun(struct tvec_state */*tv*/, - tvec_testfn */*fn*/, void */*ctx*/); - -/* --- @tvec_benchafter@ --- * +/* --- Environment implementation --- * * - * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer + * The following special variables are supported. * - * Returns: --- + * * %|@target|% is the (approximate) number of seconds to run the + * benchmark. * - * Use: Invoke the subordinate environment's @after@ function to - * clean up after the benchmark. + * Unrecognized variables are passed to the subordinate environment, if there + * is one. Other events are passed through to the subsidiary environment. */ -extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/); +extern tvec_envsetupfn tvec_benchsetup; +extern tvec_envsetfn tvec_benchset; +extern tvec_envbeforefn tvec_benchbefore; +extern tvec_envrunfn tvec_benchrun; +extern tvec_envafterfn tvec_benchafter; +extern tvec_envteardownfn tvec_benchteardown; -/* --- @tvec_benchteardown@ --- * - * - * Arguments: @struct tvec_state *tv@ = test vector state - * @void *ctx@ = context pointer - * - * Returns: --- - * - * Use: Tear down the benchmark environment. - */ - -extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/); +#define TVEC_BENCHENV \ + { sizeof(struct tvec_benchctx), \ + tvec_benchsetup, \ + tvec_benchset, \ + tvec_benchbefore, \ + tvec_benchrun, \ + tvec_benchafter, \ + tvec_benchteardown } +#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate /* --- @tvec_benchreport@ --- * * @@ -1216,40 +1155,141 @@ extern void tvec_benchreport (const struct gprintf_ops */*gops*/, void */*go*/, unsigned /*unit*/, const struct bench_timing */*tm*/); +/*----- Remote execution --------------------------------------------------*/ + +struct tvec_remotecomms { + int infd, outfd; /* input and output descriptors */ + dbuf bin, bout; /* input and output buffers */ + unsigned f; /* flags */ +#define TVRF_BROKEN 0x0001u /* communications have failed */ + /* bits 8--15 for upper layer */ +}; +#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, DBUF_INIT, 0 } + +struct tvec_remotectx { + struct tvec_state *tv; /* test vector state */ + struct tvec_remotecomms rc; /* communication state */ + const struct tvec_remoteenv *re; /* environment configuration */ + unsigned ver; /* protocol version */ + pid_t kid; /* child process id */ + int errfd; /* child stderr descriptor */ + lbuf errbuf; /* child stderr line buffer */ + dstr prgwant, progress; /* progress: wanted/reported */ + unsigned exwant, exit; /* exit status wanted/reported */ +#define TVRF_RCNMASK 0x0300u /* reconnection behaviour: */ +#define TVRCN_SKIP 0x0000u /* skip unless connected */ +#define TVRCN_DEMAND 0x0100u /* connect on demand */ +#define TVRCN_FORCE 0x0200u /* force reconnection */ +#define TVRF_MUFFLE 0x0400u /* muffle child stderr */ +}; + +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*/); + +struct tvec_remoteenv_slots { + tvec_connectfn *connect; /* connection function */ + const struct tvec_env *env; /* subsidiary environment */ +}; + +struct tvec_remoteenv { + struct tvec_env _env; + struct tvec_remoteenv_slots r; +}; + +struct tvec_remotefork_slots { + const struct tvec_test *tests; /* child tests (or null) */ +}; + +struct tvec_remotefork { + struct tvec_env _env; + struct tvec_remoteenv_slots r; + struct tvec_remotefork_slots f; +}; + +struct tvec_remoteexec_slots { + const char *const *args; /* command line to execute */ +}; + +struct tvec_remoteexec { + struct tvec_env _env; + struct tvec_remoteenv_slots r; + struct tvec_remoteexec_slots x; +}; + +union tvec_remoteenv_subclass_kludge { + struct tvec_env _env; + struct tvec_remoteenv renv; + struct tvec_remotefork fork; + struct tvec_remoteexec exec; +}; + +extern tvec_envsetupfn tvec_remotesetup; +extern tvec_envsetfn tvec_remoteset; +extern tvec_envrunfn tvec_remoterun; +extern tvec_envafterfn tvec_remoteafter; +extern tvec_envteardownfn tvec_remoteteardown; +#define TVEC_REMOTEENV \ + { sizeof(struct tvec_remotectx), \ + tvec_remotesetup, \ + tvec_remoteset, \ + 0, \ + tvec_remoterun, \ + tvec_remoteafter, \ + tvec_remoteteardown } + +extern int tvec_setprogress(const char */*status*/); + +extern int tvec_remoteserver(int /*infd*/, int /*outfd*/, + const struct tvec_config */*config*/); + +extern tvec_connectfn tvec_fork, tvec_exec; + +#define TVEC_REMOTEFORK( subenv, tests) \ + TVEC_REMOTEENV, { tvec_fork, subenv }, { tests } + +#define TVEC_REMOTEEXEC(subenv, args) \ + TVEC_REMOTEENV, { tvec_exec, subenv }, { args } + /*----- Output functions --------------------------------------------------*/ -/* --- @tvec_error@, @tvec_error_v@ --- * +/* --- @tvec_report@, @tvec_report_v@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state * @const char *msg@, @va_list ap@ = error message * - * Returns: @-1@. + * Returns: --- * - * Use: Report an error. Errors are distinct from test failures, - * and indicate that a problem was encountered which compromised - * the activity of testing. + * Use: Report an message with a given severity. Messages with level + * @TVLEV_ERR@ or higher force a nonzero exit code. */ -extern int PRINTF_LIKE(2, 3) - tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); -extern int tvec_error_v(struct tvec_state */*tv*/, - const char */*msg*/, va_list */*ap*/); +extern void PRINTF_LIKE(3, 4) + tvec_report(struct tvec_state */*tv*/, unsigned /*level*/, + const char */*msg*/, ...); +extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/, + const char */*msg*/, va_list */*ap*/); -/* --- @tvec_notice@, @tvec_notice_v@ --- * +/* --- @tvec_error@, @tvec_notice@ --- * * * Arguments: @struct tvec_state *tv@ = test-vector state - * @const char *msg@, @va_list ap@ = message + * @const char *msg@, @va_list ap@ = error message * - * Returns: --- + * Returns: The @tvec_error@ function returns @-1@ as a trivial + * convenience; @tvec_notice@ does not return a value. * - * Use: Output a notice: essentially, some important information - * which doesn't fit into any of the existing categories. + * Use: Report an error or a notice. Errors are distinct from test + * failures, and indicate that a problem was encountered which + * compromised the activity of testing. Notices are important + * information which doesn't fit into any other obvious + * category. */ +extern int PRINTF_LIKE(2, 3) + tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...); extern void PRINTF_LIKE(2, 3) tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...); -extern void tvec_notice_v(struct tvec_state */*tv*/, - const char */*msg*/, va_list */*ap*/); /* --- @tvec_humanoutput@ --- * * @@ -1308,6 +1348,23 @@ extern struct tvec_output *tvec_dfltout(FILE */*fp*/); /*------ Serialization utilities ------------------------------------------*/ +/* Serialization format. + * + * The `candidate register definitions' are those entries @r@ in the @regs@ + * vector whose index @r.i@ is strictly less than @nr@. The `selected + * register definitions' are those candidate register definitions @r@ for + * which the indicated register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The + * serialized output begins with a header bitmap: if there are %$n$% + * candidate register definitions then the header bitmap consists of %$\lceil + * n/8 \rceil$% bytes. Bits are ordered starting from the least significant + * bit of the first byte, end ending at the most significant bit of the final + * byte. The bit corresponding to a candidate register definition is set if + * and only if that register defintion is selected. The header bitmap is + * then followed by the serializations of the selected registers -- i.e., for + * each selected register definition @r@, the serialized value of register + * @rv[r.i]@ -- simply concatenated together, with no padding or alignment. + */ + /* --- @tvec_serialize@ --- * * * Arguments: @const struct tvec_reg *rv@ = vector of registers @@ -1322,23 +1379,6 @@ extern struct tvec_output *tvec_dfltout(FILE */*fp*/); * * Use: Serialize a collection of register values. * - * The `candidate register definitions' are those entries @r@ in - * the @regs@ vector whose index @r.i@ is strictly less than - * @nr@. The `selected register definitions' are those - * candidate register definitions @r@ for which the indicated - * register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The - * serialized output begins with a header bitmap: if there are - * %$n$% candidate register definitions then the header bitmap - * consists of %$\lceil n/8 \rceil$% bytes. Bits are ordered - * starting from the least significant bit of the first byte, - * end ending at the most significant bit of the final byte. - * The bit corresponding to a candidate register definition is - * set if and only if that register defintion is selected. The - * header bitmap is then followed by the serializations of the - * selected registers -- i.e., for each selected register - * definition @r@, the serialized value of register @rv[r.i]@ -- - * simply concatenated together, with no padding or alignment. - * * The serialized output is written to the buffer @b@. Failure * can be caused by running out of buffer space, or a failing * type handler. @@ -1369,10 +1409,7 @@ extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/, * slot, and set the @TVRF_LIVE@ flag on the register. See * @tvec_serialize@ for a description of the format. * - * On successful completion, store the address of the first byte - * after the serialized data in @*end_out@ if @end_out@ is not - * null. Failure results only from a failing register type - * handler. + * Failure results only from a failing register type handler. */ extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/, diff --git a/utils/bits.h b/utils/bits.h index 656eee5..4d1d61a 100644 --- a/utils/bits.h +++ b/utils/bits.h @@ -320,13 +320,13 @@ typedef unsigned char octet, uint8; /* --- Endianness swapping --- */ -#if GCC_VERSION_P(4, 8) +#if GCC_VERSION_P(4, 8) || CLANG_VERSION_P(3, 2) # define ENDSWAP16(x) ((uint16)__builtin_bswap16(x)) #endif -#if GCC_VERSION_P(4, 3) +#if GCC_VERSION_P(4, 3) || CLANG_VERSION_P(3, 2) # define ENDSWAP32(x) ((uint32)__builtin_bswap32(x)) #endif -#if GCC_VERSION_P(4, 3) && defined(HAVE_UINT64) +#if (GCC_VERSION_P(4, 3) || CLANG_VERSION_P(3, 2)) && defined(HAVE_UINT64) # define ENDSWAP64(x) ((uint64)__builtin_bswap64(x)) #endif @@ -427,7 +427,7 @@ typedef unsigned char octet, uint8; /* --- Unaligned access (GCC-specific) --- */ -#if GCC_VERSION_P(3, 3) && CHAR_BIT == 8 +#if (GCC_VERSION_P(3, 3) || CLANG_VERSION_P(3, 0)) && CHAR_BIT == 8 # define MLIB_MISALIGNED __attribute__((aligned(1), may_alias)) # if __SIZEOF_SHORT__ == 2 typedef MLIB_MISALIGNED unsigned short misaligned_uint16; diff --git a/utils/compiler.h b/utils/compiler.h index edc6e4d..ded6c80 100644 --- a/utils/compiler.h +++ b/utils/compiler.h @@ -34,7 +34,7 @@ /*----- Macros ------------------------------------------------------------*/ -#ifdef __GNUC__ +#if defined(__GNUC__) && !defined(__clang__) # define GCC_VERSION_P(maj, min) \ (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min))) #else diff --git a/utils/control.h b/utils/control.h index 97be8b6..4026e16 100644 --- a/utils/control.h +++ b/utils/control.h @@ -176,7 +176,7 @@ * share the same state, so probably don't do this. */ #define MC_FINALLY(tag, cleanup) \ - MP_WRAP(tag##__final, { ; } cleanup, { cleanup break; }) + MP_WRAP(tag##__final, { ; }, cleanup, { cleanup break; }) /* @MC_DECL(tag, decl) body@ * diff --git a/utils/macros.h b/utils/macros.h index 954ed0c..95d0ddb 100644 --- a/utils/macros.h +++ b/utils/macros.h @@ -110,7 +110,7 @@ # define EXECL_LIKE(ntrail) __attribute__((__sentinel__(ntrail))) #endif -#if GCC_VERSION_P(2, 7) +#if GCC_VERSION_P(2, 7) || CLANG_VERSION_P(0, 0) # define LAUNDER(x) \ ({ __typeof__(x) _y; __asm__("" : "=g"(_y) : "0"(x)); _y; }) # define RELAX do __asm__ __volatile__("" ::: "memory"); while (0) @@ -133,7 +133,9 @@ # define MUFFLE_WARNINGS_STMT(warns, body) \ do { MLIB__MUFFLE_WARNINGS(warns, body) } while (0) -#elif GCC_VERSION_P(4, 6) +#endif + +#if GCC_VERSION_P(4, 6) /* --- Diagnostic suppression in GCC: a tale of woe --- * * -- 2.11.0