Skip to content

Commit b894b66

Browse files
vstinnerzooba
andauthored
Update libregrtest from master (GH-19517)
* bpo-36670: regrtest bug fixes (GH-16537) * Fix TestWorkerProcess.__repr__(): start_time is only valid if _popen is not None. * Fix _kill(): don't set _killed to True if _popen is None. * _run_process(): only set _killed to False after calling run_test_in_subprocess(). (cherry picked from commit 2ea71a0) * [3.8] Update libregrtest from master (GH-19516) * bpo-37531: regrtest now catchs ProcessLookupError (GH-16827) Fix a warning on a race condition on TestWorkerProcess.kill(): ignore silently ProcessLookupError rather than logging an useless warning. (cherry picked from commit a661392) * bpo-38502: regrtest uses process groups if available (GH-16829) test.regrtest now uses process groups in the multiprocessing mode (-jN command line option) if process groups are available: if os.setsid() and os.killpg() functions are available. (cherry picked from commit ecb035c) * bpo-37957: Allow regrtest to receive a file with test (and subtests) to ignore (GH-16989) When building Python in some uncommon platforms there are some known tests that will fail. Right now, the test suite has the ability to ignore entire tests using the -x option and to receive a filter file using the --matchfile filter. The problem with the --matchfile option is that it receives a file with patterns to accept and when you want to ignore a couple of tests and subtests, is too cumbersome to lists ALL tests that are not the ones that you want to accept and he problem with -x is that is not easy to ignore just a subtests that fail and the whole test needs to be ignored. For these reasons, add a new option to allow to ignore a list of test and subtests for these situations. (cherry picked from commit e0cd8aa) * regrtest: log timeout at startup (GH-19514) Reduce also worker timeout. (cherry picked from commit 4cf65a6) Co-authored-by: Pablo Galindo <[email protected]> (cherry picked from commit 67b8a1f) * bpo-36842: Fix reference leak in tests by running out-of-proc (GH-13556) (cherry picked from commit 9ddc416) * Backport libregrtest changes from master Co-authored-by: Steve Dower <[email protected]>
1 parent 8821200 commit b894b66

File tree

11 files changed

+246
-65
lines changed

11 files changed

+246
-65
lines changed

Lib/test/libregrtest/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
# We import importlib *ASAP* in order to test #15386
2-
import importlib
3-
41
from test.libregrtest.cmdline import _parse_args, RESOURCE_NAMES, ALL_RESOURCES
52
from test.libregrtest.main import main

