chiark / gitweb /
3938397b07d6315d37dbce488d3620b4a35411bf
[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 <errno.h>
33 #include <string.h>
34
35 #include "alloc.h"
36 #include "bench.h"
37 #include "tvec.h"
38
39 /*----- Output ------------------------------------------------------------*/
40
41 int tvec_error(struct tvec_state *tv, const char *msg, ...)
42 {
43   va_list ap;
44
45   va_start(ap, msg); tvec_error_v(tv, msg, &ap); va_end(ap);
46   tv->f |= TVSF_ERROR; return (-1);
47 }
48 int tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap)
49   { tv->output->ops->error(tv->output, msg, ap); return (-1); }
50
51 void tvec_notice(struct tvec_state *tv, const char *msg, ...)
52 {
53   va_list ap;
54   va_start(ap, msg); tvec_notice_v(tv, msg, &ap); va_end(ap);
55 }
56 void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
57   { tv->output->ops->notice(tv->output, msg, ap); }
58
59 int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
60 {
61   va_list ap;
62
63   va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
64   return (-1);
65 }
66 int tvec_syntax_v(struct tvec_state *tv, int ch,
67                    const char *expect, va_list *ap)
68 {
69   dstr d = DSTR_INIT;
70   char found[8];
71
72   switch (ch) {
73     case EOF: strcpy(found, "<eof>"); break;
74     case '\n': strcpy(found, "<eol>"); ungetc(ch, tv->fp); break;
75     default:
76       if (isprint(ch)) sprintf(found, "`%c'", ch);
77       else sprintf(found, "<#x%02x>", ch);
78       break;
79   }
80   dstr_vputf(&d, expect, ap);
81   tvec_error(tv, "syntax error: expected %s but found %s", expect, found);
82   return (-1);
83 }
84
85 void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
86 {
87   va_list ap;
88   va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
89 }
90 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
91 {
92   tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
93   tv->output->ops->skipgroup(tv->output, excuse, ap);
94 }
95
96 static void set_outcome(struct tvec_state *tv, unsigned out)
97 {
98   tv->f &= ~(TVSF_ACTIVE | TVSF_OUTMASK);
99   tv->f |= out << TVSF_OUTSHIFT;
100 }
101
102 void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
103 {
104   va_list ap;
105   va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
106 }
107 void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
108 {
109   assert(tv->f&TVSF_ACTIVE);
110   set_outcome(tv, TVOUT_SKIP);
111   tv->output->ops->skip(tv->output, excuse, ap);
112 }
113
114 void tvec_fail(struct tvec_state *tv, const char *detail, ...)
115 {
116   va_list ap;
117   va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
118 }
119 void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
120 {
121   assert((tv->f&TVSF_ACTIVE) ||
122          (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT));
123   set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap);
124 }
125
126 void tvec_mismatch(struct tvec_state *tv)
127   { tv->output->ops->mismatch(tv->output); }
128
129 void tvec_write(struct tvec_state *tv, const char *p, ...)
130 {
131   va_list ap;
132   va_start(ap, p); tvec_write_v(tv, p, &ap); va_end(ap);
133 }
134 void tvec_write_v(struct tvec_state *tv, const char *p, va_list *ap)
135 {
136   dstr d = DSTR_INIT;
137
138   dstr_vputf(&d, p, ap); tv->output->ops->write(tv->output, d.buf, d.len);
139   DDESTROY(&d);
140 }
141
142 /*----- Serialization and deserialization ---------------------------------*/
143
144 int tvec_serialize(const struct tvec_reg *rv,
145                    const struct tvec_regdef *regs,
146                    unsigned nr, size_t regsz,
147                    void **p_out, size_t *sz_out)
148 {
149   void *p = 0; buf b;
150   unsigned char *bitmap;
151   size_t i, nbits, bitsz, sz;
152   const struct tvec_regdef *rd;
153   const struct tvec_reg *r;
154   int rc;
155
156   for (rd = regs, nbits = 0, sz = 0; rd->name; rd++, nbits++) {
157     if (rd->i >= nr) continue;
158     r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
159     sz += rd->ty->measure(&r->v, rd);
160   }
161   bitsz = (nbits + 7)/8; sz += bitsz;
162
163   p = xmalloc(sz); buf_init(&b, p, sz);
164   bitmap = buf_get(&b, bitsz); assert(bitmap); memset(bitmap, 0, bitsz);
165   for (rd = regs, i = 0; rd->name; rd++, i++) {
166     if (rd->i >= nr) continue;
167     r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
168     bitmap[rd->i/8] |= 1 << rd->i%8;
169     if (rd->ty->tobuf(&b, &r->v, rd)) { rc = -1; goto end; }
170   }
171
172   if (BBAD(&b)) { rc = -1; goto end; }
173   *p_out = p; *sz_out = BLEN(&b); p = 0; rc = 0;
174 end:
175   xfree(p);
176   return (rc);
177 }
178
179 int tvec_deserialize(struct tvec_reg *rv,
180                      const struct tvec_regdef *regs,
181                      unsigned nr, size_t regsz,
182                      const void *p, size_t sz)
183 {
184   buf b;
185   const unsigned char *bitmap;
186   size_t i, nbits, bitsz;
187   const struct tvec_regdef *rd;
188   struct tvec_reg *r;
189   int rc;
190
191   for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
192   bitsz = (nbits + 7)/8; sz += bitsz;
193
194   buf_init(&b, (/*unconst*/ void *)p, sz);
195   bitmap = buf_get(&b, bitsz); if (!bitmap) { rc = -1; goto end; }
196   for (rd = regs, i = 0; rd->name; rd++, i++) {
197     if (rd->i >= nr) continue;
198     if (!(bitmap[rd->i/8]&(1 << rd->i%8))) continue;
199     r = TVEC_GREG(rv, rd->i, regsz);
200     if (rd->ty->frombuf(&b, &r->v, rd)) { rc = -1; goto end; }
201     r->f |= TVRF_LIVE;
202   }
203
204   if (BBAD(&b)) { rc = -1; goto end; }
205   rc = 0;
206 end:
207   return (rc);
208 }
209
210 /*----- Benchmarking ------------------------------------------------------*/
211
212 struct bench_state *tvec_benchstate;
213
214 struct benchrun {
215   unsigned long *n;
216   tvec_testfn *fn;
217   const struct tvec_reg *in; struct tvec_reg *out;
218   void *ctx;
219 };
220
221 static void benchloop_outer(unsigned long n, void *p)
222   { struct benchrun *r = p; while (n--) r->fn(r->in, r->out, r->ctx); }
223
224 static void benchloop_inner(unsigned long n, void *p)
225   { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); }
226
227 int tvec_ensurebench(struct tvec_state *tv, struct bench_state **b_out)
228 {
229   const struct tvec_bench *tvb = tv->test->arg.p;
230   struct bench_state **bb;
231   struct bench_timer *bt;
232
233   if (tvb->b) bb = tvb->b;
234   else bb = &tvec_benchstate;
235
236   if (!*bb) {
237     bt = bench_createtimer();
238     if (!bt) { tvec_skip(tv, "failed to create timer"); return (-1); }
239     *bb = xmalloc(sizeof(**bb)); bench_init(*bb, bt);
240   } else if (!(*bb)->tm)
241     { tvec_skip(tv, "failed to create timer"); return (-1); }
242
243   *b_out = *bb;
244   return (0);
245 }
246
247 int tvec_bench(struct tvec_state *tv)
248 {
249   const struct tvec_bench *tvb = tv->test->arg.p;
250   struct bench_state *b;
251   struct bench_timing tm;
252   struct benchrun r;
253   bench_fn *loopfn;
254
255   if (tvec_ensurebench(tv, &b)) goto end_0;
256
257   r.in = tv->in; r.out = tv->out; r.fn = tv->test->fn;
258   if (tvb->ctxsz) r.ctx = xmalloc(tvb->ctxsz);
259   else r.ctx = 0;
260   if (tvb->setup && tvb->setup(tv->in, tv->out, &tvb->arg, r.ctx))
261     { tvec_skip(tv, "benchmark setup failed"); goto end_1; }
262
263   if (tvb->riter < 0)
264     { r.n = 0; loopfn = benchloop_outer; }
265   else
266     { r.n = &TVEC_REG(tv, in, tvb->riter)->v.u; loopfn = benchloop_inner; }
267
268   tv->output->ops->bbench(tv->output);
269   if (bench_measure(&tm, b, loopfn, &r))
270     { tv->output->ops->ebench(tv->output, 0); goto end_2; }
271   tv->output->ops->ebench(tv->output, &tm);
272
273 end_2:
274   if (tvb->teardown) tvb->teardown(r.ctx);
275 end_1:
276   if (r.ctx) xfree(r.ctx);
277 end_0:
278   return (0);
279 }
280
281 /*----- Main machinery ----------------------------------------------------*/
282
283 void tvec_skipspc(struct tvec_state *tv)
284 {
285   int ch;
286
287   for (;;) {
288     ch = getc(tv->fp);
289     if (ch == EOF) break;
290     else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; }
291   }
292 }
293
294 int tvec_flushtoeol(struct tvec_state *tv, unsigned f)
295 {
296   int ch, rc = 0;
297
298   for (;;) {
299     ch = getc(tv->fp);
300     switch (ch) {
301       case '\n': tv->lno++; return (rc);
302       case EOF: return (rc);
303       case ';': f |= TVFF_ALLOWANY; break;
304       default:
305         if (!(f&TVFF_ALLOWANY) && !isspace(ch)) {
306           tvec_syntax(tv, ch, "end-of-line");
307           rc = -1; f |= TVFF_ALLOWANY;
308         }
309         break;
310     }
311   }
312 }
313
314 int tvec_nexttoken(struct tvec_state *tv)
315 {
316   enum { TAIL, NEWLINE, INDENT, COMMENT };
317   int ch;
318   unsigned s = TAIL;
319
320   for (;;) {
321     ch = getc(tv->fp);
322     switch (ch) {
323       case EOF:
324         return (-1);
325
326       case ';':
327         s = COMMENT;
328         break;
329
330       case '\n':
331         if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); }
332         else { tv->lno++; s = NEWLINE; }
333         break;
334
335       default:
336         if (isspace(ch))
337           { if (s == NEWLINE) s = INDENT; }
338         else if (s != COMMENT) {
339           ungetc(ch, tv->fp);
340           if (s == NEWLINE) return (-1);
341           else return (0);
342         }
343         break;
344     }
345   }
346 }
347
348 int tvec_readword(struct tvec_state *tv, dstr *d, const char *delims,
349                   const char *expect, ...)
350 {
351   va_list ap;
352   int rc;
353
354   va_start(ap, expect);
355   rc = tvec_readword_v(tv, d, delims, expect, &ap);
356   va_end(ap);
357   return (rc);
358 }
359 int tvec_readword_v(struct tvec_state *tv, dstr *d, const char *delims,
360                     const char *expect, va_list *ap)
361 {
362   int ch;
363
364   ch = getc(tv->fp);
365   if (ch == '\n' || ch == EOF || ch == ';' ||
366       (delims && strchr(delims, ch))) {
367     if (expect) return (tvec_syntax(tv, ch, expect, ap));
368     else { ungetc(ch, tv->fp); return (-1); }
369   }
370   if (d->len) DPUTC(d, ' ');
371   do {
372     DPUTC(d, ch);
373     ch = getc(tv->fp);
374   } while (ch != EOF && !isspace(ch) && (!delims || !strchr(delims, ch)));
375   DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
376   return (0);
377 }
378
379 static void init_registers(struct tvec_state *tv)
380 {
381   const struct tvec_regdef *rd;
382   struct tvec_reg *r;
383
384   for (rd = tv->test->regs; rd->name; rd++) {
385     assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
386     rd->ty->init(&r->v, rd); r->f = 0;
387     if (rd->i < tv->nrout)
388       { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
389   }
390   tv->expst = '.';
391 }
392
393 static void release_registers(struct tvec_state *tv)
394 {
395   const struct tvec_regdef *rd;
396   struct tvec_reg *r;
397
398   for (rd = tv->test->regs; rd->name; rd++) {
399     assert(rd->i < tv->nreg); r = TVEC_REG(tv, in, rd->i);
400     rd->ty->release(&r->v, rd); r->f = 0;
401     if (rd->i < tv->nrout)
402       { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
403   }
404 }
405
406 void tvec_check(struct tvec_state *tv, const char *detail, ...)
407 {
408   va_list ap;
409   va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap);
410 }
411 void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
412 {
413   const struct tvec_regdef *rd;
414   const struct tvec_reg *rin, *rout;
415   unsigned f = 0;
416 #define f_mismatch 1u
417
418   if (tv->expst != tv->st) f |= f_mismatch;
419   for (rd = tv->test->regs; rd->name; rd++) {
420     if (rd->i >= tv->nrout) continue;
421     rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
422     if (!rin->f&TVRF_LIVE) continue;
423     if (!rd->ty->eq(&rin->v, &rout->v, rd)) f |= f_mismatch;
424   }
425   if (!(f&f_mismatch)) return;
426
427   tvec_fail_v(tv, detail, ap);
428   tvec_mismatch(tv);
429
430 #undef f_mismatch
431 }
432
433 int tvec_runtest(struct tvec_state *tv)
434 {
435   tv->test->fn(tv->in, tv->out, (/*unconst*/ void *)tv->test->arg.p);
436   tvec_check(tv, 0); return (0);
437 }
438
439 static void begin_test(struct tvec_state *tv)
440 {
441   tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK; tv->st = '.';
442   tv->output->ops->btest(tv->output);
443 }
444
445 void tvec_endtest(struct tvec_state *tv)
446 {
447   unsigned out;
448
449   if (tv->f&TVSF_ACTIVE) out = TVOUT_WIN;
450   else out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
451   assert(out < TVOUT_LIMIT); tv->curr[out]++;
452   tv->output->ops->etest(tv->output, out);
453   tv->f &= ~TVSF_OPEN;
454 }
455
456 static void check(struct tvec_state *tv)
457 {
458   const struct tvec_regdef *rd;
459
460   if (!(tv->f&TVSF_OPEN)) return;
461
462   for (rd = tv->test->regs; rd->name; rd++) {
463     if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE)
464       { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; }
465     else if (!(rd->f&TVRF_OPT)) {
466       tvec_error(tv, "required register `%s' not set in test `%s'",
467                  rd->name, tv->test->name);
468       goto end;
469     }
470   }
471
472   if (!(tv->f&TVSF_SKIP))
473     { begin_test(tv); tv->test->run(tv); tvec_endtest(tv); }
474
475 end:
476   tv->f &= ~TVSF_OPEN; release_registers(tv); init_registers(tv);
477 }
478
479 static void begin_test_group(struct tvec_state *tv)
480 {
481   unsigned i;
482
483   tv->output->ops->bgroup(tv->output);
484   tv->f &= ~TVSF_SKIP;
485   init_registers(tv);
486   for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
487   if (tv->test->preflight) tv->test->preflight(tv);
488 }
489
490 void tvec_reportgroup(struct tvec_state *tv)
491 {
492   unsigned i, out, nrun;
493
494   for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
495     { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
496
497   if (tv->curr[TVOUT_SKIP] == nrun)
498     { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
499   else {
500     if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
501     else out = TVOUT_WIN;
502     tv->grps[out]++; tv->output->ops->egroup(tv->output, out);
503   }
504 }
505
506 static void end_test_group(struct tvec_state *tv)
507 {
508   if (!tv->test) return;
509   if (tv->f&TVSF_OPEN) check(tv);
510   if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
511   release_registers(tv); tv->test = 0;
512 }
513
514 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
515 {
516   dstr d = DSTR_INIT;
517   const struct tvec_test *test;
518   const struct tvec_regdef *rd;
519   struct tvec_reg *r;
520   int ch;
521   int rc = 0;
522
523   tv->infile = infile; tv->lno = 1; tv->fp = fp;
524
525   for (;;) {
526     ch = getc(tv->fp);
527     switch (ch) {
528
529       case EOF:
530         goto end;
531
532       case '[':
533         end_test_group(tv);
534         tvec_skipspc(tv);
535         DRESET(&d); tvec_readword(tv, &d, "];", "group name");
536         tvec_skipspc(tv);
537         ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
538         for (test = tv->tests; test->name; test++)
539           if (STRCMP(d.buf, ==, test->name)) goto found_test;
540         tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line;
541       found_test:
542         tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv);
543         break;
544
545       case '\n':
546         tv->lno++;
547         if (tv->f&TVSF_OPEN) check(tv);
548         break;
549
550       default:
551         if (isspace(ch)) {
552           tvec_skipspc(tv);
553           ch = getc(tv->fp);
554           if (ch == EOF) goto end;
555           else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY);
556           else if (tvec_flushtoeol(tv, 0)) rc = -1;
557           else check(tv);
558         } else if (ch == ';')
559           tvec_flushtoeol(tv, TVFF_ALLOWANY);
560         else {
561           ungetc(ch, tv->fp);
562           DRESET(&d);
563           if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line;
564           tvec_skipspc(tv); ch = getc(tv->fp);
565           if (ch != '=' && ch != ':')
566             { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; }
567           tvec_skipspc(tv);
568           if (d.buf[0] == '@') {
569             if (STRCMP(d.buf, ==, "@status")) {
570               if (!(tv->f&TVSF_OPEN))
571                 { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
572               ch = getc(tv->fp);
573               if (ch == EOF || ch == '\n' || ch == ';')
574                 { tvec_syntax(tv, ch, "status character"); goto flush_line; }
575               else if (ch == '\\') {
576                 ch = getc(tv->fp);
577                 if (ch == EOF || ch == '\n') {
578                   tvec_syntax(tv, ch, "escaped status character");
579                   goto flush_line;
580                 }
581               }
582               tv->expst = ch;
583               tvec_flushtoeol(tv, 0);
584             } else {
585               tvec_error(tv, "unknown special register `%s'", d.buf);
586               goto flush_line;
587             }
588           } else {
589             if (!tv->test)
590               { tvec_error(tv, "no current test"); goto flush_line; }
591             for (rd = tv->test->regs; rd->name; rd++)
592               if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
593             tvec_error(tv, "unknown register `%s' for test `%s'",
594                        d.buf, tv->test->name);
595             goto flush_line;
596           found_reg:
597             if (!(tv->f&TVSF_OPEN))
598               { tv->test_lno = tv->lno; tv->f |= TVSF_OPEN; }
599             tvec_skipspc(tv);
600             r = TVEC_REG(tv, in, rd->i);
601             if (r->f&TVRF_LIVE) {
602               tvec_error(tv, "register `%s' already set", rd->name);
603               goto flush_line;
604             }
605             if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
606             r->f |= TVRF_LIVE;
607           }
608         }
609         break;
610     }
611     continue;
612
613   flush_line:
614     tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1;
615   }
616   if (ferror(tv->fp))
617     { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
618 end:
619   end_test_group(tv);
620   tv->infile = 0; tv->fp = 0;
621   dstr_destroy(&d);
622   return (rc);
623 }
624
625 /*----- Ad-hoc testing ----------------------------------------------------*/
626
627 static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
628
629 static int fakerun(struct tvec_state *tv)
630   { assert(!"fake run function"); }
631 static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
632   { assert(!"fake test function"); }
633
634 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
635 {
636   t->name = "<unset>"; t->regs = &no_regs;
637   t->preflight = 0; t->run = fakerun; t->fn = fakefn; t->arg.p = 0;
638   tv->tests = t;
639 }
640
641 void tvec_begingroup(struct tvec_state *tv, const char *name,
642                      const char *file, unsigned lno)
643 {
644   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
645
646   t->name = name; tv->test = t;
647   tv->infile = file; tv->lno = tv->test_lno = lno;
648   begin_test_group(tv);
649 }
650
651 void tvec_endgroup(struct tvec_state *tv)
652 {
653   if (!(tv->f&TVSF_SKIP)) tvec_reportgroup(tv);
654   tv->test = 0;
655 }
656
657 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
658 {
659   tv->infile = file; tv->lno = tv->test_lno = lno;
660   begin_test(tv); tv->f |= TVSF_OPEN;
661 }
662
663 struct adhoc_claim {
664   unsigned f;
665 #define ACF_FRESH 1u
666   const char *saved_file; unsigned saved_lno;
667 };
668
669 static void adhoc_claim_setup(struct tvec_state *tv,
670                               struct adhoc_claim *ck,
671                               const struct tvec_regdef *regs,
672                               const char *file, unsigned lno)
673 {
674   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
675
676   ck->f = 0;
677
678   if (!(tv->f&TVSF_OPEN))
679     { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
680
681   ck->saved_file = tv->infile; if (file) tv->infile = file;
682   ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno;
683   t->regs = regs ? regs : &no_regs;
684
685   tv->st = '.';
686 }
687
688 static void adhoc_claim_teardown(struct tvec_state *tv,
689                                  struct adhoc_claim *ck)
690 {
691   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
692
693   t->regs = &no_regs;
694   tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
695
696   if (ck->f&ACF_FRESH) tvec_endtest(tv);
697 }
698
699 int tvec_claim(struct tvec_state *tv, int ok,
700                const char *file, unsigned lno, const char *expr, ...)
701 {
702   struct adhoc_claim ck;
703   va_list ap;
704
705   adhoc_claim_setup(tv, &ck, 0, file, lno);
706   if (!ok)
707     { va_start(ap, expr); tvec_fail_v(tv, expr, &ap); va_end(ap); }
708   adhoc_claim_teardown(tv, &ck);
709   return (ok);
710 }
711
712 int tvec_claimeq(struct tvec_state *tv,
713                  const struct tvec_regty *ty, const union tvec_misc *arg,
714                  const char *file, unsigned lno, const char *expr)
715 {
716   struct tvec_regdef regs[2];
717   struct adhoc_claim ck;
718   int ok;
719
720   tv->in[0].f = tv->out[0].f = TVRF_LIVE;
721
722   regs[0].name = "value"; regs[0].i = 0;
723   regs[0].ty = ty; regs[0].f = 0;
724   if (arg) regs[0].arg = *arg;
725   else regs[0].arg.p = 0;
726
727   regs[1].name = 0;
728
729   adhoc_claim_setup(tv, &ck, regs, file, lno);
730   ok = ty->eq(&tv->in[0].v, &tv->out[0].v, &regs[0]);
731   if (!ok) { tvec_fail(tv, "%s", expr ); tvec_mismatch(tv); }
732   adhoc_claim_teardown(tv, &ck);
733   return (ok);
734 }
735
736 /*----- Session lifecycle -------------------------------------------------*/
737
738 void tvec_begin(struct tvec_state *tv_out,
739                 const struct tvec_info *info,
740                 struct tvec_output *o)
741 {
742   unsigned i;
743
744   tv_out->f = 0;
745
746   assert(info->nrout <= info->nreg);
747   tv_out->nrout = info->nrout; tv_out->nreg = info->nreg;
748   tv_out->regsz = info->regsz;
749   tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz);
750   tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz);
751   for (i = 0; i < tv_out->nreg; i++) {
752     TVEC_REG(tv_out, in, i)->f = 0;
753     if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0;
754   }
755
756   for (i = 0; i < TVOUT_LIMIT; i++)
757     tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
758
759   tv_out->tests = info->tests; tv_out->test = 0;
760   tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
761   o->tv = tv_out; tv_out->output = o;
762
763   tv_out->output->ops->bsession(tv_out->output);
764 }
765
766 int tvec_end(struct tvec_state *tv)
767 {
768   int rc = tv->output->ops->esession(tv->output);
769
770   tv->output->ops->destroy(tv->output);
771   xfree(tv->in); xfree(tv->out);
772   return (rc);
773 }
774
775 /*----- That's all, folks -------------------------------------------------*/