gh-109566: Fix regrtest code adding Python options (#109926)

* On Windows, use subprocess.run() instead of os.execv().
* Only add needed options
* Rename reexec parameter to _add_python_opts.
* Rename --no-reexec option to --dont-add-python-opts.
This commit is contained in:
Victor Stinner 2023-09-26 23:59:11 +02:00 committed by GitHub
parent 87ddfa74e2
commit 9abba715e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 39 deletions

View file

@ -1,2 +1,2 @@
from test.libregrtest.main import main from test.libregrtest.main import main
main(reexec=True) main(_add_python_opts=True)

View file

@ -184,7 +184,7 @@ class Namespace(argparse.Namespace):
self.threshold = None self.threshold = None
self.fail_rerun = False self.fail_rerun = False
self.tempdir = None self.tempdir = None
self.no_reexec = False self._add_python_opts = True
super().__init__(**kwargs) super().__init__(**kwargs)
@ -344,7 +344,8 @@ def _create_parser():
help='override the working directory for the test run') help='override the working directory for the test run')
group.add_argument('--cleanup', action='store_true', group.add_argument('--cleanup', action='store_true',
help='remove old test_python_* directories') help='remove old test_python_* directories')
group.add_argument('--no-reexec', action='store_true', group.add_argument('--dont-add-python-opts', dest='_add_python_opts',
action='store_false',
help="internal option, don't use it") help="internal option, don't use it")
return parser return parser
@ -425,7 +426,7 @@ def _parse_args(args, **kwargs):
if MS_WINDOWS: if MS_WINDOWS:
ns.nowindows = True # Silence alerts under Windows ns.nowindows = True # Silence alerts under Windows
else: else:
ns.no_reexec = True ns._add_python_opts = False
# When both --slow-ci and --fast-ci options are present, # When both --slow-ci and --fast-ci options are present,
# --slow-ci has the priority # --slow-ci has the priority

View file

