3 * Main test vector driver
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
12 * mLib is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Library General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or (at
15 * your option) any later version.
17 * mLib is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 * License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
28 /*----- Header files ------------------------------------------------------*/
38 /*----- Output ------------------------------------------------------------*/
40 /* --- @tvec_report@, @tvec_report_v@ --- *
42 * Arguments: @struct tvec_state *tv@ = test-vector state
43 * @const char *msg@, @va_list ap@ = error message
47 * Use: Report an message with a given severity. Messages with level
48 * @TVLEV_ERR@ or higher force a nonzero exit code.
51 void tvec_report(struct tvec_state *tv, unsigned level, const char *msg, ...)
55 va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap);
58 void tvec_report_v(struct tvec_state *tv, unsigned level,
59 const char *msg, va_list *ap)
61 tv->output->ops->report(tv->output, level, msg, ap);
62 if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR;
65 /* --- @tvec_error@, @tvec_notice@ --- *
67 * Arguments: @struct tvec_state *tv@ = test-vector state
68 * @const char *msg@, @va_list ap@ = error message
70 * Returns: The @tvec_error@ function returns @-1@ as a trivial
71 * convenience; @tvec_notice@ does not return a value.
73 * Use: Report an error or a notice. Errors are distinct from test
74 * failures, and indicate that a problem was encountered which
75 * compromised the activity of testing. Notices are important
76 * information which doesn't fit into any other obvious
80 int tvec_error(struct tvec_state *tv, const char *msg, ...)
84 va_start(ap, msg); tvec_report_v(tv, TVLEV_ERR, msg, &ap); va_end(ap);
88 void tvec_notice(struct tvec_state *tv, const char *msg, ...)
92 va_start(ap, msg); tvec_report_v(tv, TVLEV_NOTE, msg, &ap); va_end(ap);
95 /*----- Test processing ---------------------------------------------------*/
97 void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
101 va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
103 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
105 if (!(tv->f&TVSF_SKIP)) {
106 tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
107 tv->output->ops->skipgroup(tv->output, excuse, ap);
111 static void set_outcome(struct tvec_state *tv, unsigned out)
113 tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK);
114 tv->f |= out << TVSF_OUTSHIFT;
117 void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
120 va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
122 void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
124 assert(tv->f&TVSF_ACTIVE);
125 set_outcome(tv, TVOUT_SKIP);
126 tv->output->ops->skip(tv->output, excuse, ap);
129 void tvec_fail(struct tvec_state *tv, const char *detail, ...)
132 va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
134 void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
136 assert((tv->f&TVSF_ACTIVE) ||
137 (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT));
138 set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap);
141 void tvec_dumpreg(struct tvec_state *tv,
142 unsigned disp, const union tvec_regval *r,
143 const struct tvec_regdef *rd)
144 { tv->output->ops->dumpreg(tv->output, disp, r, rd); }
146 void tvec_mismatch(struct tvec_state *tv, unsigned f)
148 const struct tvec_regdef *rd;
149 const struct tvec_reg *rin, *rout;
151 for (rd = tv->test->regs; rd->name; rd++) {
152 if (rd->i >= tv->nrout) {
153 if (!(f&TVMF_IN)) continue;
154 rin = TVEC_REG(tv, in, rd->i);
155 tvec_dumpreg(tv, TVRD_INPUT, rin->f&TVRF_LIVE ? &rin->v : 0, rd);
157 if (!(f&TVMF_OUT)) continue;
158 rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
159 if (!(rin->f&TVRF_LIVE))
160 tvec_dumpreg(tv, TVRD_OUTPUT, rout->f&TVRF_LIVE ? &rout->v : 0, rd);
161 else if ((rout->f&TVRF_LIVE) && rd->ty->eq(&rin->v, &rout->v, rd))
162 tvec_dumpreg(tv, TVRD_MATCH, &rin->v, rd);
164 tvec_dumpreg(tv, TVRD_FOUND, rout->f&TVRF_LIVE ? &rout->v : 0, rd);
165 tvec_dumpreg(tv, TVRD_EXPECT, &rin->v, rd);
171 /*----- Parsing -----------------------------------------------------------*/
173 int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
177 va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
180 int tvec_syntax_v(struct tvec_state *tv, int ch,
181 const char *expect, va_list *ap)
187 case EOF: strcpy(found, "#eof"); break;
188 case '\n': strcpy(found, "#eol"); ungetc(ch, tv->fp); break;
190 if (isprint(ch)) sprintf(found, "`%c'", ch);
191 else sprintf(found, "#\\x%02x", ch);
194 dstr_vputf(&d, expect, ap);
195 tvec_error(tv, "syntax error: expected %s but found %s", d.buf, found);
196 dstr_destroy(&d); return (-1);
199 void tvec_skipspc(struct tvec_state *tv)
205 if (ch == EOF) break;
206 else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; }
210 int tvec_flushtoeol(struct tvec_state *tv, unsigned f)
217 case '\n': tv->lno++; return (rc);
218 case EOF: return (rc);
219 case ';': f |= TVFF_ALLOWANY; break;
221 if (!(f&TVFF_ALLOWANY) && !isspace(ch)) {
222 tvec_syntax(tv, ch, "end-of-line");
223 rc = -1; f |= TVFF_ALLOWANY;
230 int tvec_nexttoken(struct tvec_state *tv)
232 enum { TAIL, NEWLINE, INDENT, COMMENT };
247 if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); }
248 else { tv->lno++; s = NEWLINE; }
253 { if (s == NEWLINE) s = INDENT; }
254 else if (s != COMMENT) {
256 if (s == NEWLINE) return (-1);
264 int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
265 const char *expect, ...)
270 va_start(ap, expect);
271 rc = tvec_readword_v(tv, d, delims, expect, &ap);
275 int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
276 const char *expect, va_list *ap)
281 if (!ch || ch == '\n' || ch == EOF || ch == ';' ||
282 (delims && strchr(delims, ch))) {
283 if (expect) return (tvec_syntax(tv, ch, expect, ap));
284 else { ungetc(ch, tv->fp); return (-1); }
286 if (d->len) DPUTC(d, ' ');
290 } while (ch && ch != EOF && !isspace(ch) &&
291 (!delims || !strchr(delims, ch)));
292 DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
296 /*----- Main machinery ----------------------------------------------------*/
301 #define GROUPSTATE_INIT { 0 }
303 void tvec_resetoutputs(struct tvec_state *tv)
305 const struct tvec_regdef *rd;
308 for (rd = tv->test->regs; rd->name; rd++) {
309 assert(rd->i < tv->nreg);
310 if (rd->i >= tv->nrout) continue;
311 r = TVEC_REG(tv, out, rd->i);
312 rd->ty->release(&r->v, rd);
313 rd->ty->init(&r->v, rd);
314 r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE;
318 void tvec_initregs(struct tvec_state *tv)
320 const struct tvec_regdef *rd;
323 for (rd = tv->test->regs; rd->name; rd++) {
324 assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
325 rd->ty->init(&r->v, rd); r->f = 0;
326 if (rd->i < tv->nrout)
327 { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
331 void tvec_releaseregs(struct tvec_state *tv)
333 const struct tvec_regdef *rd;
336 for (rd = tv->test->regs; rd->name; rd++) {
337 assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
338 rd->ty->release(&r->v, rd); r->f = 0;
339 if (rd->i < tv->nrout)
340 { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
344 int tvec_checkregs(struct tvec_state *tv)
346 const struct tvec_regdef *rd;
347 const struct tvec_reg *rin, *rout;
349 for (rd = tv->test->regs; rd->name; rd++) {
350 if (rd->i >= tv->nrout) continue;
351 rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
352 if (!rin->f&TVRF_LIVE) continue;
353 if (!(rout->f&TVRF_LIVE) || !rd->ty->eq(&rin->v, &rout->v, rd))
359 void tvec_check(struct tvec_state *tv, const char *detail, ...)
362 va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap);
364 void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
366 if (tvec_checkregs(tv))
367 { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
370 static void begin_test(struct tvec_state *tv)
372 tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK;
373 tv->output->ops->btest(tv->output);
376 void tvec_endtest(struct tvec_state *tv)
380 if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN;
381 else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
382 assert(out < TVOUT_LIMIT); tv->curr[out]++;
383 tv->output->ops->etest(tv->output, out);
387 static void check(struct tvec_state *tv, struct groupstate *g)
389 const struct tvec_test *t = tv->test;
390 const struct tvec_env *env;
391 const struct tvec_regdef *rd;
393 if (!(tv->f&TVSF_OPEN)) return;
395 for (rd = t->regs; rd->name; rd++) {
396 if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE)
397 { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; }
398 else if (!(rd->f&TVRF_OPT)) {
399 tvec_error(tv, "required register `%s' not set in test `%s'",
405 if (!(tv->f&TVSF_SKIP)) {
408 if (env && env->before) env->before(tv, g->ctx);
409 if (!(tv->f&TVSF_ACTIVE))
410 /* setup forced a skip */;
411 else if (env && env->run)
412 env->run(tv, t->fn, g->ctx);
414 t->fn(tv->in, tv->out, g->ctx);
417 if (env && env->after) env->after(tv, g->ctx);
422 tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
425 static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
427 const struct tvec_test *t = tv->test;
428 const struct tvec_env *env = t->env;
431 tv->output->ops->bgroup(tv->output);
434 for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
435 if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
436 if (env && env->setup) env->setup(tv, env, 0, g->ctx);
439 static void report_group(struct tvec_state *tv)
441 unsigned i, out, nrun;
443 for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
444 { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
446 if (tv->curr[TVOUT_SKIP] == nrun)
447 { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
449 if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
450 else out = TVOUT_WIN;
451 tv->grps[out]++; tv->output->ops->egroup(tv->output);
455 static void end_test_group(struct tvec_state *tv, struct groupstate *g)
457 const struct tvec_test *t = tv->test;
458 const struct tvec_env *env;
461 if (tv->f&TVSF_OPEN) check(tv, g);
462 if (!(tv->f&TVSF_SKIP)) report_group(tv);
463 env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx);
464 tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
467 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
470 const struct tvec_test *test;
471 const struct tvec_env *env;
472 const struct tvec_regdef *rd;
474 struct groupstate g = GROUPSTATE_INIT;
477 tv->infile = infile; tv->lno = 1; tv->fp = fp;
487 end_test_group(tv, &g);
489 DRESET(&d); tvec_readword(tv, &d, "];", "group name");
491 ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
492 for (test = tv->tests; test->name; test++)
493 if (STRCMP(d.buf, ==, test->name)) goto found_test;
494 tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line;
496 tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv, &g);
501 if (tv->f&TVSF_OPEN) check(tv, &g);
508 if (ch == EOF) goto end;
509 else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY);
510 else if (tvec_flushtoeol(tv, 0)) rc = -1;
512 } else if (ch == ';')
513 tvec_flushtoeol(tv, TVFF_ALLOWANY);
517 if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line;
518 tvec_skipspc(tv); ch = getc(tv->fp);
519 if (ch != '=' && ch != ':')
520 { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; }
523 { tvec_error(tv, "no current test"); goto flush_line; }
524 if (d.buf[0] == '@') {
526 if (!env || !env->set) ret = 0;
527 else ret = env->set(tv, d.buf, env, g.ctx);
530 tvec_error(tv, "unknown special register `%s'", d.buf);
533 if (!(tv->f&TVSF_OPEN))
534 { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
536 for (rd = tv->test->regs; rd->name; rd++)
537 if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
538 tvec_error(tv, "unknown register `%s' for test `%s'",
539 d.buf, tv->test->name);
542 if (!(tv->f&TVSF_OPEN))
543 { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
545 r = TVEC_REG(tv, in, rd->i);
546 if (r->f&TVRF_LIVE) {
547 tvec_error(tv, "register `%s' already set", rd->name);
550 if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
559 tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1;
562 { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
564 end_test_group(tv, &g);
565 tv->infile = 0; tv->fp = 0;
570 /*----- Session lifecycle -------------------------------------------------*/
572 void tvec_begin(struct tvec_state *tv_out,
573 const struct tvec_config *config,
574 struct tvec_output *o)
580 assert(config->nrout <= config->nreg);
581 tv_out->nrout = config->nrout; tv_out->nreg = config->nreg;
582 tv_out->regsz = config->regsz;
583 tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz);
584 tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz);
585 for (i = 0; i < tv_out->nreg; i++) {
586 TVEC_REG(tv_out, in, i)->f = 0;
587 if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0;
590 for (i = 0; i < TVOUT_LIMIT; i++)
591 tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
593 tv_out->tests = config->tests; tv_out->test = 0;
594 tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
595 tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out);
598 int tvec_end(struct tvec_state *tv)
600 int rc = tv->output->ops->esession(tv->output);
602 tv->output->ops->destroy(tv->output);
603 xfree(tv->in); xfree(tv->out);
607 /*----- Serialization and deserialization ---------------------------------*/
609 int tvec_serialize(const struct tvec_reg *rv, buf *b,
610 const struct tvec_regdef *regs,
611 unsigned nr, size_t regsz)
613 unsigned char *bitmap;
614 size_t i, bitoff, nbits, bitsz;
615 const struct tvec_regdef *rd;
616 const struct tvec_reg *r;
619 for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
620 bitsz = (nbits + 7)/8;
622 bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
623 memset(bitmap, 0, bitsz);
624 for (rd = regs, i = 0; rd->name; rd++, i++) {
625 if (rd->i >= nr) continue;
626 r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
627 bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8;
628 if (rd->ty->tobuf(b, &r->v, rd)) return (-1);
633 int tvec_deserialize(struct tvec_reg *rv, buf *b,
634 const struct tvec_regdef *regs,
635 unsigned nr, size_t regsz)
637 const unsigned char *bitmap;
638 size_t i, nbits, bitsz;
639 const struct tvec_regdef *rd;
642 for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
643 bitsz = (nbits + 7)/8;
645 bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
646 for (rd = regs, i = 0; rd->name; rd++, i++) {
647 if (rd->i >= nr) continue;
648 if (!(bitmap[i/8]&(1 << i%8))) continue;
649 r = TVEC_GREG(rv, rd->i, regsz);
650 if (rd->ty->frombuf(b, &r->v, rd)) return (-1);
656 /*----- Ad-hoc testing ----------------------------------------------------*/
658 static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
660 static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
661 { assert(!"fake test function"); }
663 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
665 t->name = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
669 void tvec_begingroup(struct tvec_state *tv, const char *name,
670 const char *file, unsigned lno)
672 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
674 t->name = name; tv->test = t;
675 tv->infile = file; tv->lno = tv->test_lno = lno;
676 begin_test_group(tv, 0);
679 void tvec_endgroup(struct tvec_state *tv)
681 if (!(tv->f&TVSF_SKIP)) report_group(tv);
685 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
687 tv->infile = file; tv->lno = tv->test_lno = lno;
688 begin_test(tv); tv->f |= TVSF_OPEN;
694 const char *saved_file; unsigned saved_lno;
697 static void adhoc_claim_setup(struct tvec_state *tv,
698 struct adhoc_claim *ck,
699 const struct tvec_regdef *regs,
700 const char *file, unsigned lno)
702 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
706 if (!(tv->f&TVSF_OPEN))
707 { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
709 ck->saved_file = tv->infile; if (file) tv->infile = file;
710 ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno;
711 t->regs = regs ? regs : &no_regs;
714 static void adhoc_claim_teardown(struct tvec_state *tv,
715 struct adhoc_claim *ck)
717 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
720 tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
722 if (ck->f&ACF_FRESH) tvec_endtest(tv);
725 int tvec_claim_v(struct tvec_state *tv, int ok,
726 const char *file, unsigned lno,
727 const char *msg, va_list *ap)
729 struct adhoc_claim ck;
731 adhoc_claim_setup(tv, &ck, 0, file, lno);
732 if (!ok) tvec_fail_v(tv, msg, ap);
733 adhoc_claim_teardown(tv, &ck);
737 int tvec_claim(struct tvec_state *tv, int ok,
738 const char *file, unsigned lno, const char *msg, ...)
742 va_start(ap, msg); tvec_claim_v(tv, ok, file, lno, msg, &ap); va_end(ap);
746 int tvec_claimeq(struct tvec_state *tv,
747 const struct tvec_regty *ty, const union tvec_misc *arg,
748 const char *file, unsigned lno, const char *expr)
750 struct tvec_regdef regs[2];
751 struct adhoc_claim ck;
754 tv->in[0].f = tv->out[0].f = TVRF_LIVE;
756 regs[0].name = "value"; regs[0].i = 0;
757 regs[0].ty = ty; regs[0].f = 0;
758 if (arg) regs[0].arg = *arg;
759 else regs[0].arg.p = 0;
763 adhoc_claim_setup(tv, &ck, regs, file, lno);
764 ok = ty->eq(&tv->in[0].v, &tv->out[0].v, ®s[0]);
766 { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
767 adhoc_claim_teardown(tv, &ck);
771 /*----- That's all, folks -------------------------------------------------*/