Merge pull request #12318 from bluetech/unittest-abc
python,unittest: don't collect abstract classes
This commit is contained in:
		
						commit
						d4223b9ae2
					
				| 
						 | 
				
			
			@ -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