Skip to content

Commit 46e299c

Browse files
vstinnermlouielu
andauthored
[3.5] bpo-30540, bpo-30523: Add --matchfile and --list-cases options to regrtest (#2250)
* bpo-30540: regrtest: add --matchfile option * Add a new option taking a filename to get a list of test names to filter tests. * support.match_tests becomes a list. * Modify run_unittest() to accept to match the whole test identifier, not just a part of a test identifier. For example, the following command only runs test_access() of the FileTests class of test_os: $ ./python -m test -v -m test.test_os.FileTests.test_access test_os * bpo-30523: regrtest: Add --list-cases option * Add --list-cases option to regrtest * Add get_abs_module() function, use it in list_cases() * Add ns mandatory positional argument to runtest() and runtest_inner() * Add file optional parameter to printlist() Co-Authored-By: Louie Lu <[email protected]>
1 parent f50a3b1 commit 46e299c

File tree

3 files changed

+113
-34
lines changed

3 files changed

+113
-34
lines changed

Lib/test/regrtest.py

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@
117117
To enable all resources except one, use '-uall,-<resource>'. For
118118
example, to run all the tests except for the gui tests, give the
119119
option '-uall,-gui'.
120+
121+
--matchfile filters tests using a text file, one pattern per line.
122+
Pattern examples:
123+
124+
- test method: test_stat_attributes
125+
- test class: FileTests
126+
- test identifier: test_os.FileTests.test_stat_attributes
120127
"""
121128

122129
# We import importlib *ASAP* in order to test #15386
@@ -276,8 +283,12 @@ def _create_parser():
276283
help='single step through a set of tests.' +
277284
more_details)
278285
group.add_argument('-m', '--match', metavar='PAT',
279-
dest='match_tests',
286+
dest='match_tests', action='append',
280287
help='match test cases and methods with glob pattern PAT')
288+
group.add_argument('--matchfile', metavar='FILENAME',
289+
dest='match_filename',
290+
help='similar to --match but get patterns from a '
291+
'text file, one pattern per line')
281292
group.add_argument('-G', '--failfast', action='store_true',
282293
help='fail as soon as a test fails (only with -v or -W)')
283294
group.add_argument('-u', '--use', metavar='RES1,RES2,...',
@@ -323,6 +334,9 @@ def _create_parser():
323334
group.add_argument('-F', '--forever', action='store_true',
324335
help='run the specified tests in a loop, until an '
325336
'error happens')
337+
group.add_argument('--list-cases', action='store_true',
338+
help='only write the name of test cases that will be run'
339+
' , don\'t execute them')
326340
group.add_argument('-P', '--pgo', dest='pgo', action='store_true',
327341
help='enable Profile Guided Optimization training')
328342

@@ -361,7 +375,8 @@ def _parse_args(args, **kwargs):
361375
findleaks=False, use_resources=None, trace=False, coverdir='coverage',
362376
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
363377
random_seed=None, use_mp=None, verbose3=False, forever=False,
364-
header=False, failfast=False, match_tests=None, pgo=False)
378+
header=False, failfast=False, match_tests=None, match_filename=None,
379+
pgo=False)
365380
for k, v in kwargs.items():
366381
if not hasattr(ns, k):
367382
raise TypeError('%r is an invalid keyword argument '
@@ -431,6 +446,13 @@ def _parse_args(args, **kwargs):
431446
print("WARNING: Disable --verbose3 because it's incompatible with "
432447
"--huntrleaks: see http://bugs.python.org/issue27103",
433448
file=sys.stderr)
449+
if ns.match_filename:
450+
if ns.match_tests is None:
451+
ns.match_tests = []
452+
filename = os.path.join(support.SAVEDCWD, ns.match_filename)
453+
with open(filename) as fp:
454+
for line in fp:
455+
ns.match_tests.append(line.strip())
434456

435457
return ns
436458

@@ -555,7 +577,7 @@ def main(tests=None, **kwargs):
555577
unittest.BaseTestSuite._cleanup = False
556578

557579
try:
558-
result = runtest(testname, ns.verbose, ns.quiet,
580+
result = runtest(ns, testname, ns.verbose, ns.quiet,
559581
ns.huntrleaks,
560582
output_on_failure=ns.verbose3,
561583
timeout=ns.timeout, failfast=ns.failfast,
@@ -632,18 +654,6 @@ def main(tests=None, **kwargs):
632654
nottests.add(arg)
633655
ns.args = []
634656

635-
# For a partial run, we do not need to clutter the output.
636-
if (ns.verbose or ns.header or
637-
not (ns.pgo or ns.quiet or ns.single or tests or ns.args)):
638-
# Print basic platform information
639-
print("==", platform.python_implementation(), *sys.version.split())
640-
print("== ", platform.platform(aliased=True),
641-
"%s-endian" % sys.byteorder)
642-
print("== ", "hash algorithm:", sys.hash_info.algorithm,
643-
"64bit" if sys.maxsize > 2**32 else "32bit")
644-
print("== ", os.getcwd())
645-
print("Testing with flags:", sys.flags)
646-
647657
# if testdir is set, then we are not running the python tests suite, so
648658
# don't add default tests to be executed or skipped (pass empty values)
649659
if ns.testdir:
@@ -697,6 +707,10 @@ def accumulate_result(test, result):
697707
skipped.append(test)
698708
resource_denieds.append(test)
699709

710+
if ns.list_cases:
711+
list_cases(ns, selected)
712+
sys.exit(0)
713+
700714
if ns.forever:
701715
def test_forever(tests=list(selected)):
702716
while True:
@@ -712,6 +726,18 @@ def test_forever(tests=list(selected)):
712726
test_count = '/{}'.format(len(selected))
713727
test_count_width = len(test_count) - 1
714728

729+
# For a partial run, we do not need to clutter the output.
730+
if (ns.verbose or ns.header or
731+
not (ns.pgo or ns.quiet or ns.single or tests or ns.args)):
732+
# Print basic platform information
733+
print("==", platform.python_implementation(), *sys.version.split())
734+
print("== ", platform.platform(aliased=True),
735+
"%s-endian" % sys.byteorder)
736+
print("== ", "hash algorithm:", sys.hash_info.algorithm,
737+
"64bit" if sys.maxsize > 2**32 else "32bit")
738+
print("== ", os.getcwd())
739+
print("Testing with flags:", sys.flags)
740+
715741
if ns.use_mp:
716742
try:
717743
from threading import Thread
@@ -797,11 +823,11 @@ def work():
797823
if ns.trace:
798824
# If we're tracing code coverage, then we don't exit with status
799825
# if on a false return value from main.
800-
tracer.runctx('runtest(test, ns.verbose, ns.quiet, timeout=ns.timeout)',
826+
tracer.runctx('runtest(ns, test, ns.verbose, ns.quiet, timeout=ns.timeout)',
801827
globals=globals(), locals=vars())
802828
else:
803829
try:
804-
result = runtest(test, ns.verbose, ns.quiet,
830+
result = runtest(ns, test, ns.verbose, ns.quiet,
805831
ns.huntrleaks,
806832
output_on_failure=ns.verbose3,
807833
timeout=ns.timeout, failfast=ns.failfast,
@@ -859,7 +885,7 @@ def work():
859885
sys.stdout.flush()
860886
try:
861887
ns.verbose = True
862-
ok = runtest(test, True, ns.quiet, ns.huntrleaks,
888+
ok = runtest(ns, test, True, ns.quiet, ns.huntrleaks,
863889
timeout=ns.timeout, pgo=ns.pgo)
864890
except KeyboardInterrupt:
865891
# print a newline separate from the ^C
@@ -956,7 +982,7 @@ def restore_stdout():
956982
sys.stdout = stdout
957983
atexit.register(restore_stdout)
958984

959-
def runtest(test, verbose, quiet,
985+
def runtest(ns, test, verbose, quiet,
960986
huntrleaks=False, use_resources=None,
961987
output_on_failure=False, failfast=False, match_tests=None,
962988
timeout=None, *, pgo=False):
@@ -1011,8 +1037,9 @@ def runtest(test, verbose, quiet,
10111037
try:
10121038
sys.stdout = stream
10131039
sys.stderr = stream
1014-
result = runtest_inner(test, verbose, quiet, huntrleaks,
1015-
display_failure=False, pgo=pgo)
1040+
result = runtest_inner(ns, test, verbose, quiet, huntrleaks,
1041+
display_failure=False,
1042+
pgo=pgo)
10161043
if result[0] != PASSED and not pgo:
10171044
output = stream.getvalue()
10181045
orig_stderr.write(output)
@@ -1022,8 +1049,9 @@ def runtest(test, verbose, quiet,
10221049
sys.stderr = orig_stderr
10231050
else:
10241051
support.verbose = verbose # Tell tests to be moderately quiet
1025-
result = runtest_inner(test, verbose, quiet, huntrleaks,
1026-
display_failure=not verbose, pgo=pgo)
1052+
result = runtest_inner(ns, test, verbose, quiet, huntrleaks,
1053+
display_failure=not verbose,
1054+
pgo=pgo)
10271055
return result
10281056
finally:
10291057
if use_timeout:
@@ -1294,18 +1322,14 @@ def __exit__(self, exc_type, exc_val, exc_tb):
12941322
return False
12951323

12961324

1297-
def runtest_inner(test, verbose, quiet,
1325+
def runtest_inner(ns, test, verbose, quiet,
12981326
huntrleaks=False, display_failure=True, pgo=False):
12991327
support.unload(test)
13001328

13011329
test_time = 0.0
13021330
refleak = False # True if the test leaked references.
13031331
try:
1304-
if test.startswith('test.'):
1305-
abstest = test
1306-
else:
1307-
# Always import it from the test package
1308-
abstest = 'test.' + test
1332+
abstest = get_abs_module(ns, test)
13091333
clear_caches()
13101334
with saved_test_environment(test, verbose, quiet, pgo=pgo) as environment:
13111335
start_time = time.time()
@@ -1646,7 +1670,7 @@ def count(n, word):
16461670
else:
16471671
return "%d %ss" % (n, word)
16481672

1649-
def printlist(x, width=70, indent=4):
1673+
def printlist(x, width=70, indent=4, file=None):
16501674
"""Print the elements of iterable x to stdout.
16511675
16521676
Optional arg width (default 70) is the maximum line length.
@@ -1658,7 +1682,41 @@ def printlist(x, width=70, indent=4):
16581682
blanks = ' ' * indent
16591683
# Print the sorted list: 'x' may be a '--random' list or a set()
16601684
print(fill(' '.join(str(elt) for elt in sorted(x)), width,
1661-
initial_indent=blanks, subsequent_indent=blanks))
1685+
initial_indent=blanks, subsequent_indent=blanks), file=file)
1686+
1687+
1688+
def get_abs_module(ns, test):
1689+
if test.startswith('test.') or ns.testdir:
1690+
return test
1691+
else:
1692+
# Always import it from the test package
1693+
return 'test.' + test
1694+
1695+
1696+
def _list_cases(suite):
1697+
for test in suite:
1698+
if isinstance(test, unittest.loader._FailedTest):
1699+
continue
1700+
if isinstance(test, unittest.TestSuite):
1701+
_list_cases(test)
1702+
elif isinstance(test, unittest.TestCase):
1703+
print(test.id())
1704+
1705+
1706+
def list_cases(ns, selected):
1707+
skipped = []
1708+
for test in selected:
1709+
abstest = get_abs_module(ns, test)
1710+
try:
1711+
suite = unittest.defaultTestLoader.loadTestsFromName(abstest)
1712+
_list_cases(suite)
1713+
except unittest.SkipTest:
1714+
skipped.append(test)
1715+
1716+
if skipped:
1717+
print(file=sys.stderr)
1718+
print(count(len(skipped), "test"), "skipped:", file=sys.stderr)
1719+
printlist(skipped, file=sys.stderr)
16621720

