Skip to content

Commit 7202313

Browse files
[3.14] gh-120665: make unittest loaders avoid loading test cases that are abstract base classes (GH-120666) (#151600)
gh-120665: make unittest loaders avoid loading test cases that are abstract base classes (GH-120666) (cherry picked from commit 5ad3c6d) Co-authored-by: blhsing <blhsing@gmail.com>
1 parent a565b86 commit 7202313

3 files changed

Lines changed: 61 additions & 2 deletions

File tree

Lib/test/test_unittest/test_loader.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import abc
12
import functools
23
import sys
34
import types
@@ -98,6 +99,22 @@ def test_loadTestsFromTestCase__from_FunctionTestCase(self):
9899
self.assertIsInstance(suite, loader.suiteClass)
99100
self.assertEqual(list(suite), [])
100101

102+
# "Do not load any tests from a TestCase-derived class that is an abstract
103+
# base class."
104+
def test_loadTestsFromTestCase__from_abc_TestCase(self):
105+
class FooBase(unittest.TestCase, metaclass=abc.ABCMeta):
106+
@abc.abstractmethod
107+
def test(self): ...
108+
class Foo(FooBase):
109+
def test(self): pass
110+
111+
empty_suite = unittest.TestSuite()
112+
113+
loader = unittest.TestLoader()
114+
suite = loader.loadTestsFromTestCase(Foo)
115+
self.assertEqual(loader.loadTestsFromTestCase(FooBase), empty_suite)
116+
self.assertEqual(list(suite), [Foo('test')])
117+
101118
################################################################
102119
### /Tests for TestLoader.loadTestsFromTestCase
103120

@@ -252,6 +269,24 @@ def load_tests(loader, tests, pattern):
252269

253270
self.assertRaisesRegex(TypeError, "some failure", test.m)
254271

272+
# Check that loadTestsFromModule skips abstract base classes derived from
273+
# TestCase, which can't be instantiated.
274+
def test_loadTestsFromModule__skip_abc_TestCase(self):
275+
m = types.ModuleType('m')
276+
class MyTestCaseBase(unittest.TestCase, metaclass=abc.ABCMeta):
277+
@abc.abstractmethod
278+
def test(self):
279+
...
280+
class MyTestCase(MyTestCaseBase):
281+
def test(self):
282+
pass
283+
m.testcase_1 = MyTestCaseBase
284+
m.testcase_2 = MyTestCase
285+
loader = unittest.TestLoader()
286+
suite = loader.loadTestsFromModule(m)
287+
expected = [loader.suiteClass([MyTestCase('test')])]
288+
self.assertEqual(list(suite), expected)
289+
255290
################################################################
256291
### /Tests for TestLoader.loadTestsFromModule()
257292

@@ -1052,6 +1087,22 @@ def test_loadTestsFromNames__module_not_loaded(self):
10521087
if module_name in sys.modules:
10531088
del sys.modules[module_name]
10541089

1090+
# "The specifier should not refer to a test method in a TestCase-derived
1091+
# subclass that is an abstract base class"
1092+
def test_loadTestsFromNames__testmethod_in_abc_TestCase(self):
1093+
m = types.ModuleType('m')
1094+
class Foo(unittest.TestCase, metaclass=abc.ABCMeta):
1095+
@abc.abstractmethod
1096+
def test_1(self): ...
1097+
def test_2(self): pass
1098+
m.Foo = Foo
1099+
1100+
loader = unittest.TestLoader()
1101+
for name in 'Foo.test_1', 'Foo.test_2':
1102+
with self.subTest(name=name), self.assertRaisesRegex(TypeError,
1103+
"Cannot instantiate abstract test case Foo"):
1104+
loader.loadTestsFromNames([name], m)
1105+
10551106
################################################################
10561107
### /Tests for TestLoader.loadTestsFromNames()
10571108

Lib/unittest/loader.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Loading unittests."""
22

3+
import inspect
34
import os
45
import re
56
import sys
@@ -84,8 +85,10 @@ def loadTestsFromTestCase(self, testCaseClass):
8485
raise TypeError("Test cases should not be derived from "
8586
"TestSuite. Maybe you meant to derive from "
8687
"TestCase?")
87-
if testCaseClass in (case.TestCase, case.FunctionTestCase):
88-
# We don't load any tests from base types that should not be loaded.
88+
if (testCaseClass in (case.TestCase, case.FunctionTestCase) or
89+
inspect.isabstract(testCaseClass)):
90+
# We don't load any tests from base types that should not be loaded,
91+
# and abstract base classes that can't be instantiated
8992
testCaseNames = []
9093
else:
9194
testCaseNames = self.getTestCaseNames(testCaseClass)
@@ -103,6 +106,7 @@ def loadTestsFromModule(self, module, *, pattern=None):
103106
isinstance(obj, type)
104107
and issubclass(obj, case.TestCase)
105108
and obj not in (case.TestCase, case.FunctionTestCase)
109+
and not inspect.isabstract(obj)
106110
):
107111
tests.append(self.loadTestsFromTestCase(obj))
108112

@@ -181,6 +185,9 @@ def loadTestsFromName(self, name, module=None):
181185
elif (isinstance(obj, types.FunctionType) and
182186
isinstance(parent, type) and
183187
issubclass(parent, case.TestCase)):
188+
if inspect.isabstract(parent):
189+
raise TypeError(
190+
"Cannot instantiate abstract test case %s" % parent.__name__)
184191
name = parts[-1]
185192
inst = parent(name)
186193
# static methods follow a different path
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed an issue where ``unittest`` loaders would load and instantiate :class:`unittest.TestCase`-derived subclasses that are also abstract base classes, which can't be instantiated.

0 commit comments

Comments
 (0)