Skip to content

Commit f1afef5

Browse files
ambvpablogsal
andauthored
bpo-44708: Only re-run test methods that match names of previously failing test methods (GH-27287)
* 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]>
1 parent 50ffbe3 commit f1afef5

File tree

7 files changed

+345
-178
lines changed

7 files changed

+345
-178
lines changed

Lib/test/libregrtest/cmdline.py

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

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

145178
def error(self, message):
@@ -320,13 +353,7 @@ def resources_list(string):
320353

321354
def _parse_args(args, **kwargs):
322355
# Defaults
323-
ns = argparse.Namespace(testdir=None, verbose=0, quiet=False,
324-
exclude=False, single=False, randomize=False, fromfile=None,
325-
findleaks=1, use_resources=None, trace=False, coverdir='coverage',
326-
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
327-
random_seed=None, use_mp=None, verbose3=False, forever=False,
328-
header=False, failfast=False, match_tests=None, ignore_tests=None,
329-
pgo=False)
356+
ns = Namespace()
330357
for k, v in kwargs.items():
331358
if not hasattr(ns, k):
332359
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
@@ -11,10 +11,10 @@
1111
import unittest
1212
from test.libregrtest.cmdline import _parse_args
1313
from test.libregrtest.runtest import (
14-
findtests, runtest, get_abs_module,
15-
STDTESTS, NOTTESTS, PASSED, FAILED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED,
16-
INTERRUPTED, CHILD_ERROR, TEST_DID_NOT_RUN, TIMEOUT,
17-
PROGRESS_MIN_TIME, format_test_result, is_failed)
14+
findtests, runtest, get_abs_module, is_failed,
15+
STDTESTS, NOTTESTS, PROGRESS_MIN_TIME,
16+
Passed, Failed, EnvChanged, Skipped, ResourceDenied, Interrupted,
17+
ChildError, DidNotRun)
1818
from test.libregrtest.setup import setup_tests
1919
from test.libregrtest.pgo import setup_pgo_tests
2020
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
@@ -314,15 +312,31 @@ def rerun_failed_tests(self):
314312

315313
self.log()
316314
self.log("Re-running failed tests in verbose mode")
317-
self.rerun = self.bad[:]
318-
for test_name in self.rerun:
319-
self.log(f"Re-running {test_name} in verbose mode")
315+
rerun_list = self.rerun[:]
316+
self.rerun = []
317+
for result in rerun_list:
318+
test_name = result.name
319+
errors = result.errors or []
320+
failures = result.failures or []
321+
error_names = [test_full_name.split(" ")[0] for (test_full_name, *_) in errors]
322+
failure_names = [test_full_name.split(" ")[0] for (test_full_name, *_) in failures]
320323
self.ns.verbose = True
324+
orig_match_tests = self.ns.match_tests
325+
if errors or failures:
326+
if self.ns.match_tests is None:
327+
self.ns.match_tests = []
328+
self.ns.match_tests.extend(error_names)
329+
self.ns.match_tests.extend(failure_names)
330+
matching = "matching: " + ", ".join(self.ns.match_tests)
331+
self.log(f"Re-running {test_name} in verbose mode ({matching})")
332+
else:
333+
self.log(f"Re-running {test_name} in verbose mode")
321334
result = runtest(self.ns, test_name)
335+
self.ns.match_tests = orig_match_tests
322336

323337
self.accumulate_result(result, rerun=True)
324338

325-
if result.result == INTERRUPTED:
339+
if isinstance(result, Interrupted):
326340
break
327341

328342
if self.bad:
@@ -383,7 +397,7 @@ def display_result(self):
383397
if self.rerun:
384398
print()
385399
print("%s:" % count(len(self.rerun), "re-run test"))
386-
printlist(self.rerun)
400+
printlist(r.name for r in self.rerun)
387401

388402
if self.run_no_tests:
389403
print()
@@ -423,14 +437,14 @@ def run_tests_sequential(self):
423437
result = runtest(self.ns, test_name)
424438
self.accumulate_result(result)
425439

426-
if result.result == INTERRUPTED:
440+
if isinstance(result, Interrupted):
427441
break
428442

429-
previous_test = format_test_result(result)
443+
previous_test = str(result)
430444
test_time = time.monotonic() - start_time
431445
if test_time >= PROGRESS_MIN_TIME:
432446
previous_test = "%s in %s" % (previous_test, format_duration(test_time))
433-
elif result.result == PASSED:
447+
elif isinstance(result, Passed):
434448
# be quiet: say nothing if the test passed shortly
435449
previous_test = None
436450

0 commit comments

Comments
 (0)