chiark / gitweb /
struct/buf.c: Add functions for serializing and deserializing `kludge64'.
[mLib] / test / testrig.c
1 /* -*-c-*-
2  *
3  * Generic test driver
4  *
5  * (c) 1998 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
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * mLib is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public 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
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "dstr.h"
37 #include "macros.h"
38 #include "report.h"
39 #include "quis.h"
40 #include "testrig.h"
41
42 /*----- Static variables --------------------------------------------------*/
43
44 static dstr tok = DSTR_INIT;
45
46 enum {
47   TOK_EOF = 0x100,
48   TOK_WORD
49 };
50
51 /*----- Main code ---------------------------------------------------------*/
52
53 /* --- @decode@ --- *
54  *
55  * Arguments:   @int tok@ = token type to decode
56  *
57  * Returns:     Pointer to a textual representation of the token.
58  *
59  * Use:         Produces a readable representation of a token.
60  */
61
62 static const char *decode(int t)
63 {
64   static char buf[4];
65
66   switch (t) {
67     case TOK_EOF:
68       return ("<eof>");
69     case TOK_WORD:
70       return (tok.buf);
71     default:
72       buf[0] = t;
73       buf[1] = 0;
74       return (buf);
75   }
76   return ("<buggy-program>");
77 }
78
79 /* --- @gettok@ --- *
80  *
81  * Arguments:   @FILE *fp@ = file handle to read from
82  *
83  * Returns:     Type of token read.
84  *
85  * Use:         Reads a token from the input stream.
86  */
87
88 static int gettok(FILE *fp)
89 {
90   int ch;
91
92   /* --- Clear the token accumulator --- */
93
94   DRESET(&tok);
95
96   /* --- Prime the lookahead character --- */
97
98 again:
99   ch = getc(fp);
100
101   /* --- Skip leading whitespace --- */
102
103   while (ISSPACE(ch))
104     ch = getc(fp);
105
106   /* --- Trap some special characters --- */
107
108   switch (ch) {
109
110     /* --- Comments --- */
111
112     case '#':
113       do ch = getc(fp); while (ch != EOF && ch != '\n');
114       goto again;
115
116     /* --- End of file --- */
117
118     case EOF:
119       return (TOK_EOF);
120
121     /* --- Quote characters --- */
122
123     case '`':
124       ch = '\'';
125     case '\'':
126     case '\"': {
127       int quote = ch;
128
129       for (;;) {
130         ch = getc(fp);
131         if (ch == EOF || ch == quote)
132           break;
133         if (ch == '\\') {
134           ch = getc(fp);
135           if (ch == EOF)
136             ch = '\\';
137         }
138         DPUTC(&tok, ch);
139       }
140       DPUTZ(&tok);
141       return (TOK_WORD);
142     }
143
144     /* --- Self-delimiting things --- */
145
146     case ';':
147     case '{':
148     case '}':
149       return (ch);
150
151     /* --- Anything else is a word --- */
152
153     default:
154       for (;;) {
155         DPUTC(&tok, ch);
156         ch = getc(fp);
157         switch (ch) {
158           case EOF:
159           case ';':
160           case '{':
161           case '}':
162           case '\"':
163           case '\'':
164           case '`':
165             goto done;
166           default:
167             if (ISSPACE(ch))
168               goto done;
169         }
170         if (ch == '\\') {
171           ch = getc(fp);
172           if (ch == EOF)
173             ch = '\\';
174         }
175       }
176     done:
177       ungetc(ch, fp);
178       DPUTZ(&tok);
179       return (TOK_WORD);
180   }
181 }
182
183 /* --- @type_hex@ --- */
184
185 static void cvt_hex(const char *s, dstr *d)
186 {
187   while (s[0] && s[1]) {
188     int x = s[0], y = s[1];
189     if ('0' <= x && x <= '9') x -= '0';
190     else if ('A' <= x && x <= 'F') x -= 'A' - 10;
191     else if ('a' <= x && x <= 'f') x -= 'a' - 10;
192     else x = 0;
193     if ('0' <= y && y <= '9') y -= '0';
194     else if ('A' <= y && y <= 'F') y -= 'A' - 10;
195     else if ('a' <= y && y <= 'f') y -= 'a' - 10;
196     else y = 0;
197     DPUTC(d, (x << 4) + y);
198     s += 2;
199   }
200 }
201
202 static void dump_hex(dstr *d, FILE *fp)
203 {
204   const char *p, *q;
205   for (p = d->buf, q = p + d->len; p < q; p++)
206     fprintf(fp, "%02x", *(unsigned char *)p);
207 }
208
209 const test_type type_hex = { cvt_hex, dump_hex };
210
211 /* --- @type_string@ --- */
212
213 static void cvt_string(const char *s, dstr *d)
214 {
215   DPUTS(d, s);
216 }
217
218 static void dump_string(dstr *d, FILE *fp)
219 {
220   DWRITE(d, fp);
221 }
222
223 const test_type type_string = { cvt_string, dump_string };
224
225 /* --- @type_int@ --- */
226
227 static void cvt_int(const char *s, dstr *d)
228 {
229   DENSURE(d, sizeof(int));
230   sscanf(s, "%i", (int *)d->buf);
231 }
232
233 static void dump_int(dstr *d, FILE *fp)
234 {
235   fprintf(fp, "%i", *(int *)d->buf);
236 }
237
238 const test_type type_int = { cvt_int, dump_int };
239
240 /* --- @type_long@ --- */
241
242 static void cvt_long(const char *s, dstr *d)
243 {
244   DENSURE(d, sizeof(long));
245   *(long *)d->buf = strtol(s, 0, 0);
246 }
247
248 static void dump_long(dstr *d, FILE *fp)
249 {
250   fprintf(fp, "%li", *(long *)d->buf);
251 }
252
253 const test_type type_long = { cvt_long, dump_long };
254
255 /* --- @type_ulong@ --- */
256
257 static void cvt_ulong(const char *s, dstr *d)
258 {
259   DENSURE(d, sizeof(unsigned long));
260   *(unsigned long *)d->buf = strtoul(s, 0, 0);
261 }
262
263 static void dump_ulong(dstr *d, FILE *fp)
264 {
265   fprintf(fp, "%lu", *(unsigned long *)d->buf);
266 }
267
268 const test_type type_ulong = { cvt_ulong, dump_ulong };
269
270 /* --- @type_uint32@ --- */
271
272 static void cvt_uint32(const char *buf, dstr *d)
273 {
274   DENSURE(d, sizeof(uint32));
275   *(uint32 *)d->buf = strtoul(buf, 0, 0);
276 }
277
278 static void dump_uint32(dstr *d, FILE *fp)
279 {
280   fprintf(fp, "%lu\n", (unsigned long)*(uint32 *)d->buf);
281 }
282
283 const test_type type_uint32 = { cvt_uint32, dump_uint32 };
284
285 /* --- @test_do@ --- *
286  *
287  * Arguments:   @const test_suite suites[]@ = pointer to suite definitions
288  *              @FILE *fp@ = test vector file, ready opened
289  *              @test_results *results@ = where to put results
290  *
291  * Returns:     Negative if something bad happened, or the number of
292  *              failures.
293  *
294  * Use:         Runs a collection of tests against a file of test vectors and
295  *              reports the results.
296  */
297
298 int test_do(const test_suite suites[], FILE *fp, test_results *results)
299 {
300   test_results dummy;
301   dstr dv[TEST_FIELDMAX];
302   const test_suite *ss;
303   const test_chunk *chunks = suites[0].chunks;
304   const test_chunk *cch;
305   int rc = -1;
306   int ok;
307   int i;
308
309   for (i = 0; i < TEST_FIELDMAX; i++)
310     DCREATE(&dv[i]);
311
312   if (!results)
313     results = &dummy;
314   results->tests = 0;
315   results->failed = 0;
316
317   for (;;) {
318     int t = gettok(fp);
319
320     /* --- This is a reasonable place to stop --- */
321
322     if (t == TOK_EOF)
323       break;
324
325     /* --- Pick out the chunk name --- */
326
327     if (t != TOK_WORD) {
328       moan("expected <word>; found `%s'", decode(t));
329       goto done;
330     }
331
332     if (STRCMP(tok.buf, ==, "SUITE")) {
333       t = gettok(fp);
334       if (t != TOK_WORD) {
335         moan("expected <word>; found `%s'", decode(t));
336         goto done;
337       }
338       for (ss = suites; ; ss++) {
339         if (!ss->name) {
340           chunks = 0;
341           break;
342         }
343         if (STRCMP(tok.buf, ==, ss->name)) {
344           chunks = ss->chunks;
345           break;
346         }
347       }
348       continue;
349     }
350
351     /* --- Find the right chunk block --- */
352
353     if (!chunks)
354       goto skip_chunk;
355     for (cch = chunks; ; cch++) {
356       if (!cch->name)
357         goto skip_chunk;
358       if (STRCMP(tok.buf, ==, cch->name))
359         break;
360     }
361
362     /* --- Past the open brace to the first chunk --- */
363
364     if ((t = gettok(fp)) != '{') {
365       moan("expected `{'; found `%s'", decode(t));
366       goto done;
367     }
368
369     /* --- Start on the test data now --- */
370
371     printf("%s: ", cch->name);
372     fflush(stdout);
373     ok = 1;
374
375     for (;;) {
376       t = gettok(fp);
377
378       /* --- Accept a close brace --- */
379
380       if (t == '}')
381         break;
382
383       /* --- Otherwise I expect a list of words --- */
384
385       for (i = 0; cch->f[i]; i++) {
386         DRESET(&dv[i]);
387         if (t != TOK_WORD) {
388           moan("expected <word>; found `%s'", decode(t));
389           goto done;
390         }
391         cch->f[i]->cvt(tok.buf, &dv[i]);
392         t = gettok(fp);
393       }
394
395       /* --- And a terminating semicolon --- */
396
397       if (t != ';') {
398         moan("expected `;'; found `%s'", decode(t));
399         goto done;
400       }
401
402       /* --- Run the test code --- */
403
404       if (!cch->test(dv)) {
405         ok = 0;
406         printf("%s: ", cch->name);
407         for (i = 0; i < results->tests; i++) putchar('.');
408         results->failed++;
409       }
410       putchar('.');
411       results->tests++;
412       fflush(stdout);
413     }
414
415     puts(ok ? " ok" : " failed");
416     fflush(stdout);
417     continue;
418
419   skip_chunk:
420     if ((t = gettok(fp)) != '{') {
421       moan("expected '{'; found `%s'", decode(t));
422       goto done;
423     }
424     for (;;) {
425       t = gettok(fp);
426       if (t == '}')
427         break;
428       while (t == TOK_WORD)
429         t = gettok(fp);
430       if (t != ';') {
431         moan("expected `;'; found `%s'", decode(t));
432         goto done;
433       }
434     }
435   }
436   rc = results->failed;
437
438   /* --- All done --- */
439
440 done:
441   for (i = 0; i < TEST_FIELDMAX; i++)
442     dstr_destroy(&dv[i]);
443   return (rc);
444 }
445
446 /* --- @test_run@ --- *
447  *
448  * Arguments:   @int argc@ = number of command line arguments
449  *              @char *argv[]@ = pointer to command line arguments
450  *              @const test_chunk chunk[]@ = pointer to chunk definitions
451  *              @const char *vec@ = name of default test vector file
452  *
453  * Returns:     Doesn't.
454  *
455  * Use:         Runs a set of test vectors to ensure that a component is
456  *              working properly.
457  */
458
459 void test_run(int argc, char *argv[],
460               const test_chunk chunk[],
461               const char *vec)
462 {
463   FILE *fp;
464   test_results res;
465   int rc;
466   test_suite suite[2];
467
468   /* --- Silly bits of initialization --- */
469
470   ego(argv[0]);
471
472   /* --- Parse command line arguments --- */
473
474   {
475     const char *p = 0;
476     int i = 0;
477
478     for (;;) {
479       if (!p || !*p) {
480         if (i >= argc - 1)
481           break;
482         p = argv[++i];
483         if (STRCMP(p, ==, "--")) {
484           i++;
485           break;
486         }
487         if (p[0] != '-' || p[1] == 0)
488           break;
489         p++;
490       }
491       switch (*p++) {
492         case 'h':
493           printf("%s test driver\n"
494                  "Usage: %s [-f FILENAME]\n", QUIS, QUIS);
495           exit(0);
496         case 'f':
497           if (!*p) {
498             if (i >= argc - 1)
499               die(1, "option `-f' expects an argument");
500             p = argv[++i];
501           }
502           vec = p;
503           p = 0;
504           break;
505         default:
506           die(1, "option `-%c' unknown", p[-1]);
507           break;
508       }
509     }
510   }
511
512   /* --- Start parsing from the file --- */
513
514   if ((fp = fopen(vec, "r")) == 0)
515     die(1, "couldn't open test vector file `%s': %s", vec, strerror(errno));
516   suite[0].name = "simple";
517   suite[0].chunks = chunk;
518   suite[1].name = 0;
519   rc = test_do(suite, fp, &res);
520   if (rc < 0)
521     exit(127);
522   if (res.failed) {
523     fprintf(stderr, "FAILED %u of %u test%s\n",
524             res.failed, res.tests, res.tests == 1 ? "" : "s");
525   } else {
526     fprintf(stderr, "PASSED all %u test%s\n",
527             res.tests, res.tests == 1 ? "" : "s");
528   }
529   exit(!!res.failed);
530 }
531
532 /*----- That's all, folks -------------------------------------------------*/