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