gh-109162: Refactor Regrtest.main() (#109163)

* main() now calls _parse_args() and pass 'ns' to Regrtest
  constructor.  Remove kwargs argument from Regrtest.main().
* _parse_args() checks ns.huntrleaks.
* set_temp_dir() is now responsible to call expanduser().
* Regrtest.main() sets self.tests earlier.
* Add TestTuple and TestList types.
* Rename MatchTests to FilterTuple and rename MatchTestsDict
  to FilterTestDict.
* TestResult.get_rerun_match_tests() return type
  is now FilterTuple: return a tuple instead of a list.
  RunTests.tests type becomes TestTuple.
This commit is contained in:
Victor Stinner 2023-09-09 00:41:26 +02:00 committed by GitHub
parent bcb2ab5ef8
commit 5b7303e265
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 62 deletions

View file

@ -448,4 +448,13 @@ def _parse_args(args, **kwargs):
# --forever implies --failfast # --forever implies --failfast
ns.failfast = True ns.failfast = True
if ns.huntrleaks:
warmup, repetitions, _ = ns.huntrleaks
if warmup < 1 or repetitions < 1:
msg = ("Invalid values for the --huntrleaks/-R parameters. The "
"number of warmups and repetitions must be at least 1 "
"each (1:1).")
print(msg, file=sys.stderr, flush=True)
sys.exit(2)
return ns return ns

View file

@ -9,10 +9,10 @@ import sysconfig
import tempfile import tempfile
import time import time
import unittest import unittest
from test.libregrtest.cmdline import _parse_args from test.libregrtest.cmdline import _parse_args, Namespace
from test.libregrtest.runtest import ( from test.libregrtest.runtest import (
findtests, split_test_packages, runtest, abs_module_name, findtests, split_test_packages, runtest, abs_module_name,
PROGRESS_MIN_TIME, State, MatchTestsDict, RunTests) PROGRESS_MIN_TIME, State, FilterDict, RunTests, TestResult, TestList)
from test.libregrtest.setup import setup_tests from test.libregrtest.setup import setup_tests
from test.libregrtest.pgo import setup_pgo_tests from test.libregrtest.pgo import setup_pgo_tests
from test.libregrtest.utils import (strip_py_suffix, count, format_duration, from test.libregrtest.utils import (strip_py_suffix, count, format_duration,
@ -58,9 +58,9 @@ 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): def __init__(self, ns: Namespace):
# Namespace of command line options # Namespace of command line options
self.ns = None self.ns: Namespace = ns
# tests # tests
self.tests = [] self.tests = []
@ -68,14 +68,14 @@ class Regrtest:
self.all_runtests: list[RunTests] = [] self.all_runtests: list[RunTests] = []
# test results # test results
self.good: list[str] = [] self.good: TestList = []
self.bad: list[str] = [] self.bad: TestList = []
self.rerun_bad: list[str] = [] self.rerun_bad: TestList = []
self.skipped: list[str] = [] self.skipped: TestList = []
self.resource_denied: list[str] = [] self.resource_denied: TestList = []
self.environment_changed: list[str] = [] self.environment_changed: TestList = []
self.run_no_tests: list[str] = [] self.run_no_tests: TestList = []
self.rerun: list[str] = [] self.rerun: TestList = []
self.need_rerun: list[TestResult] = [] self.need_rerun: list[TestResult] = []
self.first_state: str | None = None self.first_state: str | None = None
@ -184,29 +184,7 @@ class Regrtest:
line = f"{line}/{fails}" line = f"{line}/{fails}"
self.log(f"[{line}] {text}") self.log(f"[{line}] {text}")
def parse_args(self, kwargs): def find_tests(self):
ns = _parse_args(sys.argv[1:], **kwargs)
if ns.xmlpath:
support.junit_xml_list = self.testsuite_xml = []
strip_py_suffix(ns.args)
if ns.huntrleaks:
warmup, repetitions, _ = ns.huntrleaks
if warmup < 1 or repetitions < 1:
msg = ("Invalid values for the --huntrleaks/-R parameters. The "
"number of warmups and repetitions must be at least 1 "
"each (1:1).")
print(msg, file=sys.stderr, flush=True)
sys.exit(2)
if ns.tempdir:
ns.tempdir = os.path.expanduser(ns.tempdir)
self.ns = ns
def find_tests(self, tests):
ns = self.ns ns = self.ns
single = ns.single single = ns.single
fromfile = ns.fromfile fromfile = ns.fromfile
@ -216,8 +194,6 @@ class Regrtest:
starting_test = ns.start starting_test = ns.start
randomize = ns.randomize randomize = ns.randomize
self.tests = tests
if single: if single:
self.next_single_filename = os.path.join(self.tmp_dir, 'pynexttest') self.next_single_filename = os.path.join(self.tmp_dir, 'pynexttest')
try: try:
@ -321,7 +297,7 @@ class Regrtest:
print(count(len(skipped), "test"), "skipped:", file=stderr) print(count(len(skipped), "test"), "skipped:", file=stderr)
printlist(skipped, file=stderr) printlist(skipped, file=stderr)
def get_rerun_match(self, rerun_list) -> MatchTestsDict: def get_rerun_match(self, rerun_list) -> FilterDict:
rerun_match_tests = {} rerun_match_tests = {}
for result in rerun_list: for result in rerun_list:
match_tests = result.get_rerun_match_tests() match_tests = result.get_rerun_match_tests()
@ -352,7 +328,7 @@ class Regrtest:
# Re-run failed tests # Re-run failed tests
self.log(f"Re-running {len(tests)} failed tests in verbose mode in subprocesses") self.log(f"Re-running {len(tests)} failed tests in verbose mode in subprocesses")
runtests = RunTests(tests, match_tests=match_tests, rerun=True) runtests = RunTests(tuple(tests), match_tests=match_tests, rerun=True)
self.all_runtests.append(runtests) self.all_runtests.append(runtests)
self._run_tests_mp(runtests) self._run_tests_mp(runtests)
@ -624,7 +600,7 @@ class Regrtest:
tests = self.selected tests = self.selected
self.set_tests(tests) self.set_tests(tests)
runtests = RunTests(tests, forever=self.ns.forever) runtests = RunTests(tuple(tests), forever=self.ns.forever)
self.all_runtests.append(runtests) self.all_runtests.append(runtests)
if self.ns.use_mp: if self.ns.use_mp:
self._run_tests_mp(runtests) self._run_tests_mp(runtests)
@ -737,8 +713,12 @@ class Regrtest:
os.umask(old_mask) os.umask(old_mask)
def set_temp_dir(self): def set_temp_dir(self):
if self.ns.tempdir: ns = self.ns
self.tmp_dir = self.ns.tempdir if ns.tempdir:
ns.tempdir = os.path.expanduser(ns.tempdir)
if ns.tempdir:
self.tmp_dir = ns.tempdir
if not self.tmp_dir: if not self.tmp_dir:
# When tests are run from the Python build directory, it is best practice # When tests are run from the Python build directory, it is best practice
@ -795,14 +775,20 @@ class Regrtest:
print("Remove file: %s" % name) print("Remove file: %s" % name)
os_helper.unlink(name) os_helper.unlink(name)
def main(self, tests=None, **kwargs): def main(self, tests: TestList | None = None):
self.parse_args(kwargs) ns = self.ns
self.tests = tests
if ns.xmlpath:
support.junit_xml_list = self.testsuite_xml = []
strip_py_suffix(ns.args)
self.set_temp_dir() self.set_temp_dir()
self.fix_umask() self.fix_umask()
if self.ns.cleanup: if ns.cleanup:
self.cleanup() self.cleanup()
sys.exit(0) sys.exit(0)
@ -817,9 +803,9 @@ class Regrtest:
# When using multiprocessing, worker processes will use test_cwd # When using multiprocessing, worker processes will use test_cwd
# as their parent temporary directory. So when the main process # as their parent temporary directory. So when the main process
# exit, it removes also subdirectories of worker processes. # exit, it removes also subdirectories of worker processes.
self.ns.tempdir = test_cwd ns.tempdir = test_cwd
self._main(tests, kwargs) self._main()
except SystemExit as exc: except SystemExit as exc:
# bpo-38203: Python can hang at exit in Py_Finalize(), especially # bpo-38203: Python can hang at exit in Py_Finalize(), especially
# on threading._shutdown() call: put a timeout # on threading._shutdown() call: put a timeout
@ -862,7 +848,7 @@ class Regrtest:
self.display_summary() self.display_summary()
self.finalize() self.finalize()
def _main(self, tests, kwargs): def _main(self):
if self.is_worker(): if self.is_worker():
from test.libregrtest.runtest_mp import run_tests_worker from test.libregrtest.runtest_mp import run_tests_worker
run_tests_worker(self.ns.worker_args) run_tests_worker(self.ns.worker_args)
@ -872,7 +858,7 @@ class Regrtest:
input("Press any key to continue...") input("Press any key to continue...")
setup_tests(self.ns) setup_tests(self.ns)
self.find_tests(tests) self.find_tests()
exitcode = 0 exitcode = 0
if self.ns.list_tests: if self.ns.list_tests:
@ -888,4 +874,5 @@ class Regrtest:
def main(tests=None, **kwargs): def main(tests=None, **kwargs):
"""Run the Python suite.""" """Run the Python suite."""
Regrtest().main(tests=tests, **kwargs) ns = _parse_args(sys.argv[1:], **kwargs)
Regrtest(ns).main(tests=tests)

View file

@ -19,8 +19,13 @@ from test.libregrtest.save_env import saved_test_environment
from test.libregrtest.utils import clear_caches, format_duration, print_warning from test.libregrtest.utils import clear_caches, format_duration, print_warning
MatchTests = list[str] TestTuple = list[str]
MatchTestsDict = dict[str, MatchTests] TestList = list[str]
# --match and --ignore options: list of patterns
# ('*' joker character can be used)
FilterTuple = tuple[str, ...]
FilterDict = dict[str, FilterTuple]
# Avoid enum.Enum to reduce the number of imports when tests are run # Avoid enum.Enum to reduce the number of imports when tests are run
@ -174,7 +179,7 @@ class TestResult:
return True return True
return False return False
def get_rerun_match_tests(self): def get_rerun_match_tests(self) -> FilterTuple | None:
match_tests = [] match_tests = []
errors = self.errors or [] errors = self.errors or []
@ -195,29 +200,30 @@ class TestResult:
return None return None
match_tests.append(match_name) match_tests.append(match_name)
return match_tests if not match_tests:
return None
return tuple(match_tests)
@dataclasses.dataclass(slots=True, frozen=True) @dataclasses.dataclass(slots=True, frozen=True)
class RunTests: class RunTests:
tests: list[str] tests: TestTuple
match_tests: MatchTestsDict | None = None match_tests: FilterDict | None = None
rerun: bool = False rerun: bool = False
forever: bool = False forever: bool = False
def get_match_tests(self, test_name) -> MatchTests | None: def get_match_tests(self, test_name) -> FilterTuple | None:
if self.match_tests is not None: if self.match_tests is not None:
return self.match_tests.get(test_name, None) return self.match_tests.get(test_name, None)
else: else:
return None return None
def iter_tests(self): def iter_tests(self):
tests = tuple(self.tests)
if self.forever: if self.forever:
while True: while True:
yield from tests yield from self.tests
else: else:
yield from tests yield from self.tests
# Minimum duration of a test to display its duration or to mention that # Minimum duration of a test to display its duration or to mention that

View file

@ -20,7 +20,7 @@ from test.libregrtest.cmdline import Namespace
from test.libregrtest.main import Regrtest from test.libregrtest.main import Regrtest
from test.libregrtest.runtest import ( from test.libregrtest.runtest import (
runtest, TestResult, State, PROGRESS_MIN_TIME, runtest, TestResult, State, PROGRESS_MIN_TIME,
MatchTests, RunTests) FilterTuple, RunTests)
from test.libregrtest.setup import setup_tests from test.libregrtest.setup import setup_tests
from test.libregrtest.utils import format_duration, print_warning from test.libregrtest.utils import format_duration, print_warning
@ -49,7 +49,7 @@ class WorkerJob:
test_name: str test_name: str
namespace: Namespace namespace: Namespace
rerun: bool = False rerun: bool = False
match_tests: MatchTests | None = None match_tests: FilterTuple | None = None
class _EncodeWorkerJob(json.JSONEncoder): class _EncodeWorkerJob(json.JSONEncoder):