[3.12] gh-110918: regrtest: allow to intermix --match and --ignore options (GH-110919) (GH-111167)

Test case matching patterns specified by options --match, --ignore,
--matchfile and --ignorefile are now tested in the order of
specification, and the last match determines whether the test case be run
or ignored.
(cherry picked from commit 9a1fe09622)
This commit is contained in:
Serhiy Storchaka 2023-10-21 20:33:26 +03:00 committed by GitHub
parent 6a5ff93654
commit 1ea93024d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 128 additions and 143 deletions

View file

@ -161,8 +161,7 @@ class Namespace(argparse.Namespace):
self.forever = False self.forever = False
self.header = False self.header = False
self.failfast = False self.failfast = False
self.match_tests = None self.match_tests = []
self.ignore_tests = None
self.pgo = False self.pgo = False
self.pgo_extended = False self.pgo_extended = False
self.worker_json = None self.worker_json = None
@ -183,6 +182,20 @@ class _ArgParser(argparse.ArgumentParser):
super().error(message + "\nPass -h or --help for complete help.") super().error(message + "\nPass -h or --help for complete help.")
class FilterAction(argparse.Action):
def __call__(self, parser, namespace, value, option_string=None):
items = getattr(namespace, self.dest)
items.append((value, self.const))
class FromFileFilterAction(argparse.Action):
def __call__(self, parser, namespace, value, option_string=None):
items = getattr(namespace, self.dest)
with open(value, encoding='utf-8') as fp:
for line in fp:
items.append((line.strip(), self.const))
def _create_parser(): def _create_parser():
# Set prog to prevent the uninformative "__main__.py" from displaying in # Set prog to prevent the uninformative "__main__.py" from displaying in
# error messages when using "python -m test ...". # error messages when using "python -m test ...".
@ -192,6 +205,7 @@ def _create_parser():
epilog=EPILOG, epilog=EPILOG,
add_help=False, add_help=False,
formatter_class=argparse.RawDescriptionHelpFormatter) formatter_class=argparse.RawDescriptionHelpFormatter)
parser.set_defaults(match_tests=[])
# Arguments with this clause added to its help are described further in # Arguments with this clause added to its help are described further in
# the epilog's "Additional option details" section. # the epilog's "Additional option details" section.
@ -251,17 +265,19 @@ def _create_parser():
help='single step through a set of tests.' + help='single step through a set of tests.' +
more_details) more_details)
group.add_argument('-m', '--match', metavar='PAT', group.add_argument('-m', '--match', metavar='PAT',
dest='match_tests', action='append', dest='match_tests', action=FilterAction, const=True,
help='match test cases and methods with glob pattern PAT') help='match test cases and methods with glob pattern PAT')
group.add_argument('-i', '--ignore', metavar='PAT', group.add_argument('-i', '--ignore', metavar='PAT',
dest='ignore_tests', action='append', dest='match_tests', action=FilterAction, const=False,
help='ignore test cases and methods with glob pattern PAT') help='ignore test cases and methods with glob pattern PAT')
group.add_argument('--matchfile', metavar='FILENAME', group.add_argument('--matchfile', metavar='FILENAME',
dest='match_filename', dest='match_tests',
action=FromFileFilterAction, const=True,
help='similar to --match but get patterns from a ' help='similar to --match but get patterns from a '
'text file, one pattern per line') 'text file, one pattern per line')
group.add_argument('--ignorefile', metavar='FILENAME', group.add_argument('--ignorefile', metavar='FILENAME',
dest='ignore_filename', dest='match_tests',
action=FromFileFilterAction, const=False,
help='similar to --matchfile but it receives patterns ' help='similar to --matchfile but it receives patterns '
'from text file to ignore') 'from text file to ignore')
group.add_argument('-G', '--failfast', action='store_true', group.add_argument('-G', '--failfast', action='store_true',
@ -483,18 +499,6 @@ def _parse_args(args, **kwargs):
print("WARNING: Disable --verbose3 because it's incompatible with " print("WARNING: Disable --verbose3 because it's incompatible with "
"--huntrleaks: see http://bugs.python.org/issue27103", "--huntrleaks: see http://bugs.python.org/issue27103",
file=sys.stderr) file=sys.stderr)
if ns.match_filename:
if ns.match_tests is None:
ns.match_tests = []
with open(ns.match_filename) as fp:
for line in fp:
ns.match_tests.append(line.strip())
if ns.ignore_filename:
if ns.ignore_tests is None:
ns.ignore_tests = []
with open(ns.ignore_filename) as fp:
for line in fp:
ns.ignore_tests.append(line.strip())
if ns.forever: if ns.forever:
# --forever implies --failfast # --forever implies --failfast
ns.failfast = True ns.failfast = True

View file

@ -5,7 +5,7 @@ import unittest
from test import support from test import support
from .utils import ( from .utils import (
StrPath, TestName, TestTuple, TestList, FilterTuple, StrPath, TestName, TestTuple, TestList, TestFilter,
abs_module_name, count, printlist) abs_module_name, count, printlist)
@ -83,11 +83,10 @@ def _list_cases(suite):
print(test.id()) print(test.id())
def list_cases(tests: TestTuple, *, def list_cases(tests: TestTuple, *,
match_tests: FilterTuple | None = None, match_tests: TestFilter | None = None,
ignore_tests: FilterTuple | None = None,
test_dir: StrPath | None = None): test_dir: StrPath | None = None):
support.verbose = False support.verbose = False
support.set_match_tests(match_tests, ignore_tests) support.set_match_tests(match_tests)
skipped = [] skipped = []
for test_name in tests: for test_name in tests:

View file

@ -19,7 +19,7 @@ from .runtests import RunTests, HuntRefleak
from .setup import setup_process, setup_test_dir from .setup import setup_process, setup_test_dir
from .single import run_single_test, PROGRESS_MIN_TIME from .single import run_single_test, PROGRESS_MIN_TIME
from .utils import ( from .utils import (
StrPath, StrJSON, TestName, TestList, TestTuple, FilterTuple, StrPath, StrJSON, TestName, TestList, TestTuple, TestFilter,
strip_py_suffix, count, format_duration, strip_py_suffix, count, format_duration,
printlist, get_temp_dir, get_work_dir, exit_timeout, printlist, get_temp_dir, get_work_dir, exit_timeout,
display_header, cleanup_temp_dir, print_warning, display_header, cleanup_temp_dir, print_warning,
@ -78,14 +78,7 @@ class Regrtest:
and ns._add_python_opts) and ns._add_python_opts)
# Select tests # Select tests
if ns.match_tests: self.match_tests: TestFilter = ns.match_tests
self.match_tests: FilterTuple | None = tuple(ns.match_tests)
else:
self.match_tests = None
if ns.ignore_tests:
self.ignore_tests: FilterTuple | None = tuple(ns.ignore_tests)
else:
self.ignore_tests = None
self.exclude: bool = ns.exclude self.exclude: bool = ns.exclude
self.fromfile: StrPath | None = ns.fromfile self.fromfile: StrPath | None = ns.fromfile
self.starting_test: TestName | None = ns.start self.starting_test: TestName | None = ns.start
@ -389,7 +382,7 @@ class Regrtest:
def display_summary(self): def display_summary(self):
duration = time.perf_counter() - self.logger.start_time duration = time.perf_counter() - self.logger.start_time
filtered = bool(self.match_tests) or bool(self.ignore_tests) filtered = bool(self.match_tests)
# Total duration # Total duration
print() print()
@ -407,7 +400,6 @@ class Regrtest:
fail_fast=self.fail_fast, fail_fast=self.fail_fast,
fail_env_changed=self.fail_env_changed, fail_env_changed=self.fail_env_changed,
match_tests=self.match_tests, match_tests=self.match_tests,
ignore_tests=self.ignore_tests,
match_tests_dict=None, match_tests_dict=None,
rerun=False, rerun=False,
forever=self.forever, forever=self.forever,
@ -660,7 +652,6 @@ class Regrtest:
elif self.want_list_cases: elif self.want_list_cases:
list_cases(selected, list_cases(selected,
match_tests=self.match_tests, match_tests=self.match_tests,
ignore_tests=self.ignore_tests,
test_dir=self.test_dir) test_dir=self.test_dir)
else: else:
exitcode = self.run_tests(selected, tests) exitcode = self.run_tests(selected, tests)

View file

@ -261,7 +261,7 @@ class WorkerThread(threading.Thread):
kwargs = {} kwargs = {}
if match_tests: if match_tests:
kwargs['match_tests'] = match_tests kwargs['match_tests'] = [(test, True) for test in match_tests]
if self.runtests.output_on_failure: if self.runtests.output_on_failure:
kwargs['verbose'] = True kwargs['verbose'] = True
kwargs['output_on_failure'] = False kwargs['output_on_failure'] = False

View file

@ -8,7 +8,7 @@ from typing import Any
from test import support from test import support
from .utils import ( from .utils import (
StrPath, StrJSON, TestTuple, FilterTuple, FilterDict) StrPath, StrJSON, TestTuple, TestFilter, FilterTuple, FilterDict)
class JsonFileType: class JsonFileType:
@ -72,8 +72,7 @@ class RunTests:
tests: TestTuple tests: TestTuple
fail_fast: bool fail_fast: bool
fail_env_changed: bool fail_env_changed: bool
match_tests: FilterTuple | None match_tests: TestFilter
ignore_tests: FilterTuple | None
match_tests_dict: FilterDict | None match_tests_dict: FilterDict | None
rerun: bool rerun: bool
forever: bool forever: bool

View file

@ -92,7 +92,7 @@ def setup_tests(runtests: RunTests):
support.PGO = runtests.pgo support.PGO = runtests.pgo
support.PGO_EXTENDED = runtests.pgo_extended support.PGO_EXTENDED = runtests.pgo_extended
support.set_match_tests(runtests.match_tests, runtests.ignore_tests) support.set_match_tests(runtests.match_tests)
if runtests.use_junit: if runtests.use_junit:
support.junit_xml_list = [] support.junit_xml_list = []

View file

@ -52,6 +52,7 @@ TestTuple = tuple[TestName, ...]
TestList = list[TestName] TestList = list[TestName]
# --match and --ignore options: list of patterns # --match and --ignore options: list of patterns
# ('*' joker character can be used) # ('*' joker character can be used)
TestFilter = list[tuple[TestName, bool]]
FilterTuple = tuple[TestName, ...] FilterTuple = tuple[TestName, ...]
FilterDict = dict[TestName, FilterTuple] FilterDict = dict[TestName, FilterTuple]

View file

@ -10,7 +10,7 @@ from .setup import setup_process, setup_test_dir
from .runtests import RunTests, JsonFile, JsonFileType from .runtests import RunTests, JsonFile, JsonFileType
from .single import run_single_test from .single import run_single_test
from .utils import ( from .utils import (
StrPath, StrJSON, FilterTuple, StrPath, StrJSON, TestFilter,
get_temp_dir, get_work_dir, exit_timeout) get_temp_dir, get_work_dir, exit_timeout)
@ -73,7 +73,7 @@ def create_worker_process(runtests: RunTests, output_fd: int,
def worker_process(worker_json: StrJSON) -> NoReturn: def worker_process(worker_json: StrJSON) -> NoReturn:
runtests = RunTests.from_json(worker_json) runtests = RunTests.from_json(worker_json)
test_name = runtests.tests[0] test_name = runtests.tests[0]
match_tests: FilterTuple | None = runtests.match_tests match_tests: TestFilter = runtests.match_tests
json_file: JsonFile = runtests.json_file json_file: JsonFile = runtests.json_file
setup_test_dir(runtests.test_dir) setup_test_dir(runtests.test_dir)
@ -81,7 +81,7 @@ def worker_process(worker_json: StrJSON) -> NoReturn:
if runtests.rerun: if runtests.rerun:
if match_tests: if match_tests:
matching = "matching: " + ", ".join(match_tests) matching = "matching: " + ", ".join(pattern for pattern, result in match_tests if result)
print(f"Re-running {test_name} in verbose mode ({matching})", flush=True) print(f"Re-running {test_name} in verbose mode ({matching})", flush=True)
else: else:
print(f"Re-running {test_name} in verbose mode", flush=True) print(f"Re-running {test_name} in verbose mode", flush=True)

View file

@ -6,8 +6,10 @@ if __name__ != 'test.support':
import contextlib import contextlib
import dataclasses import dataclasses
import functools import functools
import itertools
import getpass import getpass
import opcode import opcode
import operator
import os import os
import re import re
import stat import stat
@ -1194,18 +1196,17 @@ def _run_suite(suite):
# By default, don't filter tests # By default, don't filter tests
_match_test_func = None _test_matchers = ()
_test_patterns = ()
_accept_test_patterns = None
_ignore_test_patterns = None
def match_test(test): def match_test(test):
# Function used by support.run_unittest() and regrtest --list-cases # Function used by support.run_unittest() and regrtest --list-cases
if _match_test_func is None: result = False
return True for matcher, result in reversed(_test_matchers):
else: if matcher(test.id()):
return _match_test_func(test.id()) return result
return not result
def _is_full_match_test(pattern): def _is_full_match_test(pattern):
@ -1218,47 +1219,30 @@ def _is_full_match_test(pattern):
return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern)) return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern))
def set_match_tests(accept_patterns=None, ignore_patterns=None): def set_match_tests(patterns):
global _match_test_func, _accept_test_patterns, _ignore_test_patterns global _test_matchers, _test_patterns
if accept_patterns is None: if not patterns:
accept_patterns = () _test_matchers = ()
if ignore_patterns is None: _test_patterns = ()
ignore_patterns = () else:
itemgetter = operator.itemgetter
accept_func = ignore_func = None patterns = tuple(patterns)
if patterns != _test_patterns:
if accept_patterns != _accept_test_patterns: _test_matchers = [
accept_patterns, accept_func = _compile_match_function(accept_patterns) (_compile_match_function(map(itemgetter(0), it)), result)
if ignore_patterns != _ignore_test_patterns: for result, it in itertools.groupby(patterns, itemgetter(1))
ignore_patterns, ignore_func = _compile_match_function(ignore_patterns) ]
_test_patterns = patterns
# Create a copy since patterns can be mutable and so modified later
_accept_test_patterns = tuple(accept_patterns)
_ignore_test_patterns = tuple(ignore_patterns)
if accept_func is not None or ignore_func is not None:
def match_function(test_id):
accept = True
ignore = False
if accept_func:
accept = accept_func(test_id)
if ignore_func:
ignore = ignore_func(test_id)
return accept and not ignore
_match_test_func = match_function
def _compile_match_function(patterns): def _compile_match_function(patterns):
if not patterns: patterns = list(patterns)
func = None
# set_match_tests(None) behaves as set_match_tests(()) if all(map(_is_full_match_test, patterns)):
patterns = ()
elif all(map(_is_full_match_test, patterns)):
# Simple case: all patterns are full test identifier. # Simple case: all patterns are full test identifier.
# The test.bisect_cmd utility only uses such full test identifiers. # The test.bisect_cmd utility only uses such full test identifiers.
func = set(patterns).__contains__ return set(patterns).__contains__
else: else:
import fnmatch import fnmatch
regex = '|'.join(map(fnmatch.translate, patterns)) regex = '|'.join(map(fnmatch.translate, patterns))
@ -1266,7 +1250,7 @@ def _compile_match_function(patterns):
# don't use flags=re.IGNORECASE # don't use flags=re.IGNORECASE
regex_match = re.compile(regex).match regex_match = re.compile(regex).match
def match_test_regex(test_id): def match_test_regex(test_id, regex_match=regex_match):
if regex_match(test_id): if regex_match(test_id):
# The regex matches the whole identifier, for example # The regex matches the whole identifier, for example
# 'test.test_os.FileTests.test_access'. # 'test.test_os.FileTests.test_access'.
@ -1277,9 +1261,7 @@ def _compile_match_function(patterns):
# into: 'test', 'test_os', 'FileTests' and 'test_access'. # into: 'test', 'test_os', 'FileTests' and 'test_access'.
return any(map(regex_match, test_id.split("."))) return any(map(regex_match, test_id.split(".")))
func = match_test_regex return match_test_regex
return patterns, func
def run_unittest(*classes): def run_unittest(*classes):

