"tests/test_eval.cpp" did not exist on "fb6aed21576f1de5e6b54f4a8279a7f64a717b30"
test_exceptions.py 8.83 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
3
import sys

Dean Moldovan's avatar
Dean Moldovan committed
4
5
import pytest

6
import env
7
import pybind11_cross_module_tests as cm
8
from pybind11_tests import exceptions as m
Dean Moldovan's avatar
Dean Moldovan committed
9

10

11
def test_std_exception(msg):
12
    with pytest.raises(RuntimeError) as excinfo:
13
        m.throw_std_exception()
14
15
16
    assert msg(excinfo.value) == "This exception was intentionally thrown."


17
18
def test_error_already_set(msg):
    with pytest.raises(RuntimeError) as excinfo:
19
        m.throw_already_set(False)
20
21
22
    assert msg(excinfo.value) == "Unknown internal error occurred"

    with pytest.raises(ValueError) as excinfo:
23
        m.throw_already_set(True)
24
25
26
    assert msg(excinfo.value) == "foo"


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@pytest.mark.skipif("env.PY2")
def test_raise_from(msg):
    with pytest.raises(ValueError) as excinfo:
        m.raise_from()
    assert msg(excinfo.value) == "outer"
    assert msg(excinfo.value.__cause__) == "inner"


@pytest.mark.skipif("env.PY2")
def test_raise_from_already_set(msg):
    with pytest.raises(ValueError) as excinfo:
        m.raise_from_already_set()
    assert msg(excinfo.value) == "outer"
    assert msg(excinfo.value.__cause__) == "inner"


43
def test_cross_module_exceptions(msg):
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    with pytest.raises(RuntimeError) as excinfo:
        cm.raise_runtime_error()
    assert str(excinfo.value) == "My runtime error"

    with pytest.raises(ValueError) as excinfo:
        cm.raise_value_error()
    assert str(excinfo.value) == "My value error"

    with pytest.raises(ValueError) as excinfo:
        cm.throw_pybind_value_error()
    assert str(excinfo.value) == "pybind11 value error"

    with pytest.raises(TypeError) as excinfo:
        cm.throw_pybind_type_error()
    assert str(excinfo.value) == "pybind11 type error"

    with pytest.raises(StopIteration) as excinfo:
        cm.throw_stop_iteration()

63
64
65
66
67
68
69
70
71
    with pytest.raises(cm.LocalSimpleException) as excinfo:
        cm.throw_local_simple_error()
    assert msg(excinfo.value) == "external mod"

    with pytest.raises(KeyError) as excinfo:
        cm.throw_local_error()
    # KeyError is a repr of the key, so it has an extra set of quotes
    assert str(excinfo.value) == "'just local'"

72

73
74
75
76
77
78
79
80
81
82
83
84
# TODO: FIXME
@pytest.mark.xfail(
    "env.PYPY and env.MACOS",
    raises=RuntimeError,
    reason="Expected failure with PyPy and libc++ (Issue #2847 & PR #2999)",
)
def test_cross_module_exception_translator():
    with pytest.raises(KeyError):
        # translator registered in cross_module_tests
        m.throw_should_be_translated_to_key_error()


85
86
def test_python_call_in_catch():
    d = {}
87
    assert m.python_call_in_destructor(d) is True
88
89
90
    assert d["good"] is True


91
92
93
94
95
96
97
98
99
def ignore_pytest_unraisable_warning(f):
    unraisable = "PytestUnraisableExceptionWarning"
    if hasattr(pytest, unraisable):  # Python >= 3.8 and pytest >= 6
        dec = pytest.mark.filterwarnings("ignore::pytest.{}".format(unraisable))
        return dec(f)
    else:
        return f


100
101
# TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583
@pytest.mark.xfail(env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False)
102
@ignore_pytest_unraisable_warning
103
104
105
106
def test_python_alreadyset_in_destructor(monkeypatch, capsys):
    hooked = False
    triggered = [False]  # mutable, so Python 2.7 closure can modify it

107
    if hasattr(sys, "unraisablehook"):  # Python 3.8+
108
        hooked = True
109
110
        # Don't take `sys.unraisablehook`, as that's overwritten by pytest
        default_hook = sys.__unraisablehook__
111
112
113

        def hook(unraisable_hook_args):
            exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args
114
            if obj == "already_set demo":
115
116
117
118
119
                triggered[0] = True
            default_hook(unraisable_hook_args)
            return

        # Use monkeypatch so pytest can apply and remove the patch as appropriate
120
        monkeypatch.setattr(sys, "unraisablehook", hook)
121

122
    assert m.python_alreadyset_in_destructor("already_set demo") is True
123
124
125
126
127
    if hooked:
        assert triggered[0] is True

    _, captured_stderr = capsys.readouterr()
    # Error message is different in Python 2 and 3, check for words that appear in both
128
    assert "ignored" in captured_stderr and "already_set demo" in captured_stderr
129
130


131
def test_exception_matches():
132
133
134
    assert m.exception_matches()
    assert m.exception_matches_base()
    assert m.modulenotfound_exception_matches_base()
135
136


Dean Moldovan's avatar
Dean Moldovan committed
137
def test_custom(msg):
138
139
140
    # Can we catch a MyException?
    with pytest.raises(m.MyException) as excinfo:
        m.throws1()
