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