View file

@ -192,34 +192,27 @@ class ParseArgsTestCase(unittest.TestCase):
self.assertTrue(ns.single) self.assertTrue(ns.single)
self.checkError([opt, '-f', 'foo'], "don't go together") self.checkError([opt, '-f', 'foo'], "don't go together")
def test_ignore(self):
for opt in '-i', '--ignore':
with self.subTest(opt=opt):
ns = self.parse_args([opt, 'pattern'])
self.assertEqual(ns.ignore_tests, ['pattern'])
self.checkError([opt], 'expected one argument')
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
with open(os_helper.TESTFN, "w") as fp:
print('matchfile1', file=fp)
print('matchfile2', file=fp)
filename = os.path.abspath(os_helper.TESTFN)
ns = self.parse_args(['-m', 'match',
'--ignorefile', filename])
self.assertEqual(ns.ignore_tests,
['matchfile1', 'matchfile2'])
def test_match(self): def test_match(self):
for opt in '-m', '--match': for opt in '-m', '--match':
with self.subTest(opt=opt): with self.subTest(opt=opt):
ns = self.parse_args([opt, 'pattern']) ns = self.parse_args([opt, 'pattern'])
self.assertEqual(ns.match_tests, ['pattern']) self.assertEqual(ns.match_tests, [('pattern', True)])
self.checkError([opt], 'expected one argument') self.checkError([opt], 'expected one argument')
ns = self.parse_args(['-m', 'pattern1', for opt in '-i', '--ignore':
'-m', 'pattern2']) with self.subTest(opt=opt):
self.assertEqual(ns.match_tests, ['pattern1', 'pattern2']) ns = self.parse_args([opt, 'pattern'])
self.assertEqual(ns.match_tests, [('pattern', False)])
self.checkError([opt], 'expected one argument')
ns = self.parse_args(['-m', 'pattern1', '-m', 'pattern2'])
self.assertEqual(ns.match_tests, [('pattern1', True), ('pattern2', True)])
ns = self.parse_args(['-m', 'pattern1', '-i', 'pattern2'])
self.assertEqual(ns.match_tests, [('pattern1', True), ('pattern2', False)])
ns = self.parse_args(['-i', 'pattern1', '-m', 'pattern2'])
self.assertEqual(ns.match_tests, [('pattern1', False), ('pattern2', True)])
self.addCleanup(os_helper.unlink, os_helper.TESTFN) self.addCleanup(os_helper.unlink, os_helper.TESTFN)
with open(os_helper.TESTFN, "w") as fp: with open(os_helper.TESTFN, "w") as fp:
@ -227,10 +220,13 @@ class ParseArgsTestCase(unittest.TestCase):
print('matchfile2', file=fp) print('matchfile2', file=fp)
filename = os.path.abspath(os_helper.TESTFN) filename = os.path.abspath(os_helper.TESTFN)
ns = self.parse_args(['-m', 'match', ns = self.parse_args(['-m', 'match', '--matchfile', filename])
'--matchfile', filename])
self.assertEqual(ns.match_tests, self.assertEqual(ns.match_tests,
['match', 'matchfile1', 'matchfile2']) [('match', True), ('matchfile1', True), ('matchfile2', True)])
ns = self.parse_args(['-i', 'match', '--ignorefile', filename])
self.assertEqual(ns.match_tests,
[('match', False), ('matchfile1', False), ('matchfile2', False)])
def test_failfast(self): def test_failfast(self):
for opt in '-G', '--failfast': for opt in '-G', '--failfast':

