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