|
@@ -0,0 +1,2121 @@
|
|
|
+#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 Labs Research
|
|
|
+ *
|
|
|
+ * PLEASE: publish your tests so everyone can benefit
|
|
|
+ *
|
|
|
+ * 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) 2005-05-20 $\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
|
|
|
+
|
|
|
+#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 0x00000040
|
|
|
+#define TEST_LENIENT 0x00000080
|
|
|
+
|
|
|
+#define TEST_QUERY 0x00000100
|
|
|
+#define TEST_SUB 0x00000200
|
|
|
+#define TEST_UNSPECIFIED 0x00000400
|
|
|
+#define TEST_VERIFY 0x00000800
|
|
|
+#define TEST_AND 0x00001000
|
|
|
+#define TEST_OR 0x00002000
|
|
|
+
|
|
|
+#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_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*)++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}(%d:%d)", xlen, xstr, (int)data, 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 [BEASKL] then the\n");
|
|
|
+T(" specification is a global control line. One or more of [BEASKL] 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(" 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("\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.\n");
|
|
|
+T("\n");
|
|
|
+T(" Field 3: the string to match.\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.\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_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
|
|
|
+ 0
|
|
|
+};
|
|
|
+
|
|
|
+#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;
|
|
|
+
|
|
|
+ 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:
|
|
|
+ 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;
|
|
|
+
|
|
|
+ x = sfstruse(state.disc.sp);
|
|
|
+ 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
|
|
|
+ m = strtol(p, &p, 10);
|
|
|
+ if (*p++ != ',')
|
|
|
+ bad("improper answer\n", re, s, -1, test);
|
|
|
+ if (*p == '?')
|
|
|
+ {
|
|
|
+ n = -1;
|
|
|
+ p++;
|
|
|
+ }
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+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];
|
|
|
+
|
|
|
+ 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))
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ default:
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!(spec = field[0]))
|
|
|
+ bad("NIL spec\n", NiL, NiL, 0, 0);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ 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, "C") || streq(ans, "POSIX") || !(ans = setlocale(LC_CTYPE, s)) || streq(ans, "C") || streq(ans, "POSIX"))
|
|
|
+ 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 '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_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 < 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);
|
|
|
+ strcpy(ppat = pat, re);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ ppat = 0;
|
|
|
+ nstr = -1;
|
|
|
+ if ((s = field[2]) && (test & TEST_EXPAND))
|
|
|
+ {
|
|
|
+ nstr = escape(s);
|
|
|
+#if _REG_nexec
|
|
|
+ if (nstr != strlen(s))
|
|
|
+ nexec = nstr;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ if (!(ans = field[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
|
|
|
+
|
|
|
+ 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 (!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_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)
|
|
|
+ ;
|
|
|
+ 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_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;
|
|
|
+}
|