python,unittest: don't collect abstract classes

Fix #12275.
This commit is contained in:
Ran Benita 2024-05-13 12:27:20 +03:00
parent eea04c2891
commit 37489d3c6c
5 changed files with 67 additions and 2 deletions

View File

@ -0,0 +1 @@
Fix collection error upon encountering an :mod:`abstract <abc>` class, including abstract `unittest.TestCase` subclasses.

View File

@ -368,7 +368,11 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
return False
def istestclass(self, obj: object, name: str) -> bool:
return self.classnamefilter(name) or self.isnosetest(obj)
if not (self.classnamefilter(name) or self.isnosetest(obj)):
return False
if inspect.isabstract(obj):
return False
return True
def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool:
"""Check if the given name matches the prefix or glob-pattern defined

View File

@ -1,6 +1,7 @@
# mypy: allow-untyped-defs
"""Discover and run std-library "unittest" style tests."""
import inspect
import sys
import traceback
import types
@ -49,14 +50,19 @@ if TYPE_CHECKING:
def pytest_pycollect_makeitem(
collector: Union[Module, Class], name: str, obj: object
) -> Optional["UnitTestCase"]:
# Has unittest been imported and is obj a subclass of its TestCase?
try:
# Has unittest been imported?
ut = sys.modules["unittest"]
# Is obj a subclass of unittest.TestCase?
# Type ignored because `ut` is an opaque module.
if not issubclass(obj, ut.TestCase): # type: ignore
return None
except Exception:
return None
# Is obj a concrete class?
# Abstract classes can't be instantiated so no point collecting them.
if inspect.isabstract(obj):
return None
# Yes, so let's collect it.
return UnitTestCase.from_parent(collector, name=name, obj=obj)

View File

@ -262,6 +262,32 @@ class TestClass:
result = pytester.runpytest()
assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_abstract_class_is_not_collected(self, pytester: Pytester) -> None:
"""Regression test for #12275 (non-unittest version)."""
pytester.makepyfile(
"""
import abc
class TestBase(abc.ABC):
@abc.abstractmethod
def abstract1(self): pass
@abc.abstractmethod
def abstract2(self): pass
def test_it(self): pass
class TestPartial(TestBase):
def abstract1(self): pass
class TestConcrete(TestPartial):
def abstract2(self): pass
"""
)
result = pytester.runpytest()
assert result.ret == ExitCode.OK
result.assert_outcomes(passed=1)
class TestFunction:
def test_getmodulecollector(self, pytester: Pytester) -> None:

View File

@ -1640,3 +1640,31 @@ def test_raising_unittest_skiptest_during_collection(
assert skipped == 1
assert failed == 0
assert reprec.ret == ExitCode.NO_TESTS_COLLECTED
def test_abstract_testcase_is_not_collected(pytester: Pytester) -> None:
"""Regression test for #12275."""
pytester.makepyfile(
"""
import abc
import unittest
class TestBase(unittest.TestCase, abc.ABC):
@abc.abstractmethod
def abstract1(self): pass
@abc.abstractmethod
def abstract2(self): pass
def test_it(self): pass
class TestPartial(TestBase):
def abstract1(self): pass
class TestConcrete(TestPartial):
def abstract2(self): pass
"""
)
result = pytester.runpytest()
assert result.ret == ExitCode.OK
result.assert_outcomes(passed=1)