python: collect: ignore exceptions with isinstance
Fixes https://github.com/pytest-dev/pytest/issues/4266.
This commit is contained in:
parent
56e6bb0ff6
commit
e30f7094f3
|
@ -0,0 +1 @@
|
|||
Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class.
|
|
@ -334,6 +334,14 @@ def safe_getattr(object, name, default):
|
|||
return default
|
||||
|
||||
|
||||
def safe_isclass(obj):
|
||||
"""Ignore any exception via isinstance on Python 3."""
|
||||
try:
|
||||
return isclass(obj)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _is_unittest_unexpected_success_a_failure():
|
||||
"""Return if the test suite should fail if an @expectedFailure unittest test PASSES.
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ from _pytest.compat import NoneType
|
|||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import REGEX_TYPE
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import safe_isclass
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.config import hookimpl
|
||||
|
@ -195,7 +196,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
|||
if res is not None:
|
||||
return
|
||||
# nothing was collected elsewhere, let's do it here
|
||||
if isclass(obj):
|
||||
if safe_isclass(obj):
|
||||
if collector.istestclass(obj, name):
|
||||
Class = collector._getcustomclass("Class")
|
||||
outcome.force_result(Class(name, parent=collector))
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import sys
|
||||
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.outcomes import Failed
|
||||
|
||||
|
@ -170,3 +172,25 @@ class TestRaises(object):
|
|||
Failed, match="DID NOT RAISE <class 'raises.ClassLooksIterableException'>"
|
||||
):
|
||||
pytest.raises(ClassLooksIterableException, lambda: None)
|
||||
|
||||
def test_raises_with_raising_dunder_class(self):
|
||||
"""Test current behavior with regard to exceptions via __class__ (#4284)."""
|
||||
|
||||
class CrappyClass(Exception):
|
||||
@property
|
||||
def __class__(self):
|
||||
assert False, "via __class__"
|
||||
|
||||
if six.PY2:
|
||||
with pytest.raises(pytest.fail.Exception) as excinfo:
|
||||
with pytest.raises(CrappyClass()):
|
||||
pass
|
||||
assert "DID NOT RAISE" in excinfo.value.args[0]
|
||||
|
||||
with pytest.raises(CrappyClass) as excinfo:
|
||||
raise CrappyClass()
|
||||
else:
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
with pytest.raises(CrappyClass()):
|
||||
pass
|
||||
assert "via __class__" in excinfo.value.args[0]
|
||||
|
|
|
@ -977,3 +977,30 @@ def test_collect_invalid_signature_message(testdir):
|
|||
result.stdout.fnmatch_lines(
|
||||
["Could not determine arguments of *.fix *: invalid method signature"]
|
||||
)
|
||||
|
||||
|
||||
def test_collect_handles_raising_on_dunder_class(testdir):
|
||||
"""Handle proxy classes like Django's LazySettings that might raise on
|
||||
``isinstance`` (#4266).
|
||||
"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
class ImproperlyConfigured(Exception):
|
||||
pass
|
||||
|
||||
class RaisesOnGetAttr(object):
|
||||
def raises(self):
|
||||
raise ImproperlyConfigured
|
||||
|
||||
__class__ = property(raises)
|
||||
|
||||
raises = RaisesOnGetAttr()
|
||||
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
|
|
|
@ -12,6 +12,7 @@ from _pytest.compat import _PytestWrapper
|
|||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import safe_isclass
|
||||
from _pytest.outcomes import OutcomeException
|
||||
|
||||
|
||||
|
@ -140,3 +141,14 @@ def test_safe_getattr():
|
|||
helper = ErrorsHelper()
|
||||
assert safe_getattr(helper, "raise_exception", "default") == "default"
|
||||
assert safe_getattr(helper, "raise_fail", "default") == "default"
|
||||
|
||||
|
||||
def test_safe_isclass():
|
||||
assert safe_isclass(type) is True
|
||||
|
||||
class CrappyClass(Exception):
|
||||
@property
|
||||
def __class__(self):
|
||||
assert False, "Should be ignored"
|
||||
|
||||
assert safe_isclass(CrappyClass()) is False
|
||||
|
|
Loading…
Reference in New Issue