.PP
A
.B "struct bench_time"
-representats a single instant in time,
+represents a single instant in time,
as captured by a timer's
.B now
function.
test_parserr([text], [],
[4], [syntax error: expected string but found @%:@eof])
-test_parse([text], [""], [""])
-test_parse([text], [''], [""])
+test_parse([bytes], [@%:@empty], [@%:@empty ; = ""])
+test_parse([bytes], [""], [@%:@empty ; = ""])
+test_parse([bytes], [''], [@%:@empty ; = ""])
test_parse([text], ["f\x{6f}o"], [foo])
###--------------------------------------------------------------------------
AT_SETUP([tvec type-bytes])
-test_parse([bytes], [""], ["" ; empty])
+test_parse([bytes], [@%:@empty], [@%:@empty ; = ""])
+test_parse([bytes], [""], [@%:@empty ; = ""])
test_parse([bytes], [61], [61 ; a])
test_parse([bytes], ["abc"], [616263 ; abc])
test_parse([bytes], ["abcd"], [61626364 ; abcd])
.PP
.ta \w'\fBvoid tvec_benchreport('u
.BI "void tvec_benchreport(const struct gprintf_ops *" gops ", void *" go ,
-.BI " unsigned " unit ", const struct bench_timing *" tm );
+.BI " unsigned " unit ", unsigned " style ,
+.BI " const struct bench_timing *" tm );
.fi
* Arguments: @const struct gprintf_ops *gops@ = print operations
* @void *go@ = print destination
* @unsigned unit@ = the unit being measured (~TVBU_...@)
+ * @unsigned style@ = output style (@TVSF_...@)
* @const struct bench_timing *tm@ = the benchmark result
*
* Returns: ---
*/
void tvec_benchreport(const struct gprintf_ops *gops, void *go,
- unsigned unit, const struct bench_timing *tm)
+ unsigned unit, unsigned style,
+ const struct bench_timing *tm)
{
double scale, x, n = tm->n;
const char *u, *what, *whats;
- if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; }
+ if (!tm) {
+ if (style&TVSF_RAW) gprintf(gops, go, "FAILED");
+ else gprintf(gops, go, "benchmark FAILED");
+ return;
+ }
assert(tm->f&BTF_TIMEOK);
switch (unit) {
case TVBU_OP:
- gprintf(gops, go, "%.0f iterations ", n);
+ if (style&TVSF_RAW) gprintf(gops, go, "ops=%.0f", n);
+ else 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);
+ if (style&TVSF_RAW)
+ gprintf(gops, go, "bytes=%.0f", n);
+ else {
+ x = n;
+ normalize(&x, &u, 1024); gprintf(gops, go, " %.3f %sB ", x, u);
+ }
what = whats = "B"; scale = 1024;
break;
default:
abort();
}
+ if (style&TVSF_RAW) {
+ gprintf(gops, go, " sec=%.6g", tm->t);
+ if (tm->f&BTF_CYOK) gprintf(gops, go, " cy=%.0f", tm->cy);
+ return;
+ }
+
x = tm->t; normalize(&x, &u, 1000);
gprintf(gops, go, "in %.3f %ss", x, u);
if (tm->f&BTF_CYOK) {
} else {
/* Some non-whitespace thing. */
+ /* If there's no test, then report an error. Set the muffle flag,
+ * because there's no point in complaining about every assignment
+ * in this block.
+ */
+ if (!tv->test) {
+ if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test");
+ tv->f |= TVSF_MUFFLE; goto flush_line;
+ }
+
/* Put the character back and read a word, which ought to be a
* register name.
*/
if (tvec_readword(tv, &d, 0, "=:*;", "register name"))
goto flush_line;
+ /* Open the test. This is syntactically a paragraph of settings,
+ * so it's fair to report on missing register assignments.
+ */
+ open_test(tv);
+
/* See what sort of thing we have found. */
if (d.buf[0] == '@') {
/* A special register assignment. */
{ tvec_dupregerr(tv, rd->name); goto flush_line; }
}
- /* If there's no test, then report an error. Set the muffle flag,
- * because there's no point in complaining about every assignment
- * in this block.
- */
- if (!tv->test) {
- if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test");
- tv->f |= TVSF_MUFFLE; goto flush_line;
- }
-
- /* Open the test. This is syntactically a paragraph of settings,
- * so it's fair to report on missing register assignments.
- */
- open_test(tv);
-
/* Now there should be a separator. */
tvec_skipspc(tv); ch = getc(tv->fp);
struct tvec_output *(*makefn)(FILE *fp);
} outtab[] = {
{ "human", tvec_humanoutput },
+ { "machine", tvec_machineoutput },
{ "tap", tvec_tapoutput },
{ 0, 0 }
};
.BI " unsigned " disp ", const union tvec_regval *" rv ,
.BI " const struct tvec_regdef *" rd );
.BI " void (*" etest ")(struct tvec_output *" o ", unsigned " outcome );
-.ta 2n +\w'\fBvoid (*\,\fIbbench\/\fB)('u
-.BI " void (*" bbench ")(struct tvec_output *" o ,
-.BI " const char *" ident ", unsigned " unit );
+.BI " void (*" bbench ")(struct tvec_output *" o ", unsigned " unit );
.ta 2n +\w'\fBvoid (*\,\fIebench\/\fB)('u
-.BI " void (*" ebench ")(struct tvec_output *" o ,
-.BI " const char *" ident ", unsigned " unit ,
+.BI " void (*" ebench ")(struct tvec_output *" o ", unsigned " unit ,
.BI " const struct bench_timing *" tm );
.ta 2n +\w'\fBvoid (*\,\fIreport\/\fB)('u
.BI " void (*" report ")(struct tvec_output *" o ", unsigned " level ,
case TVRD_INPUT: return "input";
case TVRD_OUTPUT: return "output";
case TVRD_MATCH: return "matched";
- case TVRD_EXPECT: return "expected";
case TVRD_FOUND: return "found";
+ case TVRD_EXPECT: return "expected";
default: abort();
}
}
static void human_btest(struct tvec_output *o)
{ struct human_output *h = (struct human_output *)o; show_progress(h); }
-/* --- @report_location@ --- *
+/* --- @human_report_location@ --- *
*
* Arguments: @struct human_output *h@ = output state
* @FILE *fp@ = stream to write the location on
* nothing.
*/
-static void report_location(struct human_output *h, FILE *fp,
- const char *file, unsigned lno)
+static void human_report_location(struct human_output *h, FILE *fp,
+ const char *file, unsigned lno)
{
unsigned f = 0;
#define f_flush 1u
struct tvec_state *tv = h->tv;
clear_progress(h);
- report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
+ human_report_location(h, h->lyt.fp, tv->infile, tv->test_lno);
fprintf(h->lyt.fp, "`%s' ", tv->test->name);
setattr(h, attr); fputs(outcome, h->lyt.fp); setattr(h, HA_PLAIN);
if (detail) { fputs(": ", h->lyt.fp); vfprintf(h->lyt.fp, detail, *ap); }
struct tvec_state *tv = h->tv;
clear_progress(h);
- fprintf(h->lyt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->lyt.fp);
+ gprintf(&human_printops, h, "%s %s: ", tv->test->name, ident);
+ if (h->f&HOF_TTY) fflush(h->lyt.fp);
}
/* --- @human_ebench@ --- *
{
struct human_output *h = (struct human_output *)o;
- tvec_benchreport(&human_printops, h, unit, tm);
- fputc('\n', h->lyt.fp);
+ tvec_benchreport(&human_printops, h, unit, 0, tm);
+ layout_char(&h->lyt, '\n');
}
/* --- @human_report@ --- *
if (h->f^HOF_PROGRESS)
{ clear_progress(h); fflush(h->lyt.fp); f |= f_progress; }
fprintf(stderr, "%s: ", QUIS);
- report_location(h, stderr, tv->infile, tv->lno);
+ human_report_location(h, stderr, tv->infile, tv->lno);
setattr(h, levattr); FLUSH; fputs(levstr, stderr); setattr(h, 0); FLUSH;
fputs(": ", stderr); fwrite(d.buf, 1, d.len, stderr);
#undef FLUSH
if (h->f&HOF_DUPERR) {
- report_location(h, h->lyt.fp, tv->infile, tv->lno);
+ human_report_location(h, h->lyt.fp, tv->infile, tv->lno);
fprintf(h->lyt.fp, "%s: ", levstr);
fwrite(d.buf, 1, d.len, h->lyt.fp);
}
return (&h->_o);
}
+/*----- Machine-readable output -------------------------------------------*/
+
+struct machine_output {
+ struct tvec_output _o; /* output base class */
+ struct tvec_state *tv; /* stashed testing state */
+ FILE *fp; /* output stream */
+ char *outbuf; size_t outsz; /* buffer for formatted output */
+ unsigned grpix, testix; /* group and test indices */
+ unsigned f; /* flags */
+#define MF_BENCH 1u
+};
+
+/* --- @machine_writech@, @machine_write@, @machine_writef@ --- *
+ *
+ * Arguments: @void *go@ = output sink, secretly a @struct machine_output@
+ * @int ch@ = character to write
+ * @const char *@p@, @size_t sz@ = string (with explicit length)
+ * to write
+ * @const char *p, ...@ = format control string and arguments to
+ * write
+ *
+ * Returns: ---
+ *
+ * Use: Write characters, strings, or formatted strings to the
+ * output, applying appropriate quoting.
+ */
+
+static void machine_escape(struct machine_output *m, int ch)
+{
+ switch (ch) {
+ case 0: fputs("\\0", m->fp); break;
+ case '\a': fputs("\\a", m->fp); break;
+ case '\b': fputs("\\b", m->fp); break;
+ case '\x1b': fputs("\\e", m->fp); break;
+ case '\f': fputs("\\f", m->fp); break;
+ case '\n': fputs("\\n", m->fp); break;
+ case '\r': fputs("\\r", m->fp); break;
+ case '\t': fputs("\\t", m->fp); break;
+ case '\v': fputs("\\v", m->fp); break;
+ case '"': fputs("\\\"", m->fp); break;
+ case '\\': fputs("\\\\", m->fp); break;
+ default: fprintf(m->fp, "\\x{%02x}", ch);
+ }
+}
+
+static int machine_writech(void *go, int ch)
+{
+ struct machine_output *m = go;
+
+ if (ISPRINT(ch)) putc(ch, m->fp);
+ else machine_escape(m, ch);
+ return (1);
+}
+
+static int machine_writem(void *go, const char *p, size_t sz)
+{
+ struct machine_output *m = go;
+ const char *q, *l = p + sz;
+
+ for (;;) {
+ q = p;
+ for (;;) {
+ if (q >= l) goto final;
+ if (!ISPRINT(*q) || *q == '\\' || *q == '"') break;
+ q++;
+ }
+ if (p < q) fwrite(p, 1, q - p, m->fp);
+ p = q; machine_escape(m, (unsigned char)*p++);
+ }
+final:
+ if (p < l) fwrite(p, 1, l - p, m->fp);
+ return (sz);
+}
+
+static int machine_nwritef(void *go, size_t maxsz, const char *p, ...)
+{
+ struct machine_output *m = go;
+ int n;
+ va_list ap;
+
+ va_start(ap, p);
+ n = gprintf_memputf(&m->outbuf, &m->outsz, maxsz, p, ap);
+ va_end(ap);
+ return (machine_writem(m, m->outbuf, n));
+}
+
+static const struct gprintf_ops machine_printops =
+ { machine_writech, machine_writem, machine_nwritef };
+
+/* --- @machine_maybe_quote@ --- *
+ *
+ * Arguments: @struct machine_output *m@ = output sink
+ * @const char *p@ = pointer to string
+ *
+ * Returns: ---
+ *
+ * Use: Print the string @p@, quoting it if necessary.
+ */
+
+static void machine_maybe_quote(struct machine_output *m, const char *p)
+{
+ const char *q;
+
+ for (q = p; *q; q++)
+ if (!ISPRINT(*q)) goto quote;
+ fputs(p, m->fp); return;
+quote:
+ putc('"', m->fp); machine_writem(m, p, strlen(p)); putc('"', m->fp);
+}
+
+/* --- @machine_bsession@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @struct tvec_state *tv@ = the test state producing output
+ *
+ * Returns: ---
+ *
+ * Use: Begin a test session.
+ *
+ * The TAP driver records the test state for later reference,
+ * initializes the group index counter, and prints the version
+ * number.
+ */
+
+static void machine_bsession(struct tvec_output *o, struct tvec_state *tv)
+{
+ struct machine_output *m = (struct machine_output *)o;
+
+ m->tv = tv; m->grpix = 0;
+}
+
+/* --- @machine_show_stats@ --- *
+ *
+ * Arguments: @struct machine_output *m@ = output sink
+ * @unsigned *out[TVOUT_LIMIT]@ = outcome counter table
+ *
+ * Returns: ---
+ *
+ * Use: Print a machine readable outcome statistics table
+ */
+
+static void machine_show_stats(struct machine_output *m,
+ unsigned out[TVOUT_LIMIT])
+{
+ static const char *outtab[] = { "lose", "skip", "xfail", "win" };
+ unsigned i;
+
+ for (i = 0; i < TVOUT_LIMIT; i++) {
+ if (i) putc(' ', m->fp);
+ fprintf(m->fp, "%s=%u", outtab[i], out[i]);
+ }
+}
+
+/* --- @machine_esession@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ *
+ * Returns: Suggested exit code.
+ *
+ * Use: End a test session.
+ *
+ * The TAP driver prints a final summary of the rest results
+ * and returns a suitable exit code. If errors occurred, it
+ * instead prints a `Bail out!' line forcing the reader to
+ * report a failure.
+ */
+
+static int machine_esession(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("END groups: ", m->fp);
+ machine_show_stats(m, tv->grps);
+ fputs("; tests: ", m->fp);
+ machine_show_stats(m, tv->all);
+ putc('\n', m->fp);
+ m->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
+}
+
+/* --- @machine_bgroup@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Begin a test group.
+ *
+ * The TAP driver determines the length of the longest
+ * register name, resets the group progress scoreboard, and
+ * activates the progress display.
+ */
+
+static void machine_bgroup(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("BGROUP ", m->fp);
+ machine_maybe_quote(m, tv->test->name);
+ putc('\n', m->fp);
+ m->grpix++; m->testix = 0;
+}
+
+/* --- @machine_skipgroup@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @const char *excuse@, @va_list *ap@ = reason for skipping the
+ * group, or null
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test group is being skipped.
+ *
+ * The TAP driver just reports the situation to its output
+ * stream.
+ */
+
+static void machine_skipgroup(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("BGROUP ", m->fp);
+ machine_maybe_quote(m, tv->test->name);
+ if (excuse) {
+ fputs(" \"", m->fp);
+ vgprintf(&machine_printops, m, excuse, ap);
+ putc('\"', m->fp);
+ }
+ putc('\n', m->fp);
+}
+
+/* --- @machine_egroup@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test group has finished.
+ *
+ * The TAP driver reports a summary of the group's tests.
+ */
+
+static void machine_egroup(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fputs("EGROUP ", m->fp); machine_maybe_quote(m, tv->test->name);
+ putc(' ', m->fp); machine_show_stats(m, tv->curr);
+ putc('\n', m->fp);
+}
+
+/* --- @machine_btest@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test is starting.
+ *
+ * The TAP driver advances its test counter. (We could do this
+ * by adding up up the counters in @tv->curr@, and add on the
+ * current test, but it's easier this way.)
+ */
+
+static void machine_btest(struct tvec_output *o) { ; }
+
+/* --- @machine_report_location@ --- *
+ *
+ * Arguments: @struct human_output *h@ = output state
+ * @const char *file@ = filename
+ * @unsigned lno@ = line number
+ *
+ * Returns: ---
+ *
+ * Use: Print the filename and line number to the output stream.
+ */
+
+static void machine_report_location(struct machine_output *m,
+ const char *file, unsigned lno)
+ { machine_maybe_quote(m, file); fprintf(m->fp, ":%u", lno); }
+
+/* --- @machine_outcome@, @machine_skip@, @machine_fail@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @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.
+ */
+
+static void machine_outcome(struct tvec_output *o, const char *outcome,
+ const char *detail, va_list *ap)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fprintf(m->fp, "%s %u ", outcome, m->testix);
+ machine_report_location(m, tv->infile, tv->test_lno);
+ if (detail)
+ { putc(' ', m->fp); vgprintf(&machine_printops, m, detail, ap); }
+ putc('\n', m->fp);
+}
+
+static void machine_skip(struct tvec_output *o,
+ const char *excuse, va_list *ap)
+ { machine_outcome(o, "SKIP", excuse, ap); }
+static void machine_fail(struct tvec_output *o,
+ const char *detail, va_list *ap)
+ { machine_outcome(o, "FAIL", detail, ap); }
+
+/* --- @machine_dumpreg@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @unsigned disp@ = register disposition
+ * @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns: ---
+ *
+ * Use: Dump a register.
+ *
+ * The TAP driver applies highlighting to mismatching output
+ * registers, but otherwise delegates to the register type
+ * handler and the layout machinery. The result is that the
+ * register dump is marked as a comment and indented.
+ */
+
+static void machine_dumpreg(struct tvec_output *o,
+ unsigned disp, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ unsigned f = 0;
+#define f_reg 1u
+#define f_nl 2u
+
+ switch (disp) {
+ case TVRD_INPUT: fputs("\tINPUT ", m->fp); f |= f_reg | f_nl; break;
+ case TVRD_OUTPUT: fputs("\tOUTPUT ", m->fp); f |= f_reg | f_nl; break;
+ case TVRD_MATCH: fputs("\tMATCH ", m->fp); f |= f_reg | f_nl; break;
+ case TVRD_FOUND: fputs("\tMISMATCH ", m->fp); f |= f_reg; break;
+ case TVRD_EXPECT: fputs(" /= ", m->fp); f |= f_nl; break;
+ default: abort();
+ }
+
+ if (f&f_reg) fprintf(m->fp, "%s = ", rd->name);
+ rd->ty->dump(rv, rd, TVSF_RAW, &file_printops, m->fp);
+ if (f&f_nl) putc('\n', m->fp);
+
+#undef f_reg
+#undef f_newline
+}
+
+/* --- @machine_etest@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @unsigned outcome@ = the test outcome
+ *
+ * Returns: ---
+ *
+ * Use: Report that a test has finished.
+ *
+ * The TAP driver reports the outcome of the test, if that's not
+ * already decided.
+ */
+
+static void machine_etest(struct tvec_output *o, unsigned outcome)
+{
+ struct machine_output *m = (struct machine_output *)o;
+
+ if (!(m->f&MF_BENCH)) switch (outcome) {
+ case TVOUT_WIN: machine_outcome(o, "WIN", 0, 0); break;
+ case TVOUT_XFAIL: machine_outcome(o, "XFAIL", 0, 0); break;
+ }
+ m->testix++; m->f &= ~MF_BENCH;
+}
+
+/* --- @machine_bbench@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @const char *ident@ = identifying register values
+ * @unsigned unit@ = measurement unit (@TVBU_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Report that a benchmark has started.
+ *
+ * The TAP driver does nothing here. All of the reporting
+ * happens in @machine_ebench@.
+ */
+
+static void machine_bbench(struct tvec_output *o,
+ const char *ident, unsigned unit)
+ { ; }
+
+/* --- @machine_ebench@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @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 TAP driver just delegates to the default benchmark
+ * reporting, via the layout machinery so that the result is
+ * printed as a comment.
+ */
+
+static void machine_ebench(struct tvec_output *o,
+ const char *ident, unsigned unit,
+ const struct bench_timing *tm)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ struct tvec_state *tv = m->tv;
+
+ fprintf(m->fp, "BENCH %u ", m->testix);
+ machine_report_location(m, tv->infile, tv->test_lno);
+ fprintf(m->fp, " %s: ", ident);
+ tvec_benchreport(&file_printops, m->fp, unit, TVSF_RAW, tm);
+ putc('\n', m->fp); m->f |= MF_BENCH;
+}
+
+/* --- @machine_report@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ * @unsigned level@ = message level (@TVLEV_...@)
+ * @const char *msg@, @va_list *ap@ = format string and
+ * arguments
+ *
+ * Returns: ---
+ *
+ * Use: Report a message to the user.
+ *
+ * Messages are reported as comments, so that they can be
+ * accumulated by the reader. An error will cause a later
+ * bailout or, if we crash before then, a missing plan line,
+ * either of which will cause the reader to report a serious
+ * problem.
+ */
+
+static void machine_report(struct tvec_output *o, unsigned level,
+ const char *msg, va_list *ap)
+{
+ struct machine_output *m = (struct machine_output *)o;
+ const char *p;
+
+ for (p = tvec_strlevel(level); *p; p++) putc(TOUPPER(*p), m->fp);
+ putc(' ', m->fp);
+ putc('"', m->fp);
+ vgprintf(&machine_printops, m, msg, ap);
+ putc('"', m->fp);
+ putc('\n', m->fp);
+}
+
+/* --- @machine_destroy@ --- *
+ *
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
+ * machine_output@
+ *
+ * Returns: ---
+ *
+ * Use: Release the resources held by the output driver.
+ */
+
+static void machine_destroy(struct tvec_output *o)
+{
+ struct machine_output *m = (struct machine_output *)o;
+
+ if (m->fp != stdout && m->fp != stderr) fclose(m->fp);
+ xfree(m->outbuf); xfree(m);
+}
+
+static const struct tvec_outops machine_ops = {
+ machine_bsession, machine_esession,
+ machine_bgroup, machine_skipgroup, machine_egroup,
+ machine_btest, machine_skip, machine_fail, machine_dumpreg, machine_etest,
+ machine_bbench, machine_ebench,
+ machine_report,
+ machine_destroy
+};
+
+/* --- @tvec_machineoutput@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file to write on
+ *
+ * Returns: An output formatter.
+ *
+ * Use: Return an output formatter which writes on @fp@ in a
+ * moderately simple machine-readable format.
+ */
+
+struct tvec_output *tvec_machineoutput(FILE *fp)
+{
+ struct machine_output *m;
+
+ m = xmalloc(sizeof(*m)); m->_o.ops = &machine_ops;
+ m->fp = fp; m->outbuf = 0; m->outsz = 0; m->testix = 0;
+ return (&m->_o);
+}
+
/*----- Perl's `Test Anything Protocol' -----------------------------------*/
struct tap_output {
*
* Arguments: @struct tvec_output *o@ = output sink, secretly a @struct
* tap_output@
- * @unsigned attr@ = attribute to apply to the outcome
- * @const char *outcome@ = outcome string to report
+ * @const char *head, *tail@ = outcome strings to report
* @const char *detail@, @va_list *ap@ = a detail message
* @const char *excuse@, @va_list *ap@ = reason for skipping the
* test
struct tvec_state *tv = t->tv;
set_layout_prefix(&t->lyt, " ## ");
- gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
- tvec_benchreport(&tap_printops, t, unit, tm);
+ gprintf(&tap_printops, t, "%s %s: ", tv->test->name, ident);
+ tvec_benchreport(&tap_printops, t, unit, 0, tm);
layout_char(&t->lyt, '\n');
}
.BI " const struct gprintf_ops *" gops ", void *" go );
.B "};"
.B "#define TVSF_COMPACT ..."
+.B "#define TVSF_RAW ..."
.PP
.ta \w'\fBint tvec_syntax('u
.BI "int tvec_syntax(struct tvec_state *" tv ", int " ch ,
{
const char *unit;
- if (!u || u%1024)
+ if (style&TVSF_RAW)
+ gprintf(gops, go, "%lu", u);
+ else if (!u || u%1024)
gprintf(gops, go, "%lu%sB", u, style&TVSF_COMPACT ? "" : " ");
else {
for (unit = size_units, u /= 1024;
if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
cdc = 0;
DRESET(&w); tvec_readword(tv, &w, 0, ";", "character name");
+ if (STRCMP(w.buf, ==, "#empty")) break;
if (read_charname(&ch, w.buf, RCF_EOFOK)) {
rc = tvec_error(tv, "unknown character name `%s'", d.buf);
goto end;
unsigned style,
const struct gprintf_ops *gops, void *go)
{
-
+ if (style&TVSF_RAW) gprintf(gops, go, "int:");
gprintf(gops, go, "%ld", rv->i);
- if (!(style&TVSF_COMPACT)) {
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
gprintf(gops, go, " ; = ");
format_signed_hex(gops, go, rv->i);
maybe_format_signed_char(gops, go, rv->i);
unsigned style,
const struct gprintf_ops *gops, void *go)
{
+ if (style&TVSF_RAW) gprintf(gops, go, "uint:");
gprintf(gops, go, "%lu", rv->u);
- if (!(style&TVSF_COMPACT)) {
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
gprintf(gops, go, " ; = ");
format_unsigned_hex(gops, go, rv->u);
maybe_format_unsigned_char(gops, go, rv->u);
unsigned style,
const struct gprintf_ops *gops, void *go)
{
+ if (style&TVSF_RAW) gprintf(gops, go, "size:");
format_size(gops, go, rv->u, style);
- if (!(style&TVSF_COMPACT)) {
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
gprintf(gops, go, " ; = %lu", (unsigned long)rv->u);
gprintf(gops, go, " = "); format_unsigned_hex(gops, go, rv->u);
maybe_format_unsigned_char(gops, go, rv->u);
const struct tvec_regdef *rd,
unsigned style,
const struct gprintf_ops *gops, void *go)
- { format_floating(gops, go, rv->f); }
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "float:");
+ format_floating(gops, go, rv->f);
+}
/* Floating-point type definition. */
const struct tvec_regty tvty_float = {
const struct duration_unit *u;
double t = rv->f;
- if (!t) u = 0;
- else {
- for (u = duration_units; u->scale > t && u[1].unit; u++);
- t /= u->scale;
- }
-
- gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");
- if (!(style&TVSF_COMPACT)) {
- gprintf(gops, go, "; = ");
+ if (style&TVSF_RAW) {
+ gprintf(gops, go, "duration:");
format_floating(gops, go, rv->f);
- gprintf(gops, go, " s");
+ gprintf(gops, go, "s");
+ } else {
+ if (!t) u = 0;
+ else {
+ for (u = duration_units; u->scale > t && u[1].unit; u++);
+ t /= u->scale;
+ }
+ gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");
+
+ if (!(style&TVSF_COMPACT)) {
+ gprintf(gops, go, "; = ");
+ format_floating(gops, go, rv->f);
+ gprintf(gops, go, " s");
+ }
}
}
dstr d = DSTR_INIT; \
int rc; \
\
- if (tvec_readword(tv, &d, 0, ";", "enumeration tag or " LITSTR_##tag_)) \
+ if (tvec_readword(tv, &d, 0, \
+ ";", "%s tag or " LITSTR_##tag_, ei->name)) \
{ rc = -1; goto end; } \
for (a = ei->av; a->tag; a++) \
if (STRCMP(a->tag, ==, d.buf)) { FOUND_##tag_ goto done; } \
const struct tvec_##slot##enuminfo *ei = rd->arg.p; \
const struct tvec_##slot##assoc *a; \
\
+ if (style&TVSF_RAW) gprintf(gops, go, #slot "enum/%s:", ei->name); \
for (a = ei->av; a->tag; a++) \
if (rv->slot == a->slot) { \
gprintf(gops, go, "%s", a->tag); \
/* Read the next item. */
DRESET(&d);
- if (tvec_readword(tv, &d, 0, "|;", "flag name or integer"))
+ if (tvec_readword(tv, &d, 0, "|;", "%s flag name or integer", fi->name))
{ rc = -1; goto end; }
/* Try to find a matching entry in the table. */
if (tvec_nexttoken(tv)) break;
ch = getc(tv->fp);
if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; }
- if (tvec_nexttoken(tv))
- { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; }
+ if (tvec_nexttoken(tv)) {
+ tvec_syntax(tv, '\n', "%s flag name or integer", fi->name);
+ rc = -1; goto end;
+ }
}
/* Done. */
unsigned long m = ~0ul, v = rv->u;
const char *sep;
+ if (style&TVSF_RAW) gprintf(gops, go, "flags/%s:", fi->name);
+
for (f = fi->fv, sep = ""; f->tag; f++)
if ((m&f->m) && (v&f->m) == f->v) {
gprintf(gops, go, "%s%s", sep, f->tag); m &= ~f->m;
if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m);
- if (m != ~0ul && !(style&TVSF_COMPACT))
+ if (m != ~0ul && !(style&(TVSF_COMPACT | TVSF_RAW)))
gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
}
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);
- if (style&TVSF_COMPACT) return;
- else { gprintf(gops, go, " ;"); f |= f_semi; }
- }
+ if (style&TVSF_RAW) {
+ /* Print the raw character unconditionally in single quotes. */
- /* 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) {
- case ' ': case '\\': case '\'': quote:
- format_char(gops, go, rv->i);
- break;
- default:
- if (!(style&TVSF_COMPACT) || !isprint(rv->i)) goto quote;
- gprintf(gops, go, "%c", (int)rv->i);
- return;
+ gprintf(gops, go, "char:'");
+ format_char(gops, go, rv->i);
+ gprintf(gops, go, "'");
+ } else {
+ /* Print ina pleasant human-readable way. */
+
+ /* 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);
+ if (style&TVSF_COMPACT) return;
+ else { gprintf(gops, go, " ;"); f |= f_semi; }
}
- }
- /* And the character code as an integer. */
- if (!(style&TVSF_COMPACT)) {
- if (!(f&f_semi)) gprintf(gops, go, " ;");
- gprintf(gops, go, " = %ld = ", rv->i);
- format_signed_hex(gops, go, rv->i);
+ /* 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) {
+ case ' ': case '\\': case '\'': quote:
+ format_char(gops, go, rv->i);
+ break;
+ default:
+ if (!(style&TVSF_COMPACT) || !isprint(rv->i)) goto quote;
+ gprintf(gops, go, "%c", (int)rv->i);
+ return;
+ }
+ }
+
+ /* And the character code as an integer. */
+ if (!(style&TVSF_COMPACT)) {
+ if (!(f&f_semi)) gprintf(gops, go, " ;");
+ gprintf(gops, go, " = %ld = ", rv->i);
+ format_signed_hex(gops, go, rv->i);
+ }
}
#undef f_semi
* 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 %|""|%.
+ * Empty strings are dumped as %|#empty|%.
*/
+static void dump_empty(const char *ty, unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "%s:", ty);
+ if (!(style&TVSF_COMPACT)) gprintf(gops, go, "#empty");
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) gprintf(gops, go, " ; = ");
+ if (!(style&TVSF_RAW)) gprintf(gops, go, "\"\"");
+}
+
+
static void dump_text(const union tvec_regval *rv,
const struct tvec_regdef *rd,
unsigned style,
#define f_nonword 1u
#define f_newline 2u
- if (!rv->text.sz) { gprintf(gops, go, "\"\""); return; }
+ if (!rv->text.sz) { dump_empty("text", style, gops, go); return; }
p = (const unsigned char *)rv->text.p; l = p + rv->text.sz;
+ if (style&TVSF_RAW) { gprintf(gops, go, "text:"); goto quote; }
+ else if (style&TVSF_COMPACT) goto quote;
+
switch (*p) {
case '!': case '#': case ';': case '"': case '\'':
case '(': case '{': case '[': case ']': case '}': case ')':
}
for (q = p; q < l; q++)
if (*q == '\n' && q != l - 1) f |= f_newline;
- else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
+ else if (!*q || !ISGRAPH(*q) || *q == '\\') f |= f_nonword;
if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; }
else if (f&f_nonword) goto quote;
quote:
gprintf(gops, go, "\"");
for (q = p; q < l; q++)
- if (!isprint(*q) || *q == '"') {
+ if (!ISPRINT(*q) || *q == '"') {
if (p < q) gops->putm(go, (const char *)p, q - p);
if (*q != '\n' || (style&TVSF_COMPACT))
format_charesc(gops, go, *q, FCF_BRACE);
unsigned i, n;
int wd;
- if (!sz) {
- gprintf(gops, go, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
- return;
- }
+ if (!rv->text.sz) { dump_empty("bytes", style, gops, go); return; }
- if (style&TVSF_COMPACT) {
+ if (style&(TVSF_COMPACT | TVSF_RAW)) {
while (p < l) gprintf(gops, go, "%02x", *p++);
return;
}
{
format_size(gops, go, rv->buf.sz, style);
if (rv->buf.m) {
- gprintf(gops, go, style&TVSF_COMPACT ? "@" : " @ ");
+ gprintf(gops, go, style&(TVSF_COMPACT | TVSF_RAW) ? "@" : " @ ");
format_size(gops, go, rv->buf.m, style);
if (rv->buf.a) {
- gprintf(gops, go, style&TVSF_COMPACT ? "+" : " + ");
+ gprintf(gops, go, style&(TVSF_COMPACT | TVSF_RAW) ? "+" : " + ");
format_size(gops, go, rv->buf.a, style);
}
}
.BI "int tvec_read(struct tvec_state *" tv ", const char *" infile ", FILE *" fp );
.PP
.BI "extern struct tvec_output *tvec_humanoutput(FILE *" fp );
+.BI "extern struct tvec_output *tvec_machineoutput(FILE *" fp );
.BI "extern struct tvec_output *tvec_tapoutput(FILE *" fp );
.BI "extern struct tvec_output *tvec_dfltoutput(FILE *" fp );
.fi
unsigned /*style*/,
const struct gprintf_ops */*gops*/, void */*go*/);
#define TVSF_COMPACT 1u
+#define TVSF_RAW 2u
/* Write a human-readable representation of the value @*rv@ using
* @gprintf@ on @gops@ and @go@. The @style@ is a collection of flags:
* if @TVSF_COMPACT@ is set, then output should be minimal, and must fit
* on a single line; otherwise, output may consist of multiple lines and
* may contain redundant information if that is likely to be useful to a
- * human reader.
+ * human reader. If @TVSF_RAW@ is set, then output should prefer
+ * machine-readability over human-readability.
*/
};
TVOUT_LOSE, /* test failed */
TVOUT_SKIP, /* test skipped */
- TVOUT_WIN, /* test passed */
TVOUT_XFAIL, /* test passed, but shouldn't have */
+ TVOUT_WIN, /* test passed */
TVOUT_LIMIT /* (number of possible outcomes) */
};
* Arguments: @const struct gprintf_ops *gops@ = print operations
* @void *go@ = print destination
* @unsigned unit@ = the unit being measured (~TVBU_...@)
+ * @unsigned style@ = output style (@TVSF_...@)
* @const struct bench_timing *tm@ = the benchmark result
*
* Returns: ---
extern void tvec_benchreport
(const struct gprintf_ops */*gops*/, void */*go*/,
- unsigned /*unit*/, const struct bench_timing */*tm*/);
+ unsigned /*unit*/, unsigned /*style*/, const struct bench_timing */*tm*/);
/*----- Remote execution --------------------------------------------------*/
/* --- @tvec_humanoutput@ --- *
*
* Arguments: @FILE *fp@ = output file to write on
+ * @unsigned style@ = output style (@TVSF_...@)
*
* Returns: An output formatter.
*
extern struct tvec_output *tvec_humanoutput(FILE */*fp*/);
+/* --- @tvec_machineoutput@ --- *
+ *
+ * Arguments: @FILE *fp@ = output file to write on
+ *
+ * Returns: An output formatter.
+ *
+ * Use: Return an output formatter which writes on @fp@ in a
+ * moderately simple machine-readable format.
+ */
+
+struct tvec_output *tvec_machineoutput(FILE *fp);
+
/* --- @tvec_tapoutput@ --- *
*
* Arguments: @FILE *fp@ = output file to write on
+ * @unsigned style@ = output style (@TVSF_...@)
*
* Returns: An output formatter.
*