chiark / gitweb /
fc1b413005d336cbc8b3e10c25bbbff68fcd8d9d
[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 "growbuf.h"
37 #include "tvec.h"
38
39 /*----- Output ------------------------------------------------------------*/
40
41 /* --- @tvec_strlevel@ --- *
42  *
43  * Arguments:   @unsigned level@ = level code
44  *
45  * Returns:     A human-readable description.
46  *
47  * Use:         Converts a level code into something that you can print in a
48  *              message.
49  */
50
51 const char *tvec_strlevel(unsigned level)
52 {
53   switch (level) {
54 #define CASE(tag, name, val)                                            \
55     case TVLEV_##tag: return (name);
56     TVEC_LEVELS(CASE)
57 #undef CASE
58     default: return ("??");
59   }
60 }
61
62 /* --- @tvec_report@, @tvec_report_v@ --- *
63  *
64  * Arguments:   @struct tvec_state *tv@ = test-vector state
65  *              @unsigned level@ = severity level (@TVlEV_...@)
66  *              @const char *msg@, @va_list ap@ = error message
67  *
68  * Returns:     ---
69  *
70  * Use:         Report an message with a given severity.  Messages with level
71  *              @TVLEV_ERR@ or higher force a nonzero exit code.
72  */
73
74 void tvec_report(struct tvec_state *tv, unsigned level, const char *msg, ...)
75 {
76   va_list ap;
77
78   va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap);
79 }
80
81 void tvec_report_v(struct tvec_state *tv, unsigned level,
82                    const char *msg, va_list *ap)
83 {
84   tv->output->ops->report(tv->output, level, msg, ap);
85   if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR;
86 }
87
88 /* --- @tvec_error@, @tvec_notice@ --- *
89  *
90  * Arguments:   @struct tvec_state *tv@ = test-vector state
91  *              @const char *msg@, @va_list ap@ = error message
92  *
93  * Returns:     The @tvec_error@ function returns @-1@ as a trivial
94  *              convenience; @tvec_notice@ does not return a value.
95  *
96  * Use:         Report an error or a notice.  Errors are distinct from test
97  *              failures, and indicate that a problem was encountered which
98  *              compromised the activity of testing.  Notices are important
99  *              information which doesn't fit into any other obvious
100  *              category.
101  */
102
103 int tvec_error(struct tvec_state *tv, const char *msg, ...)
104 {
105   va_list ap;
106
107   va_start(ap, msg); tvec_report_v(tv, TVLEV_ERR, msg, &ap); va_end(ap);
108   return (-1);
109 }
110
111 void tvec_notice(struct tvec_state *tv, const char *msg, ...)
112 {
113   va_list ap;
114
115   va_start(ap, msg); tvec_report_v(tv, TVLEV_NOTE, msg, &ap); va_end(ap);
116 }
117
118 /*----- Test processing ---------------------------------------------------*/
119
120 /* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- *
121  *
122  * Arguments:   @struct tvec_state *tv@ = test-vector state
123  *              @const char *excuse@, @va_list *ap@ = reason why skipped
124  *
125  * Returns:     ---
126  *
127  * Use:         Skip the current group.  This should only be called from a
128  *              test environment @setup@ function; a similar effect occurs if
129  *              the @setup@ function fails.
130  */
131
132 void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
133 {
134   va_list ap;
135
136   va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
137 }
138
139 void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
140 {
141   if (!(tv->f&TVSF_SKIP)) {
142     tv->f |= TVSF_SKIP; tv->grps[TVOUT_SKIP]++;
143     tv->output->ops->skipgroup(tv->output, excuse, ap);
144   }
145 }
146
147 /* --- @set_outcome@ --- *
148  *
149  * Arguments:   @struct tvec_state *tv@ = test-vector state
150  *              @unsigned out@ = the new outcome
151  *
152  * Returns:     ---
153  *
154  * Use:         Sets the outcome bits in the test state flags, and clears
155  *              @TVSF_ACTIVE@.
156  */
157
158 static void set_outcome(struct tvec_state *tv, unsigned out)
159   { tv->f = (tv->f&~(TVSF_ACTIVE | TVSF_OUTMASK)) | (out << TVSF_OUTSHIFT); }
160
161 /* --- @tvec_skip@, @tvec_skip_v@ --- *
162  *
163  * Arguments:   @struct tvec_state *tv@ = test-vector state
164  *              @const char *excuse@, @va_list *ap@ = reason why test skipped
165  *
166  * Returns:     ---
167  *
168  * Use:         Skip the current test.  This should only be called from a
169  *              test environment @run@ function; a similar effect occurs if
170  *              the @before@ function fails.
171  */
172
173 void tvec_skip(struct tvec_state *tv, const char *excuse, ...)
174 {
175   va_list ap;
176   va_start(ap, excuse); tvec_skip_v(tv, excuse, &ap); va_end(ap);
177 }
178
179 void tvec_skip_v(struct tvec_state *tv, const char *excuse, va_list *ap)
180 {
181   assert(tv->f&TVSF_ACTIVE);
182   set_outcome(tv, TVOUT_SKIP);
183   tv->output->ops->skip(tv->output, excuse, ap);
184 }
185
186 /* --- @tvec_fail@, @tvec_fail_v@ --- *
187  *
188  * Arguments:   @struct tvec_state *tv@ = test-vector state
189  *              @const char *detail@, @va_list *ap@ = description of test
190  *
191  * Returns:     ---
192  *
193  * Use:         Report the current test as a failure.  This function can be
194  *              called multiple times for a single test, e.g., if the test
195  *              environment's @run@ function invokes the test function
196  *              repeatedly; but a single test that fails repeatedly still
197  *              only counts as a single failure in the statistics.  The
198  *              @detail@ string and its format parameters can be used to
199  *              distinguish which of several invocations failed; it can
200  *              safely be left null if the test function is run only once.
201  */
202
203 void tvec_fail(struct tvec_state *tv, const char *detail, ...)
204 {
205   va_list ap;
206   va_start(ap, detail); tvec_fail_v(tv, detail, &ap); va_end(ap);
207 }
208
209 void tvec_fail_v(struct tvec_state *tv, const char *detail, va_list *ap)
210 {
211   assert((tv->f&TVSF_ACTIVE) ||
212          (tv->f&TVSF_OUTMASK) == (TVOUT_LOSE << TVSF_OUTSHIFT));
213   set_outcome(tv, TVOUT_LOSE); tv->output->ops->fail(tv->output, detail, ap);
214 }
215
216 /* --- @tvec_dumpreg@ --- *
217  *
218  * Arguments:   @struct tvec_state *tv@ = test-vector state
219  *              @unsigned disp@ = the register disposition (@TVRD_...@)
220  *              @const union tvec_regval *tv@ = register value, or null
221  *              @const struct tvec_regdef *rd@ = register definition
222  *
223  * Returns:     ---
224  *
225  * Use:         Dump a register value to the output.  This is the lowest-
226  *              level function for dumping registers, and calls the output
227  *              formatter directly.
228  *
229  *              Usually @tvec_mismatch@ is much more convenient.  Low-level
230  *              access is required for reporting `virtual' registers
231  *              corresponding to test environment settings.
232  */
233
234 void tvec_dumpreg(struct tvec_state *tv,
235                   unsigned disp, const union tvec_regval *r,
236                   const struct tvec_regdef *rd)
237   { tv->output->ops->dumpreg(tv->output, disp, r, rd); }
238
239 /* --- @tvec_mismatch@ --- *
240  *
241  * Arguments:   @struct tvec_state *tv@ = test-vector state
242  *              @unsigned f@ = flags (@TVMF_...@)
243  *
244  * Returns:     ---
245  *
246  * Use:         Dumps registers suitably to report a mismatch.  The flag bits
247  *              @TVMF_IN@ and @TVF_OUT@ select input-only and output
248  *              registers.  If both are reset then nothing happens.
249  *              Suppressing the output registers may be useful, e.g., if the
250  *              test function crashed rather than returning outputs.
251  */
252
253 void tvec_mismatch(struct tvec_state *tv, unsigned f)
254 {
255   const struct tvec_regdef *rd;
256   const struct tvec_reg *rin, *rout;
257
258   for (rd = tv->test->regs; rd->name; rd++) {
259     if (rd->i >= tv->cfg.nrout) {
260       if (!(f&TVMF_IN)) continue;
261       rin = TVEC_REG(tv, in, rd->i);
262       tvec_dumpreg(tv, TVRD_INPUT, rin->f&TVRF_LIVE ? &rin->v : 0, rd);
263     } else {
264       if (!(f&TVMF_OUT)) continue;
265       rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
266       if (!(rin->f&TVRF_LIVE))
267         tvec_dumpreg(tv, TVRD_OUTPUT, rout->f&TVRF_LIVE ? &rout->v : 0, rd);
268       else if ((rout->f&TVRF_LIVE) && rd->ty->eq(&rin->v, &rout->v, rd))
269         tvec_dumpreg(tv, TVRD_MATCH, &rin->v, rd);
270       else {
271         tvec_dumpreg(tv, TVRD_FOUND, rout->f&TVRF_LIVE ? &rout->v : 0, rd);
272         tvec_dumpreg(tv, TVRD_EXPECT, &rin->v, rd);
273       }
274     }
275   }
276 }
277
278 /*----- Parsing -----------------------------------------------------------*/
279
280 /* --- @tvec_syntax@, @tvec_syntax_v@ --- *
281  *
282  * Arguments:   @struct tvec_state *tv@ = test-vector state
283  *              @int ch@ = the character found (in @fgetc@ format)
284  *              @const char *expect@, @va_list ap@ = what was expected
285  *
286  * Returns:     %$-1$%.
287  *
288  * Use:         Report a syntax error quoting @ch@ and @expect@.  If @ch@ is
289  *              a newline, then back up so that it can be read again (e.g.,
290  *              by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also
291  *              advance the line number).
292  */
293
294 int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
295 {
296   va_list ap;
297
298   va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
299   return (-1);
300 }
301
302 int tvec_syntax_v(struct tvec_state *tv, int ch,
303                   const char *expect, va_list *ap)
304 {
305   dstr d = DSTR_INIT;
306   char found[8];
307
308   switch (ch) {
309     case EOF: strcpy(found, "#eof"); break;
310     case '\n': strcpy(found, "#eol"); ungetc(ch, tv->fp); break;
311     default:
312       if (isprint(ch)) sprintf(found, "`%c'", ch);
313       else sprintf(found, "#\\x%02x", ch);
314       break;
315   }
316   dstr_vputf(&d, expect, ap);
317   tvec_error(tv, "syntax error: expected %s but found %s", d.buf, found);
318   dstr_destroy(&d); return (-1);
319 }
320
321 /* --- @tvec_unkregerr@ --- *
322  *
323  * Arguments:   @struct tvec_state *tv@ = test-vector state
324  *              @const char *name@ = register or pseudoregister name
325  *
326  * Returns:     %$-1$%.
327  *
328  * Use:         Reports an error that the register or pseudoregister is
329  *              unrecognized.
330  */
331
332 int tvec_unkregerr(struct tvec_state *tv, const char *name)
333 {
334   return (tvec_error(tv, "unknown special register `%s' for test `%s'",
335                      name, tv->test->name));
336 }
337
338 /* --- @tvec_dupregerr@ --- *
339  *
340  * Arguments:   @struct tvec_state *tv@ = test-vector state
341  *              @const char *name@ = register or pseudoregister name
342  *
343  * Returns:     %$-1$%.
344  *
345  * Use:         Reports an error that the register or pseudoregister has been
346  *              assigned already in the current test.
347  */
348
349 int tvec_dupregerr(struct tvec_state *tv, const char *name)
350   { return (tvec_error(tv, "register `%s' is already set", name)); }
351
352 /* --- @tvec_skipspc@ --- *
353  *
354  * Arguments:   @struct tvec_state *tv@ = test-vector state
355  *
356  * Returns:     ---
357  *
358  * Use:         Advance over any whitespace characters other than newlines.
359  *              This will stop at `;', end-of-file, or any other kind of
360  *              non-whitespace; and it won't consume a newline.
361  */
362
363 void tvec_skipspc(struct tvec_state *tv)
364 {
365   int ch;
366
367   for (;;) {
368     ch = getc(tv->fp);
369     if (ch == EOF) break;
370     else if (ch == '\n' || !isspace(ch)) { ungetc(ch, tv->fp); break; }
371   }
372 }
373
374 /* --- @tvec_flushtoeol@ --- *
375  *
376  * Arguments:   @struct tvec_state *tv@ = test-vector state
377  *              @unsigned f@ = flags (@TVFF_...@)
378  *
379  * Returns:     Zero on success, @-1@ on error.
380  *
381  * Use:         Advance to the start of the next line, consuming the
382  *              preceding newline.
383  *
384  *              A syntax error is reported if no newline character is found,
385  *              i.e., the file ends in mid-line.  A syntax error is also
386  *              reported if material other than whitespace or a comment is
387  *              found before the end of the line end, and @TVFF_ALLOWANY@ is
388  *              not set in @f@.  The line number count is updated
389  *              appropriately.
390  */
391
392 int tvec_flushtoeol(struct tvec_state *tv, unsigned f)
393 {
394   int ch, rc = 0;
395
396   for (;;) {
397     ch = getc(tv->fp);
398     switch (ch) {
399       case '\n': tv->lno++; return (rc);
400       case EOF: return (rc);
401       case ';': f |= TVFF_ALLOWANY; break;
402       default:
403         if (!(f&TVFF_ALLOWANY) && !isspace(ch)) {
404           tvec_syntax(tv, ch, "end-of-line");
405           rc = -1; f |= TVFF_ALLOWANY;
406         }
407         break;
408     }
409   }
410 }
411
412 /* --- @tvec_nexttoken@ --- *
413  *
414  * Arguments:   @struct tvec_state *tv@ = test-vector state
415  *
416  * Returns:     Zero if there is a next token which can be read; @-1@ if no
417  *              token is available.
418  *
419  * Use:         Advance to the next whitespace-separated token, which may be
420  *              on the next line.
421  *
422  *              Tokens are separated by non-newline whitespace, comments, and
423  *              newlines followed by whitespace; a newline /not/ followed by
424  *              whitespace instead begins the next assignment, and two
425  *              newlines separated only by whitespace terminate the data for
426  *              a test.
427  *
428  *              If this function returns zero, then the next character in the
429  *              file begins a suitable token which can be read and
430  *              processed.  If it returns @-1@ then there is no such token,
431  *              and the file position is left correctly.  The line number
432  *              count is updated appropriately.
433  */
434
435 int tvec_nexttoken(struct tvec_state *tv)
436 {
437   enum { TAIL, NEWLINE, INDENT, COMMENT };
438   int ch;
439   unsigned s = TAIL;
440
441   for (;;) {
442     ch = getc(tv->fp);
443     switch (ch) {
444       case EOF:
445         return (-1);
446
447       case ';':
448         s = COMMENT;
449         break;
450
451       case '\n':
452         if (s == NEWLINE || s == INDENT) { ungetc(ch, tv->fp); return (-1); }
453         else { tv->lno++; s = NEWLINE; }
454         break;
455
456       default:
457         if (isspace(ch))
458           { if (s == NEWLINE) s = INDENT; }
459         else if (s != COMMENT) {
460           ungetc(ch, tv->fp);
461           if (s == NEWLINE) return (-1);
462           else return (0);
463         }
464         break;
465     }
466   }
467 }
468
469 /* --- @tvec_readword@, @tvec_readword_v@ --- *
470  *
471  * Arguments:   @struct tvec_state *tv@ = test-vector state
472  *              @dstr *d@ = string to append the word to
473  *              @const char **p_inout@ = pointer into string, updated
474  *              @const char *delims@ = additional delimiters to stop at
475  *              @const char *expect@, @va_list ap@ = what was expected
476  *
477  * Returns:     Zero on success, @-1@ on failure.
478  *
479  * Use:         A `word' consists of characters other than whitespace, null
480  *              characters, and other than those listed in @delims@;
481  *              furthermore, a word does not begin with a `;'.  (If you want
482  *              reading to stop at comments not preceded by whitespace, then
483  *              include `;' in @delims@.  This is a common behaviour.)
484  *
485  *              If there is no word beginning at the current file position,
486  *              then return @-1@; furthermore, if @expect@ is not null, then
487  *              report an appropriate error via @tvec_syntax@.
488  *
489  *              Otherwise, the word is accumulated in @d@ and zero is
490  *              returned; if @d@ was not empty at the start of the call, the
491  *              newly read word is separated from the existing material by a
492  *              single space character.  Since null bytes are never valid
493  *              word constituents, a null terminator is written to @d@, and
494  *              it is safe to treat the string in @d@ as being null-
495  *              terminated.
496  *
497  *              If @p_inout@ is not null, then @*p_inout@ must be a pointer
498  *              into @d->buf@, which will be adjusted so that it will
499  *              continue to point at the same position even if the buffer is
500  *              reallocated.  As a subtle tweak, if @*p_inout@ initially
501  *              points at the end of the buffer, then it will be adjusted to
502  *              point at the beginning of the next word, rather than at the
503  *              additional intervening space.
504  */
505
506 int tvec_readword(struct tvec_state *tv, dstr *d, const char **p_inout,
507                   const char *delims, const char *expect, ...)
508 {
509   va_list ap;
510   int rc;
511
512   va_start(ap, expect);
513   rc = tvec_readword_v(tv, d, p_inout, delims, expect, &ap);
514   va_end(ap);
515   return (rc);
516 }
517
518 int tvec_readword_v(struct tvec_state *tv, dstr *d, const char **p_inout,
519                     const char *delims, const char *expect, va_list *ap)
520 {
521   size_t pos = 0;
522   int ch;
523
524   tvec_skipspc(tv);
525
526   ch = getc(tv->fp);
527   if (!ch || ch == '\n' || ch == EOF || ch == ';' ||
528       (delims && strchr(delims, ch))) {
529     if (expect) return (tvec_syntax(tv, ch, expect, ap));
530     else { ungetc(ch, tv->fp); return (-1); }
531   }
532   if (p_inout) pos = *p_inout - d->buf;
533   if (d->len) {
534     if (pos == d->len) pos++;
535     DPUTC(d, ' ');
536   }
537   do {
538     DPUTC(d, ch);
539     ch = getc(tv->fp);
540   } while (ch && ch != EOF && !isspace(ch) &&
541            (!delims || !strchr(delims, ch)));
542   DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
543   if (p_inout) *p_inout = d->buf + pos;
544   return (0);
545 }
546
547 /*----- Main machinery ----------------------------------------------------*/
548
549 struct groupstate {
550   void *ctx;                            /* test environment context */
551   unsigned f;                           /* flags */
552 #define GRPF_SETOUTC 1u                 /*   set outcome */
553 #define GRPF_SETMASK (GRPF_SETOUTC)     /*   mask of all variable flags */
554 };
555 #define GROUPSTATE_INIT { 0, 0 }
556
557 /* --- @tvec_initregs@, @tvec_releaseregs@ --- *
558  *
559  * Arguments:   @struct tvec_state *tv@ = test-vector state
560  *
561  * Returns:     ---
562  *
563  * Use:         Initialize or release, respectively, the registers required
564  *              by the current test.  All of the registers, both input and
565  *              output, are effected.  Initialized registers are not marked
566  *              live.
567  */
568
569 void tvec_initregs(struct tvec_state *tv)
570 {
571   const struct tvec_regdef *rd;
572   struct tvec_reg *r;
573
574   for (rd = tv->test->regs; rd->name; rd++) {
575     assert(rd->i < tv->cfg.nreg); r = TVEC_REG(tv, in, rd->i);
576     rd->ty->init(&r->v, rd); r->f = 0;
577     if (rd->i < tv->cfg.nrout)
578       { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
579   }
580 }
581
582 void tvec_releaseregs(struct tvec_state *tv)
583 {
584   const struct tvec_regdef *rd;
585   struct tvec_reg *r;
586
587   for (rd = tv->test->regs; rd->name; rd++) {
588     assert(rd->i < tv->cfg.nreg); r = TVEC_REG(tv, in, rd->i);
589     rd->ty->release(&r->v, rd); r->f = 0;
590     if (rd->i < tv->cfg.nrout)
591       { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
592   }
593 }
594
595 /* --- @tvec_resetoutputs@ --- *
596  *
597  * Arguments:   @struct tvec_state *tv@ = test-vector state
598  *
599  * Returns:     ---
600  *
601  * Use:         Reset (releases and reinitializes) the output registers in
602  *              the test state.  This is mostly of use to test environment
603  *              @run@ functions, between invocations of the test function.
604  *              Output registers are marked live if and only if the
605  *              corresponding input register is live.
606  */
607
608 void tvec_resetoutputs(struct tvec_state *tv)
609 {
610   const struct tvec_regdef *rd;
611   struct tvec_reg *r;
612
613   for (rd = tv->test->regs; rd->name; rd++) {
614     assert(rd->i < tv->cfg.nreg);
615     if (rd->i >= tv->cfg.nrout) continue;
616     r = TVEC_REG(tv, out, rd->i);
617     rd->ty->release(&r->v, rd);
618     rd->ty->init(&r->v, rd);
619     r->f = TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE;
620   }
621 }
622
623 /* --- @tvec_checkregs@ --- *
624  *
625  * Arguments:   @struct tvec_state *tv@ = test-vector state
626  *
627  * Returns:     Zero on success, @-1@ on mismatch.
628  *
629  * Use:         Compare the active output registers (according to the current
630  *              test group definition) with the corresponding input register
631  *              values.  A mismatch occurs if the two values differ
632  *              (according to the register type's @eq@ method), or if the
633  *              input is live but the output is dead.
634  *
635  *              This function only checks for a mismatch and returns the
636  *              result; it takes no other action.  In particular, it doesn't
637  *              report a failure, or dump register values.
638  */
639
640 int tvec_checkregs(struct tvec_state *tv)
641 {
642   const struct tvec_regdef *rd;
643   const struct tvec_reg *rin, *rout;
644
645   for (rd = tv->test->regs; rd->name; rd++) {
646     if (rd->i >= tv->cfg.nrout) continue;
647     rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
648     if (!(rin->f&TVRF_LIVE)) continue;
649     if (!(rout->f&TVRF_LIVE) || !rd->ty->eq(&rin->v, &rout->v, rd))
650       return (-1);
651   }
652   return (0);
653 }
654
655 /* --- @tvec_check@, @tvec_check_v@ --- *
656  *
657  * Arguments:   @struct tvec_state *tv@ = test-vector state
658  *              @const char *detail@, @va_list *ap@ = description of test
659  *
660  * Returns:     ---
661  *
662  * Use:         Check the register values, reporting a failure and dumping
663  *              the registers in the event of a mismatch.  This just wraps up
664  *              @tvec_checkregs@, @tvec_fail@ and @tvec_mismatch@ in the
665  *              obvious way.
666  */
667
668 void tvec_check(struct tvec_state *tv, const char *detail, ...)
669 {
670   va_list ap;
671   va_start(ap, detail); tvec_check_v(tv, detail, &ap); va_end(ap);
672 }
673
674 void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
675 {
676   if (tvec_checkregs(tv))
677     { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
678 }
679
680 /* --- @open_test@ --- *
681  *
682  * Arguments:   @struct tvec_state *tv@ = test-vector state
683  *
684  * Returns:     ---
685  *
686  * Use:         Note that we are now collecting data for a new test.  The
687  *              current line number is recorded in @test_lno@.  The
688  *              @TVSF_OPEN@ flag is set, and @TVSF_XFAIL@ is reset.
689  *
690  *              If a test is already open, then do nothing.
691  */
692
693 static void open_test(struct tvec_state *tv)
694 {
695   if (!(tv->f&TVSF_OPEN)) {
696     tv->test_lno = tv->lno;
697     tv->f |= TVSF_OPEN; tv->f &= ~TVSF_XFAIL;
698   }
699 }
700
701 /* --- @begin_test@ --- *
702  *
703  * Arguments:   @struct tvec_state *tv@ = test-vector state
704  *
705  * Returns:     ---
706  *
707  * Use:         Note that we're about to try running a state.  This is called
708  *              before the test environment's @before@ function.  Mark the
709  *              test as active, clear the outcome, and inform the output
710  *              driver.
711  */
712
713 static void begin_test(struct tvec_state *tv)
714 {
715   tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK;
716   tv->output->ops->btest(tv->output);
717 }
718
719 /* --- @tvec_endtest@ --- *
720  *
721  * Arguments:   @struct tvec_state *tv@ = test-vector state
722  *
723  * Returns:     ---
724  *
725  * Use:         End an ad-hoc test case,  The statistics are updated and the
726  *              outcome is reported to the output formatter.
727  */
728
729 void tvec_endtest(struct tvec_state *tv)
730 {
731   unsigned out;
732
733   if (!(tv->f&TVSF_ACTIVE)) /* nothing to do */;
734   else if (tv->f&TVSF_XFAIL) set_outcome(tv, TVOUT_XFAIL);
735   else set_outcome(tv, TVOUT_WIN);
736   out = (tv->f&TVSF_OUTMASK) >> TVSF_OUTSHIFT;
737   assert(out < TVOUT_LIMIT); tv->curr[out]++;
738   tv->output->ops->etest(tv->output, out);
739   tv->f &= ~TVSF_OPEN;
740 }
741
742 /* --- @tvec_xfail@ --- *
743  *
744  * Arguments:   @struct tvec_state *tv@ = test-vector state
745  *
746  * Returns:     ---
747  *
748  * Use:         Mark the current test as an `expected failure'.  That is, the
749  *              behaviour -- if everything works as expected -- is known to
750  *              be incorrect, perhaps as a result of a longstanding bug, so
751  *              calling it a `success' would be inappropriate.  A failure, as
752  *              reported by @tvec_fail@, means that the behaviour is not as
753  *              expected -- either the long-standing bug has been fixed, or a
754  *              new bug has been introduced -- so investigation is required.
755  *
756  *              An expected failure doesn't cause the test group or the
757  *              session as a whole to fail, but it isn't counted as a win
758  *              either.
759  */
760
761 void tvec_xfail(struct tvec_state *tv)
762   { tv->f |= TVSF_XFAIL; }
763
764 /* --- @check@ --- *
765  *
766  * Arguments:   @struct tvec_state *tv@ = test-vector state
767  *              @struct groupstate *g@ = private test group state
768  *
769  * Returns:     ---
770  *
771  * Use:         Run the current test.
772  *
773  *              This function is called once the data for a test has been
774  *              collected.  It's responsible for checking that all of the
775  *              necessary registers have been assigned values.  It marks the
776  *              output registers as live if the corresponding inputs are
777  *              live.  It calls the environment's @before@, @run@, and
778  *              @after@ functions if provided; if there is no @run@ function,
779  *              then it calls the test function directly, passing it the
780  *              environment's context pointer, and then calls @tvec_check@ to
781  *              verify the output values.
782  */
783
784 static void check(struct tvec_state *tv, struct groupstate *g)
785 {
786   const struct tvec_test *t = tv->test;
787   const struct tvec_env *env = t->env;
788   const struct tvec_regdef *rd;
789   struct tvec_reg *r;
790   unsigned f = 0;
791 #define f_err 1u
792
793   if (!(tv->f&TVSF_OPEN)) return;
794
795   for (rd = t->regs; rd->name; rd++) {
796     r = TVEC_REG(tv, in, rd->i);
797     if (r->f&TVRF_LIVE) {
798       if (rd->i < tv->cfg.nrout)
799         TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE;
800     } else if (!(r->f&TVRF_SEEN) && !(rd->f&TVRF_OPT)) {
801       tvec_error(tv, "required register `%s' not set in test `%s'",
802                  rd->name, t->name);
803       f |= f_err;
804     }
805   }
806
807   if (!(tv->f&TVSF_SKIP)) {
808     begin_test(tv);
809     if (f&f_err) tvec_skip(tv, "erroneous test data");
810     if (env && env->before) env->before(tv, g->ctx);
811     if (!(tv->f&TVSF_ACTIVE))
812       /* forced a skip */;
813     else if (env && env->run)
814       env->run(tv, t->fn, g->ctx);
815     else {
816       t->fn(tv->in, tv->out, g->ctx);
817       tvec_check(tv, 0);
818     }
819     tvec_endtest(tv);
820   }
821
822   if (env && env->after) env->after(tv, g->ctx);
823   g->f &= ~GRPF_SETMASK;
824   tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
825
826 #undef f_err
827 }
828
829 /* --- @begin_test_group@ --- *
830  *
831  * Arguments:   @struct tvec_state *tv@ = test-vector state
832  *              @struct groupstate *g@ = private test group state
833  *
834  * Returns:     ---
835  *
836  * Use:         Begins a test group.  Expects @tv->test@ to have been set
837  *              already.  Calls the output driver, initializes the registers,
838  *              clears the @tv->curr@ counters, allocates the environment
839  *              context and calls the environment @setup@ function.
840  */
841
842 static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
843 {
844   const struct tvec_test *t = tv->test;
845   const struct tvec_env *env = t->env;
846   const struct tvec_regdef *rd0, *rd1;
847   unsigned i;
848
849 #ifndef NDEBUG
850   /* Check that the register names and indices are distinct. */
851   for (rd0 = t->regs; rd0->name; rd0++) {
852     assert(rd0->i < tv->cfg.nreg);
853     for (rd1 = t->regs; rd1->name; rd1++)
854       if (rd0 != rd1) {
855         assert(rd0->i != rd1->i);
856         assert(STRCMP(rd0->name, !=, rd1->name));
857       }
858   }
859 #endif
860
861   tv->output->ops->bgroup(tv->output);
862   tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE);
863   tvec_initregs(tv);
864   for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
865   if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
866   if (env && env->setup) env->setup(tv, env, 0, g->ctx);
867 }
868
869 /* --- @report_group@ --- *
870  *
871  * Arguments:   @struct tvec_state *tv@ = test-vector state
872  *
873  * Returns:     ---
874  *
875  * Use:         Reports the result of the test group to the output driver.
876  *
877  *              If all of the tests have been skipped then report this as a
878  *              group skip.  Otherwise, determine and report the group
879  *              outcome.
880  */
881
882 static void report_group(struct tvec_state *tv)
883 {
884   unsigned i, out, nrun;
885
886   for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
887     { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
888
889   if (tv->curr[TVOUT_SKIP] == nrun)
890     { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
891   else {
892     if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
893     else out = TVOUT_WIN;
894     tv->grps[out]++; tv->output->ops->egroup(tv->output);
895   }
896 }
897
898 /* --- @end_test_group@ --- *
899  *
900  * Arguments:   @struct tvec_state *tv@ = test-vector state
901  *              @struct groupstate *g@ = private test group state
902  *
903  * Returns:     ---
904  *
905  * Use:         Handles the end of a test group.  Called at the end of the
906  *              input file or when a new test group header is found.
907  *
908  *              If a test is open, call @check@ to see whether it worked.  If
909  *              the test group is not being skipped, report the group
910  *              result.  Call the test environment @teardown@ function.  Free
911  *              the environment context and release the registers.
912  *
913  *              If there's no test group active, then nothing happens.
914  */
915
916 static void end_test_group(struct tvec_state *tv, struct groupstate *g)
917 {
918   const struct tvec_test *t = tv->test;
919   const struct tvec_env *env;
920
921   if (!t) return;
922   if (tv->f&TVSF_OPEN) check(tv, g);
923   if (!(tv->f&TVSF_SKIP)) report_group(tv);
924   env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx);
925   tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
926 }
927
928 /* --- @core_findvar@, @core_setvar@ --- *
929  *
930  * Arguments:   @struct tvec_state *tv@ = test vector state
931  *              @const char *var@ = variable name to set
932  *              @const union tvec_regval *rv@ = register value
933  *              @void **ctx_out@ = where to put the @setvar@ context
934  *              @void *ctx@ = context pointer
935  *
936  * Returns:     @core_findvar@ returns a pointer to the variable definition,
937  *              or null; @core_setvar@ returns zero on success or %$-1$% on
938  *              error.
939  *
940  * Use:         Find a definition for a special variable.  The following
941  *              special variables are supported.
942  *
943  *                * %|@outcome|% is a token describing how a successful
944  *                  outcome of the test should be interpreted: %|success|% or
945  *                  %|win|% are the default: a successful test is counted as
946  *                  a pass; or %|expected-failure|% or %|xfail|% means a
947  *                  successful test is counted as an expected failure.  A
948  *                  mismatch is always considered a failure.
949  */
950
951 enum { WIN, XFAIL, NOUT };
952
953 static int core_setvar(struct tvec_state *tv, const char *name,
954                        const union tvec_regval *rv, void *ctx)
955 {
956   struct groupstate *g = ctx;
957
958   if (STRCMP(name, ==, "@outcome")) {
959     if (g->f&GRPF_SETOUTC) return (tvec_dupregerr(tv, name));
960     if (rv->u == XFAIL) tvec_xfail(tv);
961     g->f |= GRPF_SETOUTC;
962   } else assert(!"unknown var");
963   return (0);
964 }
965
966 static const struct tvec_uassoc outcome_assoc[] = {
967   { "success",          WIN },
968   { "win",              WIN },
969   { "expected-failure", XFAIL },
970   { "xfail",            XFAIL },
971   TVEC_ENDENUM
972 };
973 static const struct tvec_urange outcome_range = { 0, NOUT - 1 };
974 static const struct tvec_uenuminfo outcome_enum =
975   { "test-outcome", outcome_assoc, &outcome_range };
976 static const struct tvec_vardef outcome_vardef =
977   { sizeof(struct tvec_reg), core_setvar,
978     { "@outcome", &tvty_uenum, -1, 0, { &outcome_enum } } };
979
980 static const struct tvec_vardef *core_findvar
981   (struct tvec_state *tv, const char *name, void **ctx_out, void *ctx)
982 {
983   if (STRCMP(name, ==, "@outcome"))
984     { *ctx_out = ctx; return (&outcome_vardef); }
985   else
986     return (0);
987 }
988
989 /* --- @tvec_read@ --- *
990  *
991  * Arguments:   @struct tvec_state *tv@ = test-vector state
992  *              @const char *infile@ = the name of the input file
993  *              @FILE *fp@ = stream to read from
994  *
995  * Returns:     Zero on success, @-1@ on error.
996  *
997  * Use:         Read test vector data from @fp@ and exercise test functions.
998  *              THe return code doesn't indicate test failures: it's only
999  *              concerned with whether there were problems with the input
1000  *              file or with actually running the tests.
1001  */
1002
1003 int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
1004 {
1005   dstr d = DSTR_INIT;
1006   const struct tvec_test *test;
1007   const struct tvec_env *env;
1008   const struct tvec_regdef *rd;
1009   const struct tvec_vardef *vd = 0; void *varctx;
1010   struct tvec_reg *r = 0, rbuf, *r_alloc = 0; size_t rsz = 0;
1011   struct groupstate g = GROUPSTATE_INIT;
1012   int ch, rc = 0;
1013
1014   /* Set the initial location. */
1015   tv->infile = infile; tv->lno = 1; tv->fp = fp;
1016
1017   for (;;) {
1018
1019     /* Get the next character and dispatch.  Note that we're always at the
1020      * start of a line here.
1021      */
1022     ch = getc(tv->fp);
1023     switch (ch) {
1024
1025       case EOF:
1026         /* End of the file.  Exit the loop. */
1027
1028         goto end;
1029
1030       case '[':
1031         /* A test group header. */
1032
1033         /* End the current group, if there is one. */
1034         end_test_group(tv, &g);
1035
1036         /* Read the group name.  There may be leading and trailing
1037          * whitespace.
1038          */
1039         DRESET(&d); tvec_readword(tv, &d, 0, "];", "group name");
1040         tvec_skipspc(tv);
1041         ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
1042
1043         /* Find the matching test definition. */
1044         for (test = tv->cfg.tests; test->name; test++)
1045           if (STRCMP(d.buf, ==, test->name)) goto found_test;
1046
1047         /* There wasn't one.  Report the error.  Muffle errors about the
1048          * contents of this section because they won't be interesting.
1049          */
1050         tvec_error(tv, "unknown test group `%s'", d.buf);
1051         tv->f |= TVSF_MUFFLE; goto flush_line;
1052
1053       found_test:
1054         /* Eat trailing whitespace and comments. */
1055         tvec_flushtoeol(tv, 0);
1056
1057         /* Set up the new test group. */
1058         tv->test = test; begin_test_group(tv, &g);
1059         break;
1060
1061       case '\n':
1062         /* A newline, so this was a completely empty line.  Advance the line
1063          * counter, and run the current test.
1064          */
1065
1066         tv->lno++;
1067         if (tv->f&TVSF_OPEN) check(tv, &g);
1068         break;
1069
1070       case ';':
1071         /* A semicolon.  Skip the comment. */
1072
1073         tvec_flushtoeol(tv, TVFF_ALLOWANY);
1074         break;
1075
1076       default:
1077         /* Something else. */
1078
1079         if (isspace(ch)) {
1080           /* Whitespace.  Skip and see what we find. */
1081
1082           tvec_skipspc(tv); ch = getc(tv->fp);
1083
1084           /* If the file ends, then we're done.  If we find a comment then we
1085            * skip it.  If there's some non-whitespace, then report an error.
1086            * Otherwise the line was effectively blank, so run the test.
1087            */
1088           if (ch == EOF) goto end;
1089           else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY);
1090           else if (tvec_flushtoeol(tv, 0)) rc = -1;
1091           else check(tv, &g);
1092         } else {
1093           /* Some non-whitespace thing. */
1094
1095           /* Put the character back and read a word, which ought to be a
1096            * register name.
1097            */
1098           ungetc(ch, tv->fp);
1099           DRESET(&d);
1100           if (tvec_readword(tv, &d, 0, "=:*;", "register name"))
1101             goto flush_line;
1102
1103           /* See what sort of thing we have found. */
1104           if (d.buf[0] == '@') {
1105             /* A special register assignment.  */
1106
1107             env = tv->test->env;
1108
1109             /* Find a variable definition. */
1110             vd = core_findvar(tv, d.buf, &varctx, &g);
1111               if (vd) goto found_var;
1112             if (env && env->findvar) {
1113               vd = env->findvar(tv, d.buf, &varctx, g.ctx);
1114                 if (vd) goto found_var;
1115             }
1116             tvec_unkregerr(tv, d.buf); goto flush_line;
1117           found_var:
1118             rd = &vd->def;
1119           } else {
1120             /* A standard register. */
1121
1122             /* Find the definition. */
1123             for (rd = tv->test->regs; rd->name; rd++)
1124               if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
1125             tvec_error(tv, "unknown register `%s' for test `%s'",
1126                        d.buf, tv->test->name);
1127             goto flush_line;
1128           found_reg:
1129
1130             /* Complain if the register is already set. */
1131             r = TVEC_REG(tv, in, rd->i);
1132             if (r->f&TVRF_SEEN)
1133               { tvec_dupregerr(tv, rd->name); goto flush_line; }
1134           }
1135
1136           /* If there's no test, then report an error.  Set the muffle flag,
1137            * because there's no point in complaining about every assignment
1138            * in this block.
1139            */
1140           if (!tv->test) {
1141             if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test");
1142             tv->f |= TVSF_MUFFLE; goto flush_line;
1143           }
1144
1145           /* Open the test.  This is syntactically a paragraph of settings,
1146            * so it's fair to report on missing register assignments.
1147            */
1148           open_test(tv);
1149
1150           /* Now there should be a separator. */
1151           tvec_skipspc(tv); ch = getc(tv->fp);
1152
1153           if (ch == '*') {
1154             /* Register explicitly marked unset. */
1155
1156             if (vd) {
1157               tvec_error(tv, "can't unset special variables");
1158               goto flush_line;
1159             }
1160             if (!(rd->f&(TVRF_OPT | TVRF_UNSET))) {
1161               tvec_error(tv, "register `%s' must be assigned "
1162                          "a value in test `%s'", rd->name, tv->test->name);
1163               goto flush_line;
1164             }
1165             r->f |= TVRF_SEEN;
1166             if (tvec_flushtoeol(tv, 0)) goto bad;
1167           } else {
1168             /* Common case of a proper assignment. */
1169
1170             /* We must have a separator. */
1171             if (ch != '=' && ch != ':')
1172               { tvec_syntax(tv, ch, "`=', `:', or `*'"); goto flush_line; }
1173             tvec_skipspc(tv);
1174
1175             if (!vd) {
1176               /* An ordinary register.  Parse a value and mark the register
1177                * as live.
1178                */
1179
1180               if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
1181               r->f |= TVRF_LIVE | TVRF_SEEN;
1182             } else {
1183               /* A special register defined by an environment. */
1184
1185               /* Set up the register. */
1186               if (vd->regsz <= sizeof(rbuf))
1187                 r = &rbuf;
1188               else {
1189                 GROWBUF_REPLACE(&arena_stdlib, r_alloc, rsz, vd->regsz,
1190                                 8*sizeof(void *), 1);
1191                 r = r_alloc;
1192               }
1193
1194               /* Read and set the value. */
1195               rd->ty->init(&r->v, rd);
1196               if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
1197               if (!(tv->f&TVSF_SKIP) && vd->setvar(tv, d.buf, &r->v, varctx))
1198                 goto bad;
1199
1200               /* Clean up. */
1201               rd->ty->release(&r->v, &vd->def); vd = 0;
1202             }
1203           }
1204         }
1205         break;
1206     }
1207     continue;
1208
1209   flush_line:
1210     /* This is a general parse-failure handler.  Skip to the next line and
1211      * remember that things didn't go so well.
1212      */
1213     tvec_flushtoeol(tv, TVFF_ALLOWANY);
1214   bad:
1215     if (vd) { vd->def.ty->release(&r->v, &vd->def); vd = 0; }
1216     rc = -1;
1217   }
1218
1219   /* We reached the end.  If that was actually an I/O error then report it.
1220    */
1221   if (ferror(tv->fp))
1222     { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
1223
1224 end:
1225   /* Process the final test, if there was one, and wrap up the final
1226    * group.
1227    */
1228   end_test_group(tv, &g);
1229
1230   /* Clean up. */
1231   tv->infile = 0; tv->fp = 0;
1232   dstr_destroy(&d);
1233   xfree(r_alloc);
1234   return (rc);
1235
1236 #undef rlive
1237 }
1238
1239 /*----- Session lifecycle -------------------------------------------------*/
1240
1241 /* --- @tvec_begin@ --- *
1242  *
1243  * Arguments:   @struct tvec_state *tv_out@ = state structure to fill in
1244  *              @const struct tvec_config *config@ = test configuration
1245  *              @struct tvec_output *o@ = output driver
1246  *
1247  * Returns:     ---
1248  *
1249  * Use:         Initialize a state structure ready to do some testing.
1250  */
1251
1252 void tvec_begin(struct tvec_state *tv_out,
1253                 const struct tvec_config *config,
1254                 struct tvec_output *o)
1255 {
1256   unsigned i;
1257
1258   tv_out->f = 0;
1259
1260   assert(config->nrout <= config->nreg);
1261   tv_out->cfg = *config;
1262   tv_out->in = xmalloc(tv_out->cfg.nreg*tv_out->cfg.regsz);
1263   tv_out->out = xmalloc(tv_out->cfg.nrout*tv_out->cfg.regsz);
1264   for (i = 0; i < tv_out->cfg.nreg; i++) {
1265     TVEC_REG(tv_out, in, i)->f = 0;
1266     if (i < tv_out->cfg.nrout) TVEC_REG(tv_out, out, i)->f = 0;
1267   }
1268
1269   for (i = 0; i < TVOUT_LIMIT; i++)
1270     tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
1271
1272   tv_out->test = 0;
1273   tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
1274   tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out);
1275 }
1276
1277 /* --- @tvec_end@ --- *
1278  *
1279  * Arguments:   @struct tvec_state *tv@ = test-vector state
1280  *
1281  * Returns:     A proposed exit code.
1282  *
1283  * Use:         Conclude testing and suggests an exit code to be returned to
1284  *              the calling program.  (The exit code comes from the output
1285  *              driver's @esession@ method.)
1286  */
1287
1288 int tvec_end(struct tvec_state *tv)
1289 {
1290   int rc = tv->output->ops->esession(tv->output);
1291
1292   if (tv->test) tvec_releaseregs(tv);
1293   tv->output->ops->destroy(tv->output);
1294   xfree(tv->in); xfree(tv->out);
1295   return (rc);
1296 }
1297
1298 /*----- Serialization and deserialization ---------------------------------*/
1299
1300 /* --- @tvec_serialize@ --- *
1301  *
1302  * Arguments:   @const struct tvec_reg *rv@ = vector of registers
1303  *              @buf *b@ = buffer to write on
1304  *              @const struct tvec_regdef *regs@ = vector of register
1305  *                      descriptions, terminated by an entry with a null
1306  *                      @name@ slot
1307  *              @unsigned nr@ = number of entries in the @rv@ vector
1308  *              @size_t regsz@ = true size of each element of @rv@
1309  *
1310  * Returns:     Zero on success, @-1@ on failure.
1311  *
1312  * Use:         Serialize a collection of register values.
1313  *
1314  *              The serialized output is written to the buffer @b@.  Failure
1315  *              can be caused by running out of buffer space, or a failing
1316  *              type handler.
1317  */
1318
1319 int tvec_serialize(const struct tvec_reg *rv, buf *b,
1320                    const struct tvec_regdef *regs,
1321                    unsigned nr, size_t regsz)
1322 {
1323   unsigned char *bitmap;
1324   size_t i, bitoff, nbits, bitsz;
1325   const struct tvec_regdef *rd;
1326   const struct tvec_reg *r;
1327
1328   bitoff = BLEN(b);
1329   for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
1330   bitsz = (nbits + 7)/8;
1331
1332   bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
1333   memset(bitmap, 0, bitsz);
1334   for (rd = regs, i = 0; rd->name; rd++, i++) {
1335     if (rd->i >= nr) continue;
1336     r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
1337     bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8;
1338     if (rd->ty->tobuf(b, &r->v, rd)) return (-1);
1339   }
1340   return (0);
1341 }
1342
1343 /* --- @tvec_deserialize@ --- *
1344  *
1345  * Arguments:   @struct tvec_reg *rv@ = vector of registers
1346  *              @buf *b@ = buffer to write on
1347  *              @const struct tvec_regdef *regs@ = vector of register
1348  *                      descriptions, terminated by an entry with a null
1349  *                      @name@ slot
1350  *              @unsigned nr@ = number of entries in the @rv@ vector
1351  *              @size_t regsz@ = true size of each element of @rv@
1352  *
1353  * Returns:     Zero on success, @-1@ on failure.
1354  *
1355  * Use:         Deserialize a collection of register values.
1356  *
1357  *              The size of the register vector @nr@ and the register
1358  *              definitions @regs@ must match those used when producing the
1359  *              serialization.  For each serialized register value,
1360  *              deserialize and store the value into the appropriate register
1361  *              slot, and set the @TVRF_LIVE@ flag on the register.  See
1362  *              @tvec_serialize@ for a description of the format.
1363  *
1364  *              Failure results only from a failing register type handler.
1365  */
1366
1367 int tvec_deserialize(struct tvec_reg *rv, buf *b,
1368                      const struct tvec_regdef *regs,
1369                      unsigned nr, size_t regsz)
1370 {
1371   const unsigned char *bitmap;
1372   size_t i, nbits, bitsz;
1373   const struct tvec_regdef *rd;
1374   struct tvec_reg *r;
1375
1376   for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
1377   bitsz = (nbits + 7)/8;
1378
1379   bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
1380   for (rd = regs, i = 0; rd->name; rd++, i++) {
1381     if (rd->i >= nr) continue;
1382     if (!(bitmap[i/8]&(1 << i%8))) continue;
1383     r = TVEC_GREG(rv, rd->i, regsz);
1384     if (rd->ty->frombuf(b, &r->v, rd)) return (-1);
1385     r->f |= TVRF_LIVE;
1386   }
1387   return (0);
1388 }
1389
1390 /*----- Ad-hoc testing ----------------------------------------------------*/
1391
1392 static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
1393
1394 static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
1395   { assert(!"fake test function"); }
1396
1397 /* --- @tvec_adhoc@ --- *
1398  *
1399  * Arguments:   @struct tvec_state *tv@ = test-vector state
1400  *              @struct tvec_test *t@ = space for a test definition
1401  *
1402  * Returns:     ---
1403  *
1404  * Use:         Begin ad-hoc testing, i.e., without reading a file of
1405  *              test-vector data.
1406  *
1407  *              The structure at @t@ will be used to record information about
1408  *              the tests underway, which would normally come from a static
1409  *              test definition.  The other functions in this section assume
1410  *              that @tvec_adhoc@ has been called.
1411  */
1412
1413 void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
1414 {
1415   t->name = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
1416   tv->cfg.tests = t;
1417 }
1418
1419 /* --- @tvec_begingroup@ --- *
1420  *
1421  * Arguments:   @struct tvec_state *tv@ = test-vector state
1422  *              @const char *name@ = name for this test group
1423  *              @const char *file@, @unsigned @lno@ = calling file and line
1424  *
1425  * Returns:     ---
1426  *
1427  * Use:         Begin an ad-hoc test group with the given name.  The @file@
1428  *              and @lno@ can be anything, but it's usually best if they
1429  *              refer to the source code performing the test: the macro
1430  *              @TVEC_BEGINGROUP@ does this automatically.
1431  */
1432
1433 void tvec_begingroup(struct tvec_state *tv, const char *name,
1434                      const char *file, unsigned lno)
1435 {
1436   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->cfg.tests;
1437
1438   t->name = name; tv->test = t;
1439   tv->infile = file; tv->lno = tv->test_lno = lno;
1440   begin_test_group(tv, 0);
1441 }
1442
1443 /* --- @tvec_endgroup@ --- *
1444  *
1445  * Arguments:   @struct tvec_state *tv@ = test-vector state
1446  *
1447  * Returns:     ---
1448  *
1449  * Use:         End an ad-hoc test group.  The statistics are updated and the
1450  *              outcome is reported to the output formatter.
1451  */
1452
1453 void tvec_endgroup(struct tvec_state *tv)
1454 {
1455   if (!(tv->f&TVSF_SKIP)) report_group(tv);
1456   tv->test = 0;
1457 }
1458
1459 /* --- @tvec_begintest@ --- *
1460  *
1461  * Arguments:   @struct tvec_state *tv@ = test-vector state
1462  *              @const char *file@, @unsigned @lno@ = calling file and line
1463  *
1464  * Returns:     ---
1465  *
1466  * Use:         Begin an ad-hoc test case.  The @file@ and @lno@ can be
1467  *              anything, but it's usually best if they refer to the source
1468  *              code performing the test: the macro @TVEC_BEGINGROUP@ does
1469  *              this automatically.
1470  */
1471
1472 void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
1473 {
1474   tv->infile = file; tv->lno = tv->test_lno = lno;
1475   open_test(tv); begin_test(tv);
1476 }
1477
1478 struct adhoc_claim {
1479   unsigned f;
1480 #define ACF_FRESH 1u
1481   const char *saved_file; unsigned saved_lno;
1482 };
1483
1484 static void adhoc_claim_setup(struct tvec_state *tv,
1485                               struct adhoc_claim *ck,
1486                               const struct tvec_regdef *regs,
1487                               const char *file, unsigned lno)
1488 {
1489   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
1490
1491   ck->f = 0;
1492
1493   if (!(tv->f&TVSF_OPEN))
1494     { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
1495
1496   ck->saved_file = tv->infile; if (file) tv->infile = file;
1497   ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno;
1498   t->regs = regs ? regs : &no_regs;
1499 }
1500
1501 static void adhoc_claim_teardown(struct tvec_state *tv,
1502                                  struct adhoc_claim *ck)
1503 {
1504   struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
1505
1506   t->regs = &no_regs;
1507   tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
1508
1509   if (ck->f&ACF_FRESH) tvec_endtest(tv);
1510 }
1511
1512 /* --- @tvec_claim@, @tvec_claim_v@, @TVEC_CLAIM@ --- *
1513  *
1514  * Arguments:   @struct tvec_state *tv@ = test-vector state
1515  *              @int ok@ = a flag
1516  *              @const char *file@, @unsigned @lno@ = calling file and line
1517  *              @const char *msg@, @va_list *ap@ = message to report on
1518  *                      failure
1519  *
1520  * Returns:     The value @ok@.
1521  *
1522  * Use:         Check that a claimed condition holds, as (part of) a test.
1523  *              If no test case is underway (i.e., if @TVSF_OPEN@ is reset in
1524  *              @tv->f@), then a new test case is begun and ended.  The
1525  *              @file@ and @lno@ are passed to the output formatter to be
1526  *              reported in case of a failure.  If @ok@ is nonzero, then
1527  *              nothing else happens; so, in particular, if @tvec_claim@
1528  *              established a new test case, then the test case succeeds.  If
1529  *              @ok@ is zero, then a failure is reported, quoting @msg@.
1530  *
1531  *              The @TVEC_CLAIM@ macro is similar, only it (a) identifies the
1532  *              file and line number of the call site automatically, and (b)
1533  *              implicitly quotes the source text of the @ok@ condition in
1534  *              the failure message.
1535  */
1536
1537 int tvec_claim_v(struct tvec_state *tv, int ok,
1538                  const char *file, unsigned lno,
1539                  const char *msg, va_list *ap)
1540 {
1541   struct adhoc_claim ck;
1542
1543   adhoc_claim_setup(tv, &ck, 0, file, lno);
1544   if (!ok) tvec_fail_v(tv, msg, ap);
1545   adhoc_claim_teardown(tv, &ck);
1546   return (ok);
1547 }
1548
1549 int tvec_claim(struct tvec_state *tv, int ok,
1550                const char *file, unsigned lno, const char *msg, ...)
1551 {
1552   va_list ap;
1553
1554   va_start(ap, msg); tvec_claim_v(tv, ok, file, lno, msg, &ap); va_end(ap);
1555   return (ok);
1556 }
1557
1558 int tvec_claimeq(struct tvec_state *tv,
1559                  const struct tvec_regty *ty, const union tvec_misc *arg,
1560                  const char *file, unsigned lno, const char *expr)
1561 {
1562   struct tvec_regdef regs[2];
1563   struct adhoc_claim ck;
1564   int ok;
1565
1566   tv->in[0].f = tv->out[0].f = TVRF_LIVE;
1567
1568   regs[0].name = "value"; regs[0].i = 0;
1569   regs[0].ty = ty; regs[0].f = 0;
1570   if (arg) regs[0].arg = *arg;
1571   else regs[0].arg.p = 0;
1572
1573   regs[1].name = 0;
1574
1575   adhoc_claim_setup(tv, &ck, regs, file, lno);
1576   ok = ty->eq(&tv->in[0].v, &tv->out[0].v, &regs[0]);
1577   if (!ok)
1578     { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
1579   adhoc_claim_teardown(tv, &ck);
1580   return (ok);
1581 }
1582
1583 /*----- That's all, folks -------------------------------------------------*/