diff --git a/_pytest/compat.py b/_pytest/compat.py index 45f9f86d45c..54271ce4f40 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -11,6 +11,7 @@ import py import _pytest +from _pytest.outcomes import TEST_OUTCOME try: @@ -221,14 +222,16 @@ def getimfunc(func): def safe_getattr(object, name, default): - """ Like getattr but return default upon any Exception. + """ Like getattr but return default upon any Exception or any OutcomeException. Attribute access can potentially fail for 'evil' Python objects. See issue #214. + It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException + instead of Exception (for more details check #2707) """ try: return getattr(object, name, default) - except Exception: + except TEST_OUTCOME: return default diff --git a/changelog/2707.bugfix b/changelog/2707.bugfix new file mode 100644 index 00000000000..291e1489cf2 --- /dev/null +++ b/changelog/2707.bugfix @@ -0,0 +1 @@ +Fixed edge-case during collection: attributes which raised ``pytest.fail`` when accessed would abort the entire collection. diff --git a/testing/test_compat.py b/testing/test_compat.py index 7b2251ef6f2..c74801c6c85 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -2,7 +2,8 @@ import sys import pytest -from _pytest.compat import is_generator, get_real_func +from _pytest.compat import is_generator, get_real_func, safe_getattr +from _pytest.outcomes import OutcomeException def test_is_generator(): @@ -74,3 +75,27 @@ async def bar(): """) result = testdir.runpytest() result.stdout.fnmatch_lines(['*1 passed*']) + + +class ErrorsHelper(object): + @property + def raise_exception(self): + raise Exception('exception should be catched') + + @property + def raise_fail(self): + pytest.fail('fail should be catched') + + +def test_helper_failures(): + helper = ErrorsHelper() + with pytest.raises(Exception): + helper.raise_exception + with pytest.raises(OutcomeException): + helper.raise_fail + + +def test_safe_getattr(): + helper = ErrorsHelper() + assert safe_getattr(helper, 'raise_exception', 'default') == 'default' + assert safe_getattr(helper, 'raise_fail', 'default') == 'default'