16631721

16641722
def main_in_temp_cwd():

Lib/test/support/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,9 +1883,15 @@ def run_unittest(*classes):
18831883
def case_pred(test):
18841884
if match_tests is None:
18851885
return True
1886-
for name in test.id().split("."):
1887-
if fnmatch.fnmatchcase(name, match_tests):
1886+
test_id = test.id()
1887+
1888+
for match_test in match_tests:
1889+
if fnmatch.fnmatchcase(test_id, match_test):
18881890
return True
1891+
1892+
for name in test_id.split("."):
1893+
if fnmatch.fnmatchcase(name, match_test):
1894+
return True
18891895
return False
18901896
_filter_suite(suite, case_pred)
18911897
_run_suite(suite)

Lib/test/test_regrtest.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,24 @@ def test_match(self):
129129
for opt in '-m', '--match':
130130
with self.subTest(opt=opt):
131131
ns = regrtest._parse_args([opt, 'pattern'])
132-
self.assertEqual(ns.match_tests, 'pattern')
132+
self.assertEqual(ns.match_tests, ['pattern'])
133133
self.checkError([opt], 'expected one argument')
134134

135+
ns = regrtest._parse_args(['-m', 'pattern1',
136+
'-m', 'pattern2'])
137+
self.assertEqual(ns.match_tests, ['pattern1', 'pattern2'])
138+
139+
self.addCleanup(support.unlink, support.TESTFN)
140+
with open(support.TESTFN, "w") as fp:
141+
print('matchfile1', file=fp)
142+
print('matchfile2', file=fp)
143+
144+
filename = os.path.abspath(support.TESTFN)
145+
ns = regrtest._parse_args(['-m', 'match',
146+
'--matchfile', filename])
147+
self.assertEqual(ns.match_tests,
148+
['match', 'matchfile1', 'matchfile2'])
149+
135150
def test_failfast(self):
136151
for opt in '-G', '--failfast':
137152
with self.subTest(opt=opt):

0 commit comments

Comments
 (0)