Skip to content

Commit 0f55d21

Browse files
authored
[3.9] bpo-44708: Only re-run test methods that match names of previously failing test methods (GH-27287) (GH-27293)
* Move to a static argparse.Namespace subclass * Roughly annotate runtest.py * Refactor libregrtest to use lossless test result objects * Only re-run test methods that match names of previously failing test methods * Adopt tests to cover test method name matching Co-authored-by: Pablo Galindo Salgado <[email protected]>. (cherry picked from commit f1afef5) Co-authored-by: Łukasz Langa <[email protected]>
1 parent 5ffbb05 commit 0f55d21

File tree

7 files changed

+346
-174
lines changed

7 files changed

+346
-174
lines changed

Lib/test/libregrtest/cmdline.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,39 @@
139139
# default (see bpo-30822).
140140
RESOURCE_NAMES = ALL_RESOURCES + ('extralargefile', 'tzdata')
141141

142+
143+
class Namespace(argparse.Namespace):
144+
def __init__(self, **kwargs) -> None:
145+
self.testdir = None
146+
self.verbose = 0
147+
self.quiet = False
148+
self.exclude = False
149+
self.single = False
150+
self.randomize = False
151+
self.fromfile = None
152+
self.findleaks = 1
153+
self.fail_env_changed = False
154+
self.use_resources = None
155+
self.trace = False
156+
self.coverdir = 'coverage'
157+
self.runleaks = False
158+
self.huntrleaks = False
159+
self.verbose2 = False
160+
self.verbose3 = False
161+
self.print_slow = False
162+
self.random_seed = None
163+
self.use_mp = None
164+
self.forever = False
165+
self.header = False
166+
self.failfast = False
167+
self.match_tests = None
168+
self.ignore_tests = None
169+
self.pgo = False
170+
self.pgo_extended = False
171+
172+
super().__init__(**kwargs)
173+
174+
142175
class _ArgParser(argparse.ArgumentParser):
143176

144177
def error(self, message):
@@ -319,13 +352,7 @@ def resources_list(string):
319352

320353
def _parse_args(args, **kwargs):
321354
# Defaults
322-
ns = argparse.Namespace(testdir=None, verbose=0, quiet=False,
323-
exclude=False, single=False, randomize=False, fromfile=None,
324-
findleaks=1, use_resources=None, trace=False, coverdir='coverage',
325-
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
326-
random_seed=None, use_mp=None, verbose3=False, forever=False,
327-
header=False, failfast=False, match_tests=None, ignore_tests=None,
328-
pgo=False)
355+
ns = Namespace()
329356
for k, v in kwargs.items():
330357
if not hasattr(ns, k):
331358
raise TypeError('%r is an invalid keyword argument '

Lib/test/libregrtest/main.py

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
import unittest
1313
from test.libregrtest.cmdline import _parse_args
1414
from test.libregrtest.runtest import (
15-
findtests, runtest, get_abs_module,
16-
STDTESTS, NOTTESTS, PASSED, FAILED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED,
17-
INTERRUPTED, CHILD_ERROR, TEST_DID_NOT_RUN, TIMEOUT,
18-
PROGRESS_MIN_TIME, format_test_result, is_failed)
15+
findtests, runtest, get_abs_module, is_failed,
16+
STDTESTS, NOTTESTS, PROGRESS_MIN_TIME,
17+
Passed, Failed, EnvChanged, Skipped, ResourceDenied, Interrupted,
18+
ChildError, DidNotRun)
1919
from test.libregrtest.setup import setup_tests
2020
from test.libregrtest.pgo import setup_pgo_tests
2121
from test.libregrtest.utils import removepy, count, format_duration, printlist
@@ -99,34 +99,32 @@ def get_executed(self):
9999
| set(self.run_no_tests))
100100

101101
def accumulate_result(self, result, rerun=False):
102-
test_name = result.test_name
103-
ok = result.result
102+
test_name = result.name
104103

105-
if ok not in (CHILD_ERROR, INTERRUPTED) and not rerun:
106-
self.test_times.append((result.test_time, test_name))
104+
if not isinstance(result, (ChildError, Interrupted)) and not rerun:
105+
self.test_times.append((result.duration_sec, test_name))
107106

