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