Lib/test/libregrtest/cmdline.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,17 @@ def _create_parser():
207207
group.add_argument('-m', '--match', metavar='PAT',
208208
dest='match_tests', action='append',
209209
help='match test cases and methods with glob pattern PAT')
210+
group.add_argument('-i', '--ignore', metavar='PAT',
211+
dest='ignore_tests', action='append',
212+
help='ignore test cases and methods with glob pattern PAT')
210213
group.add_argument('--matchfile', metavar='FILENAME',
211214
dest='match_filename',
212215
help='similar to --match but get patterns from a '
213216
'text file, one pattern per line')
217+
group.add_argument('--ignorefile', metavar='FILENAME',
218+
dest='ignore_filename',
219+
help='similar to --matchfile but it receives patterns '
220+
'from text file to ignore')
214221
group.add_argument('-G', '--failfast', action='store_true',
215222
help='fail as soon as a test fails (only with -v or -W)')
216223
group.add_argument('-u', '--use', metavar='RES1,RES2,...',
@@ -315,7 +322,8 @@ def _parse_args(args, **kwargs):
315322
findleaks=1, use_resources=None, trace=False, coverdir='coverage',
316323
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
317324
random_seed=None, use_mp=None, verbose3=False, forever=False,
318-
header=False, failfast=False, match_tests=None, pgo=False)
325+
header=False, failfast=False, match_tests=None, ignore_tests=None,
326+
pgo=False)
319327
for k, v in kwargs.items():
320328
if not hasattr(ns, k):
321329
raise TypeError('%r is an invalid keyword argument '
@@ -391,6 +399,12 @@ def _parse_args(args, **kwargs):
391399
with open(ns.match_filename) as fp:
392400
for line in fp:
393401
ns.match_tests.append(line.strip())
402+
if ns.ignore_filename:
403+
if ns.ignore_tests is None:
404+
ns.ignore_tests = []
405+
with open(ns.ignore_filename) as fp:
406+
for line in fp:
407+
ns.ignore_tests.append(line.strip())
394408
if ns.forever:
395409
# --forever implies --failfast
396410
ns.failfast = True

Lib/test/libregrtest/main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ def _list_cases(self, suite):
282282

283283
def list_cases(self):
284284
support.verbose = False
285-
support.set_match_tests(self.ns.match_tests)
285+
support.set_match_tests(self.ns.match_tests, self.ns.ignore_tests)
286286

287287
for test_name in self.selected:
288288
abstest = get_abs_module(self.ns, test_name)
@@ -389,7 +389,10 @@ def run_tests_sequential(self):
389389

390390
save_modules = sys.modules.keys()
391391

392-
self.log("Run tests sequentially")
392+
msg = "Run tests sequentially"
393+
if self.ns.timeout:
394+
msg += " (timeout: %s)" % format_duration(self.ns.timeout)
395+
self.log(msg)
393396

394397
previous_test = None
395398
for test_index, test_name in enumerate(self.tests, 1):

Lib/test/libregrtest/runtest.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def _runtest(ns, test_name):
123123

124124
start_time = time.perf_counter()
125125
try:
126-
support.set_match_tests(ns.match_tests)
126+
support.set_match_tests(ns.match_tests, ns.ignore_tests)
127127
support.junit_xml_list = xml_list = [] if ns.xmlpath else None
128128
if ns.failfast:
129129
support.failfast = True
@@ -313,9 +313,7 @@ def cleanup_test_droppings(test_name, verbose):
313313
# since if a test leaves a file open, it cannot be deleted by name (while
314314
# there's nothing we can do about that here either, we can display the
315315
# name of the offending test, which is a real help).
316-
for name in (support.TESTFN,
317-
"db_home",
318-
):
316+
for name in (support.TESTFN,):
319317
if not os.path.exists(name):
320318
continue
321319

Lib/test/libregrtest/runtest_mp.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import queue
6+
import signal
67
import subprocess
78
import sys
89
import threading
@@ -31,6 +32,8 @@
3132
# Time to wait until a worker completes: should be immediate
3233
JOIN_TIMEOUT = 30.0 # seconds
3334

35+
USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg"))
36+
3437

3538
def must_stop(result, ns):
3639
if result.result == INTERRUPTED:
@@ -59,12 +62,16 @@ def run_test_in_subprocess(testname, ns):
5962
# Running the child from the same working directory as regrtest's original
6063
# invocation ensures that TEMPDIR for the child is the same when
6164
# sysconfig.is_python_build() is true. See issue 15300.
65+
kw = {}
66+
if USE_PROCESS_GROUP:
67+
kw['start_new_session'] = True
6268
return subprocess.Popen(cmd,
6369
stdout=subprocess.PIPE,
6470
stderr=subprocess.PIPE,
6571
universal_newlines=True,
6672
close_fds=(os.name != 'nt'),
67-
cwd=support.SAVEDCWD)
73+
cwd=support.SAVEDCWD,
74+
**kw)
6875

6976

7077
def run_tests_worker(ns, test_name):
@@ -127,32 +134,46 @@ def __init__(self, worker_id, runner):
127134
def __repr__(self):
128135
info = [f'TestWorkerProcess #{self.worker_id}']
129136
if self.is_alive():
130-
dt = time.monotonic() - self.start_time
131-
info.append("running for %s" % format_duration(dt))
137+
info.append("running")
132138
else:
133139
info.append('stopped')
134140
test = self.current_test_name
135141
if test:
136142
info.append(f'test={test}')
137143
popen = self._popen
138-
if popen:
139-
info.append(f'pid={popen.pid}')
144+
if popen is not None:
145+
dt = time.monotonic() - self.start_time
146+
info.extend((f'pid={self._popen.pid}',
147+
f'time={format_duration(dt)}'))
140148
return '<%s>' % ' '.join(info)
141149

