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