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 void tvec_error(struct tvec_state *tv, const char *msg, ...)
41 { va_list ap; va_start(ap, msg); tvec_error_v(tv, msg, &ap); }
42 void tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap)
43 { tv->output->ops->error(tv->output, msg, ap); exit(2); }
45 void tvec_notice(struct tvec_state *tv, const char *msg, ...)
48 va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap);
50 void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
51 { tv->output->ops->notice(tv->output, msg, ap); }
53 void tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
56 va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
58 void tvec_syntax_v(struct tvec_state *tv, int ch,
59 const char *expect, va_list *ap)
65 case EOF: strcpy(found, "<eof>"); break;
66 case '\n': strcpy(found, "<eol>"); break;
68 if (isprint(ch)) sprintf(found, "`%c'", ch);
69 else sprintf(found, "<#x%02x>", ch);
72 dstr_vputf(&d, expect, ap);
73 tvec_error(tv, "syntax error: expected %s but found %s", expect, found);
76 void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
79 va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
81 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
83 tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
84 tv->output->ops->skipgroup(tv->output, excuse, ap);
87 static void set_outcome(struct tvec_state *tv, unsigned out)
89 tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK);
90 tv->f |= out << TVSF_OUTSHIFT;
93 void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
96 va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
98 void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
100 assert(tv->f&TVSF_ACTIVE);
101 set_outcome(tv, TVOUT_SKIP);
102 tv->output->ops->skip(tv->output, excuse, ap);
105 void tvec_fail(struct tvec_state *tv, const char *detail, ...)
108 va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
110 void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
112 assert((tv->f&TVSF_ACTIVE) ||
113 (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT));
114 set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap);
117 void tvec_mismatch(struct tvec_state *tv)
118 { tv->output->ops->mismatch(tv->output); }
120 void tvec_write(struct tvec_state *tv, const char *p, ...)
123 va_start(ap, p); tvec_write_v(tv, p, &ap); va_end(ap);
125 void tvec_write_v(struct tvec_state *tv, const char *p, va_list *ap)
129 dstr_vputf(&d, p, ap); tv->output->ops->write(tv->output, d.buf, d.len);
133 /*----- Serialization and deserialization ---------------------------------*/
135 int tvec_serialize(const struct tvec_reg *rv,
136 const struct tvec_regdef *regs,
137 unsigned nr, size_t regsz,
138 void **p_out, size_t *sz_out)
141 unsigned char *bitmap;
142 size_t i, nbits, bitsz, sz;
143 const struct tvec_regdef *rd;
144 const struct tvec_reg *r;
147 for (rd = regs, nbits = 0, sz = 0; rd->name; rd++, nbits++) {
148 if (rd->i >= nr) continue;
149 r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
150 sz += rd->ty->measure(&r->v, rd);
152 bitsz = (nbits + 7)/8; sz += bitsz;
154 p = xmalloc(sz); buf_init(&b, p, sz);
155 bitmap = buf_get(&b, bitsz); assert(bitmap); memset(bitmap, 0, bitsz);
156 for (rd = regs, i = 0; rd->name; rd++, i++) {
157 if (rd->i >= nr) continue;
158 r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
159 bitmap[rd->i/8] |= 1 << rd->i%8;
160 if (rd->ty->tobuf(&b, &r->v, rd)) { rc = -1; goto end; }
163 if (BBAD(&b)) { rc = -1; goto end; }
164 *p_out = p; *sz_out = BLEN(&b); p = 0; rc = 0;
170 int tvec_deserialize(struct tvec_reg *rv,
171 const struct tvec_regdef *regs,
172 unsigned nr, size_t regsz,
173 const void *p, size_t sz)
176 const unsigned char *bitmap;
177 size_t i, nbits, bitsz;
178 const struct tvec_regdef *rd;
182 for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
183 bitsz = (nbits + 7)/8; sz += bitsz;
185 buf_init(&b, (/*unconst*/ void *)p, sz);
186 bitmap = buf_get(&b, bitsz); if (!bitmap) { rc = -1; goto end; }
187 for (rd = regs, i = 0; rd->name; rd++, i++) {
188 if (rd->i >= nr) continue;
189 if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue;
190 r = TVEC_GREG(rv, rd->i, regsz);
191 if (rd->ty->frombuf(&b, &r->v, rd)) { rc = -1; goto end; }
195 if (BBAD(&b)) { rc = -1; goto end; }
201 /*----- Benchmarking ------------------------------------------------------*/
203 struct bench_state *tvec_benchstate;
208 const struct tvec_reg *in; struct tvec_reg *out;
212 static void benchloop_outer(unsigned long n, void *p)
213 { struct benchrun *r = p; while (n--) r->fn(r->in, r->out, r->ctx); }
215 static void benchloop_inner(unsigned long n, void *p)
216 { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); }
218 int tvec_ensurebench(struct tvec_state *tv, struct bench_state **b_out)
220 const struct tvec_bench *tvb = tv->test->arg.p;
221 struct bench_state **bb;
222 struct bench_timer *bt;
224 if (tvb->b) bb = tvb->b;
225 else bb = &tvec_benchstate;
228 bt = bench_createtimer();
229 if (!bt) { tvec_skip(tv, "failed to create timer"); return (-1); }
230 *bb = xmalloc(sizeof(**bb)); bench_init(*bb, bt);
231 } else if (!(*bb)->tm)
232 { tvec_skip(tv, "failed to create timer"); return (-1); }
238 void tvec_bench(struct tvec_state *tv)
240 const struct tvec_bench *tvb = tv->test->arg.p;
241 struct bench_state *b;
242 struct bench_timing tm;
246 if (tvec_ensurebench(tv, &b)) goto end_0;
248 r.in = tv->in; r.out = tv->out; r.fn = tv->test->fn;
249 if (tvb->ctxsz) r.ctx = xmalloc(tvb->ctxsz);
251 if (tvb->setup && tvb->setup(tv->in, tv->out, &tvb->arg, r.ctx))
252 { tvec_skip(tv, "benchmark setup failed"); goto end_1; }
255 { r.n = 0; loopfn = benchloop_outer; }
257 { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; }
259 tv->output->ops->bbench(tv->output);
260 if (bench_measure(&tm, b, loopfn, &r))
261 { tv->output->ops->ebench(tv->output, 0); goto end_2; }
262 tv->output->ops->ebench(tv->output, &tm);
265 if (tvb->teardown) tvb->teardown(r.ctx);
267 if (r.ctx) xfree(r.ctx);
272 /*----- Main machinery ----------------------------------------------------*/
274 void tvec_skipspc(struct tvec_state *tv)
280 if (ch == EOF) break;
281 else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; }
285 void tvec_flushtoeol(struct tvec_state *tv, unsigned f)
292 case '\n': tv->lno++; return;
294 case ';': f |= TVFF_ALLOWANY; break;
296 if (!(f&TVFF_ALLOWANY) && !isspace(ch))
297 tvec_syntax(tv, ch, "end-of-line");
303 int tvec_nexttoken(struct tvec_state *tv)
305 enum { TAIL, NEWLINE, INDENT, COMMENT };
320 if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); }
321 else { tv->lno++; s = NEWLINE; }
326 { if (s == NEWLINE) s = INDENT; }
327 else if (s != COMMENT) {
329 if (s == NEWLINE) return (-1);
337 int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
338 const char *expect, ...)
343 va_start(ap, expect);
344 rc = tvec_readword_v(tv, d, delims, expect, &ap);
348 int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
349 const char *expect, va_list *ap)
354 if (ch == '\n' || ch == EOF || ch == ';' ||
355 (delims && strchr(delims, ch))) {
356 if (expect) tvec_syntax(tv, ch, expect, ap);
357 else { ungetc(ch, tv->fp); return (-1); }
359 if (d->len) DPUTC(d, ' ');
363 } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch)));
364 DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
368 static void init_registers(struct tvec_state *tv)
370 const struct tvec_regdef *rd;
373 for (rd = tv->test->regs; rd->name; rd++) {
374 assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
375 rd->ty->init(&r->v, rd); r->f = 0;
376 if (rd->i < tv->nrout)
377 { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
382 static void release_registers(struct tvec_state *tv)
384 const struct tvec_regdef *rd;
387 for (rd = tv->test->regs; rd->name; rd++) {
388 assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
389 rd->ty->release(&r->v, rd); r->f = 0;
390 if (rd->i < tv->nrout)
391 { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
395 void tvec_check(struct tvec_state *tv, const char *detail, ...)
398 va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap);
400 void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
402 const struct tvec_regdef *rd;
403 const struct tvec_reg *rin, *rout;
405 #define f_mismatch 1u
407 if (tv->expst != tv->st) f |= f_mismatch;
408 for (rd = tv->test->regs; rd->name; rd++) {
409 if (rd->i >= tv->nrout) continue;
410 rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
411 if (!rin->f&TVRF_LIVE) continue;
412 if (!rd->ty->eq(&rin->v, &rout->v, rd)) f |= f_mismatch;
414 if (!(f&f_mismatch)) return;
416 tvec_fail_v(tv, detail, ap);
422 void tvec_runtest(struct tvec_state *tv)
424 tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p);
428 static void begin_test(struct tvec_state *tv)
430 tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.';
431 tv->output->ops->btest(tv->output);
434 void tvec_endtest(struct tvec_state *tv)
438 if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN;
439 else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
440 assert(out < TVOUT_LIMIT); tv->curr[out]++;
441 tv->output->ops->etest(tv->output, out);
445 static void check(struct tvec_state *tv)
447 const struct tvec_regdef *rd;
449 if (!(tv->f&TVSF_OPEN)) return;
451 for (rd = tv->test->regs; rd->name; rd++) {
452 if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE)
453 { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; }
454 else if (!(rd->f&TVRF_OPT))
455 tvec_error(tv, "required register `%s' not set in test `%s'",
456 rd->name, tv->test->name);
459 if (!(tv->f&TVSF_SKIP))
460 { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); }
462 tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv);
465 static void begin_test_group(struct tvec_state *tv)
469 tv->output->ops->bgroup(tv->output);
472 for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
473 if (tv->test->preflight) tv->test->preflight(tv);
476 void tvec_reportgroup(struct tvec_state *tv)
478 unsigned i, out, nrun;
480 for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
481 { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
483 if (tv->curr[TVOUT_SKIP] == nrun)
484 { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
486 if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
487 else out = TVOUT_WIN;
488 tv->grps[out]++; tv->output->ops->egroup(tv->output, out);
492 static void end_test_group(struct tvec_state *tv)
494 if (!tv->test) return;
495 if (tv->f&TVSF_OPEN) check(tv);
496 if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
497 release_registers(tv); tv->test = 0;
500 void tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
503 const struct tvec_test *test;
504 const struct tvec_regdef *rd;
508 tv->infile = infile; tv->lno = 1; tv->fp = fp;
519 DRESET(&d); tvec_readword(tv, &d, "];", "group name");
520 for (test = tv->tests; test->name; test++)
521 if (STRCMP(d.buf, ==, test->name)) goto found_test;
522 tvec_error(tv, "unknown test group `%s'", d.buf);
525 ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
526 tvec_flushtoeol(tv, 0);
529 begin_test_group(tv);
534 if (tv->f&TVSF_OPEN) check(tv);
544 { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; }
546 { tvec_flushtoeol(tv, 0); check(tv); }
547 } else if (ch == ';')
548 { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; }
551 DRESET(&d); tvec_readword(tv, &d, "=:;", "register name");
552 tvec_skipspc(tv); ch = getc(tv->fp);
553 if (ch != '=' && ch != ':') tvec_syntax(tv, ch, "`=' or `:'");
555 if (d.buf[0] == '@') {
556 if (STRCMP(d.buf, ==, "@status")) {
557 if (!(tv->f&TVSF_OPEN))
558 { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
560 if (ch == EOF || ch == '\n' || ch == ';')
561 tvec_syntax(tv, ch, "status character");
564 if (ch == EOF || ch == '\n')
565 tvec_syntax(tv, ch, "escaped status character");
568 tvec_flushtoeol(tv, 0);
570 tvec_error(tv, "unknown special register `%s'", d.buf);
572 if (!tv->test) tvec_error(tv, "no current test");
573 for (rd = tv->test->regs; rd->name; rd++)
574 if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
575 tvec_error(tv, "unknown register `%s' for test `%s'",
576 d.buf, tv->test->name);
578 if (!(tv->f&TVSF_OPEN))
579 { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
581 r = TVEC_REG(tv, in, rd->i);
583 tvec_error(tv, "register `%s' already set", rd->name);
584 rd->ty->parse(&r->v, rd, tv); r->f |= TVRF_LIVE;
592 tv->infile = 0; tv->fp = 0;
596 /*----- Ad-hoc testing ----------------------------------------------------*/
598 static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
600 static void fakerun(struct tvec_state *tv)
601 { assert(!"fake run function"); }
602 static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
603 { assert(!"fake test function"); }
605 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
607 t->name = "<unset>"; t->regs = &no_regs;
608 t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0;
612 void tvec_begingroup(struct tvec_state *tv, const char *name,
613 const char *file, unsigned lno)
615 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
617 t->name = name; tv->test = t;
618 tv->infile = file; tv->lno = tv->test_lno = lno;
619 begin_test_group(tv);
622 void tvec_endgroup(struct tvec_state *tv)
624 if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
628 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
630 tv->infile = file; tv->lno = tv->test_lno = lno;
631 begin_test(tv); tv->f |= TVSF_OPEN;
637 const char *saved_file; unsigned saved_lno;
640 static void adhoc_claim_setup(struct tvec_state *tv,
641 struct adhoc_claim *ck,
642 const struct tvec_regdef *regs,
643 const char *file, unsigned lno)
645 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
649 if (!(tv->f&TVSF_OPEN))
650 { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
652 ck->saved_file = tv->infile; if (file) tv->infile = file;
653 ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno;
654 t->regs = regs ? regs : &no_regs;
659 static void adhoc_claim_teardown(struct tvec_state *tv,
660 struct adhoc_claim *ck)
662 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
665 tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
667 if (ck->f&ACF_FRESH) tvec_endtest(tv);
670 int tvec_claim(struct tvec_state *tv, int ok,
671 const char *file, unsigned lno, const char *expr, ...)
673 struct adhoc_claim ck;
676 adhoc_claim_setup(tv, &ck, 0, file, lno);
678 { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); }
679 adhoc_claim_teardown(tv, &ck);
683 int tvec_claimeq(struct tvec_state *tv,
684 const struct tvec_regty *ty, const union tvec_misc *arg,
685 const char *file, unsigned lno, const char *expr)
687 struct tvec_regdef regs[2];
688 struct adhoc_claim ck;
691 tv->in[0].f = tv->out[0].f = TVRF_LIVE;
693 regs[0].name = "value"; regs[0].i = 0;
694 regs[0].ty = ty; regs[0].f = 0;
695 if (arg) regs[0].arg = *arg;
696 else regs[0].arg.p = 0;
700 adhoc_claim_setup(tv, &ck, regs, file, lno);
701 ok = ty->eq(&tv->in[0].v, &tv->out[0].v, ®s[0]);
702 if (!ok) { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv); }
703 adhoc_claim_teardown(tv, &ck);
707 /*----- Session lifecycle -------------------------------------------------*/
709 void tvec_begin(struct tvec_state *tv_out,
710 const struct tvec_info *info,
711 struct tvec_output *o)
717 assert(info->nrout <= info->nreg);
718 tv_out->nrout = info->nrout; tv_out->nreg = info->nreg;
719 tv_out->regsz = info->regsz;
720 tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz);
721 tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz);
722 for (i = 0; i < tv_out->nreg; i++) {
723 TVEC_REG(tv_out, in, i)->f = 0;
724 if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0;
727 for (i = 0; i < TVOUT_LIMIT; i++)
728 tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
730 tv_out->tests = info->tests; tv_out->test = 0;
731 tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
732 o->tv = tv_out; tv_out->output = o;
734 tv_out->output->ops->bsession(tv_out->output);
737 int tvec_end(struct tvec_state *tv)
739 int rc = tv->output->ops->esession(tv->output);
741 tv->output->ops->destroy(tv->output);
742 xfree(tv->in); xfree(tv->out);
746 /*----- That's all, folks -------------------------------------------------*/