142150
def _kill(self):
151+
popen = self._popen
152+
if popen is None:
153+
return
154+
143155
if self._killed:
144156
return
145157
self._killed = True
146158

147-
popen = self._popen
148-
if popen is None:
149-
return
159+
if USE_PROCESS_GROUP:
160+
what = f"{self} process group"
161+
else:
162+
what = f"{self}"
150163

151-
print(f"Kill {self}", file=sys.stderr, flush=True)
164+
print(f"Kill {what}", file=sys.stderr, flush=True)
152165
try:
153-
popen.kill()
166+
if USE_PROCESS_GROUP:
167+
os.killpg(popen.pid, signal.SIGKILL)
168+
else:
169+
popen.kill()
170+
except ProcessLookupError:
171+
# popen.kill(): the process completed, the TestWorkerProcess thread
172+
# read its exit status, but Popen.send_signal() read the returncode
173+
# just before Popen.wait() set returncode.
174+
pass
154175
except OSError as exc:
155-
print_warning(f"Failed to kill {self}: {exc!r}")
176+
print_warning(f"Failed to kill {what}: {exc!r}")
156177

157178
def stop(self):
158179
# Method called from a different thread to stop this thread
@@ -170,9 +191,10 @@ def _run_process(self, test_name):
170191

171192
self.current_test_name = test_name
172193
try:
194+
popen = run_test_in_subprocess(test_name, self.ns)
195+
173196
self._killed = False
174-
self._popen = run_test_in_subprocess(test_name, self.ns)
175-
popen = self._popen
197+
self._popen = popen
176198
except:
177199
self.current_test_name = None
178200
raise
@@ -330,16 +352,24 @@ def __init__(self, regrtest):
330352
self.output = queue.Queue()
331353
self.pending = MultiprocessIterator(self.regrtest.tests)
332354
if self.ns.timeout is not None:
333-
self.worker_timeout = self.ns.timeout * 1.5
355+
# Rely on faulthandler to kill a worker process. This timouet is
356+
# when faulthandler fails to kill a worker process. Give a maximum
357+
# of 5 minutes to faulthandler to kill the worker.
358+
self.worker_timeout = min(self.ns.timeout * 1.5,
359+
self.ns.timeout + 5 * 60)
334360
else:
335361
self.worker_timeout = None
336362
self.workers = None
337363

338364
def start_workers(self):
339365
self.workers = [TestWorkerProcess(index, self)
340366
for index in range(1, self.ns.use_mp + 1)]
341-
self.log("Run tests in parallel using %s child processes"
342-
% len(self.workers))
367+
msg = f"Run tests in parallel using {len(self.workers)} child processes"
368+
if self.ns.timeout:
369+
msg += (" (timeout: %s, worker timeout: %s)"
370+
% (format_duration(self.ns.timeout),
371+
format_duration(self.worker_timeout)))
372+
self.log(msg)
343373
for worker in self.workers:
344374
worker.start()
345375

Lib/test/libregrtest/setup.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,29 +67,34 @@ def setup_tests(ns):
6767
if ns.threshold is not None:
6868
gc.set_threshold(ns.threshold)
6969

