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