From ec2d8223cff2fe83119d142c3b4b365786b3182d Mon Sep 17 00:00:00 2001 From: Tim Strazny Date: Fri, 6 Apr 2018 14:16:12 +0200 Subject: [PATCH 1/4] Fix issue #3372 --- AUTHORS | 1 + _pytest/python_api.py | 9 +++++---- changelog/3372.bugfix.rst | 1 + testing/python/raises.py | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 changelog/3372.bugfix.rst diff --git a/AUTHORS b/AUTHORS index ad9541423..eb3c016a5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -188,6 +188,7 @@ Tareq Alayan Ted Xiao Thomas Grainger Thomas Hisch +Tim Strazny Tom Dalton Tom Viner Trevor Bekolay diff --git a/_pytest/python_api.py b/_pytest/python_api.py index 0b30b7ac8..1d1617d72 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -584,10 +584,11 @@ def raises(expected_exception, *args, **kwargs): """ __tracebackhide__ = True - for exc in filterfalse(isclass, always_iterable(expected_exception)): - msg = ("exceptions must be old-style classes or" - " derived from BaseException, not %s") - raise TypeError(msg % type(exc)) + if not issubclass(expected_exception, BaseException): + for exc in filterfalse(isclass, always_iterable(expected_exception)): + msg = ("exceptions must be old-style classes or" + " derived from BaseException, not %s") + raise TypeError(msg % type(exc)) message = "DID NOT RAISE {0}".format(expected_exception) match_expr = None diff --git a/changelog/3372.bugfix.rst b/changelog/3372.bugfix.rst new file mode 100644 index 000000000..722bdab1e --- /dev/null +++ b/changelog/3372.bugfix.rst @@ -0,0 +1 @@ +``pytest.raises`` now works with exception classes that look like iterables. diff --git a/testing/python/raises.py b/testing/python/raises.py index 156319816..053426395 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,3 +1,4 @@ +from _pytest.outcomes import Failed import pytest import sys @@ -147,3 +148,20 @@ class TestRaises(object): with pytest.raises(ValueError): with pytest.raises(IndexError, match='nomatch'): int('asdf') + + def test_raises_exception_looks_iterable(self): + from six import add_metaclass + + class Meta(type(object)): + def __getitem__(self, item): + return 1/0 + + def __len__(self): + return 1 + + @add_metaclass(Meta) + class ClassLooksIterableException(Exception): + pass + + with pytest.raises(Failed, match="DID NOT RAISE "): + pytest.raises(ClassLooksIterableException, lambda: None) From 0cd74dc3243877dcb5e0d266e6a1e9862c60f207 Mon Sep 17 00:00:00 2001 From: Tim Strazny Date: Fri, 6 Apr 2018 14:40:30 +0200 Subject: [PATCH 2/4] Ensure object is class before calling issubclass. --- _pytest/python_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/python_api.py b/_pytest/python_api.py index 1d1617d72..92eaa5bd5 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -584,7 +584,7 @@ def raises(expected_exception, *args, **kwargs): """ __tracebackhide__ = True - if not issubclass(expected_exception, BaseException): + if not isclass(expected_exception) or not issubclass(expected_exception, BaseException): for exc in filterfalse(isclass, always_iterable(expected_exception)): msg = ("exceptions must be old-style classes or" " derived from BaseException, not %s") From 846d91fb95cc27c3a8b4ca50317a7de0bc0c510e Mon Sep 17 00:00:00 2001 From: Tim Strazny Date: Fri, 6 Apr 2018 16:23:04 +0200 Subject: [PATCH 3/4] Follow Ronny's advice and use ``type`` in ``base_type``. --- _pytest/python_api.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/_pytest/python_api.py b/_pytest/python_api.py index 92eaa5bd5..521c3bc48 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -2,6 +2,7 @@ import math import sys import py +from six import binary_type, text_type from six.moves import zip, filterfalse from more_itertools.more import always_iterable @@ -584,11 +585,11 @@ def raises(expected_exception, *args, **kwargs): """ __tracebackhide__ = True - if not isclass(expected_exception) or not issubclass(expected_exception, BaseException): - for exc in filterfalse(isclass, always_iterable(expected_exception)): - msg = ("exceptions must be old-style classes or" - " derived from BaseException, not %s") - raise TypeError(msg % type(exc)) + base_type = (type, text_type, binary_type) + for exc in filterfalse(isclass, always_iterable(expected_exception, base_type)): + msg = ("exceptions must be old-style classes or" + " derived from BaseException, not %s") + raise TypeError(msg % type(exc)) message = "DID NOT RAISE {0}".format(expected_exception) match_expr = None From 5bd85610163df47dfe5070532c09268032dc4b42 Mon Sep 17 00:00:00 2001 From: Tim Strazny Date: Fri, 6 Apr 2018 17:36:35 +0200 Subject: [PATCH 4/4] linting: unfortunate dedent be gone. --- _pytest/python_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/python_api.py b/_pytest/python_api.py index 521c3bc48..8e09a4a6f 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -588,7 +588,7 @@ def raises(expected_exception, *args, **kwargs): base_type = (type, text_type, binary_type) for exc in filterfalse(isclass, always_iterable(expected_exception, base_type)): msg = ("exceptions must be old-style classes or" - " derived from BaseException, not %s") + " derived from BaseException, not %s") raise TypeError(msg % type(exc)) message = "DID NOT RAISE {0}".format(expected_exception)