| #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; | |
| } |