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