View file

@ -560,101 +560,110 @@ class TestSupport(unittest.TestCase):
test_access = Test('test.test_os.FileTests.test_access') test_access = Test('test.test_os.FileTests.test_access')
test_chdir = Test('test.test_os.Win32ErrorTests.test_chdir') test_chdir = Test('test.test_os.Win32ErrorTests.test_chdir')
test_copy = Test('test.test_shutil.TestCopy.test_copy')
# Test acceptance # Test acceptance
with support.swap_attr(support, '_match_test_func', None): with support.swap_attr(support, '_test_matchers', ()):
# match all # match all
support.set_match_tests([]) support.set_match_tests([])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
# match all using None # match all using None
support.set_match_tests(None, None) support.set_match_tests(None)
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
# match the full test identifier # match the full test identifier
support.set_match_tests([test_access.id()], None) support.set_match_tests([(test_access.id(), True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir)) self.assertFalse(support.match_test(test_chdir))
# match the module name # match the module name
support.set_match_tests(['test_os'], None) support.set_match_tests([('test_os', True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
self.assertFalse(support.match_test(test_copy))
# Test '*' pattern # Test '*' pattern
support.set_match_tests(['test_*'], None) support.set_match_tests([('test_*', True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
# Test case sensitivity # Test case sensitivity
support.set_match_tests(['filetests'], None) support.set_match_tests([('filetests', True)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
support.set_match_tests(['FileTests'], None) support.set_match_tests([('FileTests', True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
# Test pattern containing '.' and a '*' metacharacter # Test pattern containing '.' and a '*' metacharacter
support.set_match_tests(['*test_os.*.test_*'], None) support.set_match_tests([('*test_os.*.test_*', True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
self.assertFalse(support.match_test(test_copy))
# Multiple patterns # Multiple patterns
support.set_match_tests([test_access.id(), test_chdir.id()], None) support.set_match_tests([(test_access.id(), True), (test_chdir.id(), True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
self.assertFalse(support.match_test(test_copy))
support.set_match_tests(['test_access', 'DONTMATCH'], None) support.set_match_tests([('test_access', True), ('DONTMATCH', True)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir)) self.assertFalse(support.match_test(test_chdir))
# Test rejection # Test rejection
with support.swap_attr(support, '_match_test_func', None): with support.swap_attr(support, '_test_matchers', ()):
# match all
support.set_match_tests(ignore_patterns=[])
self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir))
# match all using None
support.set_match_tests(None, None)
self.assertTrue(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir))
# match the full test identifier # match the full test identifier
support.set_match_tests(None, [test_access.id()]) support.set_match_tests([(test_access.id(), False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
# match the module name # match the module name
support.set_match_tests(None, ['test_os']) support.set_match_tests([('test_os', False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir)) self.assertFalse(support.match_test(test_chdir))
self.assertTrue(support.match_test(test_copy))
# Test '*' pattern # Test '*' pattern
support.set_match_tests(None, ['test_*']) support.set_match_tests([('test_*', False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir)) self.assertFalse(support.match_test(test_chdir))
# Test case sensitivity # Test case sensitivity
support.set_match_tests(None, ['filetests']) support.set_match_tests([('filetests', False)])
self.assertTrue(support.match_test(test_access)) self.assertTrue(support.match_test(test_access))
support.set_match_tests(None, ['FileTests']) support.set_match_tests([('FileTests', False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
# Test pattern containing '.' and a '*' metacharacter # Test pattern containing '.' and a '*' metacharacter
support.set_match_tests(None, ['*test_os.*.test_*']) support.set_match_tests([('*test_os.*.test_*', False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir)) self.assertFalse(support.match_test(test_chdir))
self.assertTrue(support.match_test(test_copy))
# Multiple patterns # Multiple patterns
support.set_match_tests(None, [test_access.id(), test_chdir.id()]) support.set_match_tests([(test_access.id(), False), (test_chdir.id(), False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir)) self.assertFalse(support.match_test(test_chdir))
self.assertTrue(support.match_test(test_copy))
support.set_match_tests(None, ['test_access', 'DONTMATCH']) support.set_match_tests([('test_access', False), ('DONTMATCH', False)])
self.assertFalse(support.match_test(test_access)) self.assertFalse(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir)) self.assertTrue(support.match_test(test_chdir))
# Test mixed filters
with support.swap_attr(support, '_test_matchers', ()):
support.set_match_tests([('*test_os', False), ('test_access', True)])
self.assertTrue(support.match_test(test_access))
self.assertFalse(support.match_test(test_chdir))
self.assertTrue(support.match_test(test_copy))
support.set_match_tests([('*test_os', True), ('test_access', False)])
self.assertFalse(support.match_test(test_access))
self.assertTrue(support.match_test(test_chdir))
self.assertFalse(support.match_test(test_copy))
@unittest.skipIf(support.is_emscripten, "Unstable in Emscripten") @unittest.skipIf(support.is_emscripten, "Unstable in Emscripten")
@unittest.skipIf(support.is_wasi, "Unavailable on WASI") @unittest.skipIf(support.is_wasi, "Unavailable on WASI")
def test_fd_count(self): def test_fd_count(self):

View file

@ -0,0 +1,4 @@
Test case matching patterns specified by options ``--match``, ``--ignore``,
``--matchfile`` and ``--ignorefile`` are now tested in the order of
specification, and the last match determines whether the test case be run or
ignored.