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