gh-109162: Regrtest copies 'ns' attributes (#109168)

* Regrtest.__init__() now copies 'ns' namespace attributes to
  Regrtest attributes. Regrtest match_tests and ignore_tests
  attributes have type FilterTuple (tuple), instead of a list.
* Add RunTests.copy(). Regrtest._rerun_failed_tests() now uses
  RunTests.copy().
* Replace Regrtest.all_tests (list) with Regrtest.first_runtests
  (RunTests).
* Make random_seed maximum 10x larger (9 digits, instead of 8).
This commit is contained in:
Victor Stinner 2023-09-09 01:48:54 +02:00 committed by GitHub
parent 5b7303e265
commit ac8409b38b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 48 deletions

View file

@ -12,7 +12,8 @@ import unittest
from test.libregrtest.cmdline import _parse_args, Namespace 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, FilterDict, RunTests, TestResult, TestList) PROGRESS_MIN_TIME, State, RunTests, TestResult,
FilterTuple, FilterDict, 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,
@ -62,10 +63,35 @@ class Regrtest:
# Namespace of command line options # Namespace of command line options
self.ns: Namespace = ns self.ns: Namespace = ns
# Actions
self.want_header = ns.header
self.want_list_tests = ns.list_tests
self.want_list_cases = ns.list_cases
self.want_wait = ns.wait
self.want_cleanup = ns.cleanup
# Select tests
if ns.match_tests:
self.match_tests: FilterTuple = tuple(ns.match_tests)
else:
self.match_tests = None
if ns.ignore_tests:
self.ignore_tests: FilterTuple = tuple(ns.ignore_tests)
else:
self.ignore_tests = None
self.exclude = ns.exclude
self.fromfile = ns.fromfile
self.starting_test = ns.start
# Options to run tests
self.forever = ns.forever
self.randomize = ns.randomize
self.random_seed = ns.random_seed
# tests # tests
self.tests = [] self.tests = []
self.selected = [] self.selected = []
self.all_runtests: list[RunTests] = [] self.first_runtests: RunTests | None = None
# test results # test results
self.good: TestList = [] self.good: TestList = []
@ -187,12 +213,8 @@ class Regrtest:
def find_tests(self): def find_tests(self):
ns = self.ns ns = self.ns
single = ns.single single = ns.single
fromfile = ns.fromfile
pgo = ns.pgo pgo = ns.pgo
exclude = ns.exclude
test_dir = ns.testdir test_dir = ns.testdir
starting_test = ns.start
randomize = ns.randomize
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')
@ -203,12 +225,12 @@ class Regrtest:
except OSError: except OSError:
pass pass
if fromfile: if self.fromfile:
self.tests = [] self.tests = []
# regex to match 'test_builtin' in line: # regex to match 'test_builtin' in line:
# '0:00:00 [ 4/400] test_builtin -- test_dict took 1 sec' # '0:00:00 [ 4/400] test_builtin -- test_dict took 1 sec'
regex = re.compile(r'\btest_[a-zA-Z0-9_]+\b') regex = re.compile(r'\btest_[a-zA-Z0-9_]+\b')
with open(os.path.join(os_helper.SAVEDCWD, fromfile)) as fp: with open(os.path.join(os_helper.SAVEDCWD, self.fromfile)) as fp:
for line in fp: for line in fp:
line = line.split('#', 1)[0] line = line.split('#', 1)[0]
line = line.strip() line = line.strip()
@ -223,14 +245,14 @@ class Regrtest:
setup_pgo_tests(ns) setup_pgo_tests(ns)
exclude_tests = set() exclude_tests = set()
if exclude: if self.exclude:
for arg in ns.args: for arg in ns.args:
exclude_tests.add(arg) exclude_tests.add(arg)
ns.args = [] ns.args = []
alltests = findtests(testdir=test_dir, exclude=exclude_tests) alltests = findtests(testdir=test_dir, exclude=exclude_tests)
if not fromfile: if not self.fromfile:
self.selected = self.tests or ns.args self.selected = self.tests or ns.args
if self.selected: if self.selected:
self.selected = split_test_packages(self.selected) self.selected = split_test_packages(self.selected)
@ -248,17 +270,17 @@ class Regrtest:
pass pass
# Remove all the selected tests that precede start if it's set. # Remove all the selected tests that precede start if it's set.
if starting_test: if self.starting_test:
try: try:
del self.selected[:self.selected.index(starting_test)] del self.selected[:self.selected.index(self.starting_test)]
except ValueError: except ValueError:
print(f"Cannot find starting test: {starting_test}") print(f"Cannot find starting test: {self.starting_test}")
sys.exit(1) sys.exit(1)
if randomize: if self.randomize:
if ns.random_seed is None: if self.random_seed is None:
ns.random_seed = random.randrange(10000000) self.random_seed = random.randrange(100_000_000)
random.seed(ns.random_seed) random.seed(self.random_seed)
random.shuffle(self.selected) random.shuffle(self.selected)
def list_tests(self): def list_tests(self):
@ -279,7 +301,7 @@ class Regrtest:
ns = self.ns ns = self.ns
test_dir = ns.testdir test_dir = ns.testdir
support.verbose = False support.verbose = False
support.set_match_tests(ns.match_tests, ns.ignore_tests) support.set_match_tests(self.match_tests, self.ignore_tests)
skipped = [] skipped = []
for test_name in self.selected: for test_name in self.selected:
@ -306,20 +328,18 @@ class Regrtest:
rerun_match_tests[result.test_name] = match_tests rerun_match_tests[result.test_name] = match_tests
return rerun_match_tests return rerun_match_tests
def _rerun_failed_tests(self, need_rerun): def _rerun_failed_tests(self, need_rerun, runtests: RunTests):
# Configure the runner to re-run tests # Configure the runner to re-run tests
ns = self.ns ns = self.ns
ns.verbose = True ns.verbose = True
ns.failfast = False ns.failfast = False
ns.verbose3 = False ns.verbose3 = False
ns.forever = False
if ns.use_mp is None: if ns.use_mp is None:
ns.use_mp = 1 ns.use_mp = 1
# Get tests to re-run # Get tests to re-run
tests = [result.test_name for result in need_rerun] tests = [result.test_name for result in need_rerun]
match_tests = self.get_rerun_match(need_rerun) match_tests = self.get_rerun_match(need_rerun)
self.set_tests(tests)
# Clear previously failed tests # Clear previously failed tests
self.rerun_bad.extend(self.bad) self.rerun_bad.extend(self.bad)
@ -328,11 +348,14 @@ 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(tuple(tests), match_tests=match_tests, rerun=True) runtests = runtests.copy(tests=tuple(tests),
self.all_runtests.append(runtests) match_tests=match_tests,
rerun=True,
forever=False)
self.set_tests(runtests)
self._run_tests_mp(runtests) self._run_tests_mp(runtests)
def rerun_failed_tests(self, need_rerun): def rerun_failed_tests(self, need_rerun, runtests: RunTests):
if self.ns.python: if self.ns.python:
# Temp patch for https://github.com/python/cpython/issues/94052 # Temp patch for https://github.com/python/cpython/issues/94052
self.log( self.log(
@ -344,7 +367,7 @@ class Regrtest:
self.first_state = self.get_tests_state() self.first_state = self.get_tests_state()
print() print()
self._rerun_failed_tests(need_rerun) self._rerun_failed_tests(need_rerun, runtests)
if self.bad: if self.bad:
print(count(len(self.bad), 'test'), "failed again:") print(count(len(self.bad), 'test'), "failed again:")
@ -572,9 +595,9 @@ class Regrtest:
self.win_load_tracker.close() self.win_load_tracker.close()
self.win_load_tracker = None self.win_load_tracker = None
def set_tests(self, tests): def set_tests(self, runtests: RunTests):
self.tests = tests self.tests = runtests.tests
if self.ns.forever: if runtests.forever:
self.test_count_text = '' self.test_count_text = ''
self.test_count_width = 3 self.test_count_width = 3
else: else:
@ -583,7 +606,7 @@ class Regrtest:
def run_tests(self): def run_tests(self):
# For a partial run, we do not need to clutter the output. # For a partial run, we do not need to clutter the output.
if (self.ns.header if (self.want_header
or not(self.ns.pgo or self.ns.quiet or self.ns.single or not(self.ns.pgo or self.ns.quiet or self.ns.single
or self.tests or self.ns.args)): or self.tests or self.ns.args)):
self.display_header() self.display_header()
@ -595,17 +618,18 @@ class Regrtest:
"3 warmup repetitions can give false positives!") "3 warmup repetitions can give false positives!")
print(msg, file=sys.stdout, flush=True) print(msg, file=sys.stdout, flush=True)
if self.ns.randomize: if self.randomize:
print("Using random seed", self.ns.random_seed) print("Using random seed", self.random_seed)
tests = self.selected tests = self.selected
self.set_tests(tests) runtests = RunTests(tuple(tests), forever=self.forever)
runtests = RunTests(tuple(tests), forever=self.ns.forever) self.first_runtests = runtests
self.all_runtests.append(runtests) self.set_tests(runtests)
if self.ns.use_mp: if self.ns.use_mp:
self._run_tests_mp(runtests) self._run_tests_mp(runtests)
else: else:
self.run_tests_sequentially(runtests) self.run_tests_sequentially(runtests)
return runtests
def finalize(self): def finalize(self):
if self.next_single_filename: if self.next_single_filename:
@ -627,11 +651,7 @@ class Regrtest:
def display_summary(self): def display_summary(self):
duration = time.perf_counter() - self.start_time duration = time.perf_counter() - self.start_time
first_runtests = self.all_runtests[0] filtered = bool(self.match_tests) or bool(self.ignore_tests)
# the second runtests (re-run failed tests) disables forever,
# use the first runtests
forever = first_runtests.forever
filtered = bool(self.ns.match_tests) or bool(self.ns.ignore_tests)
# Total duration # Total duration
print() print()
@ -655,8 +675,8 @@ class Regrtest:
self.environment_changed, self.run_no_tests] self.environment_changed, self.run_no_tests]
run = sum(map(len, all_tests)) run = sum(map(len, all_tests))
text = f'run={run}' text = f'run={run}'
if not forever: if not self.first_runtests.forever:
ntest = len(first_runtests.tests) ntest = len(self.first_runtests.tests)
text = f"{text}/{ntest}" text = f"{text}/{ntest}"
if filtered: if filtered:
text = f"{text} (filtered)" text = f"{text} (filtered)"
@ -788,7 +808,7 @@ class Regrtest:
self.fix_umask() self.fix_umask()
if ns.cleanup: if self.want_cleanup:
self.cleanup() self.cleanup()
sys.exit(0) sys.exit(0)
@ -838,12 +858,12 @@ class Regrtest:
return exitcode return exitcode
def action_run_tests(self): def action_run_tests(self):
self.run_tests() runtests = self.run_tests()
self.display_result() self.display_result()
need_rerun = self.need_rerun need_rerun = self.need_rerun
if self.ns.rerun and need_rerun: if self.ns.rerun and need_rerun:
self.rerun_failed_tests(need_rerun) self.rerun_failed_tests(need_rerun, runtests)
self.display_summary() self.display_summary()
self.finalize() self.finalize()
@ -854,16 +874,16 @@ class Regrtest:
run_tests_worker(self.ns.worker_args) run_tests_worker(self.ns.worker_args)
return return
if self.ns.wait: if self.want_wait:
input("Press any key to continue...") input("Press any key to continue...")
setup_tests(self.ns) setup_tests(self.ns)
self.find_tests() self.find_tests()
exitcode = 0 exitcode = 0
if self.ns.list_tests: if self.want_list_tests:
self.list_tests() self.list_tests()
elif self.ns.list_cases: elif self.want_list_cases:
self.list_cases() self.list_cases()
else: else:
self.action_run_tests() self.action_run_tests()

View file

@ -212,6 +212,11 @@ class RunTests:
rerun: bool = False rerun: bool = False
forever: bool = False forever: bool = False
def copy(self, **override):
state = dataclasses.asdict(self)
state.update(override)
return RunTests(**state)
def get_match_tests(self, test_name) -> FilterTuple | 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)

View file

@ -589,7 +589,7 @@ class BaseTestCase(unittest.TestCase):
def parse_random_seed(self, output): def parse_random_seed(self, output):
match = self.regex_search(r'Using random seed ([0-9]+)', output) match = self.regex_search(r'Using random seed ([0-9]+)', output)
randseed = int(match.group(1)) randseed = int(match.group(1))
self.assertTrue(0 <= randseed <= 10000000, randseed) self.assertTrue(0 <= randseed <= 100_000_000, randseed)
return randseed return randseed
def run_command(self, args, input=None, exitcode=0, **kw): def run_command(self, args, input=None, exitcode=0, **kw):