Skip to content

Commit 14ef8cf

Browse files
sobolevnJelleZijlstraambv
authored andcommitted
bpo-46523: fix tests rerun when setUp[Class|Module] fails (python#30895)
Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 9953860)
1 parent c5bfed5 commit 14ef8cf

File tree

3 files changed

+184
-3
lines changed

3 files changed

+184
-3
lines changed

Lib/test/libregrtest/main.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@
2929
# Must be smaller than buildbot "1200 seconds without output" limit.
3030
EXIT_TIMEOUT = 120.0
3131

32+
# gh-90681: When rerunning tests, we might need to rerun the whole
33+
# class or module suite if some its life-cycle hooks fail.
34+
# Test level hooks are not affected.
35+
_TEST_LIFECYCLE_HOOKS = frozenset((
36+
'setUpClass', 'tearDownClass',
37+
'setUpModule', 'tearDownModule',
38+
))
39+
3240
EXITCODE_BAD_TEST = 2
3341
EXITCODE_INTERRUPTED = 130
3442
EXITCODE_ENV_CHANGED = 3
@@ -337,8 +345,12 @@ def rerun_failed_tests(self):
337345

338346
errors = result.errors or []
339347
failures = result.failures or []
340-
error_names = [test_full_name.split(" ")[0] for (test_full_name, *_) in errors]
341-
failure_names = [test_full_name.split(" ")[0] for (test_full_name, *_) in failures]
348+
error_names = [
349+
self.normalize_test_name(test_full_name, is_error=True)
350+
for (test_full_name, *_) in errors]
351+
failure_names = [
352+
self.normalize_test_name(test_full_name)
353+
for (test_full_name, *_) in failures]
342354
self.ns.verbose = True
343355
orig_match_tests = self.ns.match_tests
344356
if errors or failures:
@@ -364,6 +376,21 @@ def rerun_failed_tests(self):
364376

365377
self.display_result()
366378

379+
def normalize_test_name(self, test_full_name, *, is_error=False):
380+
short_name = test_full_name.split(" ")[0]
381+
if is_error and short_name in _TEST_LIFECYCLE_HOOKS:
382+
# This means that we have a failure in a life-cycle hook,
383+
# we need to rerun the whole module or class suite.
384+
# Basically the error looks like this:
385+
# ERROR: setUpClass (test.test_reg_ex.RegTest)
386+
# or
387+
# ERROR: setUpModule (test.test_reg_ex)
388+
# So, we need to parse the class / module name.
389+
lpar = test_full_name.index('(')
390+
rpar = test_full_name.index(')')
391+
return test_full_name[lpar + 1: rpar].split('.')[-1]
392+
return short_name
393+
367394
def display_result(self):
368395
# If running the test suite for PGO then no one cares about results.
369396
if self.ns.pgo:

Lib/test/support/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,7 @@ def _run_suite(suite):
10971097
if junit_xml_list is not None:
10981098
junit_xml_list.append(result.get_xml_element())
10991099

1100-
if not result.testsRun and not result.skipped:
1100+
if not result.testsRun and not result.skipped and not result.errors:
11011101
raise TestDidNotRun
11021102
if not result.wasSuccessful():
11031103
if len(result.errors) == 1 and not result.failures:

Lib/test/test_regrtest.py

+154
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,160 @@ def test_fail_once(self):
11221122
self.check_executed_tests(output, [testname],
11231123
rerun={testname: "test_fail_once"})
11241124

