Skip to content

Commit 9abba71

Browse files
authored
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.
1 parent 87ddfa7 commit 9abba71

File tree

4 files changed

+62
-38
lines changed

4 files changed

+62
-38
lines changed

Lib/test/__main__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
from test.libregrtest.main import main
2-
main(reexec=True)
2+
main(_add_python_opts=True)

Lib/test/libregrtest/cmdline.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def __init__(self, **kwargs) -> None:
184184
self.threshold = None
185185
self.fail_rerun = False
186186
self.tempdir = None
187-
self.no_reexec = False
187+
self._add_python_opts = True
188188

189189
super().__init__(**kwargs)
190190

@@ -344,7 +344,8 @@ def _create_parser():
344344
help='override the working directory for the test run')
345345
group.add_argument('--cleanup', action='store_true',
346346
help='remove old test_python_* directories')
347-
group.add_argument('--no-reexec', action='store_true',
347+
group.add_argument('--dont-add-python-opts', dest='_add_python_opts',
348+
action='store_false',
348349
help="internal option, don't use it")
349350
return parser
350351

@@ -425,7 +426,7 @@ def _parse_args(args, **kwargs):
425426
if MS_WINDOWS:
426427
ns.nowindows = True # Silence alerts under Windows
427428
else:
428-
ns.no_reexec = True
429+
ns._add_python_opts = False
429430

430431
# When both --slow-ci and --fast-ci options are present,
431432
# --slow-ci has the priority

Lib/test/libregrtest/main.py

+42-23
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class Regrtest:
4848
directly to set the values that would normally be set by flags
4949
on the command line.
5050
"""
51-
def __init__(self, ns: Namespace, reexec: bool = False):
51+
def __init__(self, ns: Namespace, _add_python_opts: bool = False):
5252
# Log verbosity
5353
self.verbose: int = int(ns.verbose)
5454
self.quiet: bool = ns.quiet
@@ -70,7 +70,11 @@ def __init__(self, ns: Namespace, reexec: bool = False):
7070
self.want_cleanup: bool = ns.cleanup
7171
self.want_rerun: bool = ns.rerun
7272
self.want_run_leaks: bool = ns.runleaks
73-
self.want_reexec: bool = (reexec and not ns.no_reexec)
73+
74+
ci_mode = (ns.fast_ci or ns.slow_ci)
75+
self.want_add_python_opts: bool = (_add_python_opts
76+
and ns._add_python_opts
77+
and ci_mode)
7478

7579
# Select tests
7680
if ns.match_tests:
@@ -97,7 +101,6 @@ def __init__(self, ns: Namespace, reexec: bool = False):
97101
self.worker_json: StrJSON | None = ns.worker_json
98102

99103
# Options to run tests
100-
self.ci_mode: bool = (ns.fast_ci or ns.slow_ci)
101104
self.fail_fast: bool = ns.failfast
102105
self.fail_env_changed: bool = ns.fail_env_changed
103106
self.fail_rerun: bool = ns.fail_rerun
@@ -486,32 +489,48 @@ def run_tests(self, selected: TestTuple, tests: TestList | None) -> int:
486489
# processes.
487490
return self._run_tests(selected, tests)
488491

489-
def _reexecute_python(self):
490-
if self.python_cmd:
491-
# Do nothing if --python=cmd option is used
492-
return
492+
def _add_python_opts(self):
493+
python_opts = []
494+
495+
# Unbuffered stdout and stderr
496+
if not sys.stdout.write_through:
497+
python_opts.append('-u')
498+
499+
# Add warnings filter 'default'
500+
if 'default' not in sys.warnoptions:
501+
python_opts.extend(('-W', 'default'))
493502

494-
python_opts = [
495-
'-u', # Unbuffered stdout and stderr
496-
'-W', 'default', # Add warnings filter 'default'
497-
'-bb', # Error on bytes/str comparison
498-
'-E', # Ignore PYTHON* environment variables
499-
]
503+
# Error on bytes/str comparison
504+
if sys.flags.bytes_warning < 2:
505+
python_opts.append('-bb')
500506

501-
cmd = [*sys.orig_argv, "--no-reexec"]
507+
# Ignore PYTHON* environment variables
508+
if not sys.flags.ignore_environment:
509+
python_opts.append('-E')
510+
511+
if not python_opts:
512+
return
513+
514+
cmd = [*sys.orig_argv, "--dont-add-python-opts"]
502515
cmd[1:1] = python_opts
503516

504517
# Make sure that messages before execv() are logged
505518
sys.stdout.flush()
506519
sys.stderr.flush()
507520

521+
cmd_text = shlex.join(cmd)
508522
try:
509-
os.execv(cmd[0], cmd)
510-
# execv() do no return and so we don't get to this line on success
511-
except OSError as exc:
512-
cmd_text = shlex.join(cmd)
513-
print_warning(f"Failed to reexecute Python: {exc!r}\n"
523+
if hasattr(os, 'execv') and not MS_WINDOWS:
524+
os.execv(cmd[0], cmd)
525+
# execv() do no return and so we don't get to this line on success
526+
else:
527+
import subprocess
528+
proc = subprocess.run(cmd)
529+
sys.exit(proc.returncode)
530+
except Exception as exc:
531+
print_warning(f"Failed to change Python options: {exc!r}\n"
514532
f"Command: {cmd_text}")
533+
# continue executing main()
515534

516535
def _init(self):
517536
# Set sys.stdout encoder error handler to backslashreplace,
@@ -527,8 +546,8 @@ def _init(self):
527546
self.tmp_dir = get_temp_dir(self.tmp_dir)
528547

529548
def main(self, tests: TestList | None = None):
530-
if self.want_reexec and self.ci_mode:
531-
self._reexecute_python()
549+
if self.want_add_python_opts:
550+
self._add_python_opts()
532551

533552
self._init()
534553

@@ -556,7 +575,7 @@ def main(self, tests: TestList | None = None):
556575
sys.exit(exitcode)
557576

558577

559-
def main(tests=None, reexec=False, **kwargs):
578+
def main(tests=None, _add_python_opts=False, **kwargs):
560579
"""Run the Python suite."""
561580
ns = _parse_args(sys.argv[1:], **kwargs)
562-
Regrtest(ns, reexec=reexec).main(tests=tests)
581+
Regrtest(ns, _add_python_opts=_add_python_opts).main(tests=tests)

Lib/test/test_regrtest.py

+15-11
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,6 @@ def check_ci_mode(self, args, use_resources):
382382
# Check Regrtest attributes which are more reliable than Namespace
383383
# which has an unclear API
384384
regrtest = main.Regrtest(ns)
385-
self.assertTrue(regrtest.ci_mode)
386385
self.assertEqual(regrtest.num_workers, -1)
387386
self.assertTrue(regrtest.want_rerun)
388387
self.assertTrue(regrtest.randomize)
@@ -413,6 +412,11 @@ def test_slow_ci(self):
413412
regrtest = self.check_ci_mode(args, use_resources)
414413
self.assertEqual(regrtest.timeout, 20 * 60)
415414

415+
def test_dont_add_python_opts(self):
416+
args = ['--dont-add-python-opts']
417+
ns = cmdline._parse_args(args)
418+
self.assertFalse(ns._add_python_opts)
419+
416420

417421
@dataclasses.dataclass(slots=True)
418422
class Rerun:
@@ -1984,22 +1988,23 @@ def test_config(self):
19841988
# -E option
19851989
self.assertTrue(config['use_environment'], 0)
19861990
1987-
# test if get_config() is not available
1988-
def test_unbuffered(self):
1991+
def test_python_opts(self):
19891992
# -u option
1990-
self.assertFalse(sys.stdout.line_buffering)
1991-
self.assertFalse(sys.stderr.line_buffering)
1993+
self.assertTrue(sys.__stdout__.write_through)
1994+
self.assertTrue(sys.__stderr__.write_through)
19921995
1993-
def test_python_opts(self):
19941996
# -W default option
19951997
self.assertTrue(sys.warnoptions, ['default'])
1998+
19961999
# -bb option
19972000
self.assertEqual(sys.flags.bytes_warning, 2)
2001+
19982002
# -E option
19992003
self.assertTrue(sys.flags.ignore_environment)
20002004
""")
20012005
testname = self.create_test(code=code)
20022006

2007+
# Use directly subprocess to control the exact command line
20032008
cmd = [sys.executable,
20042009
"-m", "test", option,
20052010
f'--testdir={self.tmptestdir}',
@@ -2010,11 +2015,10 @@ def test_python_opts(self):
20102015
text=True)
20112016
self.assertEqual(proc.returncode, 0, proc)
20122017

2013-
def test_reexec_fast_ci(self):
2014-
self.check_reexec("--fast-ci")
2015-
2016-
def test_reexec_slow_ci(self):
2017-
self.check_reexec("--slow-ci")
2018+
def test_add_python_opts(self):
2019+
for opt in ("--fast-ci", "--slow-ci"):
2020+
with self.subTest(opt=opt):
2021+
self.check_reexec(opt)
20182022

20192023

20202024
class TestUtils(unittest.TestCase):

0 commit comments

Comments
 (0)