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