Skip to content

gh-109566: regrtest reexecutes the process #109909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/test/__main__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from test.libregrtest.main import main
main()
main(reexec=True)
5 changes: 5 additions & 0 deletions Lib/test/libregrtest/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def __init__(self, **kwargs) -> None:
self.threshold = None
self.fail_rerun = False
self.tempdir = None
self.no_reexec = False

super().__init__(**kwargs)

Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down
41 changes: 37 additions & 4 deletions Lib/test/libregrtest/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import random
import re
import shlex
import sys
import time

Expand All @@ -20,7 +21,7 @@
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)


Expand All @@ -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
Expand All @@ -69,6 +70,7 @@ def __init__(self, ns: Namespace):
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:
Expand All @@ -95,6 +97,7 @@ def __init__(self, ns: Namespace):
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
Expand Down Expand Up @@ -483,7 +486,37 @@ def run_tests(self, selected: TestTuple, tests: TestList | None) -> int:
# 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)

Expand Down Expand Up @@ -515,7 +548,7 @@ def main(self, tests: TestList | None = None):
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)
58 changes: 57 additions & 1 deletion Lib/test/test_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ def check_ci_mode(self, args, use_resources):
# 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)
Expand Down Expand Up @@ -1960,6 +1961,61 @@ def test_dev_mode(self):
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
regrtest: 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. Patch by Victor Stinner.
2 changes: 1 addition & 1 deletion PCbuild/rt.bat
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ if NOT "%1"=="" (set regrtestargs=%regrtestargs% %1) & shift & goto CheckOpts

if not defined prefix set prefix=%pcbuild%amd64
set exe=%prefix%\python%suffix%.exe
set cmd="%exe%" %dashO% -u -Wd -E -bb -m test %regrtestargs%
set cmd="%exe%" %dashO% -m test %regrtestargs%
if defined qmode goto Qmode

echo Deleting .pyc files ...
Expand Down
10 changes: 3 additions & 7 deletions Tools/scripts/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ def is_python_flag(arg):


def main(regrtest_args):
args = [sys.executable,
'-u', # Unbuffered stdout and stderr
'-W', 'default', # Warnings set to 'default'
'-bb', # Warnings about bytes/bytearray
]
args = [sys.executable]

cross_compile = '_PYTHON_HOST_PLATFORM' in os.environ
if (hostrunner := os.environ.get("_PYTHON_HOSTRUNNER")) is None:
Expand All @@ -47,7 +43,6 @@ def main(regrtest_args):
}
else:
environ = os.environ.copy()
args.append("-E")

# Allow user-specified interpreter options to override our defaults.
args.extend(test.support.args_from_interpreter_flags())
Expand All @@ -70,7 +65,8 @@ def main(regrtest_args):

args.extend(regrtest_args)

print(shlex.join(args))
print(shlex.join(args), flush=True)

if sys.platform == 'win32':
from subprocess import call
sys.exit(call(args))
Expand Down