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
|
return False
|
||||||
|
|
||||||
def istestclass(self, obj: object, name: str) -> bool:
|
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:
|
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
|
"""Check if the given name matches the prefix or glob-pattern defined
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
"""Discover and run std-library "unittest" style tests."""
|
"""Discover and run std-library "unittest" style tests."""
|
||||||
|
|
||||||
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import types
|
import types
|
||||||
|
@ -49,14 +50,19 @@ if TYPE_CHECKING:
|
||||||
def pytest_pycollect_makeitem(
|
def pytest_pycollect_makeitem(
|
||||||
collector: Union[Module, Class], name: str, obj: object
|
collector: Union[Module, Class], name: str, obj: object
|
||||||
) -> Optional["UnitTestCase"]:
|
) -> Optional["UnitTestCase"]:
|
||||||
# Has unittest been imported and is obj a subclass of its TestCase?
|
|
||||||
try:
|
try:
|
||||||
|
# Has unittest been imported?
|
||||||
ut = sys.modules["unittest"]
|
ut = sys.modules["unittest"]
|
||||||
|
# Is obj a subclass of unittest.TestCase?
|
||||||
# Type ignored because `ut` is an opaque module.
|
# Type ignored because `ut` is an opaque module.
|
||||||
if not issubclass(obj, ut.TestCase): # type: ignore
|
if not issubclass(obj, ut.TestCase): # type: ignore
|
||||||
return None
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
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.
|
# Yes, so let's collect it.
|
||||||
return UnitTestCase.from_parent(collector, name=name, obj=obj)
|
return UnitTestCase.from_parent(collector, name=name, obj=obj)
|
||||||
|
|
||||||
|
|
|
@ -262,6 +262,32 @@ class TestClass:
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest()
|
||||||
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
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:
|
class TestFunction:
|
||||||
def test_getmodulecollector(self, pytester: Pytester) -> None:
|
def test_getmodulecollector(self, pytester: Pytester) -> None:
|
||||||
|
|
|
@ -1640,3 +1640,31 @@ def test_raising_unittest_skiptest_during_collection(
|
||||||
assert skipped == 1
|
assert skipped == 1
|
||||||
assert failed == 0
|
assert failed == 0
|
||||||
assert reprec.ret == ExitCode.NO_TESTS_COLLECTED
|
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