diff options
author | Victor Stinner <vstinner@python.org> | 2023-09-26 18:46:52 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-26 18:46:52 (GMT) |
commit | fbfec5642edd9d7690bbff088ee43c08e8067044 (patch) | |
tree | 83d83c672f597aa789b2e8a3553c0bcdd47c5def /Lib | |
parent | ecd813f054e0dee890d484b8210e202175abd632 (diff) | |
download | cpython-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__.py | 2 | ||||
-rw-r--r-- | Lib/test/libregrtest/cmdline.py | 5 | ||||
-rw-r--r-- | Lib/test/libregrtest/main.py | 41 | ||||
-rw-r--r-- | Lib/test/test_regrtest.py | 58 |
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): |