@ -48,7 +48,7 @@ class Regrtest:
directly to set the values that would normally be set by flags directly to set the values that would normally be set by flags
on the command line. on the command line.
""" """
def __init__(self, ns: Namespace, reexec: bool = False): def __init__(self, ns: Namespace, _add_python_opts: bool = False):
# Log verbosity # Log verbosity
self.verbose: int = int(ns.verbose) self.verbose: int = int(ns.verbose)
self.quiet: bool = ns.quiet self.quiet: bool = ns.quiet
@ -70,7 +70,11 @@ class Regrtest:
self.want_cleanup: bool = ns.cleanup self.want_cleanup: bool = ns.cleanup
self.want_rerun: bool = ns.rerun self.want_rerun: bool = ns.rerun
self.want_run_leaks: bool = ns.runleaks self.want_run_leaks: bool = ns.runleaks
self.want_reexec: bool = (reexec and not ns.no_reexec)
ci_mode = (ns.fast_ci or ns.slow_ci)
self.want_add_python_opts: bool = (_add_python_opts
and ns._add_python_opts
and ci_mode)
# Select tests # Select tests
if ns.match_tests: if ns.match_tests:
@ -97,7 +101,6 @@ class Regrtest:
self.worker_json: StrJSON | None = ns.worker_json self.worker_json: StrJSON | None = ns.worker_json
# Options to run tests # Options to run tests
self.ci_mode: bool = (ns.fast_ci or ns.slow_ci)
self.fail_fast: bool = ns.failfast self.fail_fast: bool = ns.failfast
self.fail_env_changed: bool = ns.fail_env_changed self.fail_env_changed: bool = ns.fail_env_changed
self.fail_rerun: bool = ns.fail_rerun self.fail_rerun: bool = ns.fail_rerun
@ -486,32 +489,48 @@ class Regrtest:
# processes. # processes.
return self._run_tests(selected, tests) return self._run_tests(selected, tests)
def _reexecute_python(self): def _add_python_opts(self):
if self.python_cmd: python_opts = []
# Do nothing if --python=cmd option is used
# Unbuffered stdout and stderr
if not sys.stdout.write_through:
python_opts.append('-u')
# Add warnings filter 'default'
if 'default' not in sys.warnoptions:
python_opts.extend(('-W', 'default'))
# Error on bytes/str comparison
if sys.flags.bytes_warning < 2:
python_opts.append('-bb')
# Ignore PYTHON* environment variables
if not sys.flags.ignore_environment:
python_opts.append('-E')
if not python_opts:
return return
python_opts = [ cmd = [*sys.orig_argv, "--dont-add-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 cmd[1:1] = python_opts
# Make sure that messages before execv() are logged # Make sure that messages before execv() are logged
sys.stdout.flush() sys.stdout.flush()
sys.stderr.flush() sys.stderr.flush()
cmd_text = shlex.join(cmd)
try: try:
os.execv(cmd[0], cmd) if hasattr(os, 'execv') and not MS_WINDOWS:
# execv() do no return and so we don't get to this line on success os.execv(cmd[0], cmd)
except OSError as exc: # execv() do no return and so we don't get to this line on success
cmd_text = shlex.join(cmd) else:
print_warning(f"Failed to reexecute Python: {exc!r}\n" import subprocess
proc = subprocess.run(cmd)
sys.exit(proc.returncode)
except Exception as exc:
print_warning(f"Failed to change Python options: {exc!r}\n"
f"Command: {cmd_text}") f"Command: {cmd_text}")
# continue executing main()
def _init(self): def _init(self):
# Set sys.stdout encoder error handler to backslashreplace, # Set sys.stdout encoder error handler to backslashreplace,
@ -527,8 +546,8 @@ class Regrtest:
self.tmp_dir = get_temp_dir(self.tmp_dir) self.tmp_dir = get_temp_dir(self.tmp_dir)
def main(self, tests: TestList | None = None): def main(self, tests: TestList | None = None):
if self.want_reexec and self.ci_mode: if self.want_add_python_opts:
self._reexecute_python() self._add_python_opts()
self._init() self._init()
@ -556,7 +575,7 @@ class Regrtest:
sys.exit(exitcode) sys.exit(exitcode)
def main(tests=None, reexec=False, **kwargs): def main(tests=None, _add_python_opts=False, **kwargs):
"""Run the Python suite.""" """Run the Python suite."""
ns = _parse_args(sys.argv[1:], **kwargs) ns = _parse_args(sys.argv[1:], **kwargs)
Regrtest(ns, reexec=reexec).main(tests=tests) Regrtest(ns, _add_python_opts=_add_python_opts).main(tests=tests)

View file

@ -382,7 +382,6 @@ class ParseArgsTestCase(unittest.TestCase):
# Check Regrtest attributes which are more reliable than Namespace # Check Regrtest attributes which are more reliable than Namespace
# which has an unclear API # which has an unclear API
regrtest = main.Regrtest(ns) regrtest = main.Regrtest(ns)
self.assertTrue(regrtest.ci_mode)
self.assertEqual(regrtest.num_workers, -1) self.assertEqual(regrtest.num_workers, -1)
self.assertTrue(regrtest.want_rerun) self.assertTrue(regrtest.want_rerun)
self.assertTrue(regrtest.randomize) self.assertTrue(regrtest.randomize)
@ -413,6 +412,11 @@ class ParseArgsTestCase(unittest.TestCase):
regrtest = self.check_ci_mode(args, use_resources) regrtest = self.check_ci_mode(args, use_resources)
self.assertEqual(regrtest.timeout, 20 * 60) self.assertEqual(regrtest.timeout, 20 * 60)
def test_dont_add_python_opts(self):
args = ['--dont-add-python-opts']
ns = cmdline._parse_args(args)
self.assertFalse(ns._add_python_opts)
@dataclasses.dataclass(slots=True) @dataclasses.dataclass(slots=True)
class Rerun: class Rerun:
@ -1984,22 +1988,23 @@ class ArgsTestCase(BaseTestCase):
# -E option # -E option
self.assertTrue(config['use_environment'], 0) 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): def test_python_opts(self):
# -u option
self.assertTrue(sys.__stdout__.write_through)
self.assertTrue(sys.__stderr__.write_through)
# -W default option # -W default option
self.assertTrue(sys.warnoptions, ['default']) self.assertTrue(sys.warnoptions, ['default'])
# -bb option # -bb option
self.assertEqual(sys.flags.bytes_warning, 2) self.assertEqual(sys.flags.bytes_warning, 2)
# -E option # -E option
self.assertTrue(sys.flags.ignore_environment) self.assertTrue(sys.flags.ignore_environment)
""") """)
testname = self.create_test(code=code) testname = self.create_test(code=code)
# Use directly subprocess to control the exact command line
cmd = [sys.executable, cmd = [sys.executable,
"-m", "test", option, "-m", "test", option,
f'--testdir={self.tmptestdir}', f'--testdir={self.tmptestdir}',
@ -2010,11 +2015,10 @@ class ArgsTestCase(BaseTestCase):
text=True) text=True)
self.assertEqual(proc.returncode, 0, proc) self.assertEqual(proc.returncode, 0, proc)
def test_reexec_fast_ci(self): def test_add_python_opts(self):
self.check_reexec("--fast-ci") for opt in ("--fast-ci", "--slow-ci"):
with self.subTest(opt=opt):
def test_reexec_slow_ci(self): self.check_reexec(opt)
self.check_reexec("--slow-ci")
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):