108-
if ok == PASSED:
107+
if isinstance(result, Passed):
109108
self.good.append(test_name)
110-
elif ok in (FAILED, CHILD_ERROR):
111-
if not rerun:
112-
self.bad.append(test_name)
113-
elif ok == ENV_CHANGED:
114-
self.environment_changed.append(test_name)
115-
elif ok == SKIPPED:
116-
self.skipped.append(test_name)
117-
elif ok == RESOURCE_DENIED:
109+
elif isinstance(result, ResourceDenied):
118110
self.skipped.append(test_name)
119111
self.resource_denieds.append(test_name)
120-
elif ok == TEST_DID_NOT_RUN:
112+
elif isinstance(result, Skipped):
113+
self.skipped.append(test_name)
114+
elif isinstance(result, EnvChanged):
115+
self.environment_changed.append(test_name)
116+
elif isinstance(result, Failed):
117+
if not rerun:
118+
self.bad.append(test_name)
119+
self.rerun.append(result)
120+
elif isinstance(result, DidNotRun):
121121
self.run_no_tests.append(test_name)
122-
elif ok == INTERRUPTED:
122+
elif isinstance(result, Interrupted):
123123
self.interrupted = True
124-
elif ok == TIMEOUT:
125-
self.bad.append(test_name)
126124
else:
127-
raise ValueError("invalid test result: %r" % ok)
125+
raise ValueError("invalid test result: %r" % result)
128126

129-
if rerun and ok not in {FAILED, CHILD_ERROR, INTERRUPTED}:
127+
if rerun and not isinstance(result, (Failed, Interrupted)):
130128
self.bad.remove(test_name)
131129

132130
xml_data = result.xml_data
@@ -311,15 +309,31 @@ def rerun_failed_tests(self):
311309

312310
self.log()
313311
self.log("Re-running failed tests in verbose mode")
314-
self.rerun = self.bad[:]
315-
for test_name in self.rerun:
316-
self.log(f"Re-running {test_name} in verbose mode")
312+
rerun_list = self.rerun[:]
313+
self.rerun = []
314+
for result in rerun_list:
315+
test_name = result.name
316+
errors = result.errors or []
317+
failures = result.failures or []
318+
error_names = [test_full_name.split(" ")[0] for (test_full_name, *_) in errors]
319+
failure_names = [test_full_name.split(" ")[0] for (test_full_name, *_) in failures]
317320
self.ns.verbose = True
321+
orig_match_tests = self.ns.match_tests
322+
if errors or failures:
323+
if self.ns.match_tests is None:
324+
self.ns.match_tests = []
325+
self.ns.match_tests.extend(error_names)
326+
self.ns.match_tests.extend(failure_names)
327+
matching = "matching: " + ", ".join(self.ns.match_tests)
328+
self.log(f"Re-running {test_name} in verbose mode ({matching})")
329+
else:
330+
self.log(f"Re-running {test_name} in verbose mode")
318331
result = runtest(self.ns, test_name)
332+
self.ns.match_tests = orig_match_tests
319333

320334
self.accumulate_result(result, rerun=True)
321335

322-
if result.result == INTERRUPTED:
336+
if isinstance(result, Interrupted):
323337
break
324338

325339
if self.bad:
@@ -380,7 +394,7 @@ def display_result(self):
380394
if self.rerun:
381395
print()
382396
print("%s:" % count(len(self.rerun), "re-run test"))
383-
printlist(self.rerun)
397+
printlist(r.name for r in self.rerun)
384398

385399
if self.run_no_tests:
386400
print()
@@ -420,14 +434,14 @@ def run_tests_sequential(self):
420434
result = runtest(self.ns, test_name)
421435
self.accumulate_result(result)
422436

423-
if result.result == INTERRUPTED:
437+
if isinstance(result, Interrupted):
424438
break
425439

426-
previous_test = format_test_result(result)
440+
previous_test = str(result)
427441
test_time = time.monotonic() - start_time
428442
if test_time >= PROGRESS_MIN_TIME:
429443
previous_test = "%s in %s" % (previous_test, format_duration(test_time))
430-
elif result.result == PASSED:
444+
elif isinstance(result, Passed):
431445
# be quiet: say nothing if the test passed shortly
432446
previous_test = None
433447

0 commit comments

Comments
 (0)