Dean Moldovan's avatar
Dean Moldovan committed
141
142
143
144
    assert msg(excinfo.value) == "this error should go to a custom type"

    # Can we translate to standard Python exceptions?
    with pytest.raises(RuntimeError) as excinfo:
145
        m.throws2()
Dean Moldovan's avatar
Dean Moldovan committed
146
147
148
149
    assert msg(excinfo.value) == "this error should go to a standard Python exception"

    # Can we handle unknown exceptions?
    with pytest.raises(RuntimeError) as excinfo:
150
        m.throws3()
Dean Moldovan's avatar
Dean Moldovan committed
151
152
153
    assert msg(excinfo.value) == "Caught an unknown exception!"

    # Can we delegate to another handler by rethrowing?
154
155
    with pytest.raises(m.MyException) as excinfo:
        m.throws4()
Dean Moldovan's avatar
Dean Moldovan committed
156
157
    assert msg(excinfo.value) == "this error is rethrown"

158
    # Can we fall-through to the default handler?
Dean Moldovan's avatar
Dean Moldovan committed
159
    with pytest.raises(RuntimeError) as excinfo:
160
        m.throws_logic_error()
161
162
163
    assert (
        msg(excinfo.value) == "this error should fall through to the standard handler"
    )
164

165
166
167
168
    # OverFlow error translation.
    with pytest.raises(OverflowError) as excinfo:
        m.throws_overflow_error()

169
    # Can we handle a helper-declared exception?
170
171
    with pytest.raises(m.MyException5) as excinfo:
        m.throws5()
172
173
174
    assert msg(excinfo.value) == "this is a helper-defined translated exception"

    # Exception subclassing:
175
176
    with pytest.raises(m.MyException5) as excinfo:
        m.throws5_1()
177
    assert msg(excinfo.value) == "MyException5 subclass"
178
    assert isinstance(excinfo.value, m.MyException5_1)
179

180
181
    with pytest.raises(m.MyException5_1) as excinfo:
        m.throws5_1()
182
183
    assert msg(excinfo.value) == "MyException5 subclass"

184
    with pytest.raises(m.MyException5) as excinfo:
185
        try:
186
187
            m.throws5()
        except m.MyException5_1:
188
189
            raise RuntimeError("Exception error: caught child from parent")
    assert msg(excinfo.value) == "this is a helper-defined translated exception"
Jason Rhinelander's avatar
Jason Rhinelander committed
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221


def test_nested_throws(capture):
    """Tests nested (e.g. C++ -> Python -> C++) exception handling"""

    def throw_myex():
        raise m.MyException("nested error")

    def throw_myex5():
        raise m.MyException5("nested error 5")

    # In the comments below, the exception is caught in the first step, thrown in the last step

    # C++ -> Python
    with capture:
        m.try_catch(m.MyException5, throw_myex5)
    assert str(capture).startswith("MyException5: nested error 5")

    # Python -> C++ -> Python
    with pytest.raises(m.MyException) as excinfo:
        m.try_catch(m.MyException5, throw_myex)
    assert str(excinfo.value) == "nested error"

    def pycatch(exctype, f, *args):
        try:
            f(*args)
        except m.MyException as e:
            print(e)

    # C++ -> Python -> C++ -> Python
    with capture:
        m.try_catch(
222
223
224
225
226
227
228
            m.MyException5,
            pycatch,
            m.MyException,
            m.try_catch,
            m.MyException,
            throw_myex5,
        )
Jason Rhinelander's avatar
Jason Rhinelander committed
229
230
231
232
233
234
235
236
237
238
239
    assert str(capture).startswith("MyException5: nested error 5")

    # C++ -> Python -> C++
    with capture:
        m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4)
    assert capture == "this error is rethrown"

    # Python -> C++ -> Python -> C++
    with pytest.raises(m.MyException5) as excinfo:
        m.try_catch(m.MyException, pycatch, m.MyException, m.throws5)
    assert str(excinfo.value) == "this is a helper-defined translated exception"
240
241


242
243
244
245
246
247
248
249
@pytest.mark.skipif("env.PY2")
def test_throw_nested_exception():
    with pytest.raises(RuntimeError) as excinfo:
        m.throw_nested_exception()
    assert str(excinfo.value) == "Outer Exception"
    assert str(excinfo.value.__cause__) == "Inner Exception"


250
251
252
253
254
255
256
257
# This can often happen if you wrap a pybind11 class in a Python wrapper
def test_invalid_repr():
    class MyRepr(object):
        def __repr__(self):
            raise AttributeError("Example error")

    with pytest.raises(TypeError):
        m.simple_bool_passthrough(MyRepr())
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275


def test_local_translator(msg):
    """Tests that a local translator works and that the local translator from
    the cross module is not applied"""
    with pytest.raises(RuntimeError) as excinfo:
        m.throws6()
    assert msg(excinfo.value) == "MyException6 only handled in this module"

    with pytest.raises(RuntimeError) as excinfo:
        m.throws_local_error()
    assert not isinstance(excinfo.value, KeyError)
    assert msg(excinfo.value) == "never caught"

    with pytest.raises(Exception) as excinfo:
        m.throws_local_simple_error()
    assert not isinstance(excinfo.value, cm.LocalSimpleException)
    assert msg(excinfo.value) == "this mod"