parent
eea04c2891
commit
37489d3c6c
|
@ -0,0 +1 @@
|
|||
Fix collection error upon encountering an :mod:`abstract <abc>` class, including abstract `unittest.TestCase` subclasses.
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue