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