mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-31324: Optimize support._match_test() (#4421)
* Rename support._match_test() to support.match_test(): make it public * Remove support.match_tests global variable. It is replaced with a new support.set_match_tests() function, so match_test() doesn't have to check each time if patterns were modified. * Rewrite match_test(): use different code paths depending on the kind of patterns for best performances. Co-Authored-By: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
431665bf19
commit
803ddd8ce2
4 changed files with 111 additions and 15 deletions
|
@ -257,12 +257,12 @@ class Regrtest:
|
||||||
if isinstance(test, unittest.TestSuite):
|
if isinstance(test, unittest.TestSuite):
|
||||||
self._list_cases(test)
|
self._list_cases(test)
|
||||||
elif isinstance(test, unittest.TestCase):
|
elif isinstance(test, unittest.TestCase):
|
||||||
if support._match_test(test):
|
if support.match_test(test):
|
||||||
print(test.id())
|
print(test.id())
|
||||||
|
|
||||||
def list_cases(self):
|
def list_cases(self):
|
||||||
support.verbose = False
|
support.verbose = False
|
||||||
support.match_tests = self.ns.match_tests
|
support.set_match_tests(self.ns.match_tests)
|
||||||
|
|
||||||
for test in self.selected:
|
for test in self.selected:
|
||||||
abstest = get_abs_module(self.ns, test)
|
abstest = get_abs_module(self.ns, test)
|
||||||
|
|
|
@ -102,7 +102,7 @@ def runtest(ns, test):
|
||||||
if use_timeout:
|
if use_timeout:
|
||||||
faulthandler.dump_traceback_later(ns.timeout, exit=True)
|
faulthandler.dump_traceback_later(ns.timeout, exit=True)
|
||||||
try:
|
try:
|
||||||
support.match_tests = ns.match_tests
|
support.set_match_tests(ns.match_tests)
|
||||||
# reset the environment_altered flag to detect if a test altered
|
# reset the environment_altered flag to detect if a test altered
|
||||||
# the environment
|
# the environment
|
||||||
support.environment_altered = False
|
support.environment_altered = False
|
||||||
|
|
|
@ -278,7 +278,6 @@ max_memuse = 0 # Disable bigmem tests (they will still be run with
|
||||||
# small sizes, to make sure they work.)
|
# small sizes, to make sure they work.)
|
||||||
real_max_memuse = 0
|
real_max_memuse = 0
|
||||||
failfast = False
|
failfast = False
|
||||||
match_tests = None
|
|
||||||
|
|
||||||
# _original_stdout is meant to hold stdout at the time regrtest began.
|
# _original_stdout is meant to hold stdout at the time regrtest began.
|
||||||
# This may be "the real" stdout, or IDLE's emulation of stdout, or whatever.
|
# This may be "the real" stdout, or IDLE's emulation of stdout, or whatever.
|
||||||
|
@ -1900,21 +1899,65 @@ def _run_suite(suite):
|
||||||
raise TestFailed(err)
|
raise TestFailed(err)
|
||||||
|
|
||||||
|
|
||||||
def _match_test(test):
|
# By default, don't filter tests
|
||||||
global match_tests
|
_match_test_func = None
|
||||||
|
_match_test_patterns = None
|
||||||
|
|
||||||
if match_tests is None:
|
|
||||||
|
def match_test(test):
|
||||||
|
# Function used by support.run_unittest() and regrtest --list-cases
|
||||||
|
if _match_test_func is None:
|
||||||
return True
|
return True
|
||||||
test_id = test.id()
|
else:
|
||||||
|
return _match_test_func(test.id())
|
||||||
|
|
||||||
for match_test in match_tests:
|
|
||||||
if fnmatch.fnmatchcase(test_id, match_test):
|
|
||||||
return True
|
|
||||||
|
|
||||||
for name in test_id.split("."):
|
def _is_full_match_test(pattern):
|
||||||
if fnmatch.fnmatchcase(name, match_test):
|
# If a pattern contains at least one dot, it's considered
|
||||||
|
# as a full test identifier.
|
||||||
|
# Example: 'test.test_os.FileTests.test_access'.
|
||||||
|
#
|
||||||
|
# Reject patterns which contain fnmatch patterns: '*', '?', '[...]'
|
||||||
|
# or '[!...]'. For example, reject 'test_access*'.
|
||||||
|
return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern))
|
||||||
|
|
||||||
|
|
||||||
|
def set_match_tests(patterns):
|
||||||
|
global _match_test_func, _match_test_patterns
|
||||||
|
|
||||||
|
if patterns == _match_test_patterns:
|
||||||
|
# No change: no need to recompile patterns.
|
||||||
|
return
|
||||||
|
|
||||||
|
if not patterns:
|
||||||
|
func = None
|
||||||
|
elif all(map(_is_full_match_test, patterns)):
|
||||||
|
# Simple case: all patterns are full test identifier.
|
||||||
|
# The test.bisect utility only uses such full test identifiers.
|
||||||
|
func = set(patterns).__contains__
|
||||||
|
else:
|
||||||
|
regex = '|'.join(map(fnmatch.translate, patterns))
|
||||||
|
# The search *is* case sensitive on purpose:
|
||||||
|
# don't use flags=re.IGNORECASE
|
||||||
|
regex_match = re.compile(regex).match
|
||||||
|
|
||||||
|
def match_test_regex(test_id):
|
||||||
|
if regex_match(test_id):
|
||||||
|
# The regex matchs the whole identifier like
|
||||||
|
# 'test.test_os.FileTests.test_access'
|
||||||
return True
|
return True
|
||||||
return False
|
else:
|
||||||
|
# Try to match parts of the test identifier.
|
||||||
|
# For example, split 'test.test_os.FileTests.test_access'
|
||||||
|
# into: 'test', 'test_os', 'FileTests' and 'test_access'.
|
||||||
|
return any(map(regex_match, test_id.split(".")))
|
||||||
|
|
||||||
|
func = match_test_regex
|
||||||
|
|
||||||
|
# Create a copy since patterns can be mutable and so modified later
|
||||||
|
_match_test_patterns = tuple(patterns)
|
||||||
|
_match_test_func = func
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_unittest(*classes):
|
def run_unittest(*classes):
|
||||||
|
@ -1931,7 +1974,7 @@ def run_unittest(*classes):
|
||||||
suite.addTest(cls)
|
suite.addTest(cls)
|
||||||
else:
|
else:
|
||||||
suite.addTest(unittest.makeSuite(cls))
|
suite.addTest(unittest.makeSuite(cls))
|
||||||
_filter_suite(suite, _match_test)
|
_filter_suite(suite, match_test)
|
||||||
_run_suite(suite)
|
_run_suite(suite)
|
||||||
|
|
||||||
#=======================================================================
|
#=======================================================================
|
||||||
|
|
|
@ -483,6 +483,59 @@ class TestSupport(unittest.TestCase):
|
||||||
with self.subTest(opts=opts):
|
with self.subTest(opts=opts):
|
||||||
self.check_options(opts, 'optim_args_from_interpreter_flags')
|
self.check_options(opts, 'optim_args_from_interpreter_flags')
|
||||||
|
|
||||||
|
def test_match_test(self):
|
||||||
|
class Test:
|
||||||
|
def __init__(self, test_id):
|
||||||
|
self.test_id = test_id
|
||||||
|
|
||||||
|
def id(self):
|
||||||
|
return self.test_id
|
||||||
|
|
||||||
|
test_access = Test('test.test_os.FileTests.test_access')
|
||||||
|
test_chdir = Test('test.test_os.Win32ErrorTests.test_chdir')
|
||||||
|
|
||||||
|
with support.swap_attr(support, '_match_test_func', None):
|
||||||
|
# match all
|
||||||
|
support.set_match_tests([])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertTrue(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
# match the full test identifier
|
||||||
|
support.set_match_tests([test_access.id()])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertFalse(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
# match the module name
|
||||||
|
support.set_match_tests(['test_os'])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertTrue(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
# Test '*' pattern
|
||||||
|
support.set_match_tests(['test_*'])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertTrue(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
# Test case sensitivity
|
||||||
|
support.set_match_tests(['filetests'])
|
||||||
|
self.assertFalse(support.match_test(test_access))
|
||||||
|
support.set_match_tests(['FileTests'])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
|
||||||
|
# Test pattern containing '.' and a '*' metacharacter
|
||||||
|
support.set_match_tests(['*test_os.*.test_*'])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertTrue(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
# Multiple patterns
|
||||||
|
support.set_match_tests([test_access.id(), test_chdir.id()])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertTrue(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
support.set_match_tests(['test_access', 'DONTMATCH'])
|
||||||
|
self.assertTrue(support.match_test(test_access))
|
||||||
|
self.assertFalse(support.match_test(test_chdir))
|
||||||
|
|
||||||
|
|
||||||
# XXX -follows a list of untested API
|
# XXX -follows a list of untested API
|
||||||
# make_legacy_pyc
|
# make_legacy_pyc
|
||||||
# is_resource_enabled
|
# is_resource_enabled
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue