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