+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const struct tvec_regty *ty@ = register type
+ * @const union tvec_misc *arg@ = register type argument
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the input and output values of register 0 are
+ * equal, zero if they differ.
+ *
+ * Use: Check that the input and output values of register 0 are
+ * equal (according to the register type @ty@). As for
+ * @tvec_claim@ above, a test case is automatically begun and
+ * ended if none is already underway. If the values are
+ * unequal, then @tvec_fail@ is called, quoting @expr@, and the
+ * mismatched values are dumped.
+ *
+ * This function is not expected to be called directly, but
+ * through type-specific wrapper functions or macros such as
+ * @TVEC_CLAIMEQ_INT@.
+ */
+
+extern int tvec_claimeq(struct tvec_state */*tv*/,
+ const struct tvec_regty */*ty*/,
+ const union tvec_misc */*arg*/,
+ const char */*file*/, unsigned /*lno*/,
+ const char */*expr*/);
+
+/*----- Benchmarking ------------------------------------------------------*/
+
+struct tvec_benchenv {
+ struct tvec_env _env; /* benchmarking is an environment */
+ struct bench_state **bst; /* benchmark state anchor or null */
+ unsigned long niter; /* iterations done per unit */
+ int riter, rbuf; /* iterations and buffer registers */
+ const struct tvec_env *env; /* subordinate environment */
+};
+
+struct tvec_benchctx {
+ const struct tvec_benchenv *be; /* environment configuration */
+ struct bench_state *bst; /* benchmark state */
+ double dflt_target; /* default time in seconds */
+ unsigned f; /* flags */
+#define TVBF_SETTRG 1u /* set `@target' */
+#define TVBF_SETMASK (TVBF_SETTRG)) /* mask of @TVBF_SET...@ */
+ void *subctx; /* subsidiary environment context */
+};
+
+extern struct bench_state *tvec_benchstate;
+
+/* --- Environment implementation --- *
+ *
+ * The following special variables are supported.
+ *
+ * * %|@target|% is the (approximate) number of seconds to run the
+ * benchmark.
+ *
+ * Unrecognized variables are passed to the subordinate environment, if there
+ * is one. Other events are passed through to the subsidiary environment.
+ */
+
+extern tvec_envsetupfn tvec_benchsetup;
+extern tvec_envfindvarfn tvec_benchfindvar;
+extern tvec_envbeforefn tvec_benchbefore;
+extern tvec_envrunfn tvec_benchrun;
+extern tvec_envafterfn tvec_benchafter;
+extern tvec_envteardownfn tvec_benchteardown;
+
+#define TVEC_BENCHENV \
+ { sizeof(struct tvec_benchctx), \
+ tvec_benchsetup, \
+ tvec_benchfindvar, \
+ tvec_benchbefore, \
+ tvec_benchrun, \
+ tvec_benchafter, \
+ tvec_benchteardown }
+#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate
+
+/* --- @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.
+ */
+
+extern void tvec_benchreport
+ (const struct gprintf_ops */*gops*/, void */*go*/,
+ unsigned /*unit*/, const struct bench_timing */*tm*/);
+
+/*----- Remote execution --------------------------------------------------*/
+
+struct tvec_remoteenv;
+
+struct tvec_remotecomms {
+ int infd, outfd; /* input and output descriptors */
+ dbuf bout; /* output buffer */
+ unsigned char *bin; /* input buffer */
+ size_t binoff, binlen, binsz; /* input offset, length, and size */
+ size_t t; /* temporary offset */
+ unsigned f; /* flags */
+#define TVRF_BROKEN 0x0001u /* communications have failed */
+};
+#define TVEC_REMOTECOMMS_INIT { -1, -1, DBUF_INIT, 0, 0, 0, 0, 0, 0 }
+
+struct tvec_remotectx {
+ struct tvec_state *tv; /* test vector state */
+ struct tvec_remotecomms rc; /* communication state */
+ const struct tvec_remoteenv *re; /* environment configuration */
+ void *subctx; /* subenvironment context */
+ struct tvec_vardef vd; /* temporary variable definition */
+ unsigned ver; /* protocol version */
+ pid_t kid; /* child process id */
+ int errfd; /* child stderr descriptor */
+ lbuf errbuf; /* child stderr line buffer */
+ dstr prgwant, progress; /* progress: wanted/reported */
+ unsigned exwant, exit; /* exit status wanted/reported */
+#define TVRF_RCNMASK 0x0300u /* reconnection behaviour: */
+#define TVRCN_DEMAND 0x0000u /* connect on demand */
+#define TVRCN_SKIP 0x0100u /* skip unless connected */
+#define TVRCN_FORCE 0x0200u /* force reconnection */
+#define TVRF_MUFFLE 0x0400u /* muffle child stderr */
+#define TVRF_SETEXIT 0x0800u /* set `@exit' */
+#define TVRF_SETPRG 0x1000u /* set `@progress' */
+#define TVRF_SETRCN 0x2000u /* set `@reconnect' */
+#define TVRF_SETMASK (TVRF_SETEXIT | TVRF_SETPRG | TVRF_SETRCN)
+ /* mask of @TVTF_SET...@ */
+};
+
+typedef int tvec_connectfn(pid_t */*kid_out*/, int */*infd_out*/,
+ int */*outfd_out*/, int */*errfd_out*/,
+ struct tvec_state */*tv*/,
+ const struct tvec_remoteenv */*env*/);
+ /* A connection function. On entry, @tv@ holds the test-vector state, and
+ * @env@ is the test group's remote environment structure, which will
+ * typically really be some subclass of @struct tvec_remoteenv@ containing
+ * additional parameters for establishing the child process.
+ *
+ * On successful completion, the function stores input and output
+ * descriptors (which need not be distinct) in @*infd_out@ and
+ * @*outfd_out@, and returns zero; if it creates a child process, it should
+ * additionally store the child's process-id in @*kid_out@ and store in
+ * @*errfd_out@ a descriptor from which the child's error output can be
+ * read. On error, the function should report an appropriate message via
+ * @tvec_error@ and return %$-1$%.
+ */
+
+struct tvec_remoteenv_slots {
+ tvec_connectfn *connect; /* connection function */
+ const struct tvec_env *env; /* subsidiary environment */
+ unsigned dflt_reconn; /* default reconnection */
+};
+
+struct tvec_remoteenv {
+ struct tvec_env _env;
+ struct tvec_remoteenv_slots r;
+};
+
+struct tvec_remotefork_slots {
+ const struct tvec_test *tests; /* child tests (or null) */
+};
+
+struct tvec_remotefork {
+ struct tvec_env _env;
+ struct tvec_remoteenv_slots r;
+ struct tvec_remotefork_slots f;
+};
+
+struct tvec_remoteexec_slots {
+ const char *const *args; /* command line to execute */
+};
+
+struct tvec_remoteexec {
+ struct tvec_env _env;
+ struct tvec_remoteenv_slots r;
+ struct tvec_remoteexec_slots x;
+};
+
+union tvec_remoteenv_subclass_kludge {
+ struct tvec_env _env;
+ struct tvec_remoteenv renv;
+ struct tvec_remotefork fork;
+ struct tvec_remoteexec exec;
+};
+
+/* 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_envfindvarfn tvec_remotefindvar;
+extern tvec_envbeforefn tvec_remotebefore;
+extern tvec_envrunfn tvec_remoterun;
+extern tvec_envafterfn tvec_remoteafter;
+extern tvec_envteardownfn tvec_remoteteardown;
+#define TVEC_REMOTEENV \
+ { sizeof(struct tvec_remotectx), \
+ tvec_remotesetup, \
+ tvec_remotefindvar, \
+ tvec_remotebefore, \
+ 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*/);
+
+extern tvec_connectfn tvec_fork, tvec_exec;
+
+#define TVEC_REMOTEFORK(subenv, tests) \
+ TVEC_REMOTEENV, { tvec_fork, subenv }, { tests }
+
+#define TVEC_REMOTEEXEC(subenv, args) \
+ TVEC_REMOTEENV, { tvec_exec, subenv }, { args }
+
+/*----- Timeouts ----------------------------------------------------------*/
+
+struct tvec_timeoutenv {
+ struct tvec_env _env;
+ int 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; /* saved environment description */
+ int timer; /* timer code (as overridden) */
+ double t; /* time to wait (as overridden) */
+ unsigned f; /* flags */
+#define TVTF_SETTMO 1u /* set `@timeout' */
+#define TVTF_SETTMR 2u /* set `@timer' */
+#define TVTF_SETMASK (TVTF_SETTMO | TVTF_SETTMR)
+ /* mask of @TVTF_SET...@ */
+ void *subctx;
+};
+
+extern tvec_envsetupfn tvec_timeoutsetup;
+extern tvec_envfindvarfn tvec_timeoutfindvar;
+extern tvec_envbeforefn tvec_timeoutbefore;
+extern tvec_envrunfn tvec_timeoutrun;
+extern tvec_envafterfn tvec_timeoutafter;
+extern tvec_envteardownfn tvec_timeoutteardown;
+#define TVEC_TIMEOUTENV \
+ { sizeof(struct tvec_timeoutctx), \
+ tvec_timeoutsetup, \
+ tvec_timeoutfindvar, \
+ tvec_timeoutbefore, \
+ tvec_timeoutrun, \
+ tvec_timeoutafter, \
+ tvec_timeoutteardown }
+#define TVEC_TIMEOUTINIT(timer, t) TVEC_TIMEOUTENV, timer, t
+
+/*----- Output functions --------------------------------------------------*/
+
+/* --- @tvec_strlevel@ --- *
+ *
+ * Arguments: @unsigned level@ = level code
+ *
+ * Returns: A human-readable description.
+ *
+ * Use: Converts a level code into something that you can print in a
+ * message.
+ */
+
+extern const char *tvec_strlevel(unsigned /*level*/);
+
+/* --- @tvec_report@, @tvec_report_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *msg@, @va_list ap@ = error message
+ *
+ * Returns: ---
+ *
+ * Use: Report an message with a given severity. Messages with level
+ * @TVLEV_ERR@ or higher force a nonzero exit code.
+ */
+
+extern PRINTF_LIKE(3, 4)
+ void tvec_report(struct tvec_state */*tv*/, unsigned /*level*/,
+ const char */*msg*/, ...);
+extern void tvec_report_v(struct tvec_state */*tv*/, unsigned /*level*/,
+ const char */*msg*/, va_list */*ap*/);
+
+/* --- @tvec_error@, @tvec_notice@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *msg@, @va_list ap@ = error message
+ *
+ * Returns: The @tvec_error@ function returns %$-1$% as a trivial
+ * convenience; @tvec_notice@ does not return a value.
+ *
+ * Use: Report an error or a notice. Errors are distinct from test
+ * failures, and indicate that a problem was encountered which
+ * compromised the activity of testing. Notices are important
+ * information which doesn't fit into any other obvious
+ * category.
+ */
+
+extern PRINTF_LIKE(2, 3)
+ int tvec_error(struct tvec_state */*tv*/, const char */*msg*/, ...);
+extern PRINTF_LIKE(2, 3)
+ void tvec_notice(struct tvec_state */*tv*/, const char */*msg*/, ...);
+
+/* --- @tvec_unkregerr@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *name@ = register or pseudoregister name
+ *
+ * Returns: %$-1$%.
+ *
+ * Use: Reports an error that the register or pseudoregister is
+ * unrecognized.
+ */
+
+extern int tvec_unkregerr(struct tvec_state */*tv*/, const char */*name*/);
+
+/* --- @tvec_dupregerr@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *name@ = register or pseudoregister name
+ *
+ * Returns: %$-1$%.
+ *
+ * Use: Reports an error that the register or pseudoregister has been
+ * assigned already in the current test.
+ */
+
+extern int tvec_dupregerr(struct tvec_state */*tv*/, const char */*name*/);
+
+/* --- @tvec_humanoutput@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file to write on
+ *
+ * Returns: An output formatter.
+ *
+ * Use: Return an output formatter which writes on @fp@ with the
+ * expectation that a human will be watching and interpreting
+ * the output. If @fp@ denotes a terminal, the display shows a
+ * `scoreboard' indicating the outcome of each test case
+ * attempted, and may in addition use colour and other
+ * highlighting.
+ */
+
+extern struct tvec_output *tvec_humanoutput(FILE */*fp*/);
+
+/* --- @tvec_tapoutput@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file to write on
+ *
+ * Returns: An output formatter.
+ *
+ * Use: Return an output formatter which writes on @fp@ in `TAP'
+ * (`Test Anything Protocol') format.
+ *
+ * TAP comes from the Perl community, but has spread rather
+ * further. This driver currently produces TAP version 14, but
+ * pretends to be version 13. The driver produces a TAP `test
+ * point' -- i.e., a result reported as `ok' or `not ok' -- for
+ * each input test group. Failure reports and register dumps
+ * are produced as diagnostic messages before the final group
+ * result. (TAP permits structuerd YAML data after the
+ * test-point result, which could be used to report details, but
+ * (a) postponing the details until after the report is
+ * inconvenient, and (b) there is no standardization for the
+ * YAML anyway, so in practice it's no more useful than the
+ * unstructured diagnostics.
+ */
+
+extern struct tvec_output *tvec_tapoutput(FILE */*fp*/);
+
+/* --- @tvec_dfltoutput@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file to write on
+ *
+ * Returns: An output formatter.
+ *
+ * Use: Selects and instantiates an output formatter suitable for
+ * writing on @fp@. The policy is subject to change, but
+ * currently the `human' output format is selected if @fp@ is
+ * interactive (i.e., if @isatty(fileno(fp))@ is true), and
+ * otherwise the `tap' format is used.
+ */
+
+extern struct tvec_output *tvec_dfltout(FILE */*fp*/);
+
+/*------ Serialization utilities ------------------------------------------*/
+
+/* Serialization format.
+ *
+ * The `candidate register definitions' are those entries @r@ in the @regs@
+ * vector whose index @r.i@ is strictly less than @nr@. The `selected
+ * register definitions' are those candidate register definitions @r@ for
+ * which the indicated register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The
+ * serialized output begins with a header bitmap: if there are %$n$%
+ * candidate register definitions then the header bitmap consists of %$\lceil
+ * n/8 \rceil$% bytes. Bits are ordered starting from the least significant
+ * bit of the first byte, end ending at the most significant bit of the final
+ * byte. The bit corresponding to a candidate register definition is set if
+ * and only if that register defintion is selected. The header bitmap is
+ * then followed by the serializations of the selected registers -- i.e., for
+ * each selected register definition @r@, the serialized value of register
+ * @rv[r.i]@ -- simply concatenated together, with no padding or alignment.
+ */
+
+/* --- @tvec_serialize@ --- *
+ *
+ * Arguments: @const struct tvec_reg *rv@ = vector of registers
+ * @buf *b@ = buffer to write on
+ * @const struct tvec_regdef *regs@ = vector of register
+ * descriptions, terminated by an entry with a null
+ * @name@ slot
+ * @unsigned nr@ = number of entries in the @rv@ vector
+ * @size_t regsz@ = true size of each element of @rv@
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Serialize a collection of register values.
+ *
+ * The serialized output is written to the buffer @b@. Failure
+ * can be caused by running out of buffer space, or a failing
+ * type handler.
+ */
+
+extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/,
+ const struct tvec_regdef */*regs*/,
+ unsigned /*nr*/, size_t /*regsz*/);
+
+/* --- @tvec_deserialize@ --- *
+ *
+ * Arguments: @struct tvec_reg *rv@ = vector of registers
+ * @buf *b@ = buffer to write on
+ * @const struct tvec_regdef *regs@ = vector of register
+ * descriptions, terminated by an entry with a null
+ * @name@ slot
+ * @unsigned nr@ = number of entries in the @rv@ vector
+ * @size_t regsz@ = true size of each element of @rv@
+ *
+ * Returns: Zero on success, %$-1$% on failure.
+ *
+ * Use: Deserialize a collection of register values.
+ *
+ * The size of the register vector @nr@ and the register
+ * definitions @regs@ must match those used when producing the
+ * serialization. For each serialized register value,
+ * deserialize and store the value into the appropriate register
+ * slot, and set the @TVRF_LIVE@ flag on the register. See
+ * @tvec_serialize@ for a description of the format.
+ *
+ * Failure results only from a failing register type handler.
+ */
+
+extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/,
+ const struct tvec_regdef */*regs*/,
+ unsigned /*nr*/, size_t /*regsz*/);
+
+/*----- Input utilities ---------------------------------------------------*/
+
+/* These are provided by the core for the benefit of type @parse@ methods,
+ * and test-environment @set@ functions, which get to read from the test
+ * input file. The latter are usually best implemented by calling on the
+ * former.
+ *
+ * The two main rules are as follows.
+ *
+ * * Leave the file position at the beginning of the line following
+ * whatever it was that you read.
+ *
+ * * When you read and consume a newline (which you do at least once, by
+ * the previous rule), then increment @tv->lno@ to keep track of the
+ * current line number.
+ */
+
+/* --- @tvec_syntax@, @tvec_syntax_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @int ch@ = the character found (in @fgetc@ format)
+ * @const char *expect@, @va_list ap@ = what was expected
+ *
+ * Returns: %$-1$%.
+ *
+ * Use: Report a syntax error quoting @ch@ and @expect@. If @ch@ is
+ * a newline, then back up so that it can be read again (e.g.,
+ * by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also
+ * advance the line number).
+ */
+
+extern PRINTF_LIKE(3, 4)
+ int tvec_syntax(struct tvec_state */*tv*/, int /*ch*/,
+ const char */*expect*/, ...);
+extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/,
+ const char */*expect*/, va_list */*ap*/);
+
+/* --- @tvec_skipspc@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: ---
+ *
+ * Use: Advance over any whitespace characters other than newlines.
+ * This will stop at `;', end-of-file, or any other kind of
+ * non-whitespace; and it won't consume a newline.
+ */
+
+extern void tvec_skipspc(struct tvec_state */*tv*/);
+
+/* --- @tvec_flushtoeol@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned f@ = flags (@TVFF_...@)
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Advance to the start of the next line, consuming the
+ * preceding newline.
+ *
+ * A syntax error is reported if no newline character is found,
+ * i.e., the file ends in mid-line. A syntax error is also
+ * reported if material other than whitespace or a comment is
+ * found before the end of the line end, and @TVFF_ALLOWANY@ is
+ * not set in @f@. The line number count is updated
+ * appropriately.
+ */
+
+#define TVFF_ALLOWANY 1u
+extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/);
+
+/* --- @tvec_nexttoken@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: Zero if there is a next token which can be read; %$-1$% if no
+ * token is available.
+ *
+ * Use: Advance to the next whitespace-separated token, which may be
+ * on the next line.
+ *
+ * Tokens are separated by non-newline whitespace, comments, and
+ * newlines followed by whitespace; a newline /not/ followed by
+ * whitespace instead begins the next assignment, and two