[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 a661392f8fb5ac4fc095aa1845d1eb7a25c4e9be)
* 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 ecb035cd14c11521276343397151929a94018a22)
* 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 e0cd8aa70a3ce19c3d3712568940aa0cbd9aa97b)
* regrtest: log timeout at startup (GH-19514)
Reduce also worker timeout.
(cherry picked from commit 4cf65a630a8d45bad3fe5cdc4c2632ec64e7ba27)
Co-authored-by: Pablo Galindo <Pablogsal@gmail.com>
diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py
index c8fedc7..c0bb051 100644
--- a/Lib/test/libregrtest/cmdline.py
+++ b/Lib/test/libregrtest/cmdline.py
@@ -207,10 +207,17 @@
group.add_argument('-m', '--match', metavar='PAT',
dest='match_tests', action='append',
help='match test cases and methods with glob pattern PAT')
+ group.add_argument('-i', '--ignore', metavar='PAT',
+ dest='ignore_tests', action='append',
+ help='ignore test cases and methods with glob pattern PAT')
group.add_argument('--matchfile', metavar='FILENAME',
dest='match_filename',
help='similar to --match but get patterns from a '
'text file, one pattern per line')
+ group.add_argument('--ignorefile', metavar='FILENAME',
+ dest='ignore_filename',
+ help='similar to --matchfile but it receives patterns '
+ 'from text file to ignore')
group.add_argument('-G', '--failfast', action='store_true',
help='fail as soon as a test fails (only with -v or -W)')
group.add_argument('-u', '--use', metavar='RES1,RES2,...',
@@ -317,7 +324,8 @@
findleaks=1, use_resources=None, trace=False, coverdir='coverage',
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
random_seed=None, use_mp=None, verbose3=False, forever=False,
- header=False, failfast=False, match_tests=None, pgo=False)
+ header=False, failfast=False, match_tests=None, ignore_tests=None,
+ pgo=False)
for k, v in kwargs.items():
if not hasattr(ns, k):
raise TypeError('%r is an invalid keyword argument '
@@ -395,6 +403,12 @@
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:
# --forever implies --failfast
ns.failfast = True
diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index 76ad335..95b4856 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -287,7 +287,7 @@
def list_cases(self):
support.verbose = False
- support.set_match_tests(self.ns.match_tests)
+ support.set_match_tests(self.ns.match_tests, self.ns.ignore_tests)
for test_name in self.selected:
abstest = get_abs_module(self.ns, test_name)
@@ -394,7 +394,10 @@
save_modules = sys.modules.keys()
- self.log("Run tests sequentially")
+ msg = "Run tests sequentially"
+ if self.ns.timeout:
+ msg += " (timeout: %s)" % format_duration(self.ns.timeout)
+ self.log(msg)
previous_test = None
for test_index, test_name in enumerate(self.tests, 1):
diff --git a/Lib/test/libregrtest/runtest.py b/Lib/test/libregrtest/runtest.py
index eeb108b..558f209 100644
--- a/Lib/test/libregrtest/runtest.py
+++ b/Lib/test/libregrtest/runtest.py
@@ -123,7 +123,7 @@
start_time = time.perf_counter()
try:
- support.set_match_tests(ns.match_tests)
+ support.set_match_tests(ns.match_tests, ns.ignore_tests)
support.junit_xml_list = xml_list = [] if ns.xmlpath else None
if ns.failfast:
support.failfast = True
diff --git a/Lib/test/libregrtest/runtest_mp.py b/Lib/test/libregrtest/runtest_mp.py
index 71db8d6..7a18e45 100644
--- a/Lib/test/libregrtest/runtest_mp.py
+++ b/Lib/test/libregrtest/runtest_mp.py
@@ -3,6 +3,7 @@
import json
import os
import queue
+import signal
import subprocess
import sys
import threading
@@ -31,6 +32,8 @@
# Time to wait until a worker completes: should be immediate
JOIN_TIMEOUT = 30.0 # seconds
+USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg"))
+
def must_stop(result, ns):
if result.result == INTERRUPTED:
@@ -59,12 +62,16 @@
# Running the child from the same working directory as regrtest's original
# invocation ensures that TEMPDIR for the child is the same when
# sysconfig.is_python_build() is true. See issue 15300.
+ kw = {}
+ if USE_PROCESS_GROUP:
+ kw['start_new_session'] = True
return subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
close_fds=(os.name != 'nt'),
- cwd=support.SAVEDCWD)
+ cwd=support.SAVEDCWD,
+ **kw)
def run_tests_worker(ns, test_name):
@@ -149,11 +156,24 @@
return
self._killed = True
- print(f"Kill {self}", file=sys.stderr, flush=True)
+ if USE_PROCESS_GROUP:
+ what = f"{self} process group"
+ else:
+ what = f"{self}"
+
+ print(f"Kill {what}", file=sys.stderr, flush=True)
try:
- popen.kill()
+ if USE_PROCESS_GROUP:
+ os.killpg(popen.pid, signal.SIGKILL)
+ else:
+ popen.kill()
+ except ProcessLookupError:
+ # popen.kill(): the process completed, the TestWorkerProcess thread
+ # read its exit status, but Popen.send_signal() read the returncode
+ # just before Popen.wait() set returncode.
+ pass
except OSError as exc:
- print_warning(f"Failed to kill {self}: {exc!r}")
+ print_warning(f"Failed to kill {what}: {exc!r}")
def stop(self):
# Method called from a different thread to stop this thread
@@ -332,7 +352,11 @@
self.output = queue.Queue()
self.pending = MultiprocessIterator(self.regrtest.tests)
if self.ns.timeout is not None:
- self.worker_timeout = self.ns.timeout * 1.5
+ # Rely on faulthandler to kill a worker process. This timouet is
+ # when faulthandler fails to kill a worker process. Give a maximum
+ # of 5 minutes to faulthandler to kill the worker.
+ self.worker_timeout = min(self.ns.timeout * 1.5,
+ self.ns.timeout + 5 * 60)
else:
self.worker_timeout = None
self.workers = None
@@ -340,8 +364,12 @@
def start_workers(self):
self.workers = [TestWorkerProcess(index, self)
for index in range(1, self.ns.use_mp + 1)]
- self.log("Run tests in parallel using %s child processes"
- % len(self.workers))
+ msg = f"Run tests in parallel using {len(self.workers)} child processes"
+ if self.ns.timeout:
+ msg += (" (timeout: %s, worker timeout: %s)"
+ % (format_duration(self.ns.timeout),
+ format_duration(self.worker_timeout)))
+ self.log(msg)
for worker in self.workers:
worker.start()