X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/blobdiff_plain/db2bf4111cde36048ac66bbac58547d105bc7e80..67b5031ec6d160b5cae425466a34d1be3b211dd4:/test/tvec-output.c?ds=sidebyside diff --git a/test/tvec-output.c b/test/tvec-output.c index c4809bb..9ca4f29 100644 --- a/test/tvec-output.c +++ b/test/tvec-output.c @@ -30,6 +30,7 @@ #include "config.h" #include +#include #include #include #include @@ -39,12 +40,21 @@ #include "alloc.h" #include "bench.h" #include "dstr.h" +#include "macros.h" #include "quis.h" #include "report.h" #include "tvec.h" /*----- Common machinery --------------------------------------------------*/ +/* --- @regdisp@ --- * + * + * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code + * + * Returns: A human-readable adjective describing the register + * disposition. + */ + static const char *regdisp(unsigned disp) { switch (disp) { @@ -57,6 +67,15 @@ static const char *regdisp(unsigned disp) } } +/* --- @getenv_boolean@ --- * + * + * Arguments: @const char *var@ = environment variable name + * @int dflt@ = default value + * + * Returns: @0@ if the variable is set to something falseish, @1@ if it's + * set to something truish, or @dflt@ otherwise. + */ + static int getenv_boolean(const char *var, int dflt) { const char *p; @@ -75,12 +94,19 @@ static int getenv_boolean(const char *var, int dflt) STRCMP(p, ==, "0")) return (0); else { - moan("unexpected value `%s' for boolean environment variable `%s'", + moan("ignoring unexpected value `%s' for environment variable `%s'", var, p); return (dflt); } } +/* --- @register_maxnamelen@ --- * + * + * Arguments: @const struct tvec_state *tv@ = test vector state + * + * Returns: The maximum length of a register name in the current test. + */ + static int register_maxnamelen(const struct tvec_state *tv) { const struct tvec_regdef *rd; @@ -91,6 +117,285 @@ static int register_maxnamelen(const struct tvec_state *tv) return (maxlen); } +/*----- Output formatting -------------------------------------------------*/ + +/* We have two main jobs in output formatting: trimming trailing blanks; and + * adding a prefix to each line. + * + * This is somehow much more complicated than it ought to be. + */ + +struct format { + FILE *fp; /* output file */ + const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */ + dstr w; /* trailing whitespace */ + unsigned f; /* flags */ +#define FMTF_NEWL 1u /* start of output line */ +}; + +/* Support macros. These assume `fmt' is defined as a pointer to the `struct + * format' state. + */ + +#define SPLIT_RANGE(tail, base, limit) do { \ + /* Set TAIL to point just after the last nonspace character between \ + * BASE and LIMIT. If there are no nonspace characters, then set \ + * TAIL to equal BASE. \ + */ \ + \ + for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \ +} while (0) + +#define PUT_RANGE(base, limit) do { \ + /* Write the range of characters between BASE and LIMIT to the output \ + * file. Return immediately on error. \ + */ \ + \ + size_t n = limit - base; \ + if (fwrite(base, 1, n, fmt->fp) < n) return (-1); \ +} while (0) + +#define PUT_CHAR(ch) do { \ + /* Write CH to the output. Return immediately on error. */ \ + \ + if (putc(ch, fmt->fp) == EOF) return (-1); \ +} while (0) + +#define PUT_PREFIX do { \ + /* Output the prefix, if there is one. Return immediately on error. */ \ + \ + if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim); \ +} while (0) + +#define PUT_SAVED do { \ + /* Output the saved trailing blank material in the buffer. */ \ + \ + size_t n = fmt->w.len; \ + if (n && fwrite(fmt->w.buf, 1, n, fmt->fp) < n) return (-1); \ +} while (0) + +#define PUT_PFXINB do { \ + /* Output the initial nonblank portion of the prefix, if there is \ + * one. Return immediately on error. \ + */ \ + \ + if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail); \ +} while (0) + +#define SAVE_PFXTAIL do { \ + /* Save the trailing blank portion of the prefix. */ \ + \ + if (fmt->prefix) \ + DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail); \ +} while (0) + +/* --- @init_fmt@ --- * + * + * Arguments: @struct format *fmt@ = formatting state to initialize + * @FILE *fp@ = output file + * @const char *prefix@ = prefix string (or null if empty) + * + * Returns: --- + * + * Use: Initialize a formatting state. + */ + +static void init_fmt(struct format *fmt, FILE *fp, const char *prefix) +{ + const char *q, *l; + + /* Basics. */ + fmt->fp = fp; + fmt->f = FMTF_NEWL; + dstr_create(&fmt->w); + + /* Prefix portions. */ + if (!prefix || !*prefix) + fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0; + else { + fmt->prefix = prefix; + l = fmt->pfxlim = prefix + strlen(prefix); + SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q; + DPUTM(&fmt->w, q, l - q); + } +} + +/* --- @destroy_fmt@ --- * + * + * Arguments: @struct format *fmt@ = formatting state + * @unsigned f@ = flags (@DFF_...@) + * + * Returns: --- + * + * Use: Releases a formatting state and the resources it holds. + * Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave + * it open (in case it's @stderr@ or something). + */ + +#define DFF_CLOSE 1u +static void destroy_fmt(struct format *fmt, unsigned f) +{ + if (f&DFF_CLOSE) fclose(fmt->fp); + dstr_destroy(&fmt->w); +} + +/* --- @format_char@ --- * + * + * Arguments: @struct format *fmt@ = formatting state + * @int ch@ = character to write + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Write a single character to the output. + */ + +static int format_char(struct format *fmt, int ch) +{ + if (ch == '\n') { + if (fmt->f&FMTF_NEWL) PUT_PFXINB; + PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w); + } else if (isspace(ch)) + DPUTC(&fmt->w, ch); + else { + if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; } + PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w); + } + return (0); +} + +/* --- @format_string@ --- * + * + * Arguments: @struct format *fmt@ = formatting state + * @const char *p@ = string to write + * @size_t sz@ = length of string + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Write a string to the output. + */ + +static int format_string(struct format *fmt, const char *p, size_t sz) +{ + const char *q, *r, *l = p + sz; + + /* This is rather vexing. There are a small number of jobs to do, but the + * logic for deciding which to do when gets rather hairy if, as I've tried + * here, one aims to minimize the number of decisions being checked, so + * it's worth canning them into macros. + * + * Here, a `blank' is a whitespace character other than newline. The input + * buffer consists of one or more `segments', each of which consists of: + * + * * an initial portion, which is either empty or ends with a nonblank + * character; + * + * * a suffix which consists only of blanks; and + * + * * an optional newline. + * + * All segments except the last end with a newline. + */ + +#define SPLIT_SEGMENT do { \ + /* Determine the bounds of the current segment. If there is a final \ + * newline, then q is non-null and points to this newline; otherwise, \ + * q is null. The initial portion of the segment lies between p .. r \ + * and the blank suffix lies between r .. q (or r .. l if q is null). \ + * This sounds awkward, but the suffix is only relevant if there is \ + * no newline. \ + */ \ + \ + q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \ +} while (0) + +#define PUT_NONBLANK do { \ + /* Output the initial portion of the segment. */ \ + \ + PUT_RANGE(p, r); \ +} while (0) + +#define PUT_NEWLINE do { \ + /* Write a newline, and advance to the next segment. */ \ + \ + PUT_CHAR('\n'); p = q + 1; \ +} while (0) + +#define SAVE_TAIL do { \ + /* Save the trailing blank portion of the segment in the buffer. \ + * Assumes that there is no newline, since otherwise the suffix would \ + * be omitted. \ + */ \ + \ + DPUTM(&fmt->w, r, l - r); \ +} while (0) + + /* Determine the bounds of the first segment. Handling this is the most + * complicated part of this function. + */ + SPLIT_SEGMENT; + + if (!q) { + /* This is the only segment. We'll handle the whole thing here. + * + * If there's an initial nonblank portion, then we need to write that + * out. Furthermore, if we're at the start of the line then we'll need + * to write the prefix, and if there's saved blank material then we'll + * need to write that. Otherwise, there's only blank stuff, which we + * accumulate in the buffer. + * + * If we're at the start of a line here, then + */ + + if (r > p) { + if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; } + PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w); + } + SAVE_TAIL; + return (0); + } + + /* There is at least one more segment, so we know that there'll be a line + * to output. + */ + if (fmt->f&FMTF_NEWL) PUT_PFXINB; + if (r > p) { PUT_SAVED; PUT_NONBLANK; } + PUT_NEWLINE; DRESET(&fmt->w); + SPLIT_SEGMENT; + + /* Main loop over whole segments with trailing newlines. For each one, we + * know that we're starting at the beginning of a line and there's a final + * newline, so we write the initial prefix and drop the trailing blanks. + */ + while (q) { + PUT_PREFIX; PUT_NONBLANK; PUT_NEWLINE; + SPLIT_SEGMENT; + } + + /* At the end, there's no final newline. If there's nonblank material, + * then we can write the prefix and the nonblank stuff. Otherwise, stash + * the blank stuff (including the trailing blanks of the prefix) and leave + * the newline flag set. + */ + if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; } + else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; } + SAVE_TAIL; + +#undef SPLIT_SEGMENT +#undef PUT_NONBLANK +#undef PUT_NEWLINE +#undef SAVE_TAIL + + return (0); +} + +#undef SPLIT_RANGE +#undef PUT_RANGE +#undef PUT_PREFIX +#undef PUT_PFXINB +#undef PUT_SAVED +#undef PUT_CHAR +#undef SAVE_PFXTAIL + /*----- Skeleton ----------------------------------------------------------*/ /* static void ..._bsession(struct tvec_output *o, struct tvec_state *tv) @@ -152,7 +457,8 @@ static const struct tvec_outops ..._ops = { struct human_output { struct tvec_output _o; struct tvec_state *tv; - FILE *fp; + struct format fmt; + char *outbuf; size_t outsz; dstr scoreboard; unsigned attr; int maxlen; @@ -178,27 +484,29 @@ static void setattr(struct human_output *h, unsigned attr) int sep = 0; if (!diff || !(h->f&HOF_COLOUR)) return; - fputs("\x1b[", h->fp); + fputs("\x1b[", h->fmt.fp); if (diff&HAF_BOLD) { - if (attr&HAF_BOLD) putc('1', h->fp); - else { putc('0', h->fp); diff = h->attr; } + if (attr&HAF_BOLD) putc('1', h->fmt.fp); + else { putc('0', h->fmt.fp); diff = h->attr; } sep = ';'; } if (diff&(HAF_FG | HAF_FGMASK)) { if (attr&HAF_FG) - set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT); + set_colour(h->fmt.fp, &sep, "3", "9", + (attr&HAF_FGMASK) >> HAF_FGSHIFT); else - { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; } + { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; } } if (diff&(HAF_BG | HAF_BGMASK)) { if (attr&HAF_BG) - set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT); + set_colour(h->fmt.fp, &sep, "4", "10", + (attr&HAF_BGMASK) >> HAF_BGSHIFT); else - { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; } + { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; } } - putc('m', h->fp); h->attr = attr; + putc('m', h->fmt.fp); h->attr = attr; #undef f_any } @@ -209,7 +517,7 @@ static void clear_progress(struct human_output *h) if (h->f&HOF_PROGRESS) { n = strlen(h->tv->test->name) + 2 + h->scoreboard.len; - for (i = 0; i < n; i++) fputs("\b \b", h->fp); + for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp); h->f &= ~HOF_PROGRESS; } } @@ -221,7 +529,7 @@ static void write_scoreboard_char(struct human_output *h, int ch) case '_': setattr(h, HA_SKIP); break; default: setattr(h, 0); break; } - putc(ch, h->fp); setattr(h, 0); + putc(ch, h->fmt.fp); setattr(h, 0); } static void show_progress(struct human_output *h) @@ -230,12 +538,12 @@ static void show_progress(struct human_output *h) const char *p, *l; if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) { - fprintf(h->fp, "%s: ", tv->test->name); + fprintf(h->fmt.fp, "%s: ", tv->test->name); if (!(h->f&HOF_COLOUR)) - dstr_write(&h->scoreboard, h->fp); + dstr_write(&h->scoreboard, h->fmt.fp); else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++) write_scoreboard_char(h, *p); - fflush(h->fp); h->f |= HOF_PROGRESS; + fflush(h->fmt.fp); h->f |= HOF_PROGRESS; } } @@ -247,29 +555,55 @@ static void report_location(struct human_output *h, FILE *fp, #define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0) - if (fp != h->fp) f |= f_flush; + if (fp != h->fmt.fp) f |= f_flush; if (file) { - setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp); - setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp); - setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp); - setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp); - setattr(h, 0); FLUSH(h->fp); fputc(' ', fp); + setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp); + fputs(file, fp); FLUSH(fp); + setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp); + fputc(':', fp); FLUSH(fp); + setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp); + fprintf(fp, "%u", lno); FLUSH(fp); + setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp); + fputc(':', fp); FLUSH(fp); + setattr(h, 0); FLUSH(h->fmt.fp); + fputc(' ', fp); } #undef f_flush #undef FLUSH } +static int human_writech(void *go, int ch) + { struct human_output *h = go; return (format_char(&h->fmt, ch)); } + +static int human_writem(void *go, const char *p, size_t sz) + { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); } + +static int human_nwritef(void *go, size_t maxsz, const char *p, ...) +{ + struct human_output *h = go; + size_t n; + va_list ap; + + va_start(ap, p); + n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap); + va_end(ap); + return (format_string(&h->fmt, h->outbuf, n)); +} + +static const struct gprintf_ops human_printops = + { human_writech, human_writem, human_nwritef }; + static void human_bsession(struct tvec_output *o, struct tvec_state *tv) { struct human_output *h = (struct human_output *)o; h->tv = tv; } static void report_skipped(struct human_output *h, unsigned n) { if (n) { - fprintf(h->fp, " (%u ", n); - setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); - fputc(')', h->fp); + fprintf(h->fmt.fp, " (%u ", n); + setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); + fputc(')', h->fmt.fp); } } @@ -284,28 +618,28 @@ static int human_esession(struct tvec_output *o) all_run = all_win + all_lose, grps_run = grps_win + grps_lose; if (!all_lose) { - setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0); - fprintf(h->fp, " %s%u %s", + setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0); + fprintf(h->fmt.fp, " %s%u %s", !(all_skip || grps_skip) ? "all " : "", all_win, all_win == 1 ? "test" : "tests"); report_skipped(h, all_skip); - fprintf(h->fp, " in %u %s", + fprintf(h->fmt.fp, " in %u %s", grps_win, grps_win == 1 ? "group" : "groups"); report_skipped(h, grps_skip); } else { - setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); - fprintf(h->fp, " %u out of %u %s", + setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0); + fprintf(h->fmt.fp, " %u out of %u %s", all_lose, all_run, all_run == 1 ? "test" : "tests"); report_skipped(h, all_skip); - fprintf(h->fp, " in %u out of %u %s", + fprintf(h->fmt.fp, " in %u out of %u %s", grps_lose, grps_run, grps_run == 1 ? "group" : "groups"); report_skipped(h, grps_skip); } - fputc('\n', h->fp); + fputc('\n', h->fmt.fp); if (tv->f&TVSF_ERROR) { - setattr(h, HA_ERR); fputs("ERRORS", h->fp); setattr(h, 0); - fputs(" found in input; tests may not have run correctly\n", h->fp); + setattr(h, HA_ERR); fputs("ERRORS", h->fmt.fp); setattr(h, 0); + fputs(" found in input; tests may not have run correctly\n", h->fmt.fp); } h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0); @@ -326,13 +660,13 @@ static void human_skipgroup(struct tvec_output *o, if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) { h->f &= ~HOF_PROGRESS; - setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); } else { - fprintf(h->fp, "%s: ", h->tv->test->name); - setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); + fprintf(h->fmt.fp, "%s: ", h->tv->test->name); + setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); } - if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); } - fputc('\n', h->fp); + if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); } + fputc('\n', h->fmt.fp); } static void human_egroup(struct tvec_output *o) @@ -343,17 +677,18 @@ static void human_egroup(struct tvec_output *o) skip = tv->curr[TVOUT_SKIP], run = win + lose; if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS; - else fprintf(h->fp, "%s:", h->tv->test->name); + else fprintf(h->fmt.fp, "%s:", h->tv->test->name); if (lose) { - fprintf(h->fp, " %u/%u ", lose, run); - setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); + fprintf(h->fmt.fp, " %u/%u ", lose, run); + setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0); report_skipped(h, skip); } else { - fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0); + fputc(' ', h->fmt.fp); setattr(h, HA_WIN); + fputs("ok", h->fmt.fp); setattr(h, 0); report_skipped(h, skip); } - fputc('\n', h->fp); + fputc('\n', h->fmt.fp); } static void human_btest(struct tvec_output *o) @@ -366,11 +701,11 @@ static void human_skip(struct tvec_output *o, struct tvec_state *tv = h->tv; clear_progress(h); - report_location(h, h->fp, tv->infile, tv->test_lno); - fprintf(h->fp, "`%s' ", tv->test->name); - setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0); - if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); } - fputc('\n', h->fp); + report_location(h, h->fmt.fp, tv->infile, tv->test_lno); + fprintf(h->fmt.fp, "`%s' ", tv->test->name); + setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0); + if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); } + fputc('\n', h->fmt.fp); } static void human_fail(struct tvec_output *o, @@ -380,11 +715,11 @@ static void human_fail(struct tvec_output *o, struct tvec_state *tv = h->tv; clear_progress(h); - report_location(h, h->fp, tv->infile, tv->test_lno); - fprintf(h->fp, "`%s' ", tv->test->name); - setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0); - if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); } - fputc('\n', h->fp); + report_location(h, h->fmt.fp, tv->infile, tv->test_lno); + fprintf(h->fmt.fp, "`%s' ", tv->test->name); + setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0); + if (detail) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, detail, *ap); } + fputc('\n', h->fmt.fp); } static void human_dumpreg(struct tvec_output *o, @@ -394,15 +729,17 @@ static void human_dumpreg(struct tvec_output *o, struct human_output *h = (struct human_output *)o; const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); - fprintf(h->fp, "%*s%s %s = ", 10 + h->maxlen - n, "", ds, rd->name); + clear_progress(h); + gprintf(&human_printops, h, "%*s%s %s = ", + 10 + h->maxlen - n, "", ds, rd->name); if (h->f&HOF_COLOUR) { if (!rv) setattr(h, HFG(YELLOW)); else if (disp == TVRD_FOUND) setattr(h, HFG(RED)); else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN)); } - if (!rv) fprintf(h->fp, "#"); - else rd->ty->dump(rv, rd, 0, &file_printops, h->fp); - setattr(h, 0); fputc('\n', h->fp); + if (!rv) gprintf(&human_printops, h, "#unset"); + else rd->ty->dump(rv, rd, 0, &human_printops, h); + setattr(h, 0); format_char(&h->fmt, '\n'); } static void human_etest(struct tvec_output *o, unsigned outcome) @@ -419,7 +756,7 @@ static void human_etest(struct tvec_output *o, unsigned outcome) default: abort(); } dstr_putc(&h->scoreboard, ch); - write_scoreboard_char(h, ch); fflush(h->fp); + write_scoreboard_char(h, ch); fflush(h->fmt.fp); } } @@ -430,7 +767,7 @@ static void human_bbench(struct tvec_output *o, struct tvec_state *tv = h->tv; clear_progress(h); - fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp); + fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp); } static void human_ebench(struct tvec_output *o, @@ -438,7 +775,9 @@ static void human_ebench(struct tvec_output *o, const struct bench_timing *tm) { struct human_output *h = (struct human_output *)o; - tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp); + + tvec_benchreport(&human_printops, h->fmt.fp, unit, tm); + fputc('\n', h->fmt.fp); } static void human_report(struct tvec_output *o, const char *msg, va_list *ap) @@ -449,14 +788,14 @@ static void human_report(struct tvec_output *o, const char *msg, va_list *ap) dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n'); - clear_progress(h); fflush(h->fp); + clear_progress(h); fflush(h->fmt.fp); fprintf(stderr, "%s: ", QUIS); report_location(h, stderr, tv->infile, tv->lno); fwrite(d.buf, 1, d.len, stderr); if (h->f&HOF_DUPERR) { - report_location(h, h->fp, tv->infile, tv->lno); - fwrite(d.buf, 1, d.len, h->fp); + report_location(h, h->fmt.fp, tv->infile, tv->lno); + fwrite(d.buf, 1, d.len, h->fmt.fp); } show_progress(h); } @@ -465,9 +804,9 @@ static void human_destroy(struct tvec_output *o) { struct human_output *h = (struct human_output *)o; - if (h->f&HOF_DUPERR) fclose(h->fp); + destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0); dstr_destroy(&h->scoreboard); - xfree(h); + xfree(h->outbuf); xfree(h); } static const struct tvec_outops human_ops = { @@ -487,7 +826,8 @@ struct tvec_output *tvec_humanoutput(FILE *fp) h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops; h->f = 0; h->attr = 0; - h->fp = fp; + init_fmt(&h->fmt, fp, 0); + h->outbuf = 0; h->outsz = 0; switch (getenv_boolean("TVEC_TTY", -1)) { case 1: h->f |= HOF_TTY; break; @@ -517,61 +857,27 @@ struct tvec_output *tvec_humanoutput(FILE *fp) struct tap_output { struct tvec_output _o; struct tvec_state *tv; - FILE *fp; - dstr d; + struct format fmt; + char *outbuf; size_t outsz; int maxlen; - unsigned f; -#define TOF_FRESHLINE 1u }; static int tap_writech(void *go, int ch) -{ - struct tap_output *t = go; - - if (t->f&TOF_FRESHLINE) { - if (fputs("## ", t->fp) < 0) return (-1); - t->f &= ~TOF_FRESHLINE; - } - if (putc(ch, t->fp) < 0) return (-1); - if (ch == '\n') t->f |= TOF_FRESHLINE; - return (1); -} + { struct tap_output *t = go; return (format_char(&t->fmt, ch)); } static int tap_writem(void *go, const char *p, size_t sz) -{ - struct tap_output *t = go; - const char *q, *l = p + sz; - size_t n; - - if (p == l) return (0); - if (t->f&TOF_FRESHLINE) - if (fputs("## ", t->fp) < 0) return (-1); - for (;;) { - q = memchr(p, '\n', l - p); if (!q) break; - n = q + 1 - p; if (fwrite(p, 1, n, t->fp) < n) return (-1); - p = q + 1; - if (p == l) { t->f |= TOF_FRESHLINE; return (sz); } - if (fputs("## ", t->fp) < 0) return (-1); - } - n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1); - t->f &= ~TOF_FRESHLINE; return (0); -} + { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); } static int tap_nwritef(void *go, size_t maxsz, const char *p, ...) { - struct tap_output *t = go; + struct human_output *t = go; + size_t n; va_list ap; - int n; - - va_start(ap, p); DRESET(&t->d); DENSURE(&t->d, maxsz + 1); -#ifdef HAVE_SNPRINTF - n = vsnprintf(t->d.buf, maxsz + 1, p, ap); -#else - n = vsprintf(t->d.buf, p, ap); -#endif - assert(0 <= n && n <= maxsz); + + va_start(ap, p); + n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap); va_end(ap); - return (tap_writem(t, t->d.buf, n)); + return (format_string(&t->fmt, t->outbuf, n)); } static const struct gprintf_ops tap_printops = @@ -582,7 +888,7 @@ static void tap_bsession(struct tvec_output *o, struct tvec_state *tv) struct tap_output *t = (struct tap_output *)o; t->tv = tv; - fputs("TAP version 13\n", t->fp); + fputs("TAP version 13\n", t->fmt.fp); } static unsigned tap_grpix(struct tap_output *t) @@ -602,11 +908,11 @@ static int tap_esession(struct tvec_output *o) if (tv->f&TVSF_ERROR) { fputs("Bail out! " "Errors found in input; tests may not have run correctly\n", - t->fp); + t->fmt.fp); return (2); } - fprintf(t->fp, "1..%u\n", tap_grpix(t)); + fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t)); t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0); } @@ -621,9 +927,9 @@ static void tap_skipgroup(struct tvec_output *o, { struct tap_output *t = (struct tap_output *)o; - fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name); - if (excuse) { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); } - fputc('\n', t->fp); + fprintf(t->fmt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name); + if (excuse) { fputc(' ', t->fmt.fp); vfprintf(t->fmt.fp, excuse, *ap); } + fputc('\n', t->fmt.fp); } static void tap_egroup(struct tvec_output *o) @@ -637,39 +943,37 @@ static void tap_egroup(struct tvec_output *o) skip = tv->curr[TVOUT_SKIP]; if (lose) { - fprintf(t->fp, "not ok %u - %s: FAILED %u/%u", + fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u", grpix, tv->test->name, lose, win + lose); - if (skip) fprintf(t->fp, " (skipped %u)", skip); + if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip); } else { - fprintf(t->fp, "ok %u - %s: passed %u", grpix, tv->test->name, win); - if (skip) fprintf(t->fp, " (skipped %u)", skip); + fprintf(t->fmt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, win); + if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip); } - fputc('\n', t->fp); + fputc('\n', t->fmt.fp); } static void tap_btest(struct tvec_output *o) { ; } -static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap) +static void tap_outcome(struct tvec_output *o, const char *outcome, + const char *detail, va_list *ap) { struct tap_output *t = (struct tap_output *)o; struct tvec_state *tv = t->tv; - fprintf(t->fp, "## %s:%u: `%s' skipped", - tv->infile, tv->test_lno, tv->test->name); - if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); } - fputc('\n', t->fp); + gprintf(&tap_printops, t, "%s:%u: `%s' %s", + tv->infile, tv->test_lno, tv->test->name, outcome); + if (detail) { + format_string(&t->fmt, ": ", 2); + vgprintf(&tap_printops, t, detail, ap); + } + format_char(&t->fmt, '\n'); } +static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap) + { tap_outcome(o, "skipped", excuse, ap); } static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap) -{ - struct tap_output *t = (struct tap_output *)o; - struct tvec_state *tv = t->tv; - - fprintf(t->fp, "## %s:%u: `%s' FAILED", - tv->infile, tv->test_lno, tv->test->name); - if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); } - fputc('\n', t->fp); -} + { tap_outcome(o, "FAILED", detail, ap); } static void tap_dumpreg(struct tvec_output *o, unsigned disp, const union tvec_regval *rv, @@ -678,14 +982,11 @@ static void tap_dumpreg(struct tvec_output *o, struct tap_output *t = (struct tap_output *)o; const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name); - fprintf(t->fp, "## %*s%s %s = ", 10 + t->maxlen - n, "", ds, rd->name); - if (!rv) - fprintf(t->fp, "#\n"); - else { - t->f &= ~TOF_FRESHLINE; - rd->ty->dump(rv, rd, 0, &tap_printops, t); - fputc('\n', t->fp); - } + gprintf(&tap_printops, t, "%*s%s %s = ", + 10 + t->maxlen - n, "", ds, rd->name); + if (!rv) gprintf(&tap_printops, t, "#"); + else rd->ty->dump(rv, rd, 0, &tap_printops, t); + format_char(&t->fmt, '\n'); } static void tap_etest(struct tvec_output *o, unsigned outcome) { ; } @@ -701,38 +1002,43 @@ static void tap_ebench(struct tvec_output *o, struct tap_output *t = (struct tap_output *)o; struct tvec_state *tv = t->tv; - fprintf(t->fp, "## %s: %s: ", tv->test->name, ident); - t->f &= ~TOF_FRESHLINE; tvec_benchreport(&tap_printops, t, unit, tm); - fputc('\n', t->fp); + gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident); + tvec_benchreport(&tap_printops, t, unit, tm); + format_char(&t->fmt, '\n'); } -static void tap_report(struct tap_output *t, const char *msg, va_list *ap) +static void tap_report(struct tap_output *t, + const struct gprintf_ops *gops, void *go, + const char *msg, va_list *ap) { struct tvec_state *tv = t->tv; - if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno); - vfprintf(t->fp, msg, *ap); fputc('\n', t->fp); + if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno); + gprintf(gops, go, msg, ap); gops->putch(go, '\n'); } static void tap_error(struct tvec_output *o, const char *msg, va_list *ap) { struct tap_output *t = (struct tap_output *)o; - fputs("Bail out! ", t->fp); tap_report(t, msg, ap); + + fputs("Bail out! ", t->fmt.fp); + tap_report(t, &file_printops, t->fmt.fp, msg, ap); } static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap) { struct tap_output *t = (struct tap_output *)o; - fputs("## ", t->fp); tap_report(t, msg, ap); + + tap_report(t, &tap_printops, t, msg, ap); } static void tap_destroy(struct tvec_output *o) { struct tap_output *t = (struct tap_output *)o; - if (t->fp != stdout && t->fp != stderr) fclose(t->fp); - dstr_destroy(&t->d); - xfree(t); + destroy_fmt(&t->fmt, + t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE); + xfree(t->outbuf); xfree(t); } static const struct tvec_outops tap_ops = { @@ -749,8 +1055,8 @@ struct tvec_output *tvec_tapoutput(FILE *fp) struct tap_output *t; t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops; - dstr_create(&t->d); - t->f = 0; t->fp = fp; + init_fmt(&t->fmt, fp, "## "); + t->outbuf = 0; t->outsz = 0; return (&t->_o); }