chiark / gitweb /
@@@ all the mess ever
[mLib] / test / tvec-core.c
1 /* -*-c-*-
2  *
3  * Main test vector driver
4  *
5  * (c) 2023 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the mLib utilities library.
11  *
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.
16  *
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.
21  *
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,
25  * USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <string.h>
33
34 #include "alloc.h"
35 #include "bench.h"
36 #include "tvec.h"
37
38 /*----- Output ------------------------------------------------------------*/
39
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); }
44
45 void tvec_notice(struct tvec_state *tv, const char *msg, ...)
46 {
47   va_list ap;
48   va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap);
49 }
50 void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
51   { tv->output->ops->notice(tv->output, msg, ap); }
52
53 void tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
54 {
55   va_list ap;
56   va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
57 }
58 void tvec_syntax_v(struct tvec_state *tv, int ch,
59                    const char *expect, va_list *ap)
60 {
61   dstr d = DSTR_INIT;
62   char found[8];
63
64   switch (ch) {
65     case EOF: strcpy(found, "<eof>"); break;
66     case '\n': strcpy(found, "<eol>"); break;
67     default:
68       if (isprint(ch)) sprintf(found, "`%c'", ch);
69       else sprintf(found, "<#x%02x>", ch);
70       break;
71   }
72   dstr_vputf(&d, expect, ap);
73   tvec_error(tv, "syntax error: expected %s but found %s", expect, found);
74 }
75
76 void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
77 {
78   va_list ap;
79   va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
80 }
81 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
82 {
83   tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
84   tv->output->ops->skipgroup(tv->output, excuse, ap);
85 }
86
87 static void set_outcome(struct tvec_state *tv, unsigned out)
88 {
89   tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK);
90   tv->f |= out << TVSF_OUTSHIFT;
91 }
92
93 void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
94 {
95   va_list ap;
96   va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
97 }
98 void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
99 {
100   assert(tv->f&TVSF_ACTIVE);
101   set_outcome(tv, TVOUT_SKIP);
102   tv->output->ops->skip(tv->output, excuse, ap);
103 }
104
105 void tvec_fail(struct tvec_state *tv, const char *detail, ...)
106 {
107   va_list ap;
108   va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
109 }
110 void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
111 {
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);
115 }
116
117 void tvec_mismatch(struct tvec_state *tv)
118   { tv->output->ops->mismatch(tv->output); }
119
120 void tvec_write(struct tvec_state *tv, const char *p, ...)
121 {
122   va_list ap;
123   va_start(ap, p); tvec_write_v(tv, p, &ap); va_end(ap);
124 }
125 void tvec_write_v(struct tvec_state *tv, const char *p, va_list *ap)
126 {
127   dstr d = DSTR_INIT;
128
129   dstr_vputf(&d, p, ap); tv->output->ops->write(tv->output, d.buf, d.len);
130   DDESTROY(&d);
131 }
132
133 /*----- Serialization and deserialization ---------------------------------*/
134
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)
139 {
140   void *p = 0; buf b;
141   unsigned char *bitmap;
142   size_t i, nbits, bitsz, sz;
143   const struct tvec_regdef *rd;
144   const struct tvec_reg *r;
145   int rc;
146
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);
151   }
152   bitsz = (nbits + 7)/8; sz += bitsz;
153
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; }
161   }
162
163   if (BBAD(&b)) { rc = -1; goto end; }
164   *p_out = p; *sz_out = BLEN(&b); p = 0; rc = 0;
165 end:
166   xfree(p);
167   return (rc);
168 }
169
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)
174 {
175   buf b;
176   const unsigned char *bitmap;
177   size_t i, nbits, bitsz;
178   const struct tvec_regdef *rd;
179   struct tvec_reg *r;
180   int rc;
181
182   for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
183   bitsz = (nbits + 7)/8; sz += bitsz;
184
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; }
192     r->f |= TVRF_LIVE;
193   }
194
195   if (BBAD(&b)) { rc = -1; goto end; }
196   rc = 0;
197 end:
198   return (rc);
199 }
200
201 /*----- Benchmarking ------------------------------------------------------*/
202
203 struct bench_state *tvec_benchstate;
204
205 struct benchrun {
206   unsigned long *n;
207   tvec_testfn *fn;
208   const struct tvec_reg *in; struct tvec_reg *out;
209   void *ctx;
210 };
211
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); }
214
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); }
217
218 int tvec_ensurebench(struct tvec_state *tv, struct bench_state **b_out)
219 {
220   const struct tvec_bench *tvb = tv->test->arg.p;
221   struct bench_state **bb;
222   struct bench_timer *bt;
223
224   if (tvb->b) bb = tvb->b;
225   else bb = &tvec_benchstate;
226
227   if (!*bb) {
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); }
233
234   *b_out = *bb;
235   return (0);
236 }
237
238 void tvec_bench(struct tvec_state *tv)
239 {
240   const struct tvec_bench *tvb = tv->test->arg.p;
241   struct bench_state *b;
242   struct bench_timing tm;
243   struct benchrun r;
244   bench_fn *loopfn;
245
246   if (tvec_ensurebench(tv, &b)) goto end_0;
247
248   r.in = tv->in; r.out = tv->out; r.fn = tv->test->fn;
249   if (tvb->ctxsz) r.ctx = xmalloc(tvb->ctxsz);
250   else r.ctx = 0;
251   if (tvb->setup && tvb->setup(tv->in, tv->out, &tvb->arg, r.ctx))
252     { tvec_skip(tv, "benchmark setup failed"); goto end_1; }
253
254   if (tvb->riter < 0)
255     { r.n = 0; loopfn = benchloop_outer; }
256   else
257     { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; }
258
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);
263
264 end_2:
265   if (tvb->teardown) tvb->teardown(r.ctx);
266 end_1:
267   if (r.ctx) xfree(r.ctx);
268 end_0:
269   return;
270 }
271
272 /*----- Main machinery ----------------------------------------------------*/
273
274 void tvec_skipspc(struct tvec_state *tv)
275 {
276   int ch;
277
278   for (;;) {
279     ch = getc(tv->fp);
280     if (ch == EOF) break;
281     else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; }
282   }
283 }
284
285 void tvec_flushtoeol(struct tvec_state *tv, unsigned f)
286 {
287   int ch;
288
289   for (;;) {
290     ch = getc(tv->fp);
291     switch (ch) {
292       case '\n': tv->lno++; return;
293       case EOF: return;
294       case ';': f |= TVFF_ALLOWANY; break;
295       default:
296         if (!(f&TVFF_ALLOWANY) && !isspace(ch))
297           tvec_syntax(tv, ch, "end-of-line");
298         break;
299     }
300   }
301 }
302
303 int tvec_nexttoken(struct tvec_state *tv)
304 {
305   enum { TAIL, NEWLINE, INDENT, COMMENT };
306   int ch;
307   unsigned s = TAIL;
308
309   for (;;) {
310     ch = getc(tv->fp);
311     switch (ch) {
312       case EOF:
313         return (-1);
314
315       case ';':
316         s = COMMENT;
317         break;
318
319       case '\n':
320         if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); }
321         else { tv->lno++; s = NEWLINE; }
322         break;
323
324       default:
325         if (isspace(ch))
326           { if (s == NEWLINE) s = INDENT; }
327         else if (s != COMMENT) {
328           ungetc(ch, tv->fp);
329           if (s == NEWLINE) return (-1);
330           else return (0);
331         }
332         break;
333     }
334   }
335 }
336
337 int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
338                   const char *expect, ...)
339 {
340   va_list ap;
341   int rc;
342
343   va_start(ap, expect);
344   rc = tvec_readword_v(tv, d, delims, expect, &ap);
345   va_end(ap);
346   return (rc);
347 }
348 int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
349                     const char *expect, va_list *ap)
350 {
351   int ch;
352
353   ch = getc(tv->fp);
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); }
358   }
359   if (d->len) DPUTC(d, ' ');
360   do {
361     DPUTC(d, ch);
362     ch = getc(tv->fp);
363   } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch)));
364   DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
365   return (0);
366 }
367
368 static void init_registers(struct tvec_state *tv)
369 {
370   const struct tvec_regdef *rd;
371   struct tvec_reg *r;
372
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; }
378   }
379   tv->expst = '.';
380 }
381
382 static void release_registers(struct tvec_state *tv)
383 {
384   const struct tvec_regdef *rd;
385   struct tvec_reg *r;
386
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; }
392   }
393 }
394
395 void tvec_check(struct tvec_state *tv, const char *detail, ...)
396 {
397   va_list ap;
398   va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap);
399 }
400 void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
401 {
402   const struct tvec_regdef *rd;
403   const struct tvec_reg *rin, *rout;
404   unsigned f = 0;
405 #define f_mismatch 1u
406
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;
413   }
414   if (!(f&f_mismatch)) return;
415
416   tvec_fail_v(tv, detail, ap);
417   tvec_mismatch(tv);
418
419 #undef f_mismatch
420 }
421
422 void tvec_runtest(struct tvec_state *tv)
423 {
424   tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p);
425   tvec_check(tv, 0);
426 }
427
428 static void begin_test(struct tvec_state *tv)
429 {
430   tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.';
431   tv->output->ops->btest(tv->output);
432 }
433
434 void tvec_endtest(struct tvec_state *tv)
435 {
436   unsigned out;
437
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);
442   tv->f &= ~TVSF_OPEN;
443 }
444
445 static void check(struct tvec_state *tv)
446 {
447   const struct tvec_regdef *rd;
448
449   if (!(tv->f&TVSF_OPEN)) return;
450
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);
457   }
458
459   if (!(tv->f&TVSF_SKIP))
460     { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); }
461
462   tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv);
463 }
464
465 static void begin_test_group(struct tvec_state *tv)
466 {
467   unsigned i;
468
469   tv->output->ops->bgroup(tv->output);
470   tv->f &= ~TVSF_SKIP;
471   init_registers(tv);
472   for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
473   if (tv->test->preflight) tv->test->preflight(tv);
474 }
475
476 void tvec_reportgroup(struct tvec_state *tv)
477 {
478   unsigned i, out, nrun;
479
480   for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
481     { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
482
483   if (tv->curr[TVOUT_SKIP] == nrun)
484     { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
485   else {
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);
489   }
490 }
491
492 static void end_test_group(struct tvec_state *tv)
493 {
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;
498 }
499
500 void tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
501 {
502   dstr d = DSTR_INIT;
503   const struct tvec_test *test;
504   const struct tvec_regdef *rd;
505   struct tvec_reg *r;
506   int ch;
507
508   tv->infile = infile; tv->lno = 1; tv->fp = fp;
509
510   for (;;) {
511     ch = getc(tv->fp);
512     switch (ch) {
513
514       case EOF:
515         goto end;
516
517       case '[':
518         tvec_skipspc(tv);
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);
523       found_test:
524         tvec_skipspc(tv);
525         ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
526         tvec_flushtoeol(tv, 0);
527         end_test_group(tv);
528         tv->test = test;
529         begin_test_group(tv);
530         break;
531
532       case '\n':
533         tv->lno++;
534         if (tv->f&TVSF_OPEN) check(tv);
535         break;
536
537       default:
538         if (isspace(ch)) {
539           tvec_skipspc(tv);
540           ch = getc(tv->fp);
541           if (ch == EOF)
542             goto end;
543           else if (ch == ';')
544             { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; }
545           else
546             { tvec_flushtoeol(tv, 0); check(tv); }
547         } else if (ch == ';')
548           { tvec_flushtoeol(tv, TVFF_ALLOWANY); continue; }
549         else {
550           ungetc(ch, tv->fp);
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 `:'");
554           tvec_skipspc(tv);
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; }
559               ch = getc(tv->fp);
560               if (ch == EOF || ch == '\n' || ch == ';')
561                 tvec_syntax(tv, ch, "status character");
562               if (ch == '\\') {
563                 ch = getc(tv->fp);
564                 if (ch == EOF || ch == '\n')
565                   tvec_syntax(tv, ch, "escaped status character");
566               }
567               tv->expst = ch;
568               tvec_flushtoeol(tv, 0);
569             } else
570               tvec_error(tv, "unknown special register `%s'", d.buf);
571           } else {
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);
577           found_reg:
578             if (!(tv->f&TVSF_OPEN))
579               { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
580             tvec_skipspc(tv);
581             r = TVEC_REG(tv, in, rd->i);
582             if (r->f&TVRF_LIVE)
583               tvec_error(tv, "register `%s' already set", rd->name);
584             rd->ty->parse(&r->v, rd, tv); r->f |= TVRF_LIVE;
585           }
586         }
587         break;
588     }
589   }
590 end:
591   end_test_group(tv);
592   tv->infile = 0; tv->fp = 0;
593   dstr_destroy(&d);
594 }
595
596 /*----- Ad-hoc testing ----------------------------------------------------*/
597
598 static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
599
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"); }
604
605 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
606 {
607   t->name = "<unset>"; t->regs = &no_regs;
608   t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0;
609   tv->tests = t;
610 }
611
612 void tvec_begingroup(struct tvec_state *tv, const char *name,
613                      const char *file, unsigned lno)
614 {
615   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
616
617   t->name = name; tv->test = t;
618   tv->infile = file; tv->lno = tv->test_lno = lno;
619   begin_test_group(tv);
620 }
621
622 void tvec_endgroup(struct tvec_state *tv)
623 {
624   if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
625   tv->test = 0;
626 }
627
628 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
629 {
630   tv->infile = file; tv->lno = tv->test_lno = lno;
631   begin_test(tv); tv->f |= TVSF_OPEN;
632 }
633
634 struct adhoc_claim {
635   unsigned f;
636 #define ACF_FRESH 1u
637   const char *saved_file; unsigned saved_lno;
638 };
639
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)
644 {
645   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
646
647   ck->f = 0;
648
649   if (!(tv->f&TVSF_OPEN))
650     { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
651
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;
655
656   tv->st = '.';
657 }
658
659 static void adhoc_claim_teardown(struct tvec_state *tv,
660                                  struct adhoc_claim *ck)
661 {
662   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
663
664   t->regs = &no_regs;
665   tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
666
667   if (ck->f&ACF_FRESH) tvec_endtest(tv);
668 }
669
670 int tvec_claim(struct tvec_state *tv, int ok,
671                const char *file, unsigned lno, const char *expr, ...)
672 {
673   struct adhoc_claim ck;
674   va_list ap;
675
676   adhoc_claim_setup(tv, &ck, 0, file, lno);
677   if (!ok)
678     { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); }
679   adhoc_claim_teardown(tv, &ck);
680   return (ok);
681 }
682
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)
686 {
687   struct tvec_regdef regs[2];
688   struct adhoc_claim ck;
689   int ok;
690
691   tv->in[0].f = tv->out[0].f = TVRF_LIVE;
692
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;
697
698   regs[1].name = 0;
699
700   adhoc_claim_setup(tv, &ck, regs, file, lno);
701   ok = ty->eq(&tv->in[0].v, &tv->out[0].v, &regs[0]);
702   if (!ok) { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv); }
703   adhoc_claim_teardown(tv, &ck);
704   return (ok);
705 }
706
707 /*----- Session lifecycle -------------------------------------------------*/
708
709 void tvec_begin(struct tvec_state *tv_out,
710                 const struct tvec_info *info,
711                 struct tvec_output *o)
712 {
713   unsigned i;
714
715   tv_out->f = 0;
716
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;
725   }
726
727   for (i = 0; i < TVOUT_LIMIT; i++)
728     tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
729
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;
733
734   tv_out->output->ops->bsession(tv_out->output);
735 }
736
737 int tvec_end(struct tvec_state *tv)
738 {
739   int rc = tv->output->ops->esession(tv->output);
740
741   tv->output->ops->destroy(tv->output);
742   xfree(tv->in); xfree(tv->out);
743   return (rc);
744 }
745
746 /*----- That's all, folks -------------------------------------------------*/