Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python: collect: ignore exceptions with isinstance #4284

Merged
merged 1 commit into from
Nov 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/4266.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class.
8 changes: 8 additions & 0 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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
Expand Down Expand Up @@ -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))
Expand Down
24 changes: 24 additions & 0 deletions testing/python/raises.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import sys

import six

import pytest
from _pytest.outcomes import Failed

Expand Down Expand Up @@ -170,3 +172,25 @@ class ClassLooksIterableException(Exception):
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]
27 changes: 27 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -977,3 +977,30 @@ def fix():
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 changes: 12 additions & 0 deletions testing/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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


Expand Down Expand Up @@ -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