#pragma prototyped noticed /* * regex(3) test harness * * build: cc -o testregex testregex.c * help: testregex --man * note: REG_* features are detected by #ifdef; if REG_* are enums * then supply #define REG_foo REG_foo for each enum REG_foo * * Glenn Fowler <gsf@research.att.com> * AT&T Research * * PLEASE: publish your tests so everyone can benefit * * The following license covers testregex.c and all associated test data. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of THIS SOFTWARE FILE (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, and/or sell copies of the * Software, and to permit persons to whom the Software is furnished to do * so, subject to the following disclaimer: * * THIS SOFTWARE IS PROVIDED BY AT&T ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL AT&T BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static const char id[] = "\n@(#)$Id: testregex (AT&T Research) 2010-06-10 $\0\n"; #if _PACKAGE_ast #include <ast.h> #else #include <sys/types.h> #endif #include <stdio.h> #include <regex.h> #include <ctype.h> #include <setjmp.h> #include <signal.h> #include <string.h> #include <unistd.h> #ifdef __STDC__ #include <stdlib.h> #include <locale.h> #endif #ifndef RE_DUP_MAX #define RE_DUP_MAX 32767 #endif #if !_PACKAGE_ast #undef REG_DISCIPLINE #endif #ifndef REG_DELIMITED #undef _REG_subcomp #endif #define TEST_ARE 0x00000001 #define TEST_BRE 0x00000002 #define TEST_ERE 0x00000004 #define TEST_KRE 0x00000008 #define TEST_LRE 0x00000010 #define TEST_SRE 0x00000020 #define TEST_EXPAND 0x00000100 #define TEST_LENIENT 0x00000200 #define TEST_QUERY 0x00000400 #define TEST_SUB 0x00000800 #define TEST_UNSPECIFIED 0x00001000 #define TEST_VERIFY 0x00002000 #define TEST_AND 0x00004000 #define TEST_OR 0x00008000 #define TEST_DELIMIT 0x00010000 #define TEST_OK 0x00020000 #define TEST_SAME 0x00040000 #define TEST_ACTUAL 0x00100000 #define TEST_BASELINE 0x00200000 #define TEST_FAIL 0x00400000 #define TEST_PASS 0x00800000 #define TEST_SUMMARY 0x01000000 #define TEST_IGNORE_ERROR 0x02000000 #define TEST_IGNORE_OVER 0x04000000 #define TEST_IGNORE_POSITION 0x08000000 #define TEST_CATCH 0x10000000 #define TEST_VERBOSE 0x20000000 #define TEST_DECOMP 0x40000000 #define TEST_GLOBAL (TEST_ACTUAL|TEST_AND|TEST_BASELINE|TEST_CATCH|TEST_FAIL|TEST_IGNORE_ERROR|TEST_IGNORE_OVER|TEST_IGNORE_POSITION|TEST_OR|TEST_PASS|TEST_SUMMARY|TEST_VERBOSE) #ifdef REG_DISCIPLINE #include <stk.h> typedef struct Disc_s { regdisc_t disc; int ordinal; Sfio_t* sp; } Disc_t; static void* compf(const regex_t* re, const char* xstr, size_t xlen, regdisc_t* disc) { Disc_t* dp = (Disc_t*)disc; return (void*)((char*)0 + ++dp->ordinal); } static int execf(const regex_t* re, void* data, const char* xstr, size_t xlen, const char* sstr, size_t slen, char** snxt, regdisc_t* disc) { Disc_t* dp = (Disc_t*)disc; sfprintf(dp->sp, "{%-.*s}(%lu:%d)", xlen, xstr, (char*)data - (char*)0, slen); return atoi(xstr); } static void* resizef(void* handle, void* data, size_t size) { if (!size) return 0; return stkalloc((Sfio_t*)handle, size); } #endif #ifndef NiL #ifdef __STDC__ #define NiL 0 #else #define NiL (char*)0 #endif #endif #define H(x) do{if(html)fprintf(stderr,x);}while(0) #define T(x) fprintf(stderr,x) static void help(int html) { H("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"); H("<HTML>\n"); H("<HEAD>\n"); H("<TITLE>testregex man document</TITLE>\n"); H("</HEAD>\n"); H("<BODY bgcolor=white>\n"); H("<PRE>\n"); T("NAME\n"); T(" testregex - regex(3) test harness\n"); T("\n"); T("SYNOPSIS\n"); T(" testregex [ options ]\n"); T("\n"); T("DESCRIPTION\n"); T(" testregex reads regex(3) test specifications, one per line, from the\n"); T(" standard input and writes one output line for each failed test. A\n"); T(" summary line is written after all tests are done. Each successful\n"); T(" test is run again with REG_NOSUB. Unsupported features are noted\n"); T(" before the first test, and tests requiring these features are\n"); T(" silently ignored.\n"); T("\n"); T("OPTIONS\n"); T(" -c catch signals and non-terminating calls\n"); T(" -e ignore error return mismatches\n"); T(" -h list help on standard error\n"); T(" -n do not repeat successful tests with regnexec()\n"); T(" -o ignore match[] overrun errors\n"); T(" -p ignore negative position mismatches\n"); T(" -s use stack instead of malloc\n"); T(" -x do not repeat successful tests with REG_NOSUB\n"); T(" -v list each test line\n"); T(" -A list failed test lines with actual answers\n"); T(" -B list all test lines with actual answers\n"); T(" -F list failed test lines\n"); T(" -P list passed test lines\n"); T(" -S output one summary line\n"); T("\n"); T("INPUT FORMAT\n"); T(" Input lines may be blank, a comment beginning with #, or a test\n"); T(" specification. A specification is five fields separated by one\n"); T(" or more tabs. NULL denotes the empty string and NIL denotes the\n"); T(" 0 pointer.\n"); T("\n"); T(" Field 1: the regex(3) flags to apply, one character per REG_feature\n"); T(" flag. The test is skipped if REG_feature is not supported by the\n"); T(" implementation. If the first character is not [BEASKLP] then the\n"); T(" specification is a global control line. One or more of [BEASKLP] may be\n"); T(" specified; the test will be repeated for each mode.\n"); T("\n"); T(" B basic BRE (grep, ed, sed)\n"); T(" E REG_EXTENDED ERE (egrep)\n"); T(" A REG_AUGMENTED ARE (egrep with negation)\n"); T(" S REG_SHELL SRE (sh glob)\n"); T(" K REG_SHELL|REG_AUGMENTED KRE (ksh glob)\n"); T(" L REG_LITERAL LRE (fgrep)\n"); T("\n"); T(" a REG_LEFT|REG_RIGHT implicit ^...$\n"); T(" b REG_NOTBOL lhs does not match ^\n"); T(" c REG_COMMENT ignore space and #...\\n\n"); T(" d REG_SHELL_DOT explicit leading . match\n"); T(" e REG_NOTEOL rhs does not match $\n"); T(" f REG_MULTIPLE multiple \\n separated patterns\n"); T(" g FNM_LEADING_DIR testfnmatch only -- match until /\n"); T(" h REG_MULTIREF multiple digit backref\n"); T(" i REG_ICASE ignore case\n"); T(" j REG_SPAN . matches \\n\n"); T(" k REG_ESCAPE \\ to ecape [...] delimiter\n"); T(" l REG_LEFT implicit ^...\n"); T(" m REG_MINIMAL minimal match\n"); T(" n REG_NEWLINE explicit \\n match\n"); T(" o REG_ENCLOSED (|&) magic inside [@|&](...)\n"); T(" p REG_SHELL_PATH explicit / match\n"); T(" q REG_DELIMITED delimited pattern\n"); T(" r REG_RIGHT implicit ...$\n"); T(" s REG_SHELL_ESCAPED \\ not special\n"); T(" t REG_MUSTDELIM all delimiters must be specified\n"); T(" u standard unspecified behavior -- errors not counted\n"); T(" v REG_CLASS_ESCAPE \\ special inside [...]\n"); T(" w REG_NOSUB no subexpression match array\n"); T(" x REG_LENIENT let some errors slide\n"); T(" y REG_LEFT regexec() implicit ^...\n"); T(" z REG_NULL NULL subexpressions ok\n"); T(" $ expand C \\c escapes in fields 2 and 3\n"); T(" / field 2 is a regsubcomp() expression\n"); T(" = field 3 is a regdecomp() expression\n"); T("\n"); T(" Field 1 control lines:\n"); T("\n"); T(" C set LC_COLLATE and LC_CTYPE to locale in field 2\n"); T("\n"); T(" ?test ... output field 5 if passed and != EXPECTED, silent otherwise\n"); T(" &test ... output field 5 if current and previous passed\n"); T(" |test ... output field 5 if current passed and previous failed\n"); T(" ; ... output field 2 if previous failed\n"); T(" {test ... skip if failed until }\n"); T(" } end of skip\n"); T("\n"); T(" : comment comment copied as output NOTE\n"); T(" :comment:test :comment: ignored\n"); T(" N[OTE] comment comment copied as output NOTE\n"); T(" T[EST] comment comment\n"); T("\n"); T(" number use number for nmatch (20 by default)\n"); T("\n"); T(" Field 2: the regular expression pattern; SAME uses the pattern from\n"); T(" the previous specification. RE_DUP_MAX inside {...} expands to the\n"); T(" value from <limits.h>.\n"); T("\n"); T(" Field 3: the string to match. X...{RE_DUP_MAX} expands to RE_DUP_MAX\n"); T(" copies of X.\n"); T("\n"); T(" Field 4: the test outcome. This is either one of the posix error\n"); T(" codes (with REG_ omitted) or the match array, a list of (m,n)\n"); T(" entries with m and n being first and last+1 positions in the\n"); T(" field 3 string, or NULL if REG_NOSUB is in effect and success\n"); T(" is expected. BADPAT is acceptable in place of any regcomp(3)\n"); T(" error code. The match[] array is initialized to (-2,-2) before\n"); T(" each test. All array elements from 0 to nmatch-1 must be specified\n"); T(" in the outcome. Unspecified endpoints (offset -1) are denoted by ?.\n"); T(" Unset endpoints (offset -2) are denoted by X. {x}(o:n) denotes a\n"); T(" matched (?{...}) expression, where x is the text enclosed by {...},\n"); T(" o is the expression ordinal counting from 1, and n is the length of\n"); T(" the unmatched portion of the subject string. If x starts with a\n"); T(" number then that is the return value of re_execf(), otherwise 0 is\n"); T(" returned. RE_DUP_MAX[-+]N expands to the <limits.h> value -+N.\n"); T("\n"); T(" Field 5: optional comment appended to the report.\n"); T("\n"); T("CAVEAT\n"); T(" If a regex implementation misbehaves with memory then all bets are off.\n"); T("\n"); T("CONTRIBUTORS\n"); T(" Glenn Fowler gsf@research.att.com (ksh strmatch, regex extensions)\n"); T(" David Korn dgk@research.att.com (ksh glob matcher)\n"); T(" Doug McIlroy mcilroy@dartmouth.edu (ast regex/testre in C++)\n"); T(" Tom Lord lord@regexps.com (rx tests)\n"); T(" Henry Spencer henry@zoo.toronto.edu (original public regex)\n"); T(" Andrew Hume andrew@research.att.com (gre tests)\n"); T(" John Maddock John_Maddock@compuserve.com (regex++ tests)\n"); T(" Philip Hazel ph10@cam.ac.uk (pcre tests)\n"); T(" Ville Laurikari vl@iki.fi (libtre tests)\n"); H("</PRE>\n"); H("</BODY>\n"); H("</HTML>\n"); } #ifndef elementsof #define elementsof(x) (sizeof(x)/sizeof(x[0])) #endif #ifndef streq #define streq(a,b) (*(a)==*(b)&&!strcmp(a,b)) #endif #define HUNG 2 #define NOTEST (~0) #ifndef REG_TEST_DEFAULT #define REG_TEST_DEFAULT 0 #endif #ifndef REG_EXEC_DEFAULT #define REG_EXEC_DEFAULT 0 #endif static const char* unsupported[] = { "BASIC", #ifndef REG_EXTENDED "EXTENDED", #endif #ifndef REG_AUGMENTED "AUGMENTED", #endif #ifndef REG_SHELL "SHELL", #endif #ifndef REG_CLASS_ESCAPE "CLASS_ESCAPE", #endif #ifndef REG_COMMENT "COMMENT", #endif #ifndef REG_DELIMITED "DELIMITED", #endif #ifndef REG_DISCIPLINE "DISCIPLINE", #endif #ifndef REG_ESCAPE "ESCAPE", #endif #ifndef REG_ICASE "ICASE", #endif #ifndef REG_LEFT "LEFT", #endif #ifndef REG_LENIENT "LENIENT", #endif #ifndef REG_LITERAL "LITERAL", #endif #ifndef REG_MINIMAL "MINIMAL", #endif #ifndef REG_MULTIPLE "MULTIPLE", #endif #ifndef REG_MULTIREF "MULTIREF", #endif #ifndef REG_MUSTDELIM "MUSTDELIM", #endif #ifndef REG_NEWLINE "NEWLINE", #endif #ifndef REG_NOTBOL "NOTBOL", #endif #ifndef REG_NOTEOL "NOTEOL", #endif #ifndef REG_NULL "NULL", #endif #ifndef REG_RIGHT "RIGHT", #endif #ifndef REG_SHELL_DOT "SHELL_DOT", #endif #ifndef REG_SHELL_ESCAPED "SHELL_ESCAPED", #endif #ifndef REG_SHELL_GROUP "SHELL_GROUP", #endif #ifndef REG_SHELL_PATH "SHELL_PATH", #endif #ifndef REG_SPAN "SPAN", #endif #if REG_NOSUB & REG_TEST_DEFAULT "SUBMATCH", #endif #if !_REG_nexec "regnexec", #endif #if !_REG_subcomp "regsubcomp", #endif #if !_REG_decomp "redecomp", #endif 0 }; #ifndef REG_CLASS_ESCAPE #define REG_CLASS_ESCAPE NOTEST #endif #ifndef REG_COMMENT #define REG_COMMENT NOTEST #endif #ifndef REG_DELIMITED #define REG_DELIMITED NOTEST #endif #ifndef REG_ESCAPE #define REG_ESCAPE NOTEST #endif #ifndef REG_ICASE #define REG_ICASE NOTEST #endif #ifndef REG_LEFT #define REG_LEFT NOTEST #endif #ifndef REG_LENIENT #define REG_LENIENT 0 #endif #ifndef REG_MINIMAL #define REG_MINIMAL NOTEST #endif #ifndef REG_MULTIPLE #define REG_MULTIPLE NOTEST #endif #ifndef REG_MULTIREF #define REG_MULTIREF NOTEST #endif #ifndef REG_MUSTDELIM #define REG_MUSTDELIM NOTEST #endif #ifndef REG_NEWLINE #define REG_NEWLINE NOTEST #endif #ifndef REG_NOTBOL #define REG_NOTBOL NOTEST #endif #ifndef REG_NOTEOL #define REG_NOTEOL NOTEST #endif #ifndef REG_NULL #define REG_NULL NOTEST #endif #ifndef REG_RIGHT #define REG_RIGHT NOTEST #endif #ifndef REG_SHELL_DOT #define REG_SHELL_DOT NOTEST #endif #ifndef REG_SHELL_ESCAPED #define REG_SHELL_ESCAPED NOTEST #endif #ifndef REG_SHELL_GROUP #define REG_SHELL_GROUP NOTEST #endif #ifndef REG_SHELL_PATH #define REG_SHELL_PATH NOTEST #endif #ifndef REG_SPAN #define REG_SPAN NOTEST #endif #define REG_UNKNOWN (-1) #ifndef REG_ENEWLINE #define REG_ENEWLINE (REG_UNKNOWN-1) #endif #ifndef REG_ENULL #ifndef REG_EMPTY #define REG_ENULL (REG_UNKNOWN-2) #else #define REG_ENULL REG_EMPTY #endif #endif #ifndef REG_ECOUNT #define REG_ECOUNT (REG_UNKNOWN-3) #endif #ifndef REG_BADESC #define REG_BADESC (REG_UNKNOWN-4) #endif #ifndef REG_EMEM #define REG_EMEM (REG_UNKNOWN-5) #endif #ifndef REG_EHUNG #define REG_EHUNG (REG_UNKNOWN-6) #endif #ifndef REG_EBUS #define REG_EBUS (REG_UNKNOWN-7) #endif #ifndef REG_EFAULT #define REG_EFAULT (REG_UNKNOWN-8) #endif #ifndef REG_EFLAGS #define REG_EFLAGS (REG_UNKNOWN-9) #endif #ifndef REG_EDELIM #define REG_EDELIM (REG_UNKNOWN-9) #endif static const struct { int code; char* name; } codes[] = { REG_UNKNOWN, "UNKNOWN", REG_NOMATCH, "NOMATCH", REG_BADPAT, "BADPAT", REG_ECOLLATE, "ECOLLATE", REG_ECTYPE, "ECTYPE", REG_EESCAPE, "EESCAPE", REG_ESUBREG, "ESUBREG", REG_EBRACK, "EBRACK", REG_EPAREN, "EPAREN", REG_EBRACE, "EBRACE", REG_BADBR, "BADBR", REG_ERANGE, "ERANGE", REG_ESPACE, "ESPACE", REG_BADRPT, "BADRPT", REG_ENEWLINE, "ENEWLINE", REG_ENULL, "ENULL", REG_ECOUNT, "ECOUNT", REG_BADESC, "BADESC", REG_EMEM, "EMEM", REG_EHUNG, "EHUNG", REG_EBUS, "EBUS", REG_EFAULT, "EFAULT", REG_EFLAGS, "EFLAGS", REG_EDELIM, "EDELIM", }; static struct { regmatch_t NOMATCH; int errors; int extracted; int ignored; int lineno; int passed; int signals; int unspecified; int verify; int warnings; char* file; char* stack; char* which; jmp_buf gotcha; #ifdef REG_DISCIPLINE Disc_t disc; #endif } state; static void quote(char* s, int len, unsigned long test) { unsigned char* u = (unsigned char*)s; unsigned char* e; int c; #ifdef MB_CUR_MAX int w; #endif if (!u) printf("NIL"); else if (!*u && len <= 1) printf("NULL"); else if (test & TEST_EXPAND) { if (len < 0) len = strlen((char*)u); e = u + len; if (test & TEST_DELIMIT) printf("\""); while (u < e) switch (c = *u++) { case '\\': printf("\\\\"); break; case '"': if (test & TEST_DELIMIT) printf("\\\""); else printf("\""); break; case '\a': printf("\\a"); break; case '\b': printf("\\b"); break; case 033: printf("\\e"); break; case '\f': printf("\\f"); break; case '\n': printf("\\n"); break; case '\r': printf("\\r"); break; case '\t': printf("\\t"); break; case '\v': printf("\\v"); break; default: #ifdef MB_CUR_MAX s = (char*)u - 1; if ((w = mblen(s, (char*)e - s)) > 1) { u += w - 1; fwrite(s, 1, w, stdout); } else #endif if (!iscntrl(c) && isprint(c)) putchar(c); else printf("\\x%02x", c); break; } if (test & TEST_DELIMIT) printf("\""); } else printf("%s", s); } static void report(char* comment, char* fun, char* re, char* s, int len, char* msg, int flags, unsigned long test) { if (state.file) printf("%s:", state.file); printf("%d:", state.lineno); if (re) { printf(" "); quote(re, -1, test|TEST_DELIMIT); if (s) { printf(" versus "); quote(s, len, test|TEST_DELIMIT); } } if (test & TEST_UNSPECIFIED) { state.unspecified++; printf(" unspecified behavior"); } else state.errors++; if (state.which) printf(" %s", state.which); if (flags & REG_NOSUB) printf(" NOSUB"); if (fun) printf(" %s", fun); if (comment[strlen(comment)-1] == '\n') printf(" %s", comment); else { printf(" %s: ", comment); if (msg) printf("%s: ", msg); } } static void error(regex_t* preg, int code) { char* msg; char buf[256]; switch (code) { case REG_EBUS: msg = "bus error"; break; case REG_EFAULT: msg = "memory fault"; break; case REG_EHUNG: msg = "did not terminate"; break; default: regerror(code, preg, msg = buf, sizeof buf); break; } printf("%s\n", msg); } static void bad(char* comment, char* re, char* s, int len, unsigned long test) { printf("bad test case "); report(comment, NiL, re, s, len, NiL, 0, test); exit(1); } static int escape(char* s) { char* b; char* t; char* q; char* e; int c; for (b = t = s; *t = *s; s++, t++) if (*s == '\\') switch (*++s) { case '\\': break; case 'a': *t = '\a'; break; case 'b': *t = '\b'; break; case 'c': if (*t = *++s) *t &= 037; else s--; break; case 'e': case 'E': *t = 033; break; case 'f': *t = '\f'; break; case 'n': *t = '\n'; break; case 'r': *t = '\r'; break; case 's': *t = ' '; break; case 't': *t = '\t'; break; case 'v': *t = '\v'; break; case 'u': case 'x': c = 0; q = c == 'u' ? (s + 5) : (char*)0; e = s + 1; while (!e || !q || s < q) { switch (*++s) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': c = (c << 4) + *s - 'a' + 10; continue; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': c = (c << 4) + *s - 'A' + 10; continue; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': c = (c << 4) + *s - '0'; continue; case '{': case '[': if (s != e) { s--; break; } e = 0; continue; case '}': case ']': if (e) s--; break; default: s--; break; } break; } *t = c; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = *s - '0'; q = s + 2; while (s < q) { switch (*++s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = (c << 3) + *s - '0'; break; default: q = --s; break; } } *t = c; break; default: *(s + 1) = 0; bad("invalid C \\ escape\n", s - 1, NiL, 0, 0); } return t - b; } static void matchoffprint(int off) { switch (off) { case -2: printf("X"); break; case -1: printf("?"); break; default: printf("%d", off); break; } } static void matchprint(regmatch_t* match, int nmatch, int nsub, char* ans, unsigned long test) { int i; for (; nmatch > nsub + 1; nmatch--) if ((match[nmatch-1].rm_so != -1 || match[nmatch-1].rm_eo != -1) && (!(test & TEST_IGNORE_POSITION) || match[nmatch-1].rm_so >= 0 && match[nmatch-1].rm_eo >= 0)) break; for (i = 0; i < nmatch; i++) { printf("("); matchoffprint(match[i].rm_so); printf(","); matchoffprint(match[i].rm_eo); printf(")"); } if (!(test & (TEST_ACTUAL|TEST_BASELINE))) { if (ans) printf(" expected: %s", ans); printf("\n"); } } static int matchcheck(regmatch_t* match, int nmatch, int nsub, char* ans, char* re, char* s, int len, int flags, unsigned long test) { char* p; int i; int m; int n; if (streq(ans, "OK")) return test & (TEST_BASELINE|TEST_PASS|TEST_VERIFY); for (i = 0, p = ans; i < nmatch && *p; i++) { if (*p == '{') { #ifdef REG_DISCIPLINE char* x; if (!(x = sfstruse(state.disc.sp))) bad("out of space [discipline string]\n", NiL, NiL, 0, 0); if (strcmp(p, x)) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) return 0; report("callout failed", NiL, re, s, len, NiL, flags, test); quote(p, -1, test); printf(" expected, "); quote(x, -1, test); printf(" returned\n"); } #endif break; } if (*p++ != '(') bad("improper answer\n", re, s, -1, test); if (*p == '?') { m = -1; p++; } else if (*p == 'R' && !memcmp(p, "RE_DUP_MAX", 10)) { m = RE_DUP_MAX; p += 10; if (*p == '+' || *p == '-') m += strtol(p, &p, 10); } else m = strtol(p, &p, 10); if (*p++ != ',') bad("improper answer\n", re, s, -1, test); if (*p == '?') { n = -1; p++; } else if (*p == 'R' && !memcmp(p, "RE_DUP_MAX", 10)) { n = RE_DUP_MAX; p += 10; if (*p == '+' || *p == '-') n += strtol(p, &p, 10); } else n = strtol(p, &p, 10); if (*p++ != ')') bad("improper answer\n", re, s, -1, test); if (m!=match[i].rm_so || n!=match[i].rm_eo) { if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY))) { report("failed: match was", NiL, re, s, len, NiL, flags, test); matchprint(match, nmatch, nsub, ans, test); } return 0; } } for (; i < nmatch; i++) { if (match[i].rm_so!=-1 || match[i].rm_eo!=-1) { if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_VERIFY))) { if ((test & TEST_IGNORE_POSITION) && (match[i].rm_so<0 || match[i].rm_eo<0)) { state.ignored++; return 0; } if (!(test & TEST_SUMMARY)) { report("failed: match was", NiL, re, s, len, NiL, flags, test); matchprint(match, nmatch, nsub, ans, test); } } return 0; } } if (!(test & TEST_IGNORE_OVER) && match[nmatch].rm_so != state.NOMATCH.rm_so) { if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY))) { report("failed: overran match array", NiL, re, s, len, NiL, flags, test); matchprint(match, nmatch + 1, nsub, NiL, test); } return 0; } return 1; } static void sigunblock(int s) { #ifdef SIG_SETMASK int op; sigset_t mask; sigemptyset(&mask); if (s) { sigaddset(&mask, s); op = SIG_UNBLOCK; } else op = SIG_SETMASK; sigprocmask(op, &mask, NiL); #else #ifdef sigmask sigsetmask(s ? (sigsetmask(0L) & ~sigmask(s)) : 0L); #endif #endif } static void gotcha(int sig) { int ret; signal(sig, gotcha); alarm(0); state.signals++; switch (sig) { case SIGALRM: ret = REG_EHUNG; break; case SIGBUS: ret = REG_EBUS; break; default: ret = REG_EFAULT; break; } sigunblock(sig); longjmp(state.gotcha, ret); } static char* getline(FILE* fp) { static char buf[32 * 1024]; register char* s = buf; register char* e = &buf[sizeof(buf)]; register char* b; for (;;) { if (!(b = fgets(s, e - s, fp))) return 0; state.lineno++; s += strlen(s); if (s == b || *--s != '\n' || s == b || *(s - 1) != '\\') { *s = 0; break; } s--; } return buf; } static unsigned long note(unsigned long level, char* msg, unsigned long skip, unsigned long test) { if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_SUMMARY)) && !skip) { printf("NOTE\t"); if (msg) printf("%s: ", msg); printf("skipping lines %d", state.lineno); } return skip | level; } #define TABS(n) &ts[7-((n)&7)] static char ts[] = "\t\t\t\t\t\t\t"; static unsigned long extract(int* tabs, char* spec, char* re, char* s, char* ans, char* msg, char* accept, regmatch_t* match, int nmatch, int nsub, unsigned long skip, unsigned long level, unsigned long test) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_OK|TEST_PASS|TEST_SUMMARY)) { state.extracted = 1; if (test & TEST_OK) { state.passed++; if ((test & TEST_VERIFY) && !(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_SUMMARY))) { if (msg && strcmp(msg, "EXPECTED")) printf("NOTE\t%s\n", msg); return skip; } test &= ~(TEST_PASS|TEST_QUERY); } if (test & (TEST_QUERY|TEST_VERIFY)) { if (test & TEST_BASELINE) test &= ~(TEST_BASELINE|TEST_PASS); else test |= TEST_PASS; skip |= level; } if (!(test & TEST_OK)) { if (test & TEST_UNSPECIFIED) state.unspecified++; else state.errors++; } if (test & (TEST_PASS|TEST_SUMMARY)) return skip; test &= ~TEST_DELIMIT; printf("%s%s", spec, TABS(*tabs++)); if ((test & (TEST_BASELINE|TEST_SAME)) == (TEST_BASELINE|TEST_SAME)) printf("SAME"); else quote(re, -1, test); printf("%s", TABS(*tabs++)); quote(s, -1, test); printf("%s", TABS(*tabs++)); if (!(test & (TEST_ACTUAL|TEST_BASELINE)) || !accept && !match) printf("%s", ans); else if (accept) printf("%s", accept); else matchprint(match, nmatch, nsub, NiL, test); if (msg) printf("%s%s", TABS(*tabs++), msg); putchar('\n'); } else if (test & TEST_QUERY) skip = note(level, msg, skip, test); else if (test & TEST_VERIFY) state.extracted = 1; return skip; } static int catchfree(regex_t* preg, int flags, int* tabs, char* spec, char* re, char* s, char* ans, char* msg, char* accept, regmatch_t* match, int nmatch, int nsub, unsigned long skip, unsigned long level, unsigned long test) { int eret; if (!(test & TEST_CATCH)) { regfree(preg); eret = 0; } else if (!(eret = setjmp(state.gotcha))) { alarm(HUNG); regfree(preg); alarm(0); } else if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) extract(tabs, spec, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test); else { report("failed", "regfree", re, NiL, -1, msg, flags, test); error(preg, eret); } return eret; } static char* expand(char* os, char* ot) { char* s = os; char* t; int n = 0; int r; long m; for (;;) { switch (*s++) { case 0: break; case '{': n++; continue; case '}': n--; continue; case 'R': if (n == 1 && !memcmp(s, "E_DUP_MAX", 9)) { s--; for (t = ot; os < s; *t++ = *os++); r = ((t - ot) >= 5 && t[-1] == '{' && t[-2] == '.' && t[-3] == '.' && t[-4] == '.') ? t[-5] : 0; os = ot; m = RE_DUP_MAX; if (*(s += 10) == '+' || *s == '-') m += strtol(s, &s, 10); if (r) { t -= 5; while (m-- > 0) *t++ = r; while (*s && *s++ != '}'); } else t += snprintf(t, 32, "%ld", m); while (*t = *s++) t++; break; } continue; default: continue; } break; } return os; } int main(int argc, char** argv) { int flags; int cflags; int eflags; int nmatch; int nexec; int nstr; int cret; int eret; int nsub; int i; int j; int expected; int got; int locale; int subunitlen; int testno; unsigned long level; unsigned long skip; char* p; char* line; char* spec; char* re; char* s; char* ans; char* msg; char* fun; char* ppat; char* subunit; char* version; char* field[6]; char* delim[6]; FILE* fp; int tabs[6]; char unit[64]; regmatch_t match[100]; regex_t preg; static char pat[32 * 1024]; static char patbuf[32 * 1024]; static char strbuf[32 * 1024]; int nonosub = REG_NOSUB == 0; int nonexec = 0; unsigned long test = 0; static char* filter[] = { "-", 0 }; state.NOMATCH.rm_so = state.NOMATCH.rm_eo = -2; p = unit; version = (char*)id + 10; while (p < &unit[sizeof(unit)-1] && (*p = *version++) && !isspace(*p)) p++; *p = 0; while ((p = *++argv) && *p == '-') for (;;) { switch (*++p) { case 0: break; case 'c': test |= TEST_CATCH; continue; case 'e': test |= TEST_IGNORE_ERROR; continue; case 'h': case '?': help(0); return 2; case '-': help(p[1] == 'h'); return 2; case 'n': nonexec = 1; continue; case 'o': test |= TEST_IGNORE_OVER; continue; case 'p': test |= TEST_IGNORE_POSITION; continue; case 's': #ifdef REG_DISCIPLINE if (!(state.stack = stkalloc(stkstd, 0))) fprintf(stderr, "%s: out of space [stack]", unit); state.disc.disc.re_resizef = resizef; state.disc.disc.re_resizehandle = (void*)stkstd; #endif continue; case 'x': nonosub = 1; continue; case 'v': test |= TEST_VERBOSE; continue; case 'A': test |= TEST_ACTUAL; continue; case 'B': test |= TEST_BASELINE; continue; case 'F': test |= TEST_FAIL; continue; case 'P': test |= TEST_PASS; continue; case 'S': test |= TEST_SUMMARY; continue; default: fprintf(stderr, "%s: %c: invalid option\n", unit, *p); return 2; } break; } if (!*argv) argv = filter; locale = 0; while (state.file = *argv++) { if (streq(state.file, "-") || streq(state.file, "/dev/stdin") || streq(state.file, "/dev/fd/0")) { state.file = 0; fp = stdin; } else if (!(fp = fopen(state.file, "r"))) { fprintf(stderr, "%s: %s: cannot read\n", unit, state.file); return 2; } testno = state.errors = state.ignored = state.lineno = state.passed = state.signals = state.unspecified = state.warnings = 0; skip = 0; level = 1; if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_SUMMARY))) { printf("TEST\t%s ", unit); if (s = state.file) { subunit = p = 0; for (;;) { switch (*s++) { case 0: break; case '/': subunit = s; continue; case '.': p = s - 1; continue; default: continue; } break; } if (!subunit) subunit = state.file; if (p < subunit) p = s - 1; subunitlen = p - subunit; printf("%-.*s ", subunitlen, subunit); } else subunit = 0; for (s = version; *s && (*s != ' ' || *(s + 1) != '$'); s++) putchar(*s); if (test & TEST_CATCH) printf(", catch"); if (test & TEST_IGNORE_ERROR) printf(", ignore error code mismatches"); if (test & TEST_IGNORE_POSITION) printf(", ignore negative position mismatches"); #ifdef REG_DISCIPLINE if (state.stack) printf(", stack"); #endif if (test & TEST_VERBOSE) printf(", verbose"); printf("\n"); #ifdef REG_VERSIONID if (regerror(REG_VERSIONID, NiL, pat, sizeof(pat)) > 0) s = pat; else #endif #ifdef REG_TEST_VERSION s = REG_TEST_VERSION; #else s = "regex"; #endif printf("NOTE\t%s\n", s); if (elementsof(unsupported) > 1) { #if (REG_TEST_DEFAULT & (REG_AUGMENTED|REG_EXTENDED|REG_SHELL)) || !defined(REG_EXTENDED) i = 0; #else i = REG_EXTENDED != 0; #endif for (got = 0; i < elementsof(unsupported) - 1; i++) { if (!got) { got = 1; printf("NOTE\tunsupported: %s", unsupported[i]); } else printf(",%s", unsupported[i]); } if (got) printf("\n"); } } #ifdef REG_DISCIPLINE state.disc.disc.re_version = REG_VERSION; state.disc.disc.re_compf = compf; state.disc.disc.re_execf = execf; if (!(state.disc.sp = sfstropen())) bad("out of space [discipline string stream]\n", NiL, NiL, 0, 0); preg.re_disc = &state.disc.disc; #endif if (test & TEST_CATCH) { signal(SIGALRM, gotcha); signal(SIGBUS, gotcha); signal(SIGSEGV, gotcha); } while (p = getline(fp)) { /* parse: */ line = p; if (*p == ':' && !isspace(*(p + 1))) { while (*++p && *p != ':'); if (!*p++) { if (test & TEST_BASELINE) printf("%s\n", line); continue; } } while (isspace(*p)) p++; if (*p == 0 || *p == '#' || *p == 'T') { if (test & TEST_BASELINE) printf("%s\n", line); continue; } if (*p == ':' || *p == 'N') { if (test & TEST_BASELINE) printf("%s\n", line); else if (!(test & (TEST_ACTUAL|TEST_FAIL|TEST_PASS|TEST_SUMMARY))) { while (*++p && !isspace(*p)); while (isspace(*p)) p++; printf("NOTE %s\n", p); } continue; } j = 0; i = 0; field[i++] = p; for (;;) { switch (*p++) { case 0: p--; j = 0; goto checkfield; case '\t': *(delim[i] = p - 1) = 0; j = 1; checkfield: s = field[i - 1]; if (streq(s, "NIL")) field[i - 1] = 0; else if (streq(s, "NULL")) *s = 0; while (*p == '\t') { p++; j++; } tabs[i - 1] = j; if (!*p) break; if (i >= elementsof(field)) bad("too many fields\n", NiL, NiL, 0, 0); field[i++] = p; /*FALLTHROUGH*/ default: continue; } break; } if (!(spec = field[0])) bad("NIL spec\n", NiL, NiL, 0, 0); /* interpret: */ cflags = REG_TEST_DEFAULT; eflags = REG_EXEC_DEFAULT; test &= TEST_GLOBAL; state.extracted = 0; nmatch = 20; nsub = -1; for (p = spec; *p; p++) { if (isdigit(*p)) { nmatch = strtol(p, &p, 10); if (nmatch >= elementsof(match)) bad("nmatch must be < 100\n", NiL, NiL, 0, 0); p--; continue; } switch (*p) { case 'A': test |= TEST_ARE; continue; case 'B': test |= TEST_BRE; continue; case 'C': if (!(test & TEST_QUERY) && !(skip & level)) bad("locale must be nested\n", NiL, NiL, 0, 0); test &= ~TEST_QUERY; if (locale) bad("locale nesting not supported\n", NiL, NiL, 0, 0); if (i != 2) bad("locale field expected\n", NiL, NiL, 0, 0); if (!(skip & level)) { #if defined(LC_COLLATE) && defined(LC_CTYPE) s = field[1]; if (!s || streq(s, "POSIX")) s = "C"; if ((ans = setlocale(LC_COLLATE, s)) && streq(ans, "POSIX")) ans = "C"; if (!ans || !streq(ans, s) && streq(s, "C")) ans = 0; else if ((ans = setlocale(LC_CTYPE, s)) && streq(ans, "POSIX")) ans = "C"; if (!ans || !streq(ans, s) && streq(s, "C")) skip = note(level, s, skip, test); else { if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_SUMMARY))) printf("NOTE \"%s\" locale\n", s); locale = level; } #else skip = note(level, skip, test, "locales not supported"); #endif } cflags = NOTEST; continue; case 'E': test |= TEST_ERE; continue; case 'K': test |= TEST_KRE; continue; case 'L': test |= TEST_LRE; continue; case 'S': test |= TEST_SRE; continue; case 'a': cflags |= REG_LEFT|REG_RIGHT; continue; case 'b': eflags |= REG_NOTBOL; continue; case 'c': cflags |= REG_COMMENT; continue; case 'd': cflags |= REG_SHELL_DOT; continue; case 'e': eflags |= REG_NOTEOL; continue; case 'f': cflags |= REG_MULTIPLE; continue; case 'g': cflags |= NOTEST; continue; case 'h': cflags |= REG_MULTIREF; continue; case 'i': cflags |= REG_ICASE; continue; case 'j': cflags |= REG_SPAN; continue; case 'k': cflags |= REG_ESCAPE; continue; case 'l': cflags |= REG_LEFT; continue; case 'm': cflags |= REG_MINIMAL; continue; case 'n': cflags |= REG_NEWLINE; continue; case 'o': cflags |= REG_SHELL_GROUP; continue; case 'p': cflags |= REG_SHELL_PATH; continue; case 'q': cflags |= REG_DELIMITED; continue; case 'r': cflags |= REG_RIGHT; continue; case 's': cflags |= REG_SHELL_ESCAPED; continue; case 't': cflags |= REG_MUSTDELIM; continue; case 'u': test |= TEST_UNSPECIFIED; continue; case 'v': cflags |= REG_CLASS_ESCAPE; continue; case 'w': cflags |= REG_NOSUB; continue; case 'x': if (REG_LENIENT) cflags |= REG_LENIENT; else test |= TEST_LENIENT; continue; case 'y': eflags |= REG_LEFT; continue; case 'z': cflags |= REG_NULL; continue; case '$': test |= TEST_EXPAND; continue; case '/': test |= TEST_SUB; continue; case '=': test |= TEST_DECOMP; continue; case '?': test |= TEST_VERIFY; test &= ~(TEST_AND|TEST_OR); state.verify = state.passed; continue; case '&': test |= TEST_VERIFY|TEST_AND; test &= ~TEST_OR; continue; case '|': test |= TEST_VERIFY|TEST_OR; test &= ~TEST_AND; continue; case ';': test |= TEST_OR; test &= ~TEST_AND; continue; case '{': level <<= 1; if (skip & (level >> 1)) { skip |= level; cflags = NOTEST; } else { skip &= ~level; test |= TEST_QUERY; } continue; case '}': if (level == 1) bad("invalid {...} nesting\n", NiL, NiL, 0, 0); if ((skip & level) && !(skip & (level>>1))) { if (!(test & (TEST_BASELINE|TEST_SUMMARY))) { if (test & (TEST_ACTUAL|TEST_FAIL)) printf("}\n"); else if (!(test & TEST_PASS)) printf("-%d\n", state.lineno); } } #if defined(LC_COLLATE) && defined(LC_CTYPE) else if (locale & level) { locale = 0; if (!(skip & level)) { s = "C"; setlocale(LC_COLLATE, s); setlocale(LC_CTYPE, s); if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_SUMMARY))) printf("NOTE \"%s\" locale\n", s); else if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_PASS)) printf("}\n"); } else if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL)) printf("}\n"); } #endif level >>= 1; cflags = NOTEST; continue; default: bad("bad spec\n", spec, NiL, 0, test); break; } break; } if ((cflags|eflags) == NOTEST || (skip & level) && (test & TEST_BASELINE)) { if (test & TEST_BASELINE) { while (i > 1) *delim[--i] = '\t'; printf("%s\n", line); } continue; } if (test & TEST_OR) { if (!(test & TEST_VERIFY)) { test &= ~TEST_OR; if (state.passed == state.verify && i > 1) printf("NOTE\t%s\n", field[1]); continue; } else if (state.passed > state.verify) continue; } else if (test & TEST_AND) { if (state.passed == state.verify) continue; state.passed = state.verify; } if (i < ((test & TEST_DECOMP) ? 3 : 4)) bad("too few fields\n", NiL, NiL, 0, test); while (i < elementsof(field)) field[i++] = 0; if (re = field[1]) { if (streq(re, "SAME")) { re = ppat; test |= TEST_SAME; } else { if (test & TEST_EXPAND) escape(re); re = expand(re, patbuf); strcpy(ppat = pat, re); } } else ppat = 0; nstr = -1; if (s = field[2]) { s = expand(s, strbuf); if (test & TEST_EXPAND) { nstr = escape(s); #if _REG_nexec if (nstr != strlen(s)) nexec = nstr; #endif } } if (!(ans = field[(test & TEST_DECOMP) ? 2 : 3])) bad("NIL answer\n", NiL, NiL, 0, test); msg = field[4]; fflush(stdout); if (test & TEST_SUB) #if _REG_subcomp cflags |= REG_DELIMITED; #else continue; #endif #if !_REG_decomp if (test & TEST_DECOMP) continue; #endif compile: if (state.extracted || (skip & level)) continue; #if !(REG_TEST_DEFAULT & (REG_AUGMENTED|REG_EXTENDED|REG_SHELL)) #ifdef REG_EXTENDED if (REG_EXTENDED != 0 && (test & TEST_BRE)) #else if (test & TEST_BRE) #endif { test &= ~TEST_BRE; flags = cflags; state.which = "BRE"; } else #endif #ifdef REG_EXTENDED if (test & TEST_ERE) { test &= ~TEST_ERE; flags = cflags | REG_EXTENDED; state.which = "ERE"; } else #endif #ifdef REG_AUGMENTED if (test & TEST_ARE) { test &= ~TEST_ARE; flags = cflags | REG_AUGMENTED; state.which = "ARE"; } else #endif #ifdef REG_LITERAL if (test & TEST_LRE) { test &= ~TEST_LRE; flags = cflags | REG_LITERAL; state.which = "LRE"; } else #endif #ifdef REG_SHELL if (test & TEST_SRE) { test &= ~TEST_SRE; flags = cflags | REG_SHELL; state.which = "SRE"; } else #ifdef REG_AUGMENTED if (test & TEST_KRE) { test &= ~TEST_KRE; flags = cflags | REG_SHELL | REG_AUGMENTED; state.which = "KRE"; } else #endif #endif { if (test & (TEST_BASELINE|TEST_PASS|TEST_VERIFY)) extract(tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test|TEST_OK); continue; } if ((test & (TEST_QUERY|TEST_VERBOSE|TEST_VERIFY)) == TEST_VERBOSE) { printf("test %-3d %s ", state.lineno, state.which); quote(re, -1, test|TEST_DELIMIT); printf(" "); quote(s, nstr, test|TEST_DELIMIT); printf("\n"); } nosub: fun = "regcomp"; #if _REG_nexec if (nstr >= 0 && nstr != strlen(s)) nexec = nstr; else #endif nexec = -1; if (state.extracted || (skip & level)) continue; if (!(test & TEST_QUERY)) testno++; #ifdef REG_DISCIPLINE if (state.stack) stkset(stkstd, state.stack, 0); flags |= REG_DISCIPLINE; state.disc.ordinal = 0; sfstrseek(state.disc.sp, 0, SEEK_SET); #endif if (!(test & TEST_CATCH)) cret = regcomp(&preg, re, flags); else if (!(cret = setjmp(state.gotcha))) { alarm(HUNG); cret = regcomp(&preg, re, flags); alarm(0); } #if _REG_subcomp if (!cret && (test & TEST_SUB)) { fun = "regsubcomp"; p = re + preg.re_npat; if (!(test & TEST_CATCH)) cret = regsubcomp(&preg, p, NiL, 0, 0); else if (!(cret = setjmp(state.gotcha))) { alarm(HUNG); cret = regsubcomp(&preg, p, NiL, 0, 0); alarm(0); } if (!cret && *(p += preg.re_npat) && !(preg.re_sub->re_flags & REG_SUB_LAST)) { if (catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test)) continue; cret = REG_EFLAGS; } } #endif #if _REG_decomp if (!cret && (test & TEST_DECOMP)) { char buf[128]; if ((j = nmatch) > sizeof(buf)) j = sizeof(buf); fun = "regdecomp"; p = re + preg.re_npat; if (!(test & TEST_CATCH)) i = regdecomp(&preg, -1, buf, j); else if (!(cret = setjmp(state.gotcha))) { alarm(HUNG); i = regdecomp(&preg, -1, buf, j); alarm(0); } if (!cret) { catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test); if (i > j) { if (i != (strlen(ans) + 1)) { report("failed", fun, re, s, nstr, msg, flags, test); printf(" %d byte buffer supplied, %d byte buffer required\n", j, i); } } else if (strcmp(buf, ans)) { report("failed", fun, re, s, nstr, msg, flags, test); quote(ans, -1, test|TEST_DELIMIT); printf(" expected, "); quote(buf, -1, test|TEST_DELIMIT); printf(" returned\n"); } continue; } } #endif if (!cret) { if (!(flags & REG_NOSUB) && nsub < 0 && *ans == '(') { for (p = ans; *p; p++) if (*p == '(') nsub++; else if (*p == '{') nsub--; if (nsub >= 0) { if (test & TEST_IGNORE_OVER) { if (nmatch > nsub) nmatch = nsub + 1; } else if (nsub != preg.re_nsub) { if (nsub > preg.re_nsub) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, "OK", NiL, 0, 0, skip, level, test|TEST_DELIMIT); else { report("re_nsub incorrect", fun, re, NiL, -1, msg, flags, test); printf("at least %d expected, %d returned\n", nsub, preg.re_nsub); state.errors++; } } else nsub = preg.re_nsub; } } } if (!(test & (TEST_DECOMP|TEST_SUB)) && *ans && *ans != '(' && !streq(ans, "OK") && !streq(ans, "NOMATCH")) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, "OK", NiL, 0, 0, skip, level, test|TEST_DELIMIT); else if (!(test & TEST_LENIENT)) { report("failed", fun, re, NiL, -1, msg, flags, test); printf("%s expected, OK returned\n", ans); } catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test); continue; } } else { if (test & TEST_LENIENT) /* we'll let it go this time */; else if (!*ans || ans[0]=='(' || cret == REG_BADPAT && streq(ans, "NOMATCH")) { got = 0; for (i = 1; i < elementsof(codes); i++) if (cret==codes[i].code) got = i; if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, codes[got].name, NiL, 0, 0, skip, level, test|TEST_DELIMIT); else { report("failed", fun, re, NiL, -1, msg, flags, test); printf("%s returned: ", codes[got].name); error(&preg, cret); } } else { expected = got = 0; for (i = 1; i < elementsof(codes); i++) { if (streq(ans, codes[i].name)) expected = i; if (cret==codes[i].code) got = i; } if (!expected) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, codes[got].name, NiL, 0, 0, skip, level, test|TEST_DELIMIT); else { report("failed: invalid error code", NiL, re, NiL, -1, msg, flags, test); printf("%s expected, %s returned\n", ans, codes[got].name); } } else if (cret != codes[expected].code && cret != REG_BADPAT) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, codes[got].name, NiL, 0, 0, skip, level, test|TEST_DELIMIT); else if (test & TEST_IGNORE_ERROR) state.ignored++; else { report("should fail and did", fun, re, NiL, -1, msg, flags, test); printf("%s expected, %s returned: ", ans, codes[got].name); state.errors--; state.warnings++; error(&preg, cret); } } } goto compile; } #if _REG_nexec execute: if (nexec >= 0) fun = "regnexec"; else #endif fun = "regexec"; for (i = 0; i < elementsof(match); i++) match[i] = state.NOMATCH; #if _REG_nexec if (nexec >= 0) { eret = regnexec(&preg, s, nexec, nmatch, match, eflags); s[nexec] = 0; } else #endif { if (!(test & TEST_CATCH)) eret = regexec(&preg, s, nmatch, match, eflags); else if (!(eret = setjmp(state.gotcha))) { alarm(HUNG); eret = regexec(&preg, s, nmatch, match, eflags); alarm(0); } } #if _REG_subcomp if ((test & TEST_SUB) && !eret) { fun = "regsubexec"; if (!(test & TEST_CATCH)) eret = regsubexec(&preg, s, nmatch, match); else if (!(eret = setjmp(state.gotcha))) { alarm(HUNG); eret = regsubexec(&preg, s, nmatch, match); alarm(0); } } #endif if (flags & REG_NOSUB) { if (eret) { if (eret != REG_NOMATCH || !streq(ans, "NOMATCH")) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, "NOMATCH", NiL, 0, 0, skip, level, test|TEST_DELIMIT); else { report("REG_NOSUB failed", fun, re, s, nstr, msg, flags, test); error(&preg, eret); } } } else if (streq(ans, "NOMATCH")) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level, test|TEST_DELIMIT); else { report("should fail and didn't", fun, re, s, nstr, msg, flags, test); error(&preg, eret); } } } else if (eret) { if (eret != REG_NOMATCH || !streq(ans, "NOMATCH")) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, "NOMATCH", NiL, 0, nsub, skip, level, test|TEST_DELIMIT); else { report("failed", fun, re, s, nstr, msg, flags, test); if (eret != REG_NOMATCH) error(&preg, eret); else if (*ans) printf("expected: %s\n", ans); else printf("\n"); } } } else if (streq(ans, "NOMATCH")) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level, test|TEST_DELIMIT); else { report("should fail and didn't", fun, re, s, nstr, msg, flags, test); matchprint(match, nmatch, nsub, NiL, test); } } #if _REG_subcomp else if (test & TEST_SUB) { p = preg.re_sub->re_buf; if (strcmp(p, ans)) { report("failed", fun, re, s, nstr, msg, flags, test); quote(ans, -1, test|TEST_DELIMIT); printf(" expected, "); quote(p, -1, test|TEST_DELIMIT); printf(" returned\n"); } } #endif else if (!*ans) { if (match[0].rm_so != state.NOMATCH.rm_so) { if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test); else { report("failed: no match but match array assigned", NiL, re, s, nstr, msg, flags, test); matchprint(match, nmatch, nsub, NiL, test); } } } else if (matchcheck(match, nmatch, nsub, ans, re, s, nstr, flags, test)) { #if _REG_nexec if (nexec < 0 && !nonexec) { nexec = nstr >= 0 ? nstr : strlen(s); s[nexec] = '\n'; testno++; goto execute; } #endif if (!(test & (TEST_DECOMP|TEST_SUB|TEST_VERIFY)) && !nonosub) { if (catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test)) continue; flags |= REG_NOSUB; goto nosub; } if (test & (TEST_BASELINE|TEST_PASS|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level, test|TEST_OK); } else if (test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS|TEST_QUERY|TEST_SUMMARY|TEST_VERIFY)) skip = extract(tabs, line, re, s, ans, msg, NiL, match, nmatch, nsub, skip, level, test|TEST_DELIMIT); if (catchfree(&preg, flags, tabs, line, re, s, ans, msg, NiL, NiL, 0, 0, skip, level, test)) continue; goto compile; } if (test & TEST_SUMMARY) printf("tests=%-4d errors=%-4d warnings=%-2d ignored=%-2d unspecified=%-2d signals=%d\n", testno, state.errors, state.warnings, state.ignored, state.unspecified, state.signals); else if (!(test & (TEST_ACTUAL|TEST_BASELINE|TEST_FAIL|TEST_PASS))) { printf("TEST\t%s", unit); if (subunit) printf(" %-.*s", subunitlen, subunit); printf(", %d test%s", testno, testno == 1 ? "" : "s"); if (state.ignored) printf(", %d ignored mismatche%s", state.ignored, state.ignored == 1 ? "" : "s"); if (state.warnings) printf(", %d warning%s", state.warnings, state.warnings == 1 ? "" : "s"); if (state.unspecified) printf(", %d unspecified difference%s", state.unspecified, state.unspecified == 1 ? "" : "s"); if (state.signals) printf(", %d signal%s", state.signals, state.signals == 1 ? "" : "s"); printf(", %d error%s\n", state.errors, state.errors == 1 ? "" : "s"); } if (fp != stdin) fclose(fp); } return 0; }