#include "quis.h"
#include "tvec.h"
+/*----- Preliminaries -----------------------------------------------------*/
+
+/* The control macros I'm using below provoke `dangling-else' warnings from
+ * compilers. Suppress them. I generally don't care.
+ */
+
#if GCC_VERSION_P(7, 1)
# pragma GCC diagnostic ignored "-Wdangling-else"
#elif GCC_VERSION_P(4, 2)
/*----- Basic I/O ---------------------------------------------------------*/
+/* --- @init_comms@ --- *
+ *
+ * Arguments: @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a communication state. This doesn't allocate any
+ * resurces: it just ensures that everything is set up so that
+ * subsequent operations -- in particular @release_comms@ --
+ * behave sensibly.
+ */
+
static void init_comms(struct tvec_remotecomms *rc)
{
- dbuf_create(&rc->bin); dbuf_create(&rc->bout);
+ rc->bin = 0; rc->binsz = 0; dbuf_create(&rc->bout);
rc->infd = rc->outfd = -1; rc->f = 0;
}
+/* --- @close_comms@ --- *
+ *
+ * Arguments: @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns: ---
+ *
+ * Use: Close the input and output descriptors.
+ *
+ * If the descriptors are already closed -- or were never opened
+ * -- then nothing happens.
+ */
+
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; }
}
+/* --- @release_comms@ --- *
+ *
+ * Arguments: @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns: ---
+ *
+ * Use: Releases the resources -- most notably the input and output
+ * buffers -- held by the communication state. Also calls
+ * @close_comms@.
+ */
+
static void release_comms(struct tvec_remotecomms *rc)
- { close_comms(rc); dbuf_destroy(&rc->bin); dbuf_destroy(&rc->bout); }
+ { close_comms(rc); xfree(rc->bin); dbuf_destroy(&rc->bout); }
+
+/* --- @setup_comms@ --- *
+ *
+ * Arguments: @struct tvec_remotecomms *rc@ = communication state
+ * @int infd, outfd@ = input and output file descriptors
+ *
+ * Returns: ---
+ *
+ * Use: Use the given descriptors for communication.
+ *
+ * Clears the private flags.
+ */
static void setup_comms(struct tvec_remotecomms *rc, int infd, int outfd)
{
- rc->infd = infd; rc->outfd = outfd; rc->f &= ~0xffu;
- dbuf_reset(&rc->bin); dbuf_reset(&rc->bout);
+ rc->infd = infd; rc->outfd = outfd;
+ rc->binoff = rc->binlen = 0;
+ rc->f &= ~0xffu;
}
-static int PRINTF_LIKE(3, 4)
- ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
- const char *msg, ...)
+/* --- @ioerr@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ * @const char *msg, ...@ = format string and arguments
+ *
+ * Returns: %$-1$%.
+ *
+ * Use: Reports the message as an error, closes communications and
+ * marks them as broken.
+ */
+
+static PRINTF_LIKE(3, 4)
+ int ioerr(struct tvec_state *tv, struct tvec_remotecomms *rc,
+ const char *msg, ...)
{
va_list ap;
return (-1);
}
+/* --- @send_all@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ * @const unsigned char *p@, @size_t sz@ = output buffer
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Send the output buffer over the communication state's output
+ * descriptor, even if it has to be written in multiple pieces.
+ */
+
static int send_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
const unsigned char *p, size_t sz)
{
- void (*opipe)(int) = SIG_ERR;
ssize_t n;
int ret;
- opipe = signal(SIGPIPE, SIG_IGN);
- if (opipe == SIG_ERR) {
- ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
- goto end;
- }
while (sz) {
n = write(rc->outfd, p, sz);
if (n > 0)
}
ret = 0;
end:
- if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
return (ret);
}
+/* --- @recv_all@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ * @unsigned f@ = flags (@RCVF_...@)
+ * @unsigned char *p@, @size_t sz@ = input buffer
+ * @size_t min@ = minimum acceptable size to read
+ * @size_t *n_out@ = size read
+ *
+ * Returns: An @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
+ * has to be done in multiple pieces. If more data is readily
+ * available, then up to @sz@ bytes will be read in total.
+ *
+ * If the descriptor immediately reports end-of-file, and
+ * @RCVF_ALLOWEOF@ is set in @f@, then return @RECV_EOF@.
+ * Otherwise, EOF is treated as an I/O error, resulting in a
+ * call to @ioerr@ and a return code of @RECV_FAIL@. If the
+ * read succeeded, then set @*n_out@ to the number of bytes read
+ * and return @RECV_OK@.
+ */
+
#define RCVF_ALLOWEOF 1u
+
enum {
RECV_FAIL = -1,
RECV_OK = 0,
RECV_EOF = 1
};
+
static int recv_all(struct tvec_state *tv, struct tvec_remotecomms *rc,
- unsigned char *p, size_t sz, unsigned f)
+ unsigned f, unsigned char *p, size_t sz,
+ size_t min, size_t *n_out)
{
+ size_t tot = 0;
ssize_t n;
- unsigned ff = 0;
-#define f_any 1u
while (sz) {
n = read(rc->infd, p, sz);
- if (n > 0)
- { p += n; sz -= n; ff |= f_any; }
- else if (!n && (f&RCVF_ALLOWEOF) && !(ff&f_any))
+ if (n > 0) {
+ p += n; sz -= n; tot += n;
+ if (tot >= min) break;
+ } else if (!n && !tot && (f&RCVF_ALLOWEOF))
return (RECV_EOF);
else
return (ioerr(tv, rc, "failed to receive: %s",
n ? strerror(errno) : "unexpected end-of-file"));
}
- return (RECV_OK);
+ *n_out = tot; return (RECV_OK);
#undef f_any
}
+/* --- @buferr@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns: %$-$%.
+ *
+ * Use: Report a problem preparing the output buffer.
+ */
+
+static int buferr(struct tvec_state *tv, struct tvec_remotecomms *rc)
+ { return (ioerr(tv, rc, "failed to build output packet")); }
+
+/* --- @malformed@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns: %$-$%.
+ *
+ * Use: Report an I/O error that the incoming packet is malformed.
+ */
+
+static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
+ { return (ioerr(tv, rc, "received malformed packet")); }
+
+/* --- @remote_send@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Send the accuulated contents of the output buffer @rc->bout@.
+ *
+ * The function arranges to convert @SIGPIPE@ into an error.
+ *
+ * If the output buffer is broken, report this as an I/O error.
+ */
+
+#define SENDBUFSZ 4096
+
static int remote_send(struct tvec_state *tv, struct tvec_remotecomms *rc)
{
- kludge64 k; unsigned char lenbuf[8];
- const unsigned char *p; size_t sz;
+ void (*opipe)(int) = SIG_ERR;
+ int ret;
+
+ /* Various preflight checks. */
+ if (rc->f&TVRF_BROKEN) { ret = -1; goto end; }
+ if (DBBAD(&rc->bout)) { ret = buferr(tv, rc); goto end; }
- if (rc->f&TVRF_BROKEN) return (-1);
- if (BBAD(&rc->bout._b))
- return (ioerr(tv, rc, "failed to build output packet buffer"));
+ /* Arrange to trap broken-pipe errors. */
+ opipe = signal(SIGPIPE, SIG_IGN);
+ if (opipe == SIG_ERR) {
+ ret = ioerr(tv, rc, "failed to ignore `SIGPIPE': %s", strerror(errno));
+ goto end;
+ }
- p = BBASE(&rc->bout._b); sz = BLEN(&rc->bout._b);
- ASSIGN64(k, sz); STORE64_L_(lenbuf, k);
- if (send_all(tv, rc, lenbuf, sizeof(lenbuf))) return (-1);
- if (send_all(tv, rc, p, sz)) return (-1);
+ /* Transmit the packet. */
+ if (send_all(tv, rc, DBBASE(&rc->bout), DBLEN(&rc->bout)))
+ { ret = -1; goto end; }
- return (0);
+ /* Done. Put things back the way we found them. */
+ ret = 0;
+end:
+ DBRESET(&rc->bout);
+ if (opipe != SIG_ERR) signal(SIGPIPE, opipe);
+ return (ret);
}
+/* --- @receive_buffered@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ * @unsigned f@ = flags (@RCVF_...@)
+ * @size_t want@ = data block size required
+ *
+ * Returns: An @RECV_...@ code.
+ *
+ * Use: Reads a block of data from the input descriptor into the
+ * input buffer.
+ *
+ * This is the main machinery for manipulating the input buffer.
+ * The buffer has three regions:
+ *
+ * * from the buffer start to @rc->binoff@ is `consumed';
+ * * from @rc->binoff@ to @rc->binlen@ is `available'; and
+ * * from @rc->binlen@ to @rc->binsz@ is `free'.
+ *
+ * Data is read into the start of the `free' region, and the
+ * `available' region is extended to include it. Data in the
+ * `consumed' region is periodically discarded by moving the
+ * data from the `available' region to the start of the buffer
+ * and decreasing @rc->binoff@ and @rc->binlen@.
+ *
+ * This function ensures that the `available' region contains at
+ * least @want@ bytes, by (a) extending the buffer, if
+ * necessary, so that @rc->binsz >= rc->binoff + want@, and (b)
+ * reading fresh data from the input descriptor to extend the
+ * `available' region.
+ *
+ * If absolutely no data is available, and @RCVF_ALLOWEOF@ is
+ * set in @f@, then return @RECV_EOF@. On I/O errors, including
+ * a short read or end-of-file if @RCVF_ALLOWEOF@ is clear,
+ * return @RECV_FAIL@. On success, return @RECV_OK@. The
+ * amount of data read is indicated by updating the input buffer
+ * variables as described above.
+ */
+
+#define RECVBUFSZ 4096u
+
+static int receive_buffered(struct tvec_state *tv,
+ struct tvec_remotecomms *rc,
+ unsigned f, size_t want)
+{
+ size_t sz;
+ int ret;
+
+ /* If we can supply the caller's requirement from the buffer then do
+ * that.
+ */
+ if (rc->binlen - rc->binoff >= want) return (RECV_OK);
+
+ /* If the buffer is too small then we must grow it. */
+ if (want > rc->binsz) {
+ sz = rc->binsz; if (!sz) sz = RECVBUFSZ;
+ while (sz < want) { assert(sz < (size_t)-1/2); sz *= 2; }
+ if (!rc->bin) rc->bin = xmalloc(sz);
+ else rc->bin = xrealloc(rc->bin, sz, rc->binsz);
+ rc->binsz = sz;
+ }
+
+ /* Shunt the unused existing material to the start of the buffer. */
+ memmove(rc->bin, rc->bin + rc->binoff, rc->binlen - rc->binoff);
+ rc->binlen -= rc->binoff; rc->binoff = 0;
+
+ /* Satisfy the caller from the input stream, and try to fill up as much of
+ * the rest of the buffer as we can.
+ */
+ ret = recv_all(tv, rc, rc->binlen ? 0 : f,
+ rc->bin + rc->binlen, rc->binsz - rc->binlen,
+ want - rc->binlen, &sz);
+ if (ret) return (ret);
+
+ /* Note how much material we have and return. */
+ rc->binlen += sz; return (RECV_OK);
+}
+
+/* --- @remote_recv@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned f@ = flags (@RCVF_...@)
+ * @buf *b_out@ = buffer to establish around the packet contents
+ *
+ * Returns: An @RECV_...@ code.
+ *
+ * Use: Receive a packet into the input buffer @rc->bin@ and
+ * establish @*b_out@ to read from it.
+ */
+
static int remote_recv(struct tvec_state *tv, struct tvec_remotecomms *rc,
unsigned f, buf *b_out)
{
- kludge64 k, szmax; unsigned char lenbuf[8];
- unsigned char *p;
- size_t sz;
+ kludge64 k, szmax;
+ size_t want;
int ret;
- if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
ASSIGN64(szmax, (size_t)-1);
- ret = recv_all(tv, rc, lenbuf, sizeof(lenbuf), f);
- if (ret) return (ret);
- LOAD64_L_(k, lenbuf);
+
+ /* Preflight checks. */
+ if (rc->f&TVRF_BROKEN) return (RECV_FAIL);
+
+ /* See if we can read the next packet length from what we already have. */
+ ret = receive_buffered(tv, rc, f, 8); if (ret) return (ret);
+ LOAD64_L_(k, rc->bin + rc->binoff); rc->binoff += 8;
if (CMP64(k, >, szmax))
return (ioerr(tv, rc, "packet size 0x%08lx%08lx out of range",
(unsigned long)HI64(k), (unsigned long)LO64(k)));
+ want = GET64(size_t, k);
- sz = GET64(size_t, k); dbuf_reset(&rc->bin); p = buf_get(&rc->bin._b, sz);
- if (!p) return (ioerr(tv, rc, "failed to allocate receive buffer"));
- if (recv_all(tv, rc, p, sz, 0)) return (RECV_FAIL);
- buf_init(b_out, p, sz); return (RECV_OK);
+ /* Read the next packet payload. */
+ ret = receive_buffered(tv, rc, 0, want); if (ret) return (ret);
+ buf_init(b_out, rc->bin + rc->binoff, want); rc->binoff += want;
+ return (RECV_OK);
}
-#define SENDPK(tv, rc, pk) \
- if ((rc)->f&TVRF_BROKEN) MC_GOELSE(body); else \
- MC_BEFORE(setpk, \
- { dbuf_reset(&(rc)->bout); \
- buf_putu16l(&(rc)->bout._b, (pk)); }) \
- MC_ALLOWELSE(body) \
- MC_AFTER(send, \
- { if (remote_send(tv, rc)) MC_GOELSE(body); }) \
+/* --- @QUEUEPK_TAG@, @QUEUEPK@ --- *
+ *
+ * Arguments: @tag@ = control structure tag
+ * @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_remotecomms *rc@ = communication state
+ * @unsigned fl@ = flags (@QF_...@)
+ * @unsigned pk@ = packet type
+ *
+ * Use: This is syntactically a statement head: the syntax is
+ * @QUEUEPK(tv, rc, f) body [else alt]@. The @body@ should
+ * write material to the output buffer @rc->bout@. The macro
+ * applies appropriate framing. If enough material has been
+ * collected, or if @QF_FORCE@ is set in @fl@, then
+ * @remote_send@ is invoked to transmit the buffered packets.
+ * If there is an error of any kind, then the @alt@ statement,
+ * if any, is executed.
+ */
-static int malformed(struct tvec_state *tv, struct tvec_remotecomms *rc)
- { return (ioerr(tv, rc, "received malformed packet")); }
+#define QF_FORCE 1u
+#define QUEUEPK_TAG(tag, tv, rc, fl, pk) \
+ if ((rc)->f&TVRF_BROKEN) MC_GOELSE(tag##__else); else \
+ MC_ALLOWELSE(tag##__else) \
+ MC_AFTER(tag##__send, { \
+ if ((DBBAD(&(rc)->bout) && (buferr((tv), (rc)), 1)) || \
+ ((((fl)&QF_FORCE) || DBLEN(&(rc)->bout) >= SENDBUFSZ) && \
+ remote_send(tv, rc))) \
+ MC_GOELSE(tag##__else); \
+ }) \
+ DBUF_ENCLOSEITAG(tag##__frame, &(rc)->bout, (rc)->t, 64_L) \
+ MC_BEFORE(tag##__pkty, { \
+ dbuf_putu16l(&(rc)->bout, (pk)); \
+ })
+
+#define QUEUEPK(tv, rc, fl, pk) QUEUEPK_TAG(queue, (tv), (rc), (fl), (pk))
/*----- Packet types ------------------------------------------------------*/
#define TVPF_ACK 0x0001u
-#define TVPK_VER 0x0000u /* --> min, max: u16 */
- /* <-- ver: u16 */
+#define TVPK_VER 0x0000u /* --> min, max: u16 *
+ * <-- ver: u16 */
+#define TVPK_BGROUP 0x0002u /* --> name: str16
+ * <-- --- */
+#define TVPK_TEST 0x0004u /* --> in: regs
+ * <-- --- */
+#define TVPK_EGROUP 0x0006u /* --> --- *
+ * <-- --- */
#define TVPK_REPORT 0x0100u /* <-- level: u16; msg: string */
#define TVPK_PROGRESS 0x0102u /* <-- st: str16 */
-#define TVPK_BGROUP 0x0200u /* --> name: str16
- * <-- --- */
-#define TVPK_TEST 0x0202u /* --> in: regs
- * <-- --- */
-#define TVPK_EGROUP 0x0204u /* --> --- */
-
-#define TVPK_SKIPGRP 0x0300u /* <-- excuse: str16 */
-#define TVPK_SKIP 0x0302u /* <-- excuse: str16 */
-#define TVPK_FAIL 0x0304u /* <-- flag: u8, detail: str16 */
-#define TVPK_DUMPREG 0x0306u /* <-- ri: u16; disp: u16;
+#define TVPK_SKIPGRP 0x0104u /* <-- excuse: str16 */
+#define TVPK_SKIP 0x0106u /* <-- excuse: str16 */
+#define TVPK_FAIL 0x0108u /* <-- flag: u8, detail: str16 */
+#define TVPK_DUMPREG 0x010au /* <-- ri: u16; disp: u16;
* flag: u8, rv: value */
-#define TVPK_BBENCH 0x0308u /* <-- ident: str32; unit: u16 */
-#define TVPK_EBENCH 0x030au /* <-- ident: str32; unit: u16;
+#define TVPK_BBENCH 0x010cu /* <-- ident: str32; unit: u16 */
+#define TVPK_EBENCH 0x010eu /* <-- ident: str32; unit: u16;
* flags: u16; n, t, cy: f64 */
/*----- Server ------------------------------------------------------------*/
+/* Forward declaration of output operations. */
static const struct tvec_outops remote_ops;
-static struct tvec_state srvtv;
-static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT;
-static struct tvec_output srvout = { &remote_ops };
+static struct tvec_state srvtv; /* server's test-vector state */
+static struct tvec_remotecomms srvrc = TVEC_REMOTECOMMS_INIT; /* comms */
+static struct tvec_output srvout = { &remote_ops }; /* output state */
-int tvec_setprogress(const char *status)
+/* --- @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.
+ */
+
+int tvec_setprogress(const char *status, ...)
+{
+ va_list ap;
+ int rc;
+
+ va_start(ap, status); rc = tvec_setprogress_v(status, &ap); va_end(ap);
+ return (rc);
+}
+
+int tvec_setprogress_v(const char *status, va_list *ap)
{
- SENDPK(&srvtv, &srvrc, TVPK_PROGRESS)
- buf_putstr16l(&srvrc.bout._b, status);
+ /* Force immediate output in case we crash before the buffer is output
+ * organically.
+ */
+ QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_PROGRESS)
+ dbuf_vputstrf16l(&srvrc.bout, status, ap);
else return (-1);
return (0);
}
+/* --- @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.
+ */
+
int tvec_remoteserver(int infd, int outfd, const struct tvec_config *config)
{
uint16 pk, u, v;
const struct tvec_test *t;
void *p; size_t sz;
const struct tvec_env *env = 0;
- unsigned f = 0;
-#define f_regslive 1u
void *ctx = 0;
int rc;
+ /* Initialize the communication machinery. */
setup_comms(&srvrc, infd, outfd);
+
+ /* Begin a test session using our custom output driver. */
tvec_begin(&srvtv, config, &srvout);
+ /* Version negotiation. Expect a @TVPK_VER@ packet. At the moment,
+ * there's only version zero, so we return that.
+ */
if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
if (buf_getu16l(&b, &pk)) goto bad;
if (pk != TVPK_VER) {
goto end;
}
if (buf_getu16l(&b, &u) || buf_getu16l(&b, &v)) goto bad;
- SENDPK(&srvtv, &srvrc, TVPK_VER | TVPF_ACK) buf_putu16l(&srvrc.bout._b, 0);
+ QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_VER | TVPF_ACK)
+ dbuf_putu16l(&srvrc.bout, 0);
else { rc = -1; goto end; }
- tvec_setprogress("%IDLE");
-
+ /* Handle packets until the server closes the connection.
+ *
+ * The protocol looks much simpler from our point of view than from the
+ * client.
+ *
+ * * Receive @TVPK_VER@; respond with @TVPK_VER | TVPF_ACK@.
+ *
+ * * Receive zero or more @TVPK_BGROUP@. Open a test group, producing
+ * output packets, and eventually answer with @TVPK_BGROUP | TVPF_ACK@.
+ *
+ * -- Receive zero or more @TVPK_TEST@. Run a test, producing output
+ * packets, and eventually answer with @TVPK_TEST | TVPF_ACK@.
+ *
+ * -- Receive @TVPK_EGROUP@. Maybe produce output packets, and
+ * answer with @TVPK_EGROUP | TVPF_ACK@.
+ *
+ * * Read EOF. Stop.
+ */
for (;;) {
+
+ /* Read a packet. End-of-file is expected here (and pretty much nowhere
+ * else). Otherwise, we expect to see @TVPK_BGROUP@.
+ */
rc = remote_recv(&srvtv, &srvrc, RCVF_ALLOWEOF, &b);
if (rc == RECV_EOF) break;
else if (rc == RECV_FAIL) goto end;
switch (pk) {
case TVPK_BGROUP:
+ /* Start a group. */
+
+ /* Parse the packet payload. */
p = buf_getmem16l(&b, &sz); if (!p) goto bad;
if (BLEFT(&b)) goto bad;
+
+ /* Find the group given its name. */
for (t = srvtv.tests; t->name; t++)
if (strlen(t->name) == sz && MEMCMP(t->name, ==, p, sz))
goto found_group;
goto end;
found_group:
+ /* Set up the test environment. */
srvtv.test = t; env = t->env;
if (env && env->setup == tvec_remotesetup)
env = ((struct tvec_remoteenv *)env)->r.env;
else ctx = xmalloc(env->ctxsz);
if (env && env->setup) env->setup(&srvtv, env, 0, ctx);
- SENDPK(&srvtv, &srvrc, TVPK_BGROUP | TVPF_ACK);
+ /* Initialize the registers. */
+ tvec_initregs(&srvtv);
+
+ /* Report that the group has been opened and that we're ready to run
+ * tests.
+ */
+ QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_BGROUP | TVPF_ACK);
else { rc = -1; goto end; }
+ /* Handle packets until we're told to end the group. */
for (;;) {
+
+ /* Read a packet. We expect @TVPK_EGROUP@ or @TVPK_TEST@. */
if (remote_recv(&srvtv, &srvrc, 0, &b)) { rc = -1; goto end; }
if (buf_getu16l(&b, &pk)) goto bad;
+
switch (pk) {
case TVPK_EGROUP:
+ /* End the group. */
+
+ /* Check the payload. */
if (BLEFT(&b)) goto bad;
+
+ /* Leave the group loop. */
goto endgroup;
case TVPK_TEST:
- tvec_initregs(&srvtv); f |= f_regslive;
+ /* Run a test. */
+
+ /* Parse the packet payload. */
if (tvec_deserialize(srvtv.in, &b, srvtv.test->regs,
srvtv.nreg, srvtv.regsz))
goto bad;
if (BLEFT(&b)) goto bad;
+ /* If we're not skipping the test group, then actually try to
+ * run the test.
+ */
if (!(srvtv.f&TVSF_SKIP)) {
+
+ /* Prepare the output registers and reset the test outcome.
+ * (The environment may force a skip.)
+ */
+ for (i = 0; i < srvtv.nrout; i++)
+ if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
+ TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
srvtv.f |= TVSF_ACTIVE; srvtv.f &= ~TVSF_OUTMASK;
- tvec_setprogress("%SETUP");
+
+ /* Invoke the environment @before@ function. */
+ tvec_setprogress("%%SETUP");
if (env && env->before) env->before(&srvtv, ctx);
+
+ /* Run the actual test. */
if (!(srvtv.f&TVSF_ACTIVE))
/* setup forced a skip */;
else {
- for (i = 0; i < srvtv.nrout; i++)
- if (TVEC_REG(&srvtv, in, i)->f&TVRF_LIVE)
- TVEC_REG(&srvtv, out, i)->f |= TVRF_LIVE;
- tvec_setprogress("%RUN");
+ tvec_setprogress("%%RUN");
if (env && env->run)
env->run(&srvtv, t->fn, ctx);
else {
tvec_check(&srvtv, 0);
}
}
- tvec_setprogress("%DONE");
+
+ /* Conclude the test. */
+ tvec_setprogress("%%DONE");
if (env && env->after) env->after(&srvtv, ctx);
tvec_endtest(&srvtv);
}
- tvec_releaseregs(&srvtv); f &= ~f_regslive;
- SENDPK(&srvtv, &srvrc, TVPK_TEST | TVPF_ACK);
+
+ /* Reset the input registers and report completion. */
+ tvec_releaseregs(&srvtv); tvec_initregs(&srvtv);
+ QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_TEST | TVPF_ACK);
else { rc = -1; goto end; }
- tvec_setprogress("%IDLE");
break;
default:
+ /* Some other kind of packet. Complain. */
+
rc = ioerr(&srvtv, &srvrc,
- "unexpected packet type 0x%04x", pk);
+ "unexpected packet type 0x%04x during test group",
+ pk);
goto end;
}
}
endgroup:
+ /* The test group completed. */
+
+ /* Tear down the environment and release other resources. */
if (env && env->teardown) env->teardown(&srvtv, ctx);
- xfree(ctx); t = 0; env = 0; ctx = 0;
+ tvec_releaseregs(&srvtv);
+ xfree(ctx); srvtv.test = 0; env = 0; ctx = 0;
+
+ /* Report completion. */
+ QUEUEPK(&srvtv, &srvrc, QF_FORCE, TVPK_EGROUP | TVPF_ACK);
+ else { rc = -1; goto end; }
break;
default:
- goto bad;
+ rc = ioerr(&srvtv, &srvrc,
+ "unexpected packet type 0x%04x at top level", pk);
}
}
rc = 0;
end:
+ /* Clean up and return. */
if (env && env->teardown) env->teardown(&srvtv, ctx);
xfree(ctx);
- if (f&f_regslive) tvec_releaseregs(&srvtv);
- release_comms(&srvrc);
+ if (srvtv.test) tvec_releaseregs(&srvtv);
+ release_comms(&srvrc); tvec_end(&srvtv);
return (rc ? 2 : 0);
bad:
+ /* Miscellaneous malformed packet. */
rc = malformed(&srvtv, &srvrc); goto end;
-
-#undef f_regslive
}
/*----- Server output driver ----------------------------------------------*/
static void remote_skipgroup(struct tvec_output *o,
const char *excuse, va_list *ap)
{
- SENDPK(&srvtv, &srvrc, TVPK_SKIPGRP)
- buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIPGRP)
+ dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
}
static void remote_skip(struct tvec_output *o,
const char *excuse, va_list *ap)
{
- SENDPK(&srvtv, &srvrc, TVPK_SKIP)
- buf_vputstrf16l(&srvrc.bout._b, excuse, ap);
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_SKIP)
+ dbuf_vputstrf16l(&srvrc.bout, excuse, ap);
}
static void remote_fail(struct tvec_output *o,
const char *detail, va_list *ap)
{
- SENDPK(&srvtv, &srvrc, TVPK_FAIL)
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_FAIL)
if (!detail)
- buf_putbyte(&srvrc.bout._b, 0);
+ dbuf_putbyte(&srvrc.bout, 0);
else {
- buf_putbyte(&srvrc.bout._b, 1);
- buf_vputstrf16l(&srvrc.bout._b, detail, ap);
+ dbuf_putbyte(&srvrc.bout, 1);
+ dbuf_vputstrf16l(&srvrc.bout, detail, ap);
}
}
assert(!"unexpected register definition");
found:
- SENDPK(&srvtv, &srvrc, TVPK_DUMPREG) {
- buf_putu16l(&srvrc.bout._b, r);
- buf_putu16l(&srvrc.bout._b, disp);
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_DUMPREG) {
+ dbuf_putu16l(&srvrc.bout, r);
+ dbuf_putu16l(&srvrc.bout, disp);
if (!rv)
- buf_putbyte(&srvrc.bout._b, 0);
+ dbuf_putbyte(&srvrc.bout, 0);
else {
- buf_putbyte(&srvrc.bout._b, 1);
- rd->ty->tobuf(&srvrc.bout._b, rv, rd);
+ dbuf_putbyte(&srvrc.bout, 1);
+ rd->ty->tobuf(DBUF_BUF(&srvrc.bout), rv, rd);
}
}
}
static void remote_bbench(struct tvec_output *o,
const char *ident, unsigned unit)
{
- SENDPK(&srvtv, &srvrc, TVPK_BBENCH) {
- buf_putstr32l(&srvrc.bout._b, ident);
- buf_putu16l(&srvrc.bout._b, unit);
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_BBENCH) {
+ dbuf_putstr32l(&srvrc.bout, ident);
+ dbuf_putu16l(&srvrc.bout, unit);
}
}
const char *ident, unsigned unit,
const struct bench_timing *t)
{
- SENDPK(&srvtv, &srvrc, TVPK_EBENCH) {
- buf_putstr32l(&srvrc.bout._b, ident);
- buf_putu16l(&srvrc.bout._b, unit);
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_EBENCH) {
+ dbuf_putstr32l(&srvrc.bout, ident);
+ dbuf_putu16l(&srvrc.bout, unit);
if (!t || !(t->f&BTF_ANY))
- buf_putu16l(&srvrc.bout._b, 0);
+ dbuf_putu16l(&srvrc.bout, 0);
else {
- buf_putu16l(&srvrc.bout._b, t->f);
- buf_putf64l(&srvrc.bout._b, t->n);
- if (t->f&BTF_TIMEOK) buf_putf64l(&srvrc.bout._b, t->t);
- if (t->f&BTF_CYOK) buf_putf64l(&srvrc.bout._b, t->cy);
+ dbuf_putu16l(&srvrc.bout, t->f);
+ dbuf_putf64l(&srvrc.bout, t->n);
+ if (t->f&BTF_TIMEOK) dbuf_putf64l(&srvrc.bout, t->t);
+ if (t->f&BTF_CYOK) dbuf_putf64l(&srvrc.bout, t->cy);
}
}
}
static void remote_report(struct tvec_output *o, unsigned level,
const char *msg, va_list *ap)
{
- const char *what;
-
- SENDPK(&srvtv, &srvrc, TVPK_REPORT) {
- buf_putu16l(&srvrc.bout._b, level);
- buf_vputstrf16l(&srvrc.bout._b, msg, ap);
+ QUEUEPK(&srvtv, &srvrc, 0, TVPK_REPORT) {
+ dbuf_putu16l(&srvrc.bout, level);
+ dbuf_vputstrf16l(&srvrc.bout, msg, ap);
} else {
- switch (level) {
- case TVLEV_NOTE: what = "notice"; break;
- case TVLEV_ERR: what = "ERROR"; break;
- default: what = "(?level)"; break;
- }
- fprintf(stderr, "%s %s: ", QUIS, what);
+ fprintf(stderr, "%s %s: ", QUIS, tvec_strlevel(level));
vfprintf(stderr, msg, *ap);
fputc('\n', stderr);
}
for (;;) {
rc = remote_recv(tv, &r->rc, f, b); if (rc) goto end;
if (buf_getu16l(b, &pk)) goto bad;
+ if (pk == end) break;
switch (pk) {
case TVPK_EBENCH:
p = buf_getmem32l(b, &n); if (!p) goto bad;
if (buf_getu16l(b, &u) || buf_getu16l(b, &v)) goto bad;
- if (u >= TVBU_LIMIT)
- { rc = ioerr(tv, &r->rc, "unit code %u out of range", u); goto end; }
+ if (u >= TVBU_LIMIT) {
+ rc = ioerr(tv, &r->rc, "unit code %u out of range", u);
+ goto end;
+ }
if ((v&BTF_ANY) && buf_getf64l(b, &bt.n)) goto bad;
if ((v&BTF_TIMEOK) && buf_getf64l(b, &bt.t)) goto bad;
if ((v&BTF_CYOK) && buf_getf64l(b, &bt.cy)) goto bad;
break;
default:
- if (pk == end) { rc = 0; goto end; }
rc = ioerr(tv, &r->rc, "unexpected packet type 0x%04x", pk);
goto end;
}
}
+ rc = RECV_OK;
end:
DDESTROY(&d);
xfree(reg);
ssize_t n;
int rc;
+ if (r->errfd < 0) { rc = 0; goto end; }
if (f&ERF_SILENT) r->rc.f |= TVRF_MUFFLE;
else r->rc.f &= ~TVRF_MUFFLE;
if (fdflags(r->errfd, O_NONBLOCK, f&ERF_CLOSE ? 0 : O_NONBLOCK, 0, 0)) {
end:
if (f&ERF_CLOSE) {
lbuf_close(&r->errbuf);
- close(r->errfd);
+ close(r->errfd); r->errfd = -1;
}
return (rc);
}
static void disconnect_remote(struct tvec_state *tv,
struct tvec_remotectx *r, unsigned f)
{
- if (r->kid < 0) return;
if (r->kid > 0 && (f&DCF_KILL)) kill(r->kid, SIGTERM);
close_comms(&r->rc);
- if (r->kid > 0) kill(r->kid, SIGTERM);
drain_errfd(tv, r, f | ERF_CLOSE); reap_kid(tv, r);
}
lbuf_init(&r->errbuf, report_errline, r);
r->exit = TVXST_RUN; r->rc.f &= ~TVRF_BROKEN;
- SENDPK(tv, &r->rc, TVPK_VER) {
- buf_putu16l(&r->rc.bout._b, 0);
- buf_putu16l(&r->rc.bout._b, 0);
+ 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))
goto end;
}
- SENDPK(tv, &r->rc, TVPK_BGROUP)
- buf_putstr16l(&r->rc.bout._b, tv->test->name);
+ 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; }
static void reset_vars(struct tvec_remotectx *r)
{
- r->exwant = TVXST_RUN; r->rc.f = (r->rc.f&~TVRF_RCNMASK) | TVRCN_DEMAND;
+ r->exwant = TVXST_RUN;
+ r->rc.f = (r->rc.f&~(TVRF_RCNMASK | TVRF_SETMASK)) | TVRCN_DEMAND;
DRESET(&r->prgwant); DPUTS(&r->prgwant, "%DONE");
}
reset_vars(r);
}
-int tvec_remoteset(struct tvec_state *tv, const char *var,
- const struct tvec_env *env, void *ctx)
+int tvec_remoteset(struct tvec_state *tv, const char *var, void *ctx)
{
struct tvec_remotectx *r = ctx;
union tvec_regval rv;
int rc;
if (STRCMP(var, ==, "@exit")) {
+ if (r->rc.f&TVRF_SETEXIT) { rc = tvec_dupreg(tv, var); goto end; }
if (tvty_flags.parse(&rv, &exit_regdef, tv)) { rc = -1; goto end; }
- if (r) r->exwant = rv.u;
- rc = 1;
+ 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);
- if (r && !rc)
- { DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz); }
+ if (!rc) {
+ DRESET(&r->prgwant); DPUTM(&r->prgwant, rv.str.p, rv.str.sz);
+ r->rc.f |= TVRF_SETPRG;
+ }
tvty_string.release(&rv, &progress_regdef);
if (rc) { rc = -1; goto end; }
rc = 1;
} else if (STRCMP(var, ==, "@reconnect")) {
+ if (r->rc.f&TVRF_SETRCN) { rc = tvec_dupreg(tv, var); goto end; }
if (tvty_uenum.parse(&rv, &reconn_regdef, tv)) { rc = -1; goto end; }
- if (r) r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK);
+ r->rc.f = (r->rc.f&~TVRF_RCNMASK) | (rv.u&TVRF_RCNMASK) | TVRF_SETRCN;
rc = 1;
} else
rc = 0;
tvec_skip(tv, "no connection"); return;
}
- SENDPK(tv, &r->rc, TVPK_TEST)
- tvec_serialize(tv->in, &r->rc.bout._b,
+ DRESET(&r->progress); DPUTS(&r->progress, "%IDLE");
+ 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; }
rc = handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_TEST | TVPF_ACK, &b);
void tvec_remoteteardown(struct tvec_state *tv, void *ctx)
{
struct tvec_remotectx *r = ctx;
+ buf b;
- if (r) {
- disconnect_remote(tv, r, 0); release_comms(&r->rc);
- DDESTROY(&r->prgwant); DDESTROY(&r->progress);
+ if (r->rc.outfd >= 0) {
+ QUEUEPK(tv, &r->rc, QF_FORCE, TVPK_EGROUP);
+ if (!handle_packets(tv, r, RCVF_ALLOWEOF, TVPK_EGROUP | TVPF_ACK, &b))
+ if (BLEFT(&b)) malformed(tv, &r->rc);
}
+ disconnect_remote(tv, r, 0); release_comms(&r->rc);
+ DDESTROY(&r->prgwant); DDESTROY(&r->progress);
}
/*----- Connectors --------------------------------------------------------*/
if (fork_common(&kid, &infd, &outfd, &errfd, tv)) { rc = -1; goto end; }
if (!kid) {
+ if (tv->fp) fclose(tv->fp);
config.tests = rf->f.tests ? rf->f.tests : tv->tests;
config.nrout = tv->nrout; config.nreg = tv->nreg;
config.regsz = tv->regsz;