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