@@@ man wip
[mLib] / test / tvec-core.c
CommitLineData
b64eb60f
MW
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>
882a39c1 32#include <errno.h>
b64eb60f
MW
33#include <string.h>
34
35#include "alloc.h"
adec5584 36#include "growbuf.h"
b64eb60f
MW
37#include "tvec.h"
38
39/*----- Output ------------------------------------------------------------*/
40
31d0247c
MW
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
51const 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
c91413e6 62/* --- @tvec_report@, @tvec_report_v@ --- *
67b5031e
MW
63 *
64 * Arguments: @struct tvec_state *tv@ = test-vector state
31d0247c 65 * @unsigned level@ = severity level (@TVlEV_...@)
67b5031e
MW
66 * @const char *msg@, @va_list ap@ = error message
67 *
c91413e6 68 * Returns: ---
67b5031e 69 *
c91413e6
MW
70 * Use: Report an message with a given severity. Messages with level
71 * @TVLEV_ERR@ or higher force a nonzero exit code.
67b5031e
MW
72 */
73
c91413e6 74void tvec_report(struct tvec_state *tv, unsigned level, const char *msg, ...)
882a39c1
MW
75{
76 va_list ap;
77
c91413e6 78 va_start(ap, msg); tvec_report_v(tv, level, msg, &ap); va_end(ap);
882a39c1 79}
c91413e6
MW
80
81void tvec_report_v(struct tvec_state *tv, unsigned level,
82 const char *msg, va_list *ap)
67b5031e 83{
c91413e6
MW
84 tv->output->ops->report(tv->output, level, msg, ap);
85 if (level >= TVLEV_ERR) tv->f |= TVSF_ERROR;
67b5031e
MW
86}
87
c91413e6 88/* --- @tvec_error@, @tvec_notice@ --- *
67b5031e
MW
89 *
90 * Arguments: @struct tvec_state *tv@ = test-vector state
c91413e6 91 * @const char *msg@, @va_list ap@ = error message
67b5031e 92 *
c91413e6
MW
93 * Returns: The @tvec_error@ function returns @-1@ as a trivial
94 * convenience; @tvec_notice@ does not return a value.
67b5031e 95 *
c91413e6
MW
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.
67b5031e 101 */
b64eb60f 102
c91413e6
MW
103int 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
b64eb60f
MW
111void tvec_notice(struct tvec_state *tv, const char *msg, ...)
112{
113 va_list ap;
c91413e6
MW
114
115 va_start(ap, msg); tvec_report_v(tv, TVLEV_NOTE, msg, &ap); va_end(ap);
b64eb60f 116}
b64eb60f 117
67b5031e 118/*----- Test processing ---------------------------------------------------*/
b64eb60f 119
31d0247c
MW
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
b64eb60f
MW
132void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
133{
134 va_list ap;
67b5031e 135
b64eb60f
MW
136 va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
137}
31d0247c 138
b64eb60f
MW
139void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
140{
c91413e6
MW
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 }
b64eb60f
MW
145}
146
31d0247c
MW
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
b64eb60f 158static void set_outcome(struct tvec_state *tv, unsigned out)
31d0247c
MW
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 */
b64eb60f
MW
172
173void 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}
31d0247c 178
b64eb60f
MW
179void 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
31d0247c
MW
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
b64eb60f
MW
203void 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}
31d0247c 208
b64eb60f
MW
209void 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
31d0247c
MW
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
e63124bc
MW
234void 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); }
b64eb60f 238
31d0247c
MW
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
e63124bc 253void tvec_mismatch(struct tvec_state *tv, unsigned f)
b64eb60f 254{
b64eb60f 255 const struct tvec_regdef *rd;
e63124bc 256 const struct tvec_reg *rin, *rout;
b64eb60f 257
e63124bc 258 for (rd = tv->test->regs; rd->name; rd++) {
d056fbdf 259 if (rd->i >= tv->cfg.nrout) {
e63124bc
MW
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 }
b64eb60f 275 }
b64eb60f
MW
276}
277
67b5031e 278/*----- Parsing -----------------------------------------------------------*/
b64eb60f 279
31d0247c
MW
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
67b5031e
MW
294int 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}
31d0247c 301
67b5031e
MW
302int 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}
e63124bc 320
814e42ff
MW
321/* --- @tvec_unkreg@ --- *
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
332int tvec_unkreg(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
31d0247c
MW
338/* --- @tvec_dupreg@ --- *
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
349int tvec_dupreg(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
b64eb60f
MW
363void 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
31d0247c
MW
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
882a39c1 392int tvec_flushtoeol(struct tvec_state *tv, unsigned f)
b64eb60f 393{
882a39c1 394 int ch, rc = 0;
b64eb60f
MW
395
396 for (;;) {
397 ch = getc(tv->fp);
398 switch (ch) {
882a39c1
MW
399 case '\n': tv->lno++; return (rc);
400 case EOF: return (rc);
b64eb60f
MW
401 case ';': f |= TVFF_ALLOWANY; break;
402 default:
882a39c1 403 if (!(f&TVFF_ALLOWANY) && !isspace(ch)) {
b64eb60f 404 tvec_syntax(tv, ch, "end-of-line");
882a39c1
MW
405 rc = -1; f |= TVFF_ALLOWANY;
406 }
b64eb60f
MW
407 break;
408 }
409 }
410}
411
31d0247c
MW
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
b64eb60f
MW
435int 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
31d0247c
MW
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
adec5584 473 * @const char **p_inout@ = pointer into string, updated
31d0247c
MW
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.
adec5584
MW
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.
31d0247c
MW
504 */
505
adec5584
MW
506int tvec_readword(struct tvec_state *tv, dstr *d, const char **p_inout,
507 const char *delims, const char *expect, ...)
b64eb60f
MW
508{
509 va_list ap;
510 int rc;
511
512 va_start(ap, expect);
adec5584 513 rc = tvec_readword_v(tv, d, p_inout, delims, expect, &ap);
b64eb60f
MW
514 va_end(ap);
515 return (rc);
516}
31d0247c 517
adec5584
MW
518int tvec_readword_v(struct tvec_state *tv, dstr *d, const char **p_inout,
519 const char *delims, const char *expect, va_list *ap)
b64eb60f 520{
adec5584 521 size_t pos = 0;
b64eb60f
MW
522 int ch;
523
adec5584
MW
524 tvec_skipspc(tv);
525
b64eb60f 526 ch = getc(tv->fp);
e63124bc 527 if (!ch || ch == '\n' || ch == EOF || ch == ';' ||
b64eb60f 528 (delims && strchr(delims, ch))) {
882a39c1 529 if (expect) return (tvec_syntax(tv, ch, expect, ap));
b64eb60f
MW
530 else { ungetc(ch, tv->fp); return (-1); }
531 }
adec5584
MW
532 if (p_inout) pos = *p_inout - d->buf;
533 if (d->len) {
534 if (pos == d->len) pos++;
535 DPUTC(d, ' ');
536 }
b64eb60f
MW
537 do {
538 DPUTC(d, ch);
539 ch = getc(tv->fp);
e63124bc
MW
540 } while (ch && ch != EOF && !isspace(ch) &&
541 (!delims || !strchr(delims, ch)));
b64eb60f 542 DPUTZ(d); if (ch != EOF) ungetc(ch, tv->fp);
adec5584 543 if (p_inout) *p_inout = d->buf + pos;
b64eb60f
MW
544 return (0);
545}
546
67b5031e
MW
547/*----- Main machinery ----------------------------------------------------*/
548
549struct groupstate {
31d0247c 550 void *ctx; /* test environment context */
814e42ff
MW
551 unsigned f; /* flags */
552#define GRPF_SETOUTC 1u /* set outcome */
553#define GRPF_SETMASK (GRPF_SETOUTC) /* mask of all variable flags */
67b5031e 554};
814e42ff 555#define GROUPSTATE_INIT { 0, 0 }
67b5031e 556
31d0247c
MW
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 */
e63124bc 568
c91413e6 569void tvec_initregs(struct tvec_state *tv)
b64eb60f
MW
570{
571 const struct tvec_regdef *rd;
572 struct tvec_reg *r;
573
574 for (rd = tv->test->regs; rd->name; rd++) {
d056fbdf 575 assert(rd->i < tv->cfg.nreg); r = TVEC_REG(tv, in, rd->i);
b64eb60f 576 rd->ty->init(&r->v, rd); r->f = 0;
d056fbdf 577 if (rd->i < tv->cfg.nrout)
b64eb60f
MW
578 { r = TVEC_REG(tv, out, rd->i); rd->ty->init(&r->v, rd); r->f = 0; }
579 }
b64eb60f
MW
580}
581
c91413e6 582void tvec_releaseregs(struct tvec_state *tv)
b64eb60f
MW
583{
584 const struct tvec_regdef *rd;
585 struct tvec_reg *r;
586
587 for (rd = tv->test->regs; rd->name; rd++) {
d056fbdf 588 assert(rd->i < tv->cfg.nreg); r = TVEC_REG(tv, in, rd->i);
b64eb60f 589 rd->ty->release(&r->v, rd); r->f = 0;
d056fbdf 590 if (rd->i < tv->cfg.nrout)
b64eb60f
MW
591 { r = TVEC_REG(tv, out, rd->i); rd->ty->release(&r->v, rd); r->f = 0; }
592 }
593}
594
31d0247c
MW
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
608void 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++) {
d056fbdf
MW
614 assert(rd->i < tv->cfg.nreg);
615 if (rd->i >= tv->cfg.nrout) continue;
31d0247c
MW
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
e63124bc 640int tvec_checkregs(struct tvec_state *tv)
b64eb60f
MW
641{
642 const struct tvec_regdef *rd;
643 const struct tvec_reg *rin, *rout;
b64eb60f 644
b64eb60f 645 for (rd = tv->test->regs; rd->name; rd++) {
d056fbdf 646 if (rd->i >= tv->cfg.nrout) continue;
b64eb60f
MW
647 rin = TVEC_REG(tv, in, rd->i); rout = TVEC_REG(tv, out, rd->i);
648 if (!rin->f&TVRF_LIVE) continue;
e63124bc
MW
649 if (!(rout->f&TVRF_LIVE) || !rd->ty->eq(&rin->v, &rout->v, rd))
650 return (-1);
b64eb60f 651 }
e63124bc 652 return (0);
b64eb60f
MW
653}
654
31d0247c
MW
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
e63124bc
MW
668void 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}
31d0247c 673
e63124bc 674void tvec_check_v(struct tvec_state *tv, const char *detail, va_list *ap)
b64eb60f 675{
e63124bc
MW
676 if (tvec_checkregs(tv))
677 { tvec_fail_v(tv, detail, ap); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
b64eb60f
MW
678}
679
31d0247c
MW
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
20ba6b0b
MW
693static 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
31d0247c
MW
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
b64eb60f
MW
713static void begin_test(struct tvec_state *tv)
714{
e63124bc 715 tv->f |= TVSF_ACTIVE; tv->f &= ~TVSF_OUTMASK;
b64eb60f
MW
716 tv->output->ops->btest(tv->output);
717}
718
31d0247c
MW
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
b64eb60f
MW
729void tvec_endtest(struct tvec_state *tv)
730{
731 unsigned out;
732
20ba6b0b
MW
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;
b64eb60f
MW
737 assert(out < TVOUT_LIMIT); tv->curr[out]++;
738 tv->output->ops->etest(tv->output, out);
739 tv->f &= ~TVSF_OPEN;
740}
741
31d0247c
MW
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
761void 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 * it calls @tvec_check@ to verify the output values.
780 */
781
e63124bc 782static void check(struct tvec_state *tv, struct groupstate *g)
b64eb60f 783{
e63124bc 784 const struct tvec_test *t = tv->test;
c81c35df 785 const struct tvec_env *env = t->env;
b64eb60f 786 const struct tvec_regdef *rd;
814e42ff
MW
787 unsigned f = 0;
788#define f_err 1u
b64eb60f
MW
789
790 if (!(tv->f&TVSF_OPEN)) return;
791
e63124bc 792 for (rd = t->regs; rd->name; rd++) {
d056fbdf
MW
793 if (TVEC_REG(tv, in, rd->i)->f&TVRF_LIVE) {
794 if (rd->i < tv->cfg.nrout)
795 TVEC_REG(tv, out, rd->i)->f |= TVRF_LIVE;
796 } else if (!(rd->f&TVRF_OPT)) {
b64eb60f 797 tvec_error(tv, "required register `%s' not set in test `%s'",
e63124bc 798 rd->name, t->name);
814e42ff 799 f |= f_err;
882a39c1 800 }
b64eb60f
MW
801 }
802
e63124bc
MW
803 if (!(tv->f&TVSF_SKIP)) {
804 begin_test(tv);
814e42ff 805 if (f&f_err) tvec_skip(tv, "erroneous test data");
c91413e6
MW
806 if (env && env->before) env->before(tv, g->ctx);
807 if (!(tv->f&TVSF_ACTIVE))
814e42ff 808 /* forced a skip */;
c91413e6
MW
809 else if (env && env->run)
810 env->run(tv, t->fn, g->ctx);
e63124bc 811 else {
c91413e6
MW
812 t->fn(tv->in, tv->out, g->ctx);
813 tvec_check(tv, 0);
e63124bc 814 }
e63124bc
MW
815 tvec_endtest(tv);
816 }
b64eb60f 817
814e42ff
MW
818 if (env && env->after) env->after(tv, g->ctx);
819 g->f &= ~GRPF_SETMASK;
c91413e6 820 tv->f &= ~TVSF_OPEN; tvec_releaseregs(tv); tvec_initregs(tv);
814e42ff
MW
821
822#undef f_err
b64eb60f
MW
823}
824
31d0247c
MW
825/* --- @begin_test_group@ --- *
826 *
827 * Arguments: @struct tvec_state *tv@ = test-vector state
828 * @struct groupstate *g@ = private test group state
829 *
830 * Returns: ---
831 *
832 * Use: Begins a test group. Expects @tv->test@ to have been set
833 * already. Calls the output driver, initializes the registers,
834 * clears the @tv->curr@ counters, allocates the environment
835 * context and calls the environment @setup@ function.
836 */
837
e63124bc 838static void begin_test_group(struct tvec_state *tv, struct groupstate *g)
b64eb60f 839{
e63124bc
MW
840 const struct tvec_test *t = tv->test;
841 const struct tvec_env *env = t->env;
b64eb60f
MW
842 unsigned i;
843
844 tv->output->ops->bgroup(tv->output);
31d0247c 845 tv->f &= ~(TVSF_SKIP | TVSF_MUFFLE);
c91413e6 846 tvec_initregs(tv);
b64eb60f 847 for (i = 0; i < TVOUT_LIMIT; i++) tv->curr[i] = 0;
e63124bc 848 if (env && env->ctxsz) g->ctx = xmalloc(env->ctxsz);
c91413e6 849 if (env && env->setup) env->setup(tv, env, 0, g->ctx);
b64eb60f
MW
850}
851
31d0247c
MW
852/* --- @report_group@ --- *
853 *
854 * Arguments: @struct tvec_state *tv@ = test-vector state
855 *
856 * Returns: ---
857 *
858 * Use: Reports the result of the test group to the output driver.
859 *
860 * If all of the tests have been skipped then report this as a
861 * group skip. Otherwise, determine and report the group
862 * outcome.
863 */
864
3efcfd2d 865static void report_group(struct tvec_state *tv)
b64eb60f
MW
866{
867 unsigned i, out, nrun;
868
869 for (i = 0, nrun = 0; i < TVOUT_LIMIT; i++)
870 { nrun += tv->curr[i]; tv->all[i] += tv->curr[i]; }
871
872 if (tv->curr[TVOUT_SKIP] == nrun)
873 { out = TVOUT_SKIP; tvec_skipgroup(tv, nrun ? 0 : "no tests to run"); }
874 else {
875 if (tv->curr[TVOUT_LOSE]) out = TVOUT_LOSE;
876 else out = TVOUT_WIN;
3efcfd2d 877 tv->grps[out]++; tv->output->ops->egroup(tv->output);
b64eb60f
MW
878 }
879}
880
31d0247c
MW
881/* --- @end_test_group@ --- *
882 *
883 * Arguments: @struct tvec_state *tv@ = test-vector state
884 * @struct groupstate *g@ = private test group state
885 *
886 * Returns: ---
887 *
888 * Use: Handles the end of a test group. Called at the end of the
889 * input file or when a new test group header is found.
890 *
891 * If a test is open, call @check@ to see whether it worked. If
892 * the test group is not being skipped, report the group
893 * result. Call the test environment @teardown@ function. Free
894 * the environment context and release the registers.
895 *
896 * If there's no test group active, then nothing happens.
897 */
898
e63124bc 899static void end_test_group(struct tvec_state *tv, struct groupstate *g)
b64eb60f 900{
e63124bc
MW
901 const struct tvec_test *t = tv->test;
902 const struct tvec_env *env;
903
904 if (!t) return;
905 if (tv->f&TVSF_OPEN) check(tv, g);
3efcfd2d 906 if (!(tv->f&TVSF_SKIP)) report_group(tv);
e63124bc 907 env = t->env; if (env && env->teardown) env->teardown(tv, g->ctx);
c91413e6 908 tvec_releaseregs(tv); tv->test = 0; xfree(g->ctx); g->ctx = 0;
b64eb60f
MW
909}
910
814e42ff 911/* --- @core_findvar@, @core_setvar@ --- *
31d0247c 912 *
814e42ff
MW
913 * Arguments: @struct tvec_state *tv@ = test vector state
914 * @const char *var@ = variable name to set
915 * @const union tvec_regval *rv@ = register value
916 * @void **ctx_out@ = where to put the @setvar@ context
917 * @void *ctx@ = context pointer
31d0247c 918 *
814e42ff
MW
919 * Returns: @core_findvar@ returns a pointer to the variable definition,
920 * or null; @core_setvar@ returns zero on success or %$-1$% on
921 * error.
31d0247c 922 *
814e42ff
MW
923 * Use: Find a definition for a special variable. The following
924 * special variables are supported.
925 *
926 * * %|@outcome|% is a token describing how a successful
927 * outcome of the test should be interpreted: %|success|% or
928 * %|win|% are the default: a successful test is counted as
929 * a pass; or %|expected-failure|% or %|xfail|% means a
930 * successful test is counted as an expected failure. A
931 * mismatch is always considered a failure.
31d0247c
MW
932 */
933
20ba6b0b 934enum { WIN, XFAIL, NOUT };
814e42ff
MW
935
936static int core_setvar(struct tvec_state *tv, const char *name,
937 const union tvec_regval *rv, void *ctx)
938{
939 struct groupstate *g = ctx;
940
941 if (STRCMP(name, ==, "@outcome")) {
942 if (g->f&GRPF_SETOUTC) return (tvec_dupreg(tv, name));
943 if (rv->u == XFAIL) tvec_xfail(tv);
944 g->f |= GRPF_SETOUTC;
945 } else assert(!"unknown var");
946 return (0);
947}
948
20ba6b0b
MW
949static const struct tvec_uassoc outcome_assoc[] = {
950 { "success", WIN },
951 { "win", WIN },
952 { "expected-failure", XFAIL },
953 { "xfail", XFAIL },
954 TVEC_ENDENUM
955};
956static const struct tvec_urange outcome_range = { 0, NOUT - 1 };
957static const struct tvec_uenuminfo outcome_enum =
958 { "test-outcome", outcome_assoc, &outcome_range };
814e42ff
MW
959static const struct tvec_vardef outcome_vardef =
960 { sizeof(struct tvec_reg), core_setvar,
d056fbdf 961 { "@outcome", &tvty_uenum, -1, 0, { &outcome_enum } } };
814e42ff
MW
962
963static const struct tvec_vardef *core_findvar
964 (struct tvec_state *tv, const char *name, void **ctx_out, void *ctx)
965{
966 if (STRCMP(name, ==, "@outcome"))
967 { *ctx_out = ctx; return (&outcome_vardef); }
968 else
969 return (0);
970}
971
972/* --- @tvec_read@ --- *
973 *
974 * Arguments: @struct tvec_state *tv@ = test-vector state
975 * @const char *infile@ = the name of the input file
976 * @FILE *fp@ = stream to read from
977 *
978 * Returns: Zero on success, @-1@ on error.
979 *
980 * Use: Read test vector data from @fp@ and exercise test functions.
981 * THe return code doesn't indicate test failures: it's only
982 * concerned with whether there were problems with the input
983 * file or with actually running the tests.
984 */
20ba6b0b 985
882a39c1 986int tvec_read(struct tvec_state *tv, const char *infile, FILE *fp)
b64eb60f
MW
987{
988 dstr d = DSTR_INIT;
989 const struct tvec_test *test;
e63124bc 990 const struct tvec_env *env;
b64eb60f 991 const struct tvec_regdef *rd;
814e42ff
MW
992 const struct tvec_vardef *vd = 0; void *varctx;
993 struct tvec_reg *r = 0, rbuf, *r_alloc = 0; size_t rsz = 0;
e63124bc 994 struct groupstate g = GROUPSTATE_INIT;
814e42ff 995 int ch, rc = 0;
b64eb60f 996
31d0247c 997 /* Set the initial location. */
b64eb60f
MW
998 tv->infile = infile; tv->lno = 1; tv->fp = fp;
999
1000 for (;;) {
31d0247c
MW
1001
1002 /* Get the next character and dispatch. Note that we're always at the
1003 * start of a line here.
1004 */
b64eb60f
MW
1005 ch = getc(tv->fp);
1006 switch (ch) {
1007
1008 case EOF:
31d0247c
MW
1009 /* End of the file. Exit the loop. */
1010
b64eb60f
MW
1011 goto end;
1012
1013 case '[':
31d0247c
MW
1014 /* A test group header. */
1015
1016 /* End the current group, if there is one. */
e63124bc 1017 end_test_group(tv, &g);
31d0247c
MW
1018
1019 /* Read the group name. There may be leading and trailing
1020 * whitespace.
1021 */
adec5584 1022 DRESET(&d); tvec_readword(tv, &d, 0, "];", "group name");
882a39c1
MW
1023 tvec_skipspc(tv);
1024 ch = getc(tv->fp); if (ch != ']') tvec_syntax(tv, ch, "`]'");
31d0247c
MW
1025
1026 /* Find the matching test definition. */
d056fbdf 1027 for (test = tv->cfg.tests; test->name; test++)
b64eb60f 1028 if (STRCMP(d.buf, ==, test->name)) goto found_test;
31d0247c
MW
1029
1030 /* There wasn't one. Report the error. Muffle errors about the
1031 * contents of this section because they won't be interesting.
1032 */
1033 tvec_error(tv, "unknown test group `%s'", d.buf);
1034 tv->f |= TVSF_MUFFLE; goto flush_line;
1035
b64eb60f 1036 found_test:
31d0247c
MW
1037 /* Eat trailing whitespace and comments. */
1038 tvec_flushtoeol(tv, 0);
1039
1040 /* Set up the new test group. */
1041 tv->test = test; begin_test_group(tv, &g);
b64eb60f
MW
1042 break;
1043
1044 case '\n':
31d0247c
MW
1045 /* A newline, so this was a completely empty line. Advance the line
1046 * counter, and run the current test.
1047 */
1048
b64eb60f 1049 tv->lno++;
e63124bc 1050 if (tv->f&TVSF_OPEN) check(tv, &g);
b64eb60f
MW
1051 break;
1052
31d0247c
MW
1053 case ';':
1054 /* A semicolon. Skip the comment. */
1055
1056 tvec_flushtoeol(tv, TVFF_ALLOWANY);
1057 break;
1058
b64eb60f 1059 default:
31d0247c
MW
1060 /* Something else. */
1061
b64eb60f 1062 if (isspace(ch)) {
31d0247c
MW
1063 /* Whitespace. Skip and see what we find. */
1064
1065 tvec_skipspc(tv); ch = getc(tv->fp);
1066
1067 /* If the file ends, then we're done. If we find a comment then we
1068 * skip it. If there's some non-whitespace, then report an error.
1069 * Otherwise the line was effectively blank, so run the test.
1070 */
882a39c1
MW
1071 if (ch == EOF) goto end;
1072 else if (ch == ';') tvec_flushtoeol(tv, TVFF_ALLOWANY);
1073 else if (tvec_flushtoeol(tv, 0)) rc = -1;
e63124bc 1074 else check(tv, &g);
31d0247c
MW
1075 } else {
1076 /* Some non-whitespace thing. */
1077
1078 /* Put the character back and read a word, which ought to be a
1079 * register name.
1080 */
b64eb60f 1081 ungetc(ch, tv->fp);
882a39c1 1082 DRESET(&d);
adec5584
MW
1083 if (tvec_readword(tv, &d, 0, "=:;", "register name"))
1084 goto flush_line;
31d0247c
MW
1085
1086 /* Now there should be a separator. */
b64eb60f 1087 tvec_skipspc(tv); ch = getc(tv->fp);
882a39c1
MW
1088 if (ch != '=' && ch != ':')
1089 { tvec_syntax(tv, ch, "`=' or `:'"); goto flush_line; }
b64eb60f 1090 tvec_skipspc(tv);
31d0247c
MW
1091
1092 /* If there's no test, then report an error. Set the muffle flag,
1093 * because there's no point in complaining about every assignment
1094 * in this block.
1095 */
1096 if (!tv->test) {
1097 if (!(tv->f&TVSF_MUFFLE)) tvec_error(tv, "no current test");
1098 tv->f |= TVSF_MUFFLE; goto flush_line;
1099 }
1100
1101 /* Open the test. This is syntactically a stanza of settings, so
1102 * it's fair to report on missing register assignments.
1103 */
1104 open_test(tv);
1105
b64eb60f 1106 if (d.buf[0] == '@') {
31d0247c 1107 /* A special register assignment. */
31d0247c 1108
814e42ff 1109 env = tv->test->env;
31d0247c 1110
814e42ff
MW
1111 /* Find a variable definition. */
1112 vd = core_findvar(tv, d.buf, &varctx, &g);
1113 if (vd) goto found_var;
1114 if (env && env->findvar) {
1115 vd = env->findvar(tv, d.buf, &varctx, g.ctx);
1116 if (vd) goto found_var;
1117 }
1118 tvec_unkreg(tv, d.buf); goto flush_line;
1119 found_var:
1120
1121 /* Set up the register. */
1122 if (vd->regsz <= sizeof(rbuf))
1123 r = &rbuf;
1124 else {
adec5584
MW
1125 GROWBUF_REPLACE(&arena_stdlib, r_alloc, rsz, vd->regsz,
1126 8*sizeof(void *), 1);
814e42ff 1127 r = r_alloc;
31d0247c
MW
1128 }
1129
814e42ff
MW
1130 /* Read and set the value. */
1131 vd->def.ty->init(&r->v, &vd->def);
1132 if (vd->def.ty->parse(&r->v, &vd->def, tv)) goto flush_line;
1133 if (!(tv->f&TVSF_SKIP) && vd->setvar(tv, d.buf, &r->v, varctx))
1134 goto bad;
31d0247c 1135
814e42ff
MW
1136 /* Clean up. */
1137 vd->def.ty->release(&r->v, &vd->def); vd = 0;
b64eb60f 1138 } else {
31d0247c
MW
1139 /* A standard register. */
1140
1141 /* Find the definition. */
b64eb60f
MW
1142 for (rd = tv->test->regs; rd->name; rd++)
1143 if (STRCMP(rd->name, ==, d.buf)) goto found_reg;
1144 tvec_error(tv, "unknown register `%s' for test `%s'",
1145 d.buf, tv->test->name);
882a39c1 1146 goto flush_line;
31d0247c 1147
b64eb60f 1148 found_reg:
31d0247c 1149 /* Complain if the register is already set. */
b64eb60f 1150 r = TVEC_REG(tv, in, rd->i);
31d0247c
MW
1151 if (r->f&TVRF_LIVE)
1152 { tvec_dupreg(tv, rd->name); goto flush_line; }
1153
1154 /* Parse a value and mark the register as live. */
882a39c1
MW
1155 if (rd->ty->parse(&r->v, rd, tv)) goto flush_line;
1156 r->f |= TVRF_LIVE;
b64eb60f
MW
1157 }
1158 }
1159 break;
1160 }
882a39c1
MW
1161 continue;
1162
1163 flush_line:
31d0247c
MW
1164 /* This is a general parse-failure handler. Skip to the next line and
1165 * remember that things didn't go so well.
1166 */
814e42ff
MW
1167 tvec_flushtoeol(tv, TVFF_ALLOWANY);
1168 bad:
1169 if (vd) { vd->def.ty->release(&r->v, &vd->def); vd = 0; }
1170 rc = -1;
b64eb60f 1171 }
31d0247c
MW
1172
1173 /* We reached the end. If that was actually an I/O error then report it.
1174 */
882a39c1
MW
1175 if (ferror(tv->fp))
1176 { tvec_error(tv, "error reading input: %s", strerror(errno)); rc = -1; }
31d0247c 1177
b64eb60f 1178end:
31d0247c
MW
1179 /* Process the final test, if there was one, and wrap up the final
1180 * group.
1181 */
e63124bc 1182 end_test_group(tv, &g);
31d0247c
MW
1183
1184 /* Clean up. */
b64eb60f
MW
1185 tv->infile = 0; tv->fp = 0;
1186 dstr_destroy(&d);
814e42ff 1187 xfree(r_alloc);
882a39c1 1188 return (rc);
814e42ff
MW
1189
1190#undef rlive
b64eb60f
MW
1191}
1192
e63124bc 1193/*----- Session lifecycle -------------------------------------------------*/
6999eaf7 1194
31d0247c
MW
1195/* --- @tvec_begin@ --- *
1196 *
1197 * Arguments: @struct tvec_state *tv_out@ = state structure to fill in
1198 * @const struct tvec_config *config@ = test configuration
1199 * @struct tvec_output *o@ = output driver
1200 *
1201 * Returns: ---
1202 *
1203 * Use: Initialize a state structure ready to do some testing.
1204 */
1205
e63124bc 1206void tvec_begin(struct tvec_state *tv_out,
c5e0e403 1207 const struct tvec_config *config,
e63124bc
MW
1208 struct tvec_output *o)
1209{
1210 unsigned i;
6999eaf7 1211
e63124bc 1212 tv_out->f = 0;
6999eaf7 1213
c5e0e403 1214 assert(config->nrout <= config->nreg);
d056fbdf
MW
1215 tv_out->cfg = *config;
1216 tv_out->in = xmalloc(tv_out->cfg.nreg*tv_out->cfg.regsz);
1217 tv_out->out = xmalloc(tv_out->cfg.nrout*tv_out->cfg.regsz);
1218 for (i = 0; i < tv_out->cfg.nreg; i++) {
e63124bc 1219 TVEC_REG(tv_out, in, i)->f = 0;
d056fbdf 1220 if (i < tv_out->cfg.nrout) TVEC_REG(tv_out, out, i)->f = 0;
e63124bc 1221 }
6999eaf7 1222
e63124bc
MW
1223 for (i = 0; i < TVOUT_LIMIT; i++)
1224 tv_out->curr[i] = tv_out->all[i] = tv_out->grps[i] = 0;
6999eaf7 1225
d056fbdf 1226 tv_out->test = 0;
e63124bc 1227 tv_out->infile = 0; tv_out->lno = 0; tv_out->fp = 0;
3efcfd2d 1228 tv_out->output = o; tv_out->output->ops->bsession(tv_out->output, tv_out);
e63124bc 1229}
6999eaf7 1230
31d0247c
MW
1231/* --- @tvec_end@ --- *
1232 *
1233 * Arguments: @struct tvec_state *tv@ = test-vector state
1234 *
1235 * Returns: A proposed exit code.
1236 *
1237 * Use: Conclude testing and suggests an exit code to be returned to
1238 * the calling program. (The exit code comes from the output
1239 * driver's @esession@ method.)
1240 */
1241
e63124bc
MW
1242int tvec_end(struct tvec_state *tv)
1243{
1244 int rc = tv->output->ops->esession(tv->output);
6999eaf7 1245
31d0247c 1246 if (tv->test) tvec_releaseregs(tv);
e63124bc
MW
1247 tv->output->ops->destroy(tv->output);
1248 xfree(tv->in); xfree(tv->out);
1249 return (rc);
6999eaf7
MW
1250}
1251
e63124bc
MW
1252/*----- Serialization and deserialization ---------------------------------*/
1253
31d0247c
MW
1254/* --- @tvec_serialize@ --- *
1255 *
1256 * Arguments: @const struct tvec_reg *rv@ = vector of registers
1257 * @buf *b@ = buffer to write on
1258 * @const struct tvec_regdef *regs@ = vector of register
1259 * descriptions, terminated by an entry with a null
1260 * @name@ slot
1261 * @unsigned nr@ = number of entries in the @rv@ vector
1262 * @size_t regsz@ = true size of each element of @rv@
1263 *
1264 * Returns: Zero on success, @-1@ on failure.
1265 *
1266 * Use: Serialize a collection of register values.
1267 *
1268 * The serialized output is written to the buffer @b@. Failure
1269 * can be caused by running out of buffer space, or a failing
1270 * type handler.
1271 */
1272
e63124bc
MW
1273int tvec_serialize(const struct tvec_reg *rv, buf *b,
1274 const struct tvec_regdef *regs,
1275 unsigned nr, size_t regsz)
6999eaf7 1276{
e63124bc
MW
1277 unsigned char *bitmap;
1278 size_t i, bitoff, nbits, bitsz;
1279 const struct tvec_regdef *rd;
1280 const struct tvec_reg *r;
6999eaf7 1281
e63124bc
MW
1282 bitoff = BLEN(b);
1283 for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
1284 bitsz = (nbits + 7)/8;
6999eaf7 1285
e63124bc
MW
1286 bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
1287 memset(bitmap, 0, bitsz);
1288 for (rd = regs, i = 0; rd->name; rd++, i++) {
1289 if (rd->i >= nr) continue;
1290 r = TVEC_GREG(rv, rd->i, regsz); if (!(r->f&TVRF_LIVE)) continue;
c91413e6 1291 bitmap = BBASE(b) + bitoff; bitmap[i/8] |= 1 << i%8;
e63124bc
MW
1292 if (rd->ty->tobuf(b, &r->v, rd)) return (-1);
1293 }
1294 return (0);
1295}
6999eaf7 1296
31d0247c
MW
1297/* --- @tvec_deserialize@ --- *
1298 *
1299 * Arguments: @struct tvec_reg *rv@ = vector of registers
1300 * @buf *b@ = buffer to write on
1301 * @const struct tvec_regdef *regs@ = vector of register
1302 * descriptions, terminated by an entry with a null
1303 * @name@ slot
1304 * @unsigned nr@ = number of entries in the @rv@ vector
1305 * @size_t regsz@ = true size of each element of @rv@
1306 *
1307 * Returns: Zero on success, @-1@ on failure.
1308 *
1309 * Use: Deserialize a collection of register values.
1310 *
1311 * The size of the register vector @nr@ and the register
1312 * definitions @regs@ must match those used when producing the
1313 * serialization. For each serialized register value,
1314 * deserialize and store the value into the appropriate register
1315 * slot, and set the @TVRF_LIVE@ flag on the register. See
1316 * @tvec_serialize@ for a description of the format.
1317 *
1318 * Failure results only from a failing register type handler.
1319 */
1320
e63124bc
MW
1321int tvec_deserialize(struct tvec_reg *rv, buf *b,
1322 const struct tvec_regdef *regs,
1323 unsigned nr, size_t regsz)
1324{
1325 const unsigned char *bitmap;
1326 size_t i, nbits, bitsz;
1327 const struct tvec_regdef *rd;
1328 struct tvec_reg *r;
6999eaf7 1329
e63124bc
MW
1330 for (rd = regs, nbits = 0; rd->name; rd++, nbits++);
1331 bitsz = (nbits + 7)/8;
6999eaf7 1332
e63124bc
MW
1333 bitmap = buf_get(b, bitsz); if (!bitmap) return (-1);
1334 for (rd = regs, i = 0; rd->name; rd++, i++) {
1335 if (rd->i >= nr) continue;
c91413e6 1336 if (!(bitmap[i/8]&(1 << i%8))) continue;
e63124bc
MW
1337 r = TVEC_GREG(rv, rd->i, regsz);
1338 if (rd->ty->frombuf(b, &r->v, rd)) return (-1);
1339 r->f |= TVRF_LIVE;
1340 }
6999eaf7
MW
1341 return (0);
1342}
1343
b64eb60f
MW
1344/*----- Ad-hoc testing ----------------------------------------------------*/
1345
1346static const struct tvec_regdef no_regs = { 0, 0, 0, 0, { 0 } };
1347
b64eb60f
MW
1348static void fakefn(const struct tvec_reg *in, struct tvec_reg *out, void *p)
1349 { assert(!"fake test function"); }
1350
31d0247c
MW
1351/* --- @tvec_adhoc@ --- *
1352 *
1353 * Arguments: @struct tvec_state *tv@ = test-vector state
1354 * @struct tvec_test *t@ = space for a test definition
1355 *
1356 * Returns: ---
1357 *
1358 * Use: Begin ad-hoc testing, i.e., without reading a file of
1359 * test-vector data.
1360 *
1361 * The structure at @t@ will be used to record information about
1362 * the tests underway, which would normally come from a static
1363 * test definition. The other functions in this section assume
1364 * that @tvec_adhoc@ has been called.
1365 */
1366
b64eb60f
MW
1367void tvec_adhoc(struct tvec_state *tv, struct tvec_test *t)
1368{
e63124bc 1369 t->name = "<unset>"; t->regs = &no_regs; t->env = 0; t->fn = fakefn;
d056fbdf 1370 tv->cfg.tests = t;
b64eb60f
MW
1371}
1372
31d0247c
MW
1373/* --- @tvec_begingroup@ --- *
1374 *
1375 * Arguments: @struct tvec_state *tv@ = test-vector state
1376 * @const char *name@ = name for this test group
1377 * @const char *file@, @unsigned @lno@ = calling file and line
1378 *
1379 * Returns: ---
1380 *
1381 * Use: Begin an ad-hoc test group with the given name. The @file@
1382 * and @lno@ can be anything, but it's usually best if they
1383 * refer to the source code performing the test: the macro
1384 * @TVEC_BEGINGROUP@ does this automatically.
1385 */
1386
b64eb60f
MW
1387void tvec_begingroup(struct tvec_state *tv, const char *name,
1388 const char *file, unsigned lno)
1389{
d056fbdf 1390 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->cfg.tests;
b64eb60f
MW
1391
1392 t->name = name; tv->test = t;
1393 tv->infile = file; tv->lno = tv->test_lno = lno;
e63124bc 1394 begin_test_group(tv, 0);
b64eb60f
MW
1395}
1396
31d0247c
MW
1397/* --- @tvec_endgroup@ --- *
1398 *
1399 * Arguments: @struct tvec_state *tv@ = test-vector state
1400 *
1401 * Returns: ---
1402 *
1403 * Use: End an ad-hoc test group. The statistics are updated and the
1404 * outcome is reported to the output formatter.
1405 */
1406
b64eb60f
MW
1407void tvec_endgroup(struct tvec_state *tv)
1408{
3efcfd2d 1409 if (!(tv->f&TVSF_SKIP)) report_group(tv);
b64eb60f
MW
1410 tv->test = 0;
1411}
1412
31d0247c
MW
1413/* --- @tvec_begintest@ --- *
1414 *
1415 * Arguments: @struct tvec_state *tv@ = test-vector state
1416 * @const char *file@, @unsigned @lno@ = calling file and line
1417 *
1418 * Returns: ---
1419 *
1420 * Use: Begin an ad-hoc test case. The @file@ and @lno@ can be
1421 * anything, but it's usually best if they refer to the source
1422 * code performing the test: the macro @TVEC_BEGINGROUP@ does
1423 * this automatically.
1424 */
1425
b64eb60f
MW
1426void tvec_begintest(struct tvec_state *tv, const char *file, unsigned lno)
1427{
1428 tv->infile = file; tv->lno = tv->test_lno = lno;
31d0247c 1429 open_test(tv); begin_test(tv);
b64eb60f
MW
1430}
1431
1432struct adhoc_claim {
1433 unsigned f;
1434#define ACF_FRESH 1u
1435 const char *saved_file; unsigned saved_lno;
1436};
1437
1438static void adhoc_claim_setup(struct tvec_state *tv,
1439 struct adhoc_claim *ck,
1440 const struct tvec_regdef *regs,
1441 const char *file, unsigned lno)
1442{
1443 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
1444
1445 ck->f = 0;
1446
1447 if (!(tv->f&TVSF_OPEN))
1448 { ck->f |= ACF_FRESH; tvec_begintest(tv, file, lno); }
1449
1450 ck->saved_file = tv->infile; if (file) tv->infile = file;
1451 ck->saved_lno = tv->test_lno; if (file) tv->test_lno = lno;
1452 t->regs = regs ? regs : &no_regs;
b64eb60f
MW
1453}
1454
1455static void adhoc_claim_teardown(struct tvec_state *tv,
1456 struct adhoc_claim *ck)
1457{
1458 struct tvec_test *t = (/*unconst*/ struct tvec_test *)tv->test;
1459
1460 t->regs = &no_regs;
1461 tv->infile = ck->saved_file; tv->test_lno = ck->saved_lno;
1462
1463 if (ck->f&ACF_FRESH) tvec_endtest(tv);
1464}
1465
31d0247c
MW
1466/* --- @tvec_claim@, @tvec_claim_v@, @TVEC_CLAIM@ --- *
1467 *
1468 * Arguments: @struct tvec_state *tv@ = test-vector state
1469 * @int ok@ = a flag
1470 * @const char *file@, @unsigned @lno@ = calling file and line
1471 * @const char *msg@, @va_list *ap@ = message to report on
1472 * failure
1473 *
1474 * Returns: The value @ok@.
1475 *
1476 * Use: Check that a claimed condition holds, as (part of) a test.
1477 * If no test case is underway (i.e., if @TVSF_OPEN@ is reset in
1478 * @tv->f@), then a new test case is begun and ended. The
1479 * @file@ and @lno@ are passed to the output formatter to be
1480 * reported in case of a failure. If @ok@ is nonzero, then
1481 * nothing else happens; so, in particular, if @tvec_claim@
1482 * established a new test case, then the test case succeeds. If
1483 * @ok@ is zero, then a failure is reported, quoting @msg@.
1484 *
1485 * The @TVEC_CLAIM@ macro is similar, only it (a) identifies the
1486 * file and line number of the call site automatically, and (b)
1487 * implicitly quotes the source text of the @ok@ condition in
1488 * the failure message.
1489 */
1490
3efcfd2d
MW
1491int tvec_claim_v(struct tvec_state *tv, int ok,
1492 const char *file, unsigned lno,
1493 const char *msg, va_list *ap)
b64eb60f
MW
1494{
1495 struct adhoc_claim ck;
b64eb60f
MW
1496
1497 adhoc_claim_setup(tv, &ck, 0, file, lno);
3efcfd2d 1498 if (!ok) tvec_fail_v(tv, msg, ap);
b64eb60f
MW
1499 adhoc_claim_teardown(tv, &ck);
1500 return (ok);
1501}
1502
3efcfd2d
MW
1503int tvec_claim(struct tvec_state *tv, int ok,
1504 const char *file, unsigned lno, const char *msg, ...)
1505{
1506 va_list ap;
1507
1508 va_start(ap, msg); tvec_claim_v(tv, ok, file, lno, msg, &ap); va_end(ap);
1509 return (ok);
1510}
1511
b64eb60f
MW
1512int tvec_claimeq(struct tvec_state *tv,
1513 const struct tvec_regty *ty, const union tvec_misc *arg,
1514 const char *file, unsigned lno, const char *expr)
1515{
1516 struct tvec_regdef regs[2];
1517 struct adhoc_claim ck;
1518 int ok;
1519
1520 tv->in[0].f = tv->out[0].f = TVRF_LIVE;
1521
1522 regs[0].name = "value"; regs[0].i = 0;
1523 regs[0].ty = ty; regs[0].f = 0;
1524 if (arg) regs[0].arg = *arg;
1525 else regs[0].arg.p = 0;
1526
1527 regs[1].name = 0;
1528
1529 adhoc_claim_setup(tv, &ck, regs, file, lno);
1530 ok = ty->eq(&tv->in[0].v, &tv->out[0].v, &regs[0]);
e63124bc 1531 if (!ok)
c91413e6 1532 { tvec_fail(tv, "%s", expr); tvec_mismatch(tv, TVMF_IN | TVMF_OUT); }
b64eb60f
MW
1533 adhoc_claim_teardown(tv, &ck);
1534 return (ok);
1535}
1536
b64eb60f 1537/*----- That's all, folks -------------------------------------------------*/