70+
suppress_msvcrt_asserts(ns.verbose and ns.verbose >= 2)
71+
72+
support.use_resources = ns.use_resources
73+
74+
75+
def suppress_msvcrt_asserts(verbose):
7076
try:
7177
import msvcrt
7278
except ImportError:
73-
pass
74-
else:
75-
msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
76-
msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
77-
msvcrt.SEM_NOGPFAULTERRORBOX|
78-
msvcrt.SEM_NOOPENFILEERRORBOX)
79-
try:
80-
msvcrt.CrtSetReportMode
81-
except AttributeError:
82-
# release build
83-
pass
79+
return
80+
81+
msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
82+
msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
83+
msvcrt.SEM_NOGPFAULTERRORBOX|
84+
msvcrt.SEM_NOOPENFILEERRORBOX)
85+
try:
86+
msvcrt.CrtSetReportMode
87+
except AttributeError:
88+
# release build
89+
return
90+
91+
for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]:
92+
if verbose:
93+
msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
94+
msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
8495
else:
85-
for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]:
86-
if ns.verbose and ns.verbose >= 2:
87-
msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
88-
msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
89-
else:
90-
msvcrt.CrtSetReportMode(m, 0)
96+
msvcrt.CrtSetReportMode(m, 0)
9197

92-
support.use_resources = ns.use_resources
9398

9499

95100
def replace_stdout():

Lib/test/support/__init__.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,7 +1936,9 @@ def _run_suite(suite):
19361936

19371937
# By default, don't filter tests
19381938
_match_test_func = None
1939-
_match_test_patterns = None
1939+
1940+
_accept_test_patterns = None
1941+
_ignore_test_patterns = None
19401942

19411943

19421944
def match_test(test):
@@ -1952,18 +1954,45 @@ def _is_full_match_test(pattern):
19521954
# as a full test identifier.
19531955
# Example: 'test.test_os.FileTests.test_access'.
19541956
#
1955-
# Reject patterns which contain fnmatch patterns: '*', '?', '[...]'
1956-
# or '[!...]'. For example, reject 'test_access*'.
1957+
# ignore patterns which contain fnmatch patterns: '*', '?', '[...]'
1958+
# or '[!...]'. For example, ignore 'test_access*'.
19571959
return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern))
19581960

19591961

1960-
def set_match_tests(patterns):
1961-
global _match_test_func, _match_test_patterns
1962+
def set_match_tests(accept_patterns=None, ignore_patterns=None):
1963+
global _match_test_func, _accept_test_patterns, _ignore_test_patterns
19621964

1963-
if patterns == _match_test_patterns:
1964-
# No change: no need to recompile patterns.
1965-
return
19661965

1966+
if accept_patterns is None:
1967+
accept_patterns = ()
1968+
if ignore_patterns is None:
1969+
ignore_patterns = ()
1970+
1971+
accept_func = ignore_func = None
1972+
1973+
if accept_patterns != _accept_test_patterns:
1974+
accept_patterns, accept_func = _compile_match_function(accept_patterns)
1975+
if ignore_patterns != _ignore_test_patterns:
1976+
ignore_patterns, ignore_func = _compile_match_function(ignore_patterns)
1977+
1978+
# Create a copy since patterns can be mutable and so modified later
1979+
_accept_test_patterns = tuple(accept_patterns)
1980+
_ignore_test_patterns = tuple(ignore_patterns)
1981+
1982+
if accept_func is not None or ignore_func is not None:
1983+
def match_function(test_id):
1984+
accept = True
1985+
ignore = False
1986+
if accept_func:
1987+
accept = accept_func(test_id)
1988+
if ignore_func:
1989+
ignore = ignore_func(test_id)
1990+
return accept and not ignore
1991+
1992+
_match_test_func = match_function
1993+
1994+
1995+
def _compile_match_function(patterns):
19671996
if not patterns:
19681997
func = None
19691998
# set_match_tests(None) behaves as set_match_tests(())
@@ -1991,10 +2020,7 @@ def match_test_regex(test_id):
19912020

19922021
func = match_test_regex
19932022

1994-
# Create a copy since patterns can be mutable and so modified later
1995-
_match_test_patterns = tuple(patterns)
1996-
_match_test_func = func
1997-
2023+
return patterns, func
19982024

19992025

20002026
def run_unittest(*classes):

0 commit comments

Comments
 (0)