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 };
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[] = {
b->f = 0;
}
-/* --- @dbuf_init@ --- *
+/* --- @dbuf_create@ --- *
*
* Arguments: @dbuf *db@ = pointer to a dynamic buffer block
*
* 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;
void dbuf_destroy(dbuf *db)
{
if (db->_b.base) x_free(db->a, db->_b.base);
- dbuf_init(db);
+ dbuf_create(db);
}
/* --- @buf_break@ --- *
extern void buf_init(buf */*b*/, void */*p*/, size_t /*sz*/);
-/* --- @dbuf_init@ --- *
+/* --- @dbuf_create@ --- *
*
* Arguments: @dbuf *db@ = pointer to a dynamic buffer block
*
* and ready for writing.
*/
-extern void dbuf_init(dbuf */*db*/);
+extern void dbuf_create(dbuf */*db*/);
/* --- @dbuf_reset@ --- *
*
## 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)
#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 */
};
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 ------------------------------------------------*/
#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,
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;
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 = {
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 = {
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[] = {
TYPEREGS(DEFSINGLE)
#undef DEFSINGLE
+ { "crash", crash_regs, &crash_testenv._env, test_crash } ,
+
TVEC_ENDTESTS
};
@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
])])
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])
* @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
* 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;
}
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 =
* 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@ --- *
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;
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)
/* 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); }
}
}
* 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;
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 {
}
/* 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++)
/*----- 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 ---------------------------------------------------*/
}
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)
}
}
-static void init_registers(struct tvec_state *tv)
+void tvec_initregs(struct tvec_state *tv)
{
const struct tvec_regdef *rd;
struct tvec_reg *r;
}
}
-static void release_registers(struct tvec_state *tv)
+void tvec_releaseregs(struct tvec_state *tv)
{
const struct tvec_regdef *rd;
struct tvec_reg *r;
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)
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)
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)
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);
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;
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);
}
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; }
fmt->prefix = prefix;
l = fmt->pfxlim = prefix + strlen(prefix);
SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q;
- DPUTM(&fmt->w, q, l - q);
}
}
* 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;
/* 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;
* 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;
}
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);
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;
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
};
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;
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
};
/*----- Header files ------------------------------------------------------*/
#include <errno.h>
+#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
-#include <sys/uio.h>
+#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#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;
#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 -------------------------------------------------*/
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 <stdarg.h>
# include "gprintf.h"
#endif
+#ifndef MLIB_LBUF_H
+# include "lbuf.h"
+#endif
+
#ifndef MLIB_MACROS_H
# include "macros.h"
#endif
#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 {
/* 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 */
* 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*/);
* 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
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
*
* 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: ---
/*----- 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@ --- *
*
(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@ --- *
*
/*------ 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
*
* 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.
* 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*/,
/* --- 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
/* --- 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;
/*----- Macros ------------------------------------------------------------*/
-#ifdef __GNUC__
+#if defined(__GNUC__) && !defined(__clang__)
# define GCC_VERSION_P(maj, min) \
(__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
#else
* 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@
*
# 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)
# 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 --- *
*