1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1995-2012 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <glenn.s.fowler@gmail.com> *
18 * *
19 ***********************************************************************/
20 #pragma prototyped
21
22 /*
23 * fmtre(3) and fmtmatch(3) test harness
24 * see help() for details
25 */
26
27 static const char id[] = "\n@(#)$Id: testfmt (AT&T Research) 2012-06-25 $\0\n";
28
29 #include <ast.h>
30 #include <ctype.h>
31 #include <setjmp.h>
32 #include <sig.h>
33
34 #define LOOPED 2
35
36 #define ERE 1
37 #define KRE 2
38
39 typedef char* (*Call_f)(const char*);
40
41 #define H(x) sfprintf(sfstderr,x)
42
43 static void
help(void)44 help(void)
45 {
46 H("NAME\n");
47 H(" testfmt - fmtre(3) and fmtmatch(3) test harness\n");
48 H("\n");
49 H("SYNOPSIS\n");
50 H(" testfmt [ options ] < testfmt.dat\n");
51 H("\n");
52 H("DESCRIPTION\n");
53 H(" testfmt reads test specifications, one per line, from the standard\n");
54 H(" input and writes one output line for each failed test. A summary\n");
55 H(" line is written after all tests are done.\n");
56 H("\n");
57 H("OPTIONS\n");
58 H(" -c catch signals and non-terminating calls\n");
59 H(" -h list help\n");
60 H(" -v list each test line\n");
61 H("\n");
62 H("INPUT FORMAT\n");
63 H(" Input lines may be blank, a comment beginning with #, or a test\n");
64 H(" specification. A specification is three fields separated by one\n");
65 H(" or more tabs. NULL denotes the empty string and NIL denotes the\n");
66 H(" 0 pointer.\n");
67 H("\n");
68 H(" Field 1: the pattern type:\n");
69 H("\n");
70 H(" E ERE (egrep)\n");
71 H(" K KRE (ksh glob)\n");
72 H(" i invert test the inverse call too\n");
73 H("\n");
74 H(" $ expand C \\c escapes in fields 2 and 3\n");
75 H(" : comment comment copied to output\n");
76 H("\n");
77 H(" Field 2: the regular expression pattern.\n");
78 H("\n");
79 H(" Field 3: the converted pattern to match.\n");
80 H("\n");
81 H(" Field 4: optional comment appended to the report.\n");
82 H("\n");
83 H("CONTRIBUTORS\n");
84 H(" Glenn Fowler <gsf@research.att.com>\n");
85 H(" David Korn <dgk@research.att.com>\n");
86 }
87
88 static struct
89 {
90 int errors;
91 int lineno;
92 int signals;
93 int warnings;
94 char* file;
95 jmp_buf gotcha;
96 } state;
97
98 static void
quote(char * s,int expand)99 quote(char* s, int expand)
100 {
101 unsigned char* u = (unsigned char*)s;
102 int c;
103
104 if (!u)
105 sfprintf(sfstdout, "NIL");
106 else if (!*u)
107 sfprintf(sfstdout, "NULL");
108 else if (expand)
109 {
110 sfprintf(sfstdout, "\"");
111 for (;;)
112 {
113 switch (c = *u++)
114 {
115 case 0:
116 break;;
117 case '\\':
118 sfprintf(sfstdout, "\\\\");
119 continue;
120 case '"':
121 sfprintf(sfstdout, "\\\"");
122 continue;
123 case '\a':
124 sfprintf(sfstdout, "\\a");
125 continue;
126 case '\b':
127 sfprintf(sfstdout, "\\b");
128 continue;
129 case '\f':
130 sfprintf(sfstdout, "\\f");
131 continue;
132 case '\n':
133 sfprintf(sfstdout, "\\n");
134 continue;
135 case '\r':
136 sfprintf(sfstdout, "\\r");
137 continue;
138 case '\t':
139 sfprintf(sfstdout, "\\t");
140 continue;
141 case '\v':
142 sfprintf(sfstdout, "\\v");
143 continue;
144 default:
145 if (!iscntrl(c) && isprint(c))
146 sfputc(sfstdout, c);
147 else
148 sfprintf(sfstdout, "\\x%02x", c);
149 continue;
150 }
151 break;
152 }
153 sfprintf(sfstdout, "\"");
154 }
155 else
156 sfprintf(sfstdout, "%s", s);
157 }
158
159 static void
report(char * comment,char * fun,char * re,char * msg,int expand)160 report(char* comment, char* fun, char* re, char* msg, int expand)
161 {
162 if (state.file)
163 sfprintf(sfstdout, "%s:", state.file);
164 sfprintf(sfstdout, "%d:", state.lineno);
165 if (fun)
166 sfprintf(sfstdout, " %s", fun);
167 if (re)
168 {
169 sfprintf(sfstdout, " ");
170 quote(re, expand);
171 }
172 state.errors++;
173 sfprintf(sfstdout, " %s", comment);
174 if (msg && comment[strlen(comment)-1] != '\n')
175 sfprintf(sfstdout, "%s: ", msg);
176 }
177
178 static void
bad(char * comment,char * re,char * s,int expand)179 bad(char* comment, char* re, char* s, int expand)
180 {
181 sfprintf(sfstdout, "bad test case ");
182 report(comment, NiL, re, s, expand);
183 exit(1);
184 }
185
186 static void
escape(char * s)187 escape(char* s)
188 {
189 char* e;
190 char* t;
191 char* q;
192 int c;
193
194 for (t = s; *t = *s; s++, t++)
195 if (*s == '\\')
196 switch (*++s)
197 {
198 case '\\':
199 break;
200 case 'a':
201 *t = '\a';
202 break;
203 case 'b':
204 *t = '\b';
205 break;
206 case 'c':
207 if (*t = *++s)
208 *t &= 037;
209 else
210 s--;
211 break;
212 case 'e':
213 case 'E':
214 *t = 033;
215 break;
216 case 'f':
217 *t = '\f';
218 break;
219 case 'n':
220 *t = '\n';
221 break;
222 case 'r':
223 *t = '\r';
224 break;
225 case 's':
226 *t = ' ';
227 break;
228 case 't':
229 *t = '\t';
230 break;
231 case 'v':
232 *t = '\v';
233 break;
234 case 'u':
235 case 'x':
236 q = *s == 'u' ? (s + 4) : (char*)0;
237 c = 0;
238 e = s;
239 while (!e || !q || s < q)
240 {
241 switch (*s)
242 {
243 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
244 c = (c << 4) + *s++ - 'a' + 10;
245 continue;
246 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
247 c = (c << 4) + *s++ - 'A' + 10;
248 continue;
249 case '0': case '1': case '2': case '3': case '4':
250 case '5': case '6': case '7': case '8': case '9':
251 c = (c << 4) + *s++ - '0';
252 continue;
253 case '{':
254 case '[':
255 if (s != e)
256 break;
257 e = 0;
258 s++;
259 if (q && *s == 'U' && *(s + 1) == '+')
260 s += 2;
261 continue;
262 case '}':
263 case ']':
264 if (!e)
265 s++;
266 break;
267 default:
268 break;
269 }
270 break;
271 }
272 *t = c;
273 break;
274 case '0': case '1': case '2': case '3':
275 case '4': case '5': case '6': case '7':
276 c = *s - '0';
277 q = s + 2;
278 while (s < q)
279 switch (*++s)
280 {
281 case '0': case '1': case '2': case '3':
282 case '4': case '5': case '6': case '7':
283 c = (c << 3) + *s - '0';
284 break;
285 default:
286 q = --s;
287 break;
288 }
289 *t = c;
290 break;
291 default:
292 bad("invalid C \\ escape\n", NiL, NiL, 0);
293 }
294 }
295
296 static void
gotcha(int sig)297 gotcha(int sig)
298 {
299 signal(sig, gotcha);
300 alarm(0);
301 state.signals++;
302 sigunblock(sig);
303 longjmp(state.gotcha, sig);
304 }
305
306 int
main(int argc,char ** argv)307 main(int argc, char** argv)
308 {
309 Call_f call;
310 int expand;
311 int type;
312 int invert;
313 int i;
314 int subunitlen;
315 int testno;
316 Sfio_t* fp;
317 char* p;
318 char* spec;
319 char* re;
320 char* s;
321 char* ans;
322 char* msg;
323 char* fun;
324 char* subunit;
325 char* version;
326 char* field[5];
327 char unit[64];
328
329 int catch = 0;
330 int verbose = 0;
331
332 static char* filter[] = { "-", 0 };
333
334 version = fmtident(id);
335 p = unit;
336 while (p < &unit[sizeof(unit)-1] && (*p = *version++) && !isspace(*p))
337 p++;
338 *p = 0;
339 while ((p = *++argv) && *p == '-')
340 for (;;)
341 {
342 switch (*++p)
343 {
344 case 0:
345 break;
346 case 'c':
347 catch = 1;
348 continue;
349 case 'h':
350 case '?':
351 case '-':
352 help();
353 return 2;
354 case 'v':
355 verbose = 1;
356 continue;
357 default:
358 sfprintf(sfstderr, "%s: -%c: invalid option", unit, *p);
359 return 1;
360 }
361 break;
362 }
363 if (catch)
364 {
365 signal(SIGALRM, gotcha);
366 signal(SIGBUS, gotcha);
367 signal(SIGSEGV, gotcha);
368 }
369 if (!*argv)
370 argv = filter;
371 while (state.file = *argv++)
372 {
373 if (streq(state.file, "-") || streq(state.file, "/dev/stdin") || streq(state.file, "/dev/fd/0"))
374 {
375 state.file = 0;
376 fp = sfstdin;
377 }
378 else if (!(fp = sfopen(NiL, state.file, "r")))
379 {
380 sfprintf(sfstderr, "%s: %s: cannot read\n", unit, state.file);
381 return 2;
382 }
383 sfprintf(sfstdout, "TEST\t%s ", unit);
384 if (s = state.file)
385 {
386 subunit = p = 0;
387 for (;;)
388 {
389 switch (*s++)
390 {
391 case 0:
392 break;
393 case '/':
394 subunit = s;
395 continue;
396 case '.':
397 p = s - 1;
398 continue;
399 default:
400 continue;
401 }
402 break;
403 }
404 if (!subunit)
405 subunit = state.file;
406 if (p < subunit)
407 p = s - 1;
408 subunitlen = p - subunit;
409 if (subunitlen == strlen(unit) && !memcmp(subunit, unit, subunitlen))
410 subunit = 0;
411 else
412 sfprintf(sfstdout, "%-.*s ", subunitlen, subunit);
413 }
414 else
415 subunit = 0;
416 sfprintf(sfstdout, "%s", version);
417 if (catch)
418 sfprintf(sfstdout, ", catch");
419 if (verbose)
420 sfprintf(sfstdout, ", verbose");
421 sfprintf(sfstdout, "\n");
422 testno = state.errors = state.lineno = state.signals = state.warnings = 0;
423 while (p = sfgetr(fp, '\n', 1))
424 {
425 state.lineno++;
426
427 /* parse: */
428
429 if (*p == 0 || *p == '#')
430 continue;
431 if (*p == ':')
432 {
433 while (*++p == ' ');
434 sfprintf(sfstdout, "NOTE %s\n", p);
435 continue;
436 }
437 i = 0;
438 field[i++] = p;
439 for (;;)
440 {
441 switch (*p++)
442 {
443 case 0:
444 p--;
445 goto checkfield;
446 case '\t':
447 *(p - 1) = 0;
448 checkfield:
449 s = field[i - 1];
450 if (streq(s, "NIL"))
451 field[i - 1] = 0;
452 else if (streq(s, "NULL"))
453 *s = 0;
454 while (*p == '\t')
455 p++;
456 if (!*p)
457 break;
458 if (i >= elementsof(field))
459 bad("too many fields\n", NiL, NiL, 0);
460 field[i++] = p;
461 /*FALLTHROUGH*/
462 default:
463 continue;
464 }
465 break;
466 }
467 if (!(spec = field[0]))
468 bad("NIL spec\n", NiL, NiL, 0);
469
470 /* interpret: */
471
472 expand = invert = type = 0;
473 for (p = spec; *p; p++)
474 {
475 switch (*p)
476 {
477 case 'E':
478 type = ERE;
479 continue;
480 case 'K':
481 type = KRE;
482 continue;
483
484 case 'i':
485 invert = 1;
486 continue;
487
488 case '$':
489 expand = 1;
490 continue;
491
492 default:
493 bad("bad spec\n", spec, NiL, 0);
494 break;
495
496 }
497 break;
498 }
499 if (i < 3)
500 bad("too few fields\n", NiL, NiL, 0);
501 while (i < elementsof(field))
502 field[i++] = 0;
503 if ((re = field[1]) && expand)
504 escape(re);
505 if ((ans = field[2]) && expand)
506 escape(s);
507 msg = field[3];
508 sfsync(sfstdout);
509
510 for (;;)
511 {
512 if (type == ERE)
513 {
514 fun = "fmtmatch";
515 call = fmtmatch;
516 }
517 else if (type == KRE)
518 {
519 fun = "fmtre";
520 call = fmtre;
521 }
522 else
523 break;
524 testno++;
525 if (verbose)
526 sfprintf(sfstdout, "test %-3d %s \"%s\" \"%s\"\n", state.lineno, fun, re, ans ? ans : "NIL");
527 if (!catch)
528 s = (*call)(re);
529 else if (setjmp(state.gotcha))
530 s = "SIGNAL";
531 else
532 {
533 alarm(LOOPED);
534 s = (*call)(re);
535 alarm(0);
536 }
537 if (!s && ans || s && !ans || s && ans && !streq(s, ans))
538 {
539 report("failed: ", fun, re, msg, expand);
540 quote(ans, expand);
541 sfprintf(sfstdout, " expected, ");
542 quote(s, expand);
543 sfprintf(sfstdout, " returned\n");
544 }
545 if (!invert)
546 break;
547 invert = 0;
548 s = ans;
549 ans = re;
550 re = s;
551 type = (type == ERE) ? KRE : ERE;
552 }
553 }
554 sfprintf(sfstdout, "TEST\t%s", unit);
555 if (subunit)
556 sfprintf(sfstdout, " %-.*s", subunitlen, subunit);
557 sfprintf(sfstdout, ", %d test%s", testno, testno == 1 ? "" : "s");
558 if (state.warnings)
559 sfprintf(sfstdout, ", %d warning%s", state.warnings, state.warnings == 1 ? "" : "s");
560 if (state.signals)
561 sfprintf(sfstdout, ", %d signal%s", state.signals, state.signals == 1 ? "" : "s");
562 sfprintf(sfstdout, ", %d error%s\n", state.errors, state.errors == 1 ? "" : "s");
563 if (fp != sfstdin)
564 sfclose(fp);
565 }
566 return 0;
567 }
568