blob: c005d917b5bb4dadc345bc23b2c7d2b79e292096 [file] [edit]
#! /usr/bin/env python3
###############################################################################
# Run the PCRE2 tests using the pcre2test program. The appropriate tests are
# selected, depending on which build-time options were used.
#
# When JIT support is available, all appropriate tests are run with and without
# JIT, unless "-nojit" is given on the command line. There are also two tests
# for JIT-specific features, one to be run when JIT support is available
# (unless "-nojit" is specified), and one when it is not.
#
# Whichever of the 8-, 16- and 32-bit libraries exist are tested. It is also
# possible to select which to test by giving "-8", "-16" or "-32" on the
# command line.
#
# As well as "-nojit", "-8", "-16", and "-32", arguments for this script are
# individual test numbers, ranges of tests such as 3-6 or 3- (meaning 3 to the
# end), or a number preceded by ~ to exclude a test. For example, "3-15 ~10"
# runs tests 3 to 15, excluding test 10, and just "~10" runs all the tests
# except test 10. Whatever order the arguments are in, these tests are always
# run in numerical order.
#
# If no specific tests are selected (which is the case when this script is run
# via 'make check') the default is to run all the numbered tests.
#
# There may also be named (as well as numbered) tests for special purposes. At
# present there is just one, called "heap". This test's output contains the
# sizes of heap frames and frame vectors, which depend on the environment. It
# is therefore not run unless explicitly requested.
#
# Inappropriate tests are automatically skipped (with a comment to say so). For
# example, if JIT support is not compiled, test 16 is skipped, whereas if JIT
# support is compiled, test 15 is skipped.
#
# Other arguments can be one of the words "-valgrind", "-valgrind-log", or
# "-sim" followed by an argument to run cross-compiled executables under a
# simulator, for example:
#
# RunTest 3 -sim "qemu-arm -s 8388608"
#
# MESON_EXE_WRAPPER supplies the default simulator when running under Meson;
# --sim overrides it. Simulator commands use whitespace splitting, not quoting.
#
# For backwards compatibility, -nojit, -valgrind, -valgrind-log, and -sim may
# be given without the leading "-" character.
#
# When PCRE2 is compiled by clang with -fsanitize arguments, some tests need
# very much more stack than normal. In environments where the stack can be
# set at runtime, -bigstack sets a gigantic stack.
#
# Special cases where only one argument is allowed:
# - If the script is invoked as "RunTest list", a list of available tests is
# output, but none of them are run.
###############################################################################
import difflib
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
# Ensure that Python does not hold onto output, to make it clear where it got
# to on failure.
sys.stdout.reconfigure(line_buffering=True)
# Define test titles in variables so that they can be output as a list. Some
# of them are modified (e.g. with -8 or -16) when used in the actual tests.
title0 = "Test 0: Unchecked pcre2test argument tests (to improve coverage)"
title1 = "Test 1: Main non-UTF, non-UCP functionality (compatible with Perl >= 5.10)"
title2 = "Test 2: API, errors, internals and non-Perl stuff"
title3 = "Test 3: Locale-specific features"
title4A = "Test 4: UTF"
title4B = " and Unicode property support (compatible with Perl >= 5.10)"
title5A = "Test 5: API, internals, and non-Perl stuff for UTF"
title5B = " and UCP support"
title6 = "Test 6: DFA matching main non-UTF, non-UCP functionality"
title7A = "Test 7: DFA matching with UTF"
title7B = " and Unicode property support"
title8 = "Test 8: Internal offsets and code size tests"
title9 = "Test 9: Specials for the basic 8-bit library"
title10 = "Test 10: Specials for the 8-bit library with UTF-8 and UCP support"
title11 = "Test 11: Specials for the basic 16-bit and 32-bit libraries"
title12 = "Test 12: Specials for the 16-bit and 32-bit libraries UTF and UCP support"
title13 = "Test 13: DFA specials for the basic 16-bit and 32-bit libraries"
title14 = "Test 14: DFA specials for UTF and UCP support"
title15 = "Test 15: Non-JIT limits and other non-JIT tests"
title16 = "Test 16: JIT-specific features when JIT is not available"
title17 = "Test 17: JIT-specific features when JIT is available"
title18 = "Test 18: Tests of the POSIX interface, excluding UTF/UCP"
title19 = "Test 19: Tests of the POSIX interface with UTF/UCP"
title20 = "Test 20: Serialization and code copy tests"
title21 = "Test 21: \\C tests without UTF (supported for DFA matching)"
title22 = "Test 22: \\C tests with UTF (not supported for DFA matching)"
title23 = "Test 23: \\C disabled test"
title24 = "Test 24: Non-UTF pattern conversion tests"
title25 = "Test 25: UTF pattern conversion tests"
title26 = "Test 26: Unicode property tests (compatible with Perl >= 5.38)"
title27 = "Test 27: Auto-generated unicode property tests"
title28 = "Test 28: EBCDIC-specific tests"
title29 = "Test 29: EBCDIC-specific tests (for NL=0x25)"
maxtest = 29
titleheap = "Test 'heap': Environment-specific heap tests"
if len(sys.argv) == 2 and sys.argv[1] in ("list", "-list", "--list"):
for title in (title0, title1, title2 + " (not UTF or UCP)", title3, title4A + title4B, title5A + title5B, title6,
title7A + title7B, title8, title9, title10, title11, title12, title13, title14, title15, title16,
title17, title18, title19, title20, title21, title22, title23, title24, title25, title26, title27,
title28, title29):
print(title)
print()
print(titleheap)
print()
print("Numbered tests are automatically run if nothing selected.")
print("Named tests must be explicitly selected.")
sys.exit(0)
failed = False
# ------ Functions to help invoking tests -------
# This function is called with four parameters:
#
# returncode the return code after a call to pcre2test
# suffix the suffix of the output file to compare with
# opts the pcre2test option arguments (empty, -jit, or -dfa)
# bits the library code unit width
def invoke(*args, stdin=None, stdout=None, stderr=None, use_valgrind=True, use_vjs=False):
command = [str(arg) for arg in args]
if valgrind and use_valgrind:
command = [*valgrind, *(vjs if use_vjs else []), *command]
command = [*sim, *command]
return subprocess.run(command, stdin=stdin, stdout=stdout, stderr=stderr, check=False).returncode
def compare(first, second):
try:
with open(first, "rb") as first_file, open(second, "rb") as second_file:
while True:
first_bytes = first_file.read(65536)
second_bytes = second_file.read(65536)
if first_bytes != second_bytes:
return False
if not first_bytes:
return True
except OSError:
return False
# ------ Function to check results of a test -------
# This function is called with four parameters:
#
# returncode the return code after a call to pcre2test
# suffix the suffix of the output file to compare with
# opts the pcre2test option arguments ([], [-jit], or [-dfa])
# bits the library code unit width
def checkresult(returncode, suffix, opts, bits):
global failed
opt = "".join(opts)
output = Path(f"testoutput{bits}{opt}") / f"testoutput{suffix}"
if returncode != 0:
print(f"** pcre2test failed - check {output}")
failed = True
return
with_message = " with JIT" if opt == "-jit" else " with DFA" if opt == "-dfa" else ""
expected = testdata / f"testoutput{suffix}"
if uses_ebcdic or not supports_utf:
# We currently only use the #if ... #endif support in pcre2test for tests
# which are excluded in certain builds:
# - EBCDIC
# - No-UTF
# Run in "preprocess-only" mode (-E) on the testoutput file to trim
# the output lines matching the input lines which are discarded.
expected_output = Path(f"testoutput{bits}{opt}") / f"testoutput{suffix}-expected"
with open(expected_output, "wb") as stream:
invoke(pcre2test, "-q", "-E", expected, stdout=stream, use_valgrind=False)
expected = expected_output
if not compare(expected, output):
try:
print("".join(
difflib.unified_diff(expected.read_bytes().decode("latin-1").splitlines(keepends=True),
output.read_bytes().decode("latin-1").splitlines(keepends=True),
fromfile=str(expected),
tofile=str(output))),
end="")
except OSError:
pass
print()
print(f"** Test {suffix} failed{with_message}")
failed = True
return
print(f" OK{with_message}")
# ------ Test setup ------
arg8 = arg16 = arg32 = nojit = bigstack = malloc = False
sim = os.environ.get("MESON_EXE_WRAPPER", "").split()
skip = []
valgrind = []
globalopts = ["-q"]
pcre2test = os.environ.get("pcre2test", None) or ("./pcre2test.exe" if os.name == "nt" else "./pcre2test")
srcdir = os.environ.get("srcdir", "")
testdatatar = os.environ.get("testdatatar", "")
bazel_runfiles = False
# Process options and select which tests to run; for those that are explicitly
# requested, check that the necessary optional facilities are available.
do = [False] * 30
doheap = False
arguments = iter(sys.argv[1:])
for argument in arguments:
range_match = re.fullmatch(r"([0-9]+)-([0-9]*)", argument)
if argument.isascii() and argument.isdecimal() and int(argument) <= maxtest:
do[int(argument)] = True
elif argument == "heap":
doheap = True
elif argument == "-8":
arg8 = True
elif argument == "-16":
arg16 = True
elif argument == "-32":
arg32 = True
elif argument in ("bigstack", "-bigstack", "--bigstack"):
bigstack = True
elif argument in ("malloc", "-malloc", "--malloc"):
malloc = True
elif argument in ("nojit", "-nojit", "--nojit"):
nojit = True
elif argument in ("sim", "-sim", "--sim"):
try:
# The historical RunTest(.sh) behaviour is to assign the argument following
# '-sim' to one variable, then invoke it unquoted as $sim, performing word
# splitting. It's crude, but allows a simulator binary with simple arguments.
sim = next(arguments).split()
except StopIteration:
print(f"Missing argument after '{argument}'")
sys.exit(1)
elif argument in ("srcdir", "-srcdir", "--srcdir"):
try:
srcdir = next(arguments)
except StopIteration:
print(f"Missing argument after '{argument}'")
sys.exit(1)
elif argument in ("pcre2test", "-pcre2test", "--pcre2test"):
try:
pcre2test = next(arguments)
except StopIteration:
print(f"Missing argument after '{argument}'")
sys.exit(1)
elif argument in ("testdatatar", "-testdatatar", "--testdatatar"):
try:
testdatatar = next(arguments)
except StopIteration:
print(f"Missing argument after '{argument}'")
sys.exit(1)
elif argument in ("valgrind", "-valgrind", "--valgrind"):
valgrind = [
"valgrind", "--tool=memcheck", "-q", "--leak-check=yes", "--errors-for-leak-kinds=all",
"--smc-check=all-non-file", "--error-exitcode=70"
]
elif argument in ("valgrind-log", "-valgrind-log", "--valgrind-log"):
valgrind = [
"valgrind", "--tool=memcheck", "--num-callers=30", "--leak-check=yes", "--errors-for-leak-kinds=all",
"--error-limit=no", "--smc-check=all-non-file", "--log-file=report.%p"
]
elif argument in ("bazel-runfiles", "-bazel-runfiles", "--bazel-runfiles"):
bazel_runfiles = True
elif re.fullmatch(r"~[0-9]+", argument):
skip.append(int(argument[1:]))
elif range_match:
first = int(range_match.group(1))
last = int(range_match.group(2) or maxtest)
if first > maxtest or last > maxtest:
print(f"Invalid test range '{argument}'")
sys.exit(1)
for number in range(first, last + 1):
do[number] = True
else:
print(f"Unknown option or test selector '{argument}'")
sys.exit(1)
# For some reason that's hard to understand, Bazel struggles to pass actual,
# honest file paths within its build sandbox, so we have support for its
# pseudo-paths, which are handles to file paths within a manifest somewhere.
if bazel_runfiles:
from python.runfiles import runfiles
r = runfiles.Create()
if pcre2test:
pcre2test = str(r.Rlocation(pcre2test))
if srcdir:
srcdir = str(r.Rlocation(srcdir))
if testdatatar:
testdatatar = str(r.Rlocation(testdatatar))
# Validate paths after argument processing
if not Path(pcre2test).is_file() or not os.access(pcre2test, os.X_OK):
print(f"** {pcre2test} does not exist or is not executable.")
sys.exit(1)
# Do not search PATH for a bare executable filename.
pcre2test = os.path.abspath(pcre2test)
# Find the test data
if testdatatar:
import tarfile
with tarfile.open(testdatatar) as tar:
tar.extractall(path=".")
testdata = Path("testdata")
testdata_display = "./testdata"
if not testdata.is_dir():
print(f"The specified testdata tarball '{testdatatar}' does not contain a testdata directory.")
sys.exit(1)
elif srcdir:
testdata = Path(srcdir) / "testdata"
if not testdata.is_dir():
print(f"The specified srcdir '{srcdir}' does not contain a testdata directory")
sys.exit(1)
testdata_display = os.path.join(srcdir, "testdata")
else:
if Path("testdata").is_dir():
testdata = Path("testdata")
testdata_display = "./testdata"
elif Path("../testdata").is_dir():
testdata = Path("../testdata")
testdata_display = "../testdata"
else:
print("Cannot find the testdata directory")
sys.exit(1)
def has_capability(name):
with open(os.devnull, "wb") as null:
return invoke(pcre2test, "-C", name, stdout=null, stderr=null, use_valgrind=False) != 0
# If it is possible to set the system stack size and -bigstack was given,
# set up a large stack.
with open(os.devnull, "wb") as null:
supports_setstack = invoke(pcre2test,
"-S",
"32",
os.devnull,
os.devnull,
stdout=null,
stderr=null,
use_valgrind=False) == 0
if supports_setstack and bigstack:
globalopts.extend(("-S", "32"))
# If the malloc option is given, then call pcre2test with -malloc.
if malloc:
globalopts.append("-malloc")
# All of 8-bit, 16-bit, and 32-bit character strings may be supported, but only
# one need be.
supports_8 = has_capability("pcre2-8")
supports_16 = has_capability("pcre2-16")
supports_32 = has_capability("pcre2-32")
# \C may be disabled
supports_backslash_c = has_capability("backslash-C")
# Check if compiled in EBCDIC mode, and whether we have EBCDIC I/O and NL=0x25
uses_ebcdic = has_capability("ebcdic")
supports_ebcdic_io = has_capability("ebcdic-io")
uses_ebcdic_nl25 = has_capability("ebcdic-nl25")
if uses_ebcdic:
if not supports_ebcdic_io:
print("Running tests in EBCDIC mode, and expecting ASCII test data")
else:
print("Running tests in EBCDIC mode, and expecting EBCDIC test data")
print("If you are on an EBCDIC machine, you will need to convert the PCRE2")
print("testdata/ directory from ISO8859-1, so the data match the EBCDIC")
print("codepage that your C compiler is using for C character literals.")
print("For example:")
print(" iconv -f ISO8859-1 -t IBM-1047 ...")
# If no bitsize arguments, select all that are available. Otherwise, select the
# requested bit sizes.
if arg8 and not supports_8:
print("Cannot run 8-bit library tests: 8-bit library not compiled")
sys.exit(1)
if arg16 and not supports_16:
print("Cannot run 16-bit library tests: 16-bit library not compiled")
sys.exit(1)
if arg32 and not supports_32:
print("Cannot run 32-bit library tests: 32-bit library not compiled")
sys.exit(1)
width_selected = arg8 or arg16 or arg32
test8 = "-8" if supports_8 and (not width_selected or arg8) else False
test16 = "-16" if supports_16 and (not width_selected or arg16) else False
test32 = "-32" if supports_32 and (not width_selected or arg32) else False
# UTF support is implied by Unicode support, and it always applies to all bit
# sizes if both are supported; we can't have UTF-8 support without UTF-16 or
# UTF-32 support.
supports_utf = has_capability("unicode")
# When JIT is used with valgrind, we need to set up valgrind suppressions as
# otherwise there are a lot of false positive valgrind reports when the
# the hardware supports SSE2.
supports_jit = has_capability("jit")
vjs = []
jitopts = []
if supports_jit and not nojit:
jitopts = [["-jit"]]
if jitopts and valgrind:
vjs = [f"--suppressions={testdata / 'valgrind-jit.supp'}"]
# If no specific tests were requested, select all the numbered tests. Those
# that are not relevant will be automatically skipped.
if not any(do) and not doheap:
do = [True] * 30
# Handle any explicit skips at this stage, so that an argument list may consist
# only of explicit skips.
for number in skip:
if number <= maxtest:
do[number] = False
# Show which release and which test data
print()
print(f"PCRE2 C library tests using test data from {testdata_display}", flush=True)
invoke(pcre2test, os.devnull, use_valgrind=False)
print()
# ------ Normal Tests ------
for bmode in (test8, test16, test32):
if not bmode:
continue
bits = bmode[1:]
if bmode == "-16" and test8 or bmode == "-32" and (test8 or test16):
print()
print(f"---- Testing {bits}-bit library ----")
print()
# Set up directories for test output.
for opts in [[], *jitopts, ["-dfa"]]:
Path(f"testoutput{bits}{''.join(opts)}").mkdir(exist_ok=True)
# Test 0 is a special test. Its output is not checked, because it will
# be different on different hardware and with different configurations.
# Running this test just exercises the code.
if do[0]:
print(title0)
Path("testSinput").write_bytes(b"/abc/jit,memory,framesize\n abc\n")
Path("testSoutput").write_bytes(b"\n")
special_tests_passed = True
def checkspecial(arguments, expect=0, stderr=subprocess.STDOUT, use_vjs=False):
global failed
with open("testSoutput", "ab") as stream:
returncode = invoke(pcre2test, *arguments, stdout=stream, stderr=stderr, use_vjs=use_vjs)
if returncode != expect:
print(f"** pcre2test {' '.join(arguments)} failed - check testSoutput")
failed = True
return False
return True
special_tests_passed &= checkspecial((bmode, "-C"))
special_tests_passed &= checkspecial(("--help", ))
special_tests_passed &= checkspecial((bmode, "testSinput"), use_vjs=True)
special_tests_passed &= checkspecial((bmode, str(testdata / "testinputheap")))
if supports_setstack:
special_tests_passed &= checkspecial((bmode, "-S", "1", "-t", "10", "testSinput"), use_vjs=True)
special_tests_passed &= checkspecial((bmode, "reallydoesnotexist"), expect=1)
special_tests_passed &= checkspecial((bmode, "testSinput", "reallydoesnotexist/outfile"), expect=1)
special_tests_passed &= checkspecial((bmode, "-pattern", "debug", "testSinput"), use_vjs=True)
special_tests_passed &= checkspecial((bmode, "-pattern", "INVALID", "testSinput"),
expect=1,
stderr=subprocess.DEVNULL)
special_tests_passed &= checkspecial((bmode, "-subject", "notempty", "testSinput"), use_vjs=True)
special_tests_passed &= checkspecial((bmode, "-subject", "INVALID", "testSinput"),
expect=1,
stderr=subprocess.DEVNULL)
special_tests_passed &= checkspecial(("-LM", ))
special_tests_passed &= checkspecial(("-LP", ))
special_tests_passed &= checkspecial(("-LS", ))
special_tests_passed &= checkspecial((bmode, "-unittest"), use_vjs=True)
if special_tests_passed:
print(" OK")
# Primary non-UTF test, compatible with JIT and all versions of Perl >= 5.8
if do[1]:
print(title1)
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput1"
checkresult(
invoke(pcre2test, *globalopts, bmode, *opts, testdata / "testinput1", output, use_vjs=opts == ["-jit"]),
"1", opts, bits)
# PCRE2 tests that are not Perl-compatible: API, errors, internals. We copy
# the testbtables file to the current directory for use by this test.
if do[2]:
print(f"{title2} (excluding UTF-{bits})")
shutil.copyfile(testdata / "testbtables", "testbtables")
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput2"
returncode = invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput2",
output,
use_vjs=opts == ["-jit"])
if returncode == 0:
with open(output, "ab") as stream:
returncode = invoke(pcre2test,
*globalopts,
bmode,
*opts,
"-error",
"-80,-62,-2,-1,0,100,101,191,300",
stdout=stream,
use_vjs=opts == ["-jit"])
checkresult(returncode, "2", opts, bits)
# Locale-specific tests. Unfortunately, different versions of the French
# locale give different outputs for some items. This test passes if the
# output matches any one of the alternative output files.
if do[3]:
selected_locale = ""
locales = ("french", "fr_FR", "fr", "fr_CA")
if shutil.which("locale") is None:
available_locales = []
else:
available_locales = subprocess.run(
["locale", "-a"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
check=False,
).stdout.decode("latin-1").splitlines()
for loc in locales:
if loc not in available_locales:
continue
probe = subprocess.run(
[*sim, *valgrind, pcre2test, "-q", bmode],
input=f"/a/locale={loc}\n".encode("latin-1"),
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
check=False,
)
if b"Failed to set locale" in probe.stdout:
continue
selected_locale = loc
for name_in, name_out in (("testinput3", "test3input"), ("testoutput3", "test3output"),
("testoutput3A", "test3outputA"), ("testoutput3B", "test3outputB"),
("testoutput3C", "test3outputC"), ("testoutput3D", "test3outputD"),
("testoutput3E", "test3outputE"), ("testoutput3F", "test3outputF")):
Path(name_out).write_bytes((testdata / name_in).read_bytes().replace(b"fr_FR", loc.encode("latin-1")))
break
if selected_locale:
print(f"{title3} (using '{selected_locale}' locale)")
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}") / "testoutput3"
returncode = invoke(pcre2test,
*globalopts,
bmode,
*opts,
"test3input",
output,
use_vjs=opts == ["-jit"])
with_message = " with JIT" if opts == ["-jit"] else ""
expected_outputs = (
Path("test3output"),
Path("test3outputA"),
Path("test3outputB"),
Path("test3outputC"),
Path("test3outputD"),
Path("test3outputE"),
Path("test3outputF"),
)
if returncode != 0:
print(f"** pcre2test failed - check {output}")
failed = True
elif any(compare(expected, output) for expected in expected_outputs):
print(f" OK{with_message}")
else:
print()
print(f"** Locale test did not run successfully{with_message}. The output did not match")
print(" testoutput3, testoutput3A, testoutput3B, testoutput3C, testoutput3D, testoutput3E,")
print(" or testoutput3F.")
print(" This may mean that there is a problem with the locale settings rather")
print(" than a bug in PCRE2.")
failed = True
else:
print("Cannot test locale-specific features - none of the French locales")
print(f"({' '.join(locales)}) could be set.")
print(" ")
# Tests for UTF and Unicode property support
if do[4]:
print(f"{title4A}-{bits}{title4B}")
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput4"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput4",
output,
use_vjs=opts == ["-jit"]), "4", opts, bits)
if do[5]:
print(f"{title5A}-{bits}{title5B}")
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput5"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput5",
output,
use_vjs=opts == ["-jit"]), "5", opts, bits)
# Tests for DFA matching support
if do[6]:
print(title6)
output = Path(f"testoutput{bits}") / "testoutput6"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput6", output), "6", [], bits)
if do[7]:
print(f"{title7A}-{bits}{title7B}")
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
output = Path(f"testoutput{bits}") / "testoutput7"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput7", output), "7", [], bits)
# Test of internal offsets and code sizes. This test is run only when there
# is UTF/UCP support. The actual tests are mostly the same as in some of the
# above, but in this test we inspect some offsets and sizes. This is a
# doublecheck for the maintainer, just in case something changes unexpectedly.
if do[8]:
print(title8)
with open(os.devnull, "wb") as null:
bits_link_size = invoke(pcre2test, f"-{bits}", "-C", "linksize", stdout=null, stderr=null)
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
suffix = f"8-{bits}-{bits_link_size}"
output = Path(f"testoutput{bits}") / f"testoutput{suffix}"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput8", output), suffix, [], bits)
# Tests for 8-bit-specific features
if do[9]:
print(title9)
if bits in ("16", "32"):
print(" Skipped when running 16/32-bit tests")
else:
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput9"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput9",
output,
use_vjs=opts == ["-jit"]), "9", opts, bits)
# Tests for UTF-8 and UCP 8-bit-specific features
if do[10]:
print(title10)
if bits in ("16", "32"):
print(" Skipped when running 16/32-bit tests")
elif not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput10"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput10",
output,
use_vjs=opts == ["-jit"]), "10", opts, bits)
# Tests for 16-bit and 32-bit features. Output is different for the two widths.
if do[11]:
print(title11)
if bits == "8":
print(" Skipped when running 8-bit tests")
else:
for opts in [[], *jitopts]:
suffix = f"11-{bits}"
output = Path(f"testoutput{bits}{''.join(opts)}") / f"testoutput{suffix}"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput11",
output,
use_vjs=opts == ["-jit"]), suffix, opts, bits)
# Tests for 16-bit and 32-bit features with UTF-16/32 and UCP support. Output
# is different for the two widths.
if do[12]:
print(title12)
if bits == "8":
print(" Skipped when running 8-bit tests")
elif not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
suffix = f"12-{bits}"
output = Path(f"testoutput{bits}{''.join(opts)}") / f"testoutput{suffix}"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput12",
output,
use_vjs=opts == ["-jit"]), suffix, opts, bits)
# Tests for 16/32-bit-specific features in DFA non-UTF modes
if do[13]:
print(title13)
if bits == "8":
print(" Skipped when running 8-bit tests")
else:
output = Path(f"testoutput{bits}") / "testoutput13"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput13", output), "13", [], bits)
# Tests for DFA UTF and UCP features. Output is different for the different widths.
if do[14]:
print(title14)
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
suffix = f"14-{bits}"
output = Path(f"testoutput{bits}") / f"testoutput{suffix}"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput14", output), suffix, [], bits)
# Test non-JIT match and recursion limits
if do[15]:
print(title15)
output = Path(f"testoutput{bits}") / "testoutput15"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput15", output), "15", [], bits)
# Test JIT-specific features when JIT is not available
if do[16]:
print(title16)
if supports_jit:
print(" Skipped because JIT is available")
else:
output = Path(f"testoutput{bits}") / "testoutput16"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput16", output), "16", [], bits)
# Test JIT-specific features when JIT is available
if do[17]:
print(title17)
if not supports_jit or nojit:
print(" Skipped because JIT is not available or nojit was specified")
else:
output = Path(f"testoutput{bits}") / "testoutput17"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput17", output, use_vjs=True), "17", [],
bits)
# Tests for the POSIX interface without UTF/UCP (8-bit only)
if do[18]:
print(title18)
if bits in ("16", "32"):
print(" Skipped when running 16/32-bit tests")
else:
output = Path(f"testoutput{bits}") / "testoutput18"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput18", output), "18", [], bits)
# Tests for the POSIX interface with UTF/UCP (8-bit only)
if do[19]:
print(title19)
if bits in ("16", "32"):
print(" Skipped when running 16/32-bit tests")
elif not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
output = Path(f"testoutput{bits}") / "testoutput19"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput19", output), "19", [], bits)
# Serialization tests
if do[20]:
print(title20)
output = Path(f"testoutput{bits}") / "testoutput20"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput20", output), "20", [], bits)
# \C tests without UTF - DFA matching is supported
if do[21]:
print(title21)
if not supports_backslash_c:
print(r" Skipped because \C is disabled")
else:
for opts in [[], *jitopts, ["-dfa"]]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput21"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput21",
output,
use_vjs=opts == ["-jit"]), "21", opts, bits)
# \C tests with UTF - DFA matching is not supported for \C in UTF mode
if do[22]:
print(title22)
if not supports_backslash_c:
print(r" Skipped because \C is disabled")
elif not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
suffix = f"22-{bits}"
output = Path(f"testoutput{bits}{''.join(opts)}") / f"testoutput{suffix}"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput22",
output,
use_vjs=opts == ["-jit"]), suffix, opts, bits)
# Test when \C is disabled
if do[23]:
print(title23)
if supports_backslash_c:
print(r" Skipped because \C is not disabled")
else:
output = Path(f"testoutput{bits}") / "testoutput23"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput23", output), "23", [], bits)
# Non-UTF pattern conversion tests
if do[24]:
print(title24)
output = Path(f"testoutput{bits}") / "testoutput24"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput24", output), "24", [], bits)
# UTF pattern conversion tests
if do[25]:
print(title25)
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
output = Path(f"testoutput{bits}") / "testoutput25"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinput25", output), "25", [], bits)
# Unicode property tests
if do[26]:
print(title26)
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput26"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput26",
output,
use_vjs=opts == ["-jit"]), "26", opts, bits)
# Auto-generated Unicode property tests
if do[27]:
print(title27)
if not supports_utf:
print(f" Skipped because UTF-{bits} support is not available")
else:
for opts in [[], *jitopts]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput27"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput27",
output,
use_vjs=opts == ["-jit"]), "27", opts, bits)
# EBCDIC tests
if do[28]:
print(title28)
if not uses_ebcdic:
print(" Skipped when not targetting EBCDIC")
else:
for opts in [[], *jitopts, ["-dfa"]]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput28"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput28",
output,
use_vjs=opts == ["-jit"]), "28", opts, bits)
# EBCDIC tests (for NL=0x25)
if do[29]:
print(title29)
if not uses_ebcdic:
print(" Skipped when not targetting EBCDIC")
elif not uses_ebcdic_nl25:
print(" Skipped because EBCDIC newline is not 0x25")
else:
for opts in [[], *jitopts, ["-dfa"]]:
output = Path(f"testoutput{bits}{''.join(opts)}") / "testoutput29"
checkresult(
invoke(pcre2test,
*globalopts,
bmode,
*opts,
testdata / "testinput29",
output,
use_vjs=opts == ["-jit"]), "29", opts, bits)
# Manually selected heap tests - output may vary in different environments,
# which is why they are not automatically run.
if doheap:
print(titleheap)
suffix = f"heap-{bits}"
output = Path(f"testoutput{bits}") / f"testoutput{suffix}"
checkresult(invoke(pcre2test, *globalopts, bmode, testdata / "testinputheap", output), suffix, [], bits)
if not failed:
print()
print("All tests passed.")
# RunGrepTest(.py) may run concurrently in this directory. Keep working
# files and cleanup lists disjoint, and update Makefile.am and .gitignore.
for name in ("testbtables", "testSinput", "testSoutput", "test3input", "test3output", "test3outputA",
"test3outputB", "test3outputC", "test3outputD", "test3outputE", "test3outputF", "testsaved1",
"testsaved2", "teststdout"):
try:
Path(name).unlink()
except FileNotFoundError:
pass
for cleanup_bits in ("8", "16", "32"):
shutil.rmtree(f"testoutput{cleanup_bits}", ignore_errors=True)
shutil.rmtree(f"testoutput{cleanup_bits}-jit", ignore_errors=True)
shutil.rmtree(f"testoutput{cleanup_bits}-dfa", ignore_errors=True)
else:
print()
print("** Tests failed. See output above for details.")
sys.exit(1 if failed else 0)