chiark / gitweb /
@@@ wip mostly xfail
[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 open_test(struct tvec_state *tv)
371 {
372   if (!(tv->f&TVSF_OPEN)) {
373     tv->test_lno = tv->lno;
374     tv->f |= TVSF_OPEN; tv->f &= ~TVSF_XFAIL;
375   }
376 }
377
378 static void begin_test(struct tvec_state *tv)
379 {
380   tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK;
381   tv->output->ops->btest(tv->output);
382 }
383
384 void tvec_endtest(struct tvec_state *tv)
385 {
386   unsigned out;
387
388   if (!(tv->f&TVSF_ACTIVE)) /* nothing to do */;
389   else if (tv->f&TVSF_XFAIL) set_outcome(tv, TVOUT_XFAIL);
390   else set_outcome(tv, TVOUT_WIN);
391   out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
392   assert(out < TVOUT_LIMIT); tv->curr[out]++;
393   tv->output->ops->etest(tv->output, out);
394   tv->f &= ~TVSF_OPEN;
395 }
396
397 static void check(struct tvec_state *tv, struct groupstate *g)
398 {
399   const struct tvec_test *t = tv->test;
400   const struct tvec_env *env;
401   const struct tvec_regdef *rd;
402
403   if (!(tv->f&TVSF_OPEN)) return;
404
405   for (rd = t->regs; rd->name; rd++) {
406     if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE)
407       { if (rd->i < tv->nrout) TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE; }
408     else if (!(rd->f&TVRF_OPT)) {
409       tvec_error(tv, "required register `%s' not set in test `%s'",
410                  rd->name, t->name);
411       goto end;
412     }
413   }
414
415   if (!(tv->f&TVSF_SKIP)) {
416     begin_test(tv);
417     env = t->env;
418     if (env && env->before) env->before(tv, g->ctx);
419     if (!(tv->f&TVSF_ACTIVE))
420       /* setup forced a skip */;
421     else if (env && env->run)
422       env->run(tv, t->fn, g->ctx);
423     else {
424       t->fn(tv->in, tv->out, g->ctx);
425       tvec_check(tv, 0);
426     }
427     if (env && env->after) env->after(tv, g->ctx);
428     tvec_endtest(tv);
429   }
430
431 end:
432   tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
433 }
434
435 static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
436 {
437   const struct tvec_test *t = tv->test;
438   const struct tvec_env *env = t->env;
439   unsigned i;
440
441   tv->output->ops->bgroup(tv->output);
442   tv->f &= ~TVSF_SKIP;
443   tvec_initregs(tv);
444   for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
445   if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
446   if (env && env->setup) env->setup(tv, env, 0, g->ctx);
447 }
448
449 static void report_group(struct tvec_state *tv)
450 {
451   unsigned i, out, nrun;
452
453   for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
454     { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
455
456   if (tv->curr[TVOUT_SKIP] == nrun)
457     { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
458   else {
459     if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
460     else out = TVOUT_WIN;
461     tv->grps[out]++; tv->output->ops->egroup(tv->output);
462   }
463 }
464
465 static void end_test_group(struct tvec_state *tv, struct groupstate *g)
466 {
467   const struct tvec_test *t = tv->test;
468   const struct tvec_env *env;
469
470   if (!t) return;
471   if (tv->f&TVSF_OPEN) check(tv, g);
472   if (!(tv->f&TVSF_SKIP)) report_group(tv);
473   env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx);
474   tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
475 }
476
477 enum { WIN, XFAIL, NOUT };
478 static const struct tvec_uassoc outcome_assoc[] = {
479   { "success",          WIN },
480   { "win",              WIN },
481   { "expected-failure", XFAIL },
482   { "xfail",            XFAIL },
483   TVEC_ENDENUM
484 };
485 static const struct tvec_urange outcome_range = { 0, NOUT - 1 };
486 static const struct tvec_uenuminfo outcome_enum =
487   { "test-outcome", outcome_assoc, &outcome_range };
488 static const struct tvec_regdef outcome_regdef =
489   { "outcome", 0, &tvty_uenum, 0, { &outcome_enum } };
490
491 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
492 {
493   dstr d = DSTR_INIT;
494   const struct tvec_test *test;
495   const struct tvec_env *env;
496   const struct tvec_regdef *rd;
497   struct tvec_reg *r;
498   struct groupstate g = GROUPSTATE_INIT;
499   union tvec_regval rv;
500   int ch, ret, rc = 0;
501
502   tv->infile = infile; tv->lno = 1; tv->fp = fp;
503
504   for (;;) {
505     ch = getc(tv->fp);
506     switch (ch) {
507
508       case EOF:
509         goto end;
510
511       case '[':
512         end_test_group(tv, &g);
513         tvec_skipspc(tv);
514         DRESET(&d); tvec_readword(tv, &d, "];", "group name");
515         tvec_skipspc(tv);
516         ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
517         for (test = tv->tests; test->name; test++)
518           if (STRCMP(d.buf, ==, test->name)) goto found_test;
519         tvec_error(tv, "unknown test group `%s'", d.buf); goto flush_line;
520       found_test:
521         tvec_flushtoeol(tv, 0); tv->test = test; begin_test_group(tv, &g);
522         break;
523
524       case '\n':
525         tv->lno++;
526         if (tv->f&TVSF_OPEN) check(tv, &g);
527         break;
528
529       default:
530         if (isspace(ch)) {
531           tvec_skipspc(tv);
532           ch = getc(tv->fp);
533           if (ch == EOF) goto end;
534           else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY);
535           else if (tvec_flushtoeol(tv, 0)) rc = -1;
536           else check(tv, &g);
537         } else if (ch == ';')
538           tvec_flushtoeol(tv, TVFF_ALLOWANY);
539         else {
540           ungetc(ch, tv->fp);
541           DRESET(&d);
542           if (tvec_readword(tv, &d, "=:;", "register name")) goto flush_line;
543           tvec_skipspc(tv); ch = getc(tv->fp);
544           if (ch != '=' && ch != ':')
545             { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; }
546           tvec_skipspc(tv);
547           if (!tv->test)
548             { tvec_error(tv, "no current test"); goto flush_line; }
549           if (d.buf[0] == '@') {
550             env = tv->test->env;
551             if (STRCMP(d.buf, ==, "@outcome")) {
552               if (tvty_uenum.parse(&rv, &outcome_regdef, tv))
553                 ret = -1;
554               else {
555                 if (rv.u == XFAIL) tv->f |= TVSF_XFAIL;
556                 ret = 1;
557               }
558             } else if (!env || !env->set) ret = 0;
559             else ret = env->set(tv, d.buf, env, g.ctx);
560             if (ret <= 0) {
561               if (!ret)
562                 tvec_error(tv, "unknown special register `%s'", d.buf);
563               goto flush_line;
564             }
565             open_test(tv);
566           } else {
567             for (rd = tv->test->regs; rd->name; rd++)
568               if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
569             tvec_error(tv, "unknown register `%s' for test `%s'",
570                        d.buf, tv->test->name);
571             goto flush_line;
572           found_reg:
573             open_test(tv);
574             tvec_skipspc(tv);
575             r = TVEC_REG(tv, in, rd->i);
576             if (r->f&TVRF_LIVE) {
577               tvec_error(tv, "register `%s' already set", rd->name);
578               goto flush_line;
579             }
580             if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
581             r->f |= TVRF_LIVE;
582           }
583         }
584         break;
585     }
586     continue;
587
588   flush_line:
589     tvec_flushtoeol(tv, TVFF_ALLOWANY); rc = -1;
590   }
591   if (ferror(tv->fp))
592     { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
593 end:
594   end_test_group(tv, &g);
595   tv->infile = 0; tv->fp = 0;
596   dstr_destroy(&d);
597   return (rc);
598 }
599
600 /*----- Session lifecycle -------------------------------------------------*/
601
602 void tvec_begin(struct tvec_state *tv_out,
603                 const struct tvec_config *config,
604                 struct tvec_output *o)
605 {
606   unsigned i;
607
608   tv_out->f = 0;
609
610   assert(config->nrout <= config->nreg);
611   tv_out->nrout = config->nrout; tv_out->nreg = config->nreg;
612   tv_out->regsz = config->regsz;
613   tv_out->in = xmalloc(tv_out->nreg*tv_out->regsz);
614   tv_out->out = xmalloc(tv_out->nrout*tv_out->regsz);
615   for (i = 0; i < tv_out->nreg; i++) {
616     TVEC_REG(tv_out, in, i)->f = 0;
617     if (i < tv_out->nrout) TVEC_REG(tv_out, out, i)->f = 0;
618   }
619
620   for (i = 0; i < TVOUT_LIMIT; i++)
621     tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
622
623   tv_out->tests = config->tests; tv_out->test = 0;
624   tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
625   tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out);
626 }
627
628 int tvec_end(struct tvec_state *tv)
629 {
630   int rc = tv->output->ops->esession(tv->output);
631
632   tv->output->ops->destroy(tv->output);
633   xfree(tv->in); xfree(tv->out);
634   return (rc);
635 }
636
637 /*----- Serialization and deserialization ---------------------------------*/
638
639 int tvec_serialize(const struct tvec_reg *rv, buf *b,
640                    const struct tvec_regdef *regs,
641                    unsigned nr, size_t regsz)
642 {
643   unsigned char *bitmap;
644   size_t i, bitoff, nbits, bitsz;
645   const struct tvec_regdef *rd;
646   const struct tvec_reg *r;
647
648   bitoff = BLEN(b);
649   for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
650   bitsz = (nbits + 7)/8;
651
652   bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
653   memset(bitmap, 0, bitsz);
654   for (rd = regs, i = 0; rd->name; rd++, i++) {
655     if (rd->i >= nr) continue;
656     r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
657     bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8;
658     if (rd->ty->tobuf(b, &r->v, rd)) return (-1);
659   }
660   return (0);
661 }
662
663 int tvec_deserialize(struct tvec_reg *rv, buf *b,
664                      const struct tvec_regdef *regs,
665                      unsigned nr, size_t regsz)
666 {
667   const unsigned char *bitmap;
668   size_t i, nbits, bitsz;
669   const struct tvec_regdef *rd;
670   struct tvec_reg *r;
671
672   for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
673   bitsz = (nbits + 7)/8;
674
675   bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
676   for (rd = regs, i = 0; rd->name; rd++, i++) {
677     if (rd->i >= nr) continue;
678     if (!(bitmap[i/8]&(1 << i%8))) continue;
679     r = TVEC_GREG(rv, rd->i, regsz);
680     if (rd->ty->frombuf(b, &r->v, rd)) return (-1);
681     r->f |= TVRF_LIVE;
682   }
683   return (0);
684 }
685
686 /*----- Ad-hoc testing ----------------------------------------------------*/
687
688 static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
689
690 static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
691   { assert(!"fake test function"); }
692
693 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
694 {
695   t->name = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
696   tv->tests = t;
697 }
698
699 void tvec_begingroup(struct tvec_state *tv, const char *name,
700                      const char *file, unsigned lno)
701 {
702   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->tests;
703
704   t->name = name; tv->test = t;
705   tv->infile = file; tv->lno = tv->test_lno = lno;
706   begin_test_group(tv, 0);
707 }
708
709 void tvec_endgroup(struct tvec_state *tv)
710 {
711   if (!(tv->f&TVSF_SKIP)) report_group(tv);
712   tv->test = 0;
713 }
714
715 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
716 {
717   tv->infile = file; tv->lno = tv->test_lno = lno;
718   begin_test(tv); tv->f |= TVSF_OPEN;
719 }
720
721 struct adhoc_claim {
722   unsigned f;
723 #define ACF_FRESH 1u
724   const char *saved_file; unsigned saved_lno;
725 };
726
727 static void adhoc_claim_setup(struct tvec_state *tv,
728                               struct adhoc_claim *ck,
729                               const struct tvec_regdef *regs,
730                               const char *file, unsigned lno)
731 {
732   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
733
734   ck->f = 0;
735
736   if (!(tv->f&TVSF_OPEN))
737     { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
738
739   ck->saved_file = tv->infile; if (file) tv->infile = file;
740   ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno;
741   t->regs = regs ? regs : &no_regs;
742 }
743
744 static void adhoc_claim_teardown(struct tvec_state *tv,
745                                  struct adhoc_claim *ck)
746 {
747   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
748
749   t->regs = &no_regs;
750   tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
751
752   if (ck->f&ACF_FRESH) tvec_endtest(tv);
753 }
754
755 int tvec_claim_v(struct tvec_state *tv, int ok,
756                  const char *file, unsigned lno,
757                  const char *msg, va_list *ap)
758 {
759   struct adhoc_claim ck;
760
761   adhoc_claim_setup(tv, &ck, 0, file, lno);
762   if (!ok) tvec_fail_v(tv, msg, ap);
763   adhoc_claim_teardown(tv, &ck);
764   return (ok);
765 }
766
767 int tvec_claim(struct tvec_state *tv, int ok,
768                const char *file, unsigned lno, const char *msg, ...)
769 {
770   va_list ap;
771
772   va_start(ap, msg); tvec_claim_v(tv, ok, file, lno, msg, &ap); va_end(ap);
773   return (ok);
774 }
775
776 int tvec_claimeq(struct tvec_state *tv,
777                  const struct tvec_regty *ty, const union tvec_misc *arg,
778                  const char *file, unsigned lno, const char *expr)
779 {
780   struct tvec_regdef regs[2];
781   struct adhoc_claim ck;
782   int ok;
783
784   tv->in[0].f = tv->out[0].f = TVRF_LIVE;
785
786   regs[0].name = "value"; regs[0].i = 0;
787   regs[0].ty = ty; regs[0].f = 0;
788   if (arg) regs[0].arg = *arg;
789   else regs[0].arg.p = 0;
790
791   regs[1].name = 0;
792
793   adhoc_claim_setup(tv, &ck, regs, file, lno);
794   ok = ty->eq(&tv->in[0].v, &tv->out[0].v, &regs[0]);
795   if (!ok)
796     { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
797   adhoc_claim_teardown(tv, &ck);
798   return (ok);
799 }
800
801 /*----- That's all, folks -------------------------------------------------*/