1125+
def test_rerun_setup_class_hook_failure(self):
1126+
# FAILURE then FAILURE
1127+
code = textwrap.dedent("""
1128+
import unittest
1129+
1130+
class ExampleTests(unittest.TestCase):
1131+
@classmethod
1132+
def setUpClass(self):
1133+
raise RuntimeError('Fail')
1134+
1135+
def test_success(self):
1136+
return
1137+
""")
1138+
testname = self.create_test(code=code)
1139+
1140+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1141+
self.check_executed_tests(output, testname,
1142+
failed=[testname],
1143+
rerun={testname: "ExampleTests"})
1144+
1145+
def test_rerun_teardown_class_hook_failure(self):
1146+
# FAILURE then FAILURE
1147+
code = textwrap.dedent("""
1148+
import unittest
1149+
1150+
class ExampleTests(unittest.TestCase):
1151+
@classmethod
1152+
def tearDownClass(self):
1153+
raise RuntimeError('Fail')
1154+
1155+
def test_success(self):
1156+
return
1157+
""")
1158+
testname = self.create_test(code=code)
1159+
1160+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1161+
self.check_executed_tests(output, testname,
1162+
failed=[testname],
1163+
rerun={testname: "ExampleTests"})
1164+
1165+
def test_rerun_setup_module_hook_failure(self):
1166+
# FAILURE then FAILURE
1167+
code = textwrap.dedent("""
1168+
import unittest
1169+
1170+
def setUpModule():
1171+
raise RuntimeError('Fail')
1172+
1173+
class ExampleTests(unittest.TestCase):
1174+
def test_success(self):
1175+
return
1176+
""")
1177+
testname = self.create_test(code=code)
1178+
1179+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1180+
self.check_executed_tests(output, testname,
1181+
failed=[testname],
1182+
rerun={testname: testname})
1183+
1184+
def test_rerun_teardown_module_hook_failure(self):
1185+
# FAILURE then FAILURE
1186+
code = textwrap.dedent("""
1187+
import unittest
1188+
1189+
def tearDownModule():
1190+
raise RuntimeError('Fail')
1191+
1192+
class ExampleTests(unittest.TestCase):
1193+
def test_success(self):
1194+
return
1195+
""")
1196+
testname = self.create_test(code=code)
1197+
1198+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1199+
self.check_executed_tests(output, testname,
1200+
failed=[testname],
1201+
rerun={testname: testname})
1202+
1203+
def test_rerun_setup_hook_failure(self):
1204+
# FAILURE then FAILURE
1205+
code = textwrap.dedent("""
1206+
import unittest
1207+
1208+
class ExampleTests(unittest.TestCase):
1209+
def setUp(self):
1210+
raise RuntimeError('Fail')
1211+
1212+
def test_success(self):
1213+
return
1214+
""")
1215+
testname = self.create_test(code=code)
1216+
1217+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1218+
self.check_executed_tests(output, testname,
1219+
failed=[testname],
1220+
rerun={testname: "test_success"})
1221+
1222+
def test_rerun_teardown_hook_failure(self):
1223+
# FAILURE then FAILURE
1224+
code = textwrap.dedent("""
1225+
import unittest
1226+
1227+
class ExampleTests(unittest.TestCase):
1228+
def tearDown(self):
1229+
raise RuntimeError('Fail')
1230+
1231+
def test_success(self):
1232+
return
1233+
""")
1234+
testname = self.create_test(code=code)
1235+
1236+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1237+
self.check_executed_tests(output, testname,
1238+
failed=[testname],
1239+
rerun={testname: "test_success"})
1240+
1241+
def test_rerun_async_setup_hook_failure(self):
1242+
# FAILURE then FAILURE
1243+
code = textwrap.dedent("""
1244+
import unittest
1245+
1246+
class ExampleTests(unittest.IsolatedAsyncioTestCase):
1247+
async def asyncSetUp(self):
1248+
raise RuntimeError('Fail')
1249+
1250+
async def test_success(self):
1251+
return
1252+
""")
1253+
testname = self.create_test(code=code)
1254+
1255+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1256+
self.check_executed_tests(output, testname,
1257+
failed=[testname],
1258+
rerun={testname: "test_success"})
1259+
1260+
def test_rerun_async_teardown_hook_failure(self):
1261+
# FAILURE then FAILURE
1262+
code = textwrap.dedent("""
1263+
import unittest
1264+
1265+
class ExampleTests(unittest.IsolatedAsyncioTestCase):
1266+
async def asyncTearDown(self):
1267+
raise RuntimeError('Fail')
1268+
1269+
async def test_success(self):
1270+
return
1271+
""")
1272+
testname = self.create_test(code=code)
1273+
1274+
output = self.run_tests("-w", testname, exitcode=EXITCODE_BAD_TEST)
1275+
self.check_executed_tests(output, testname,
1276+
failed=[testname],
1277+
rerun={testname: "test_success"})
1278+
11251279
def test_no_tests_ran(self):
11261280
code = textwrap.dedent("""
11271281
import unittest

0 commit comments

Comments
 (0)