Skip to content

Commit a0ccc54

Browse files
authored
Synchronize libregrtest from master to 3.6 (#2244)
* bpo-30523: regrtest: Add --list-cases option (#2238) * bpo-30284: Fix regrtest for out of tree build (#1481) * bpo-30540: regrtest: add --matchfile option (#1909) * bpo-30258: regrtest: Fix run_tests_multiprocess() (#1479) * bpo-30263: regrtest: log system load (#1452)
1 parent 085a57a commit a0ccc54

File tree

7 files changed

+228
-58
lines changed

7 files changed

+228
-58
lines changed

Lib/test/libregrtest/cmdline.py

Lines changed: 24 additions & 1 deletion
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

@@ -189,8 +196,12 @@ def _create_parser():
189196
help='single step through a set of tests.' +
190197
more_details)
191198
group.add_argument('-m', '--match', metavar='PAT',
192-
dest='match_tests',
199+
dest='match_tests', action='append',
193200
help='match test cases and methods with glob pattern PAT')
201+
group.add_argument('--matchfile', metavar='FILENAME',
202+
dest='match_filename',
203+
help='similar to --match but get patterns from a '
204+
'text file, one pattern per line')
194205
group.add_argument('-G', '--failfast', action='store_true',
195206
help='fail as soon as a test fails (only with -v or -W)')
196207
group.add_argument('-u', '--use', metavar='RES1,RES2,...',
@@ -239,6 +250,9 @@ def _create_parser():
239250
group.add_argument('--list-tests', action='store_true',
240251
help="only write the name of tests that will be run, "
241252
"don't execute them")
253+
group.add_argument('--list-cases', action='store_true',
254+
help='only write the name of test cases that will be run'
255+
' , don\'t execute them')
242256
group.add_argument('-P', '--pgo', dest='pgo', action='store_true',
243257
help='enable Profile Guided Optimization training')
244258

@@ -343,10 +357,19 @@ def _parse_args(args, **kwargs):
343357
ns.use_resources.append(r)
344358
if ns.random_seed is not None:
345359
ns.randomize = True
360+
if ns.verbose:
361+
ns.header = True
346362
if ns.huntrleaks and ns.verbose3:
347363
ns.verbose3 = False
348364
print("WARNING: Disable --verbose3 because it's incompatible with "
349365
"--huntrleaks: see http://bugs.python.org/issue27103",
350366
file=sys.stderr)
367+
if ns.match_filename:
368+
if ns.match_tests is None:
369+
ns.match_tests = []
370+
filename = os.path.join(support.SAVEDCWD, ns.match_filename)
371+
with open(filename) as fp:
372+
for line in fp:
373+
ns.match_tests.append(line.strip())
351374

352375
return ns

Lib/test/libregrtest/main.py

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
import tempfile
1111
import textwrap
1212
import time
13+
import unittest
1314
from test.libregrtest.cmdline import _parse_args
1415
from test.libregrtest.runtest import (
15-
findtests, runtest,
16+
findtests, runtest, get_abs_module,
1617
STDTESTS, NOTTESTS, PASSED, FAILED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED,
1718
INTERRUPTED, CHILD_ERROR,
1819
PROGRESS_MIN_TIME, format_test_result)
@@ -28,7 +29,13 @@
2829
# to keep the test files in a subfolder. This eases the cleanup of leftover
2930
# files using the "make distclean" command.
3031
if sysconfig.is_python_build():
31-
TEMPDIR = os.path.join(sysconfig.get_config_var('srcdir'), 'build')
32+
TEMPDIR = sysconfig.get_config_var('abs_builddir')
33+
if TEMPDIR is None:
34+
# bpo-30284: On Windows, only srcdir is available. Using abs_builddir
35+
# mostly matters on UNIX when building Python out of the source tree,
36+
# especially when the source tree is read only.
37+
TEMPDIR = sysconfig.get_config_var('srcdir')
38+
TEMPDIR = os.path.join(TEMPDIR, 'build')
3239
else:
3340
TEMPDIR = tempfile.gettempdir()
3441
TEMPDIR = os.path.abspath(TEMPDIR)
@@ -107,7 +114,7 @@ def accumulate_result(self, test, result):
107114
self.test_times.append((test_time, test))
108115
if ok == PASSED:
109116
self.good.append(test)
110-
elif ok == FAILED:
117+
elif ok in (FAILED, CHILD_ERROR):
111118
self.bad.append(test)
112119
elif ok == ENV_CHANGED:
113120
self.environment_changed.append(test)
@@ -116,22 +123,28 @@ def accumulate_result(self, test, result):
116123
elif ok == RESOURCE_DENIED:
117124
self.skipped.append(test)
118125
self.resource_denieds.append(test)
126+
elif ok != INTERRUPTED:
127+
raise ValueError("invalid test result: %r" % ok)
119128

120129
def display_progress(self, test_index, test):
121130
if self.ns.quiet:
122131
return
132+
133+
# "[ 51/405/1] test_tcl passed"
134+
line = f"{test_index:{self.test_count_width}}{self.test_count}"
123135
if self.bad and not self.ns.pgo:
124-
fmt = "{time} [{test_index:{count_width}}{test_count}/{nbad}] {test_name}"
125-
else:
126-
fmt = "{time} [{test_index:{count_width}}{test_count}] {test_name}"
136+
line = f"{line}/{len(self.bad)}"
137+
line = f"[{line}] {test}"
138+
139+
# add the system load prefix: "load avg: 1.80 "
140+
if hasattr(os, 'getloadavg'):
141+
load_avg_1min = os.getloadavg()[0]
142+
line = f"load avg: {load_avg_1min:.2f} {line}"
143+
144+
# add the timestamp prefix: "0:01:05 "
127145
test_time = time.monotonic() - self.start_time
128146
test_time = datetime.timedelta(seconds=int(test_time))
129-
line = fmt.format(count_width=self.test_count_width,
130-
test_index=test_index,
131-
test_count=self.test_count,
132-
nbad=len(self.bad),
133-
test_name=test,
134-
time=test_time)
147+
line = f"{test_time} {line}"
135148
print(line, flush=True)
136149

137150
def parse_args(self, kwargs):
@@ -179,19 +192,14 @@ def find_tests(self, tests):
179192
self.tests = []
180193
# regex to match 'test_builtin' in line:
181194
# '0:00:00 [ 4/400] test_builtin -- test_dict took 1 sec'
182-
regex = (r'^(?:[0-9]+:[0-9]+:[0-9]+ *)?'
183-
r'(?:\[[0-9/ ]+\] *)?'
184-
r'(test_[a-zA-Z0-9_]+)')
185-
regex = re.compile(regex)
195+
regex = re.compile(r'\btest_[a-zA-Z0-9_]+\b')
186196
with open(os.path.join(support.SAVEDCWD, self.ns.fromfile)) as fp:
187197
for line in fp:
198+
line = line.split('#', 1)[0]
188199
line = line.strip()
189-
if line.startswith('#'):
190-
continue
191-
match = regex.match(line)
192-
if match is None:
193-
continue
194-
self.tests.append(match.group(1))
200+
match = regex.search(line)
201+
if match is not None:
202+
self.tests.append(match.group())
195203

196204
removepy(self.tests)
197205

@@ -241,6 +249,29 @@ def list_tests(self):
241249
for name in self.selected:
242250
print(name)
243251

252+
def _list_cases(self, suite):
253+
for test in suite:
254+
if isinstance(test, unittest.loader._FailedTest):
255+
continue
256+
if isinstance(test, unittest.TestSuite):
257+
self._list_cases(test)
258+
elif isinstance(test, unittest.TestCase):
259+
print(test.id())
260+
261+
def list_cases(self):
262+
for test in self.selected:
263+
abstest = get_abs_module(self.ns, test)
264+
try:
265+
suite = unittest.defaultTestLoader.loadTestsFromName(abstest)
266+
self._list_cases(suite)
267+
except unittest.SkipTest:
268+
self.skipped.append(test)
269+
270+
if self.skipped:
271+
print(file=sys.stderr)
272+
print(count(len(self.skipped), "test"), "skipped:", file=sys.stderr)
273+
printlist(self.skipped, file=sys.stderr)
274+
244275
def rerun_failed_tests(self):
245276
self.ns.verbose = True
246277
self.ns.failfast = False
@@ -381,23 +412,28 @@ def _test_forever(self, tests):
381412
if self.bad:
382413
return
383414

415+
def display_header(self):
416+
# Print basic platform information
417+
print("==", platform.python_implementation(), *sys.version.split())
418+
print("==", platform.platform(aliased=True),
419+
"%s-endian" % sys.byteorder)
420+
print("== hash algorithm:", sys.hash_info.algorithm,
421+
"64bit" if sys.maxsize > 2**32 else "32bit")
422+
print("== cwd:", os.getcwd())
423+
cpu_count = os.cpu_count()
424+
if cpu_count:
425+
print("== CPU count:", cpu_count)
426+
print("== encodings: locale=%s, FS=%s"
427+
% (locale.getpreferredencoding(False),
428+
sys.getfilesystemencoding()))
429+
print("Testing with flags:", sys.flags)
430+
384431
def run_tests(self):
385432
# For a partial run, we do not need to clutter the output.
386-
if (self.ns.verbose
387-
or self.ns.header
388-
or not (self.ns.pgo or self.ns.quiet or self.ns.single
389-
or self.tests or self.ns.args)):
390-
# Print basic platform information
391-
print("==", platform.python_implementation(), *sys.version.split())
392-
print("== ", platform.platform(aliased=True),
393-
"%s-endian" % sys.byteorder)
394-
print("== ", "hash algorithm:", sys.hash_info.algorithm,
395-
"64bit" if sys.maxsize > 2**32 else "32bit")
396-
print("== cwd:", os.getcwd())
397-
print("== encodings: locale=%s, FS=%s"
398-
% (locale.getpreferredencoding(False),
399-
sys.getfilesystemencoding()))
400-
print("Testing with flags:", sys.flags)
433+
if (self.ns.header
434+
or not(self.ns.pgo or self.ns.quiet or self.ns.single
435+
or self.tests or self.ns.args)):
436+
self.display_header()
401437

402438
if self.ns.randomize:
403439
print("Using random seed", self.ns.random_seed)
@@ -487,6 +523,10 @@ def _main(self, tests, kwargs):
487523
self.list_tests()
488524
sys.exit(0)
489525

526+
if self.ns.list_cases:
527+
self.list_cases()
528+
sys.exit(0)
529+
490530
self.run_tests()
491531
self.display_result()
492532

@@ -513,7 +553,7 @@ def count(n, word):
513553
return "%d %ss" % (n, word)
514554

515555

516-
def printlist(x, width=70, indent=4):
556+
def printlist(x, width=70, indent=4, file=None):
517557
"""Print the elements of iterable x to stdout.
518558
519559
Optional arg width (default 70) is the maximum line length.
@@ -524,7 +564,8 @@ def printlist(x, width=70, indent=4):
524564
blanks = ' ' * indent
525565
# Print the sorted list: 'x' may be a '--random' list or a set()
526566
print(textwrap.fill(' '.join(str(elt) for elt in sorted(x)), width,
527-
initial_indent=blanks, subsequent_indent=blanks))
567+
initial_indent=blanks, subsequent_indent=blanks),
568+
file=file)
528569

529570

530571
def main(tests=None, **kwargs):

Lib/test/libregrtest/refleak.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def dash_R(the_module, test, indirect_test, huntrleaks):
8484
indirect_test()
8585
alloc_after, rc_after, fd_after = dash_R_cleanup(fs, ps, pic, zdc,
8686
abcs)
87-
print('.', end='', flush=True)
87+
print('.', end='', file=sys.stderr, flush=True)
8888
if i >= nwarmup:
8989
rc_deltas[i] = rc_after - rc_before
9090
alloc_deltas[i] = alloc_after - alloc_before

Lib/test/libregrtest/runtest.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS):
7171
return stdtests + sorted(tests)
7272

7373

74+
def get_abs_module(ns, test):
75+
if test.startswith('test.') or ns.testdir:
76+
return test
77+
else:
78+
# Always import it from the test package
79+
return 'test.' + test
80+
81+
7482
def runtest(ns, test):
7583
"""Run a single test.
7684
@@ -141,11 +149,7 @@ def runtest_inner(ns, test, display_failure=True):
141149
test_time = 0.0
142150
refleak = False # True if the test leaked references.
143151
try:
144-
if test.startswith('test.') or ns.testdir:
145-
abstest = test
146-
else:
147-
# Always import it from the test package
148-
abstest = 'test.' + test
152+
abstest = get_abs_module(ns, test)
149153
clear_caches()
150154
with saved_test_environment(test, ns.verbose, ns.quiet, pgo=ns.pgo) as environment:
151155
start_time = time.time()

Lib/test/libregrtest/runtest_mp.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def run_test_in_subprocess(testname, ns):
4141
slaveargs = json.dumps(slaveargs)
4242

4343
cmd = [sys.executable, *support.args_from_interpreter_flags(),
44-
'-X', 'faulthandler',
44+
'-u', # Unbuffered stdout and stderr
4545
'-m', 'test.regrtest',
4646
'--slaveargs', slaveargs]
4747
if ns.pgo:
@@ -124,13 +124,13 @@ def _runtest(self):
124124
finally:
125125
self.current_test = None
126126

127-
stdout, _, result = stdout.strip().rpartition("\n")
128127
if retcode != 0:
129128
result = (CHILD_ERROR, "Exit code %s" % retcode)
130129
self.output.put((test, stdout.rstrip(), stderr.rstrip(),
131130
result))
132-
return True
131+
return False
133132

133+
stdout, _, result = stdout.strip().rpartition("\n")
134134
if not result:
135135
self.output.put((None, None, None, None))
136136
return True
@@ -203,6 +203,8 @@ def get_running(workers):
203203
and test_time >= PROGRESS_MIN_TIME
204204
and not regrtest.ns.pgo):
205205
text += ' (%.0f sec)' % test_time
206+
elif ok == CHILD_ERROR:
207+
text = '%s (%s)' % (text, test_time)
206208
running = get_running(workers)
207209
if running and not regrtest.ns.pgo:
208210
text += ' -- running: %s' % ', '.join(running)
@@ -216,9 +218,6 @@ def get_running(workers):
216218

217219
if result[0] == INTERRUPTED:
218220
raise KeyboardInterrupt
219-
if result[0] == CHILD_ERROR:
220-
msg = "Child error on {}: {}".format(test, result[1])
221-
raise Exception(msg)
222221
test_index += 1
223222
except KeyboardInterrupt:
224223
regrtest.interrupted = True

Lib/test/support/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,9 +1915,15 @@ def run_unittest(*classes):
19151915
def case_pred(test):
19161916
if match_tests is None:
19171917
return True
1918-
for name in test.id().split("."):
1919-
if fnmatch.fnmatchcase(name, match_tests):
1918+
test_id = test.id()
1919+
1920+
for match_test in match_tests:
1921+
if fnmatch.fnmatchcase(test_id, match_test):
19201922
return True
1923+
1924+
for name in test_id.split("."):
1925+
if fnmatch.fnmatchcase(name, match_test):
1926+
return True
19211927
return False
19221928
_filter_suite(suite, case_pred)
19231929
_run_suite(suite)

0 commit comments

Comments
 (0)