summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2023-09-26 18:46:52 (GMT)
committerGitHub <noreply@github.com>2023-09-26 18:46:52 (GMT)
commitfbfec5642edd9d7690bbff088ee43c08e8067044 (patch)
tree83d83c672f597aa789b2e8a3553c0bcdd47c5def /Lib
parentecd813f054e0dee890d484b8210e202175abd632 (diff)
downloadcpython-fbfec5642edd9d7690bbff088ee43c08e8067044.zip
cpython-fbfec5642edd9d7690bbff088ee43c08e8067044.tar.gz
cpython-fbfec5642edd9d7690bbff088ee43c08e8067044.tar.bz2
gh-109566: regrtest reexecutes the process (#109909)
When --fast-ci or --slow-ci option is used, regrtest now replaces the current process with a new process to add "-u -W default -bb -E" options to Python. Changes: * PCbuild/rt.bat and Tools/scripts/run_tests.py no longer need to add "-u -W default -bb -E" options to Python: it's now done by regrtest. * Fix Tools/scripts/run_tests.py: flush stdout before replacing the process. Previously, buffered messages were lost.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/__main__.py2
-rw-r--r--Lib/test/libregrtest/cmdline.py5
-rw-r--r--Lib/test/libregrtest/main.py41
-rw-r--r--Lib/test/test_regrtest.py58
4 files changed, 100 insertions, 6 deletions
diff --git a/Lib/test/__main__.py b/Lib/test/__main__.py
index e5780b7..42553fa 100644
--- a/Lib/test/__main__.py
+++ b/Lib/test/__main__.py
@@ -1,2 +1,2 @@
from test.libregrtest.main import main
-main()
+main(reexec=True)
diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py
index a0a8504..bc96996 100644
--- a/Lib/test/libregrtest/cmdline.py
+++ b/Lib/test/libregrtest/cmdline.py
@@ -184,6 +184,7 @@ class Namespace(argparse.Namespace):
self.threshold = None
self.fail_rerun = False
self.tempdir = None
+ self.no_reexec = False
super().__init__(**kwargs)
@@ -343,6 +344,8 @@ def _create_parser():
help='override the working directory for the test run')
group.add_argument('--cleanup', action='store_true',
help='remove old test_python_* directories')
+ group.add_argument('--no-reexec', action='store_true',
+ help="internal option, don't use it")
return parser
@@ -421,6 +424,8 @@ def _parse_args(args, **kwargs):
ns.verbose3 = True
if MS_WINDOWS:
ns.nowindows = True # Silence alerts under Windows
+ else:
+ ns.no_reexec = True
# When both --slow-ci and --fast-ci options are present,
# --slow-ci has the priority
diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py
index 2cd79a1..a93f532 100644
--- a/Lib/test/libregrtest/main.py
+++ b/Lib/test/libregrtest/main.py
@@ -1,6 +1,7 @@
import os
import random
import re
+import shlex
import sys
import time
@@ -20,7 +21,7 @@ from .utils import (
StrPath, StrJSON, TestName, TestList, TestTuple, FilterTuple,
strip_py_suffix, count, format_duration,
printlist, get_temp_dir, get_work_dir, exit_timeout,
- display_header, cleanup_temp_dir,
+ display_header, cleanup_temp_dir, print_warning,
MS_WINDOWS)
@@ -47,7 +48,7 @@ class Regrtest:
directly to set the values that would normally be set by flags
on the command line.
"""
- def __init__(self, ns: Namespace):
+ def __init__(self, ns: Namespace, reexec: bool = False):
# Log verbosity
self.verbose: int = int(ns.verbose)
self.quiet: bool = ns.quiet
@@ -69,6 +70,7 @@ class Regrtest:
self.want_cleanup: bool = ns.cleanup
self.want_rerun: bool = ns.rerun
self.want_run_leaks: bool = ns.runleaks
+ self.want_reexec: bool = (reexec and not ns.no_reexec)
# Select tests
if ns.match_tests:
@@ -95,6 +97,7 @@ class Regrtest:
self.worker_json: StrJSON | None = ns.worker_json
# Options to run tests
+ self.ci_mode: bool = (ns.fast_ci or ns.slow_ci)
self.fail_fast: bool = ns.failfast
self.fail_env_changed: bool = ns.fail_env_changed
self.fail_rerun: bool = ns.fail_rerun
@@ -483,7 +486,37 @@ class Regrtest:
# processes.
return self._run_tests(selected, tests)
+ def _reexecute_python(self):
+ if self.python_cmd:
+ # Do nothing if --python=cmd option is used
+ return
+
+ python_opts = [
+ '-u', # Unbuffered stdout and stderr
+ '-W', 'default', # Add warnings filter 'default'
+ '-bb', # Error on bytes/str comparison
+ '-E', # Ignore PYTHON* environment variables
+ ]
+
+ cmd = [*sys.orig_argv, "--no-reexec"]
+ cmd[1:1] = python_opts
+
+ # Make sure that messages before execv() are logged
+ sys.stdout.flush()
+ sys.stderr.flush()
+
+ try:
+ os.execv(cmd[0], cmd)
+ # execv() do no return and so we don't get to this line on success
+ except OSError as exc:
+ cmd_text = shlex.join(cmd)
+ print_warning(f"Failed to reexecute Python: {exc!r}\n"
+ f"Command: {cmd_text}")
+
def main(self, tests: TestList | None = None):
+ if self.want_reexec and self.ci_mode:
+ self._reexecute_python()
+
if self.junit_filename and not os.path.isabs(self.junit_filename):
self.junit_filename = os.path.abspath(self.junit_filename)
@@ -515,7 +548,7 @@ class Regrtest:
sys.exit(exitcode)
-def main(tests=None, **kwargs):
+def main(tests=None, reexec=False, **kwargs):
"""Run the Python suite."""
ns = _parse_args(sys.argv[1:], **kwargs)
- Regrtest(ns).main(tests=tests)
+ Regrtest(ns, reexec=reexec).main(tests=tests)
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 15aab60..2b77300 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -382,7 +382,8 @@ class ParseArgsTestCase(unittest.TestCase):
# Check Regrtest attributes which are more reliable than Namespace
# which has an unclear API
regrtest = main.Regrtest(ns)
- self.assertNotEqual(regrtest.num_workers, 0)
+ self.assertTrue(regrtest.ci_mode)
+ self.assertEqual(regrtest.num_workers, -1)
self.assertTrue(regrtest.want_rerun)
self.assertTrue(regrtest.randomize)
self.assertIsNone(regrtest.random_seed)
@@ -1960,6 +1961,61 @@ class ArgsTestCase(BaseTestCase):
self.check_executed_tests(output, tests,
stats=len(tests), parallel=True)
+ def check_reexec(self, option):
+ # --fast-ci and --slow-ci add "-u -W default -bb -E" options to Python
+ code = textwrap.dedent(r"""
+ import sys
+ import unittest
+ try:
+ from _testinternalcapi import get_config
+ except ImportError:
+ get_config = None
+
+ class WorkerTests(unittest.TestCase):
+ @unittest.skipUnless(get_config is None, 'need get_config()')
+ def test_config(self):
+ config = get_config()['config']
+ # -u option
+ self.assertEqual(config['buffered_stdio'], 0)
+ # -W default option
+ self.assertTrue(config['warnoptions'], ['default'])
+ # -bb option
+ self.assertTrue(config['bytes_warning'], 2)
+ # -E option
+ self.assertTrue(config['use_environment'], 0)
+
+ # test if get_config() is not available
+ def test_unbuffered(self):
+ # -u option
+ self.assertFalse(sys.stdout.line_buffering)
+ self.assertFalse(sys.stderr.line_buffering)
+
+ def test_python_opts(self):
+ # -W default option
+ self.assertTrue(sys.warnoptions, ['default'])
+ # -bb option
+ self.assertEqual(sys.flags.bytes_warning, 2)
+ # -E option
+ self.assertTrue(sys.flags.ignore_environment)
+ """)
+ testname = self.create_test(code=code)
+
+ cmd = [sys.executable,
+ "-m", "test", option,
+ f'--testdir={self.tmptestdir}',
+ testname]
+ proc = subprocess.run(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True)
+ self.assertEqual(proc.returncode, 0, proc)
+
+ def test_reexec_fast_ci(self):
+ self.check_reexec("--fast-ci")
+
+ def test_reexec_slow_ci(self):
+ self.check_reexec("--slow-ci")
+
class TestUtils(unittest.TestCase):
def test_format_duration(self):