μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
test_exceptions.py
Go to the documentation of this file.
1# -*- coding: utf-8 -*-
2import sys
3
4import pytest
5
6import env
7import pybind11_cross_module_tests as cm
8from pybind11_tests import exceptions as m
9
10
12 with pytest.raises(RuntimeError) as excinfo:
13 m.throw_std_exception()
14 assert msg(excinfo.value) == "This exception was intentionally thrown."
15
16
18 with pytest.raises(RuntimeError) as excinfo:
19 m.throw_already_set(False)
20 assert msg(excinfo.value) == "Unknown internal error occurred"
21
22 with pytest.raises(ValueError) as excinfo:
23 m.throw_already_set(True)
24 assert msg(excinfo.value) == "foo"
25
26
27@pytest.mark.skipif("env.PY2")
29 with pytest.raises(ValueError) as excinfo:
30 m.raise_from()
31 assert msg(excinfo.value) == "outer"
32 assert msg(excinfo.value.__cause__) == "inner"
33
34
35@pytest.mark.skipif("env.PY2")
37 with pytest.raises(ValueError) as excinfo:
38 m.raise_from_already_set()
39 assert msg(excinfo.value) == "outer"
40 assert msg(excinfo.value.__cause__) == "inner"
41
42
44 with pytest.raises(RuntimeError) as excinfo:
45 cm.raise_runtime_error()
46 assert str(excinfo.value) == "My runtime error"
47
48 with pytest.raises(ValueError) as excinfo:
49 cm.raise_value_error()
50 assert str(excinfo.value) == "My value error"
51
52 with pytest.raises(ValueError) as excinfo:
53 cm.throw_pybind_value_error()
54 assert str(excinfo.value) == "pybind11 value error"
55
56 with pytest.raises(TypeError) as excinfo:
57 cm.throw_pybind_type_error()
58 assert str(excinfo.value) == "pybind11 type error"
59
60 with pytest.raises(StopIteration) as excinfo:
61 cm.throw_stop_iteration()
62
63 with pytest.raises(cm.LocalSimpleException) as excinfo:
64 cm.throw_local_simple_error()
65 assert msg(excinfo.value) == "external mod"
66
67 with pytest.raises(KeyError) as excinfo:
68 cm.throw_local_error()
69 # KeyError is a repr of the key, so it has an extra set of quotes
70 assert str(excinfo.value) == "'just local'"
71
72
73# TODO: FIXME
74@pytest.mark.xfail(
75 "env.PYPY and env.MACOS",
76 raises=RuntimeError,
77 reason="Expected failure with PyPy and libc++ (Issue #2847 & PR #2999)",
78)
80 with pytest.raises(KeyError):
81 # translator registered in cross_module_tests
82 m.throw_should_be_translated_to_key_error()
83
84
86 d = {}
87 assert m.python_call_in_destructor(d) is True
88 assert d["good"] is True
89
90
92 unraisable = "PytestUnraisableExceptionWarning"
93 if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6
94 dec = pytest.mark.filterwarnings("ignore::pytest.{}".format(unraisable))
95 return dec(f)
96 else:
97 return f
98
99
100# TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583
101@pytest.mark.xfail(env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False)
102@ignore_pytest_unraisable_warning
104 hooked = False
105 triggered = [False] # mutable, so Python 2.7 closure can modify it
106
107 if hasattr(sys, "unraisablehook"): # Python 3.8+
108 hooked = True
109 # Don't take `sys.unraisablehook`, as that's overwritten by pytest
110 default_hook = sys.__unraisablehook__
111
112 def hook(unraisable_hook_args):
113 exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args
114 if obj == "already_set demo":
115 triggered[0] = True
116 default_hook(unraisable_hook_args)
117 return
118
119 # 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 if hooked:
124 assert triggered[0] is True
125
126 _, captured_stderr = capsys.readouterr()
127 # 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
132 assert m.exception_matches()
133 assert m.exception_matches_base()
134 assert m.modulenotfound_exception_matches_base()
135
136
137def test_custom(msg):
138 # Can we catch a MyException?
139 with pytest.raises(m.MyException) as excinfo:
140 m.throws1()
141 assert msg(excinfo.value) == "this error should go to a custom type"
142
143 # Can we translate to standard Python exceptions?
144 with pytest.raises(RuntimeError) as excinfo:
145 m.throws2()
146 assert msg(excinfo.value) == "this error should go to a standard Python exception"
147
148 # Can we handle unknown exceptions?
149 with pytest.raises(RuntimeError) as excinfo:
150 m.throws3()
151 assert msg(excinfo.value) == "Caught an unknown exception!"
152
153 # Can we delegate to another handler by rethrowing?
154 with pytest.raises(m.MyException) as excinfo:
155 m.throws4()
156 assert msg(excinfo.value) == "this error is rethrown"
157
158 # Can we fall-through to the default handler?
159 with pytest.raises(RuntimeError) as excinfo:
160 m.throws_logic_error()
161 assert (
162 msg(excinfo.value) == "this error should fall through to the standard handler"
163 )
164
165 # OverFlow error translation.
166 with pytest.raises(OverflowError) as excinfo:
167 m.throws_overflow_error()
168
169 # Can we handle a helper-declared exception?
170 with pytest.raises(m.MyException5) as excinfo:
171 m.throws5()
172 assert msg(excinfo.value) == "this is a helper-defined translated exception"
173
174 # Exception subclassing:
175 with pytest.raises(m.MyException5) as excinfo:
176 m.throws5_1()
177 assert msg(excinfo.value) == "MyException5 subclass"
178 assert isinstance(excinfo.value, m.MyException5_1)
179
180 with pytest.raises(m.MyException5_1) as excinfo:
181 m.throws5_1()
182 assert msg(excinfo.value) == "MyException5 subclass"
183
184 with pytest.raises(m.MyException5) as excinfo:
185 try:
186 m.throws5()
187 except m.MyException5_1:
188 raise RuntimeError("Exception error: caught child from parent")
189 assert msg(excinfo.value) == "this is a helper-defined translated exception"
190
191
193 """Tests nested (e.g. C++ -> Python -> C++) exception handling"""
194
195 def throw_myex():
196 raise m.MyException("nested error")
197
198 def throw_myex5():
199 raise m.MyException5("nested error 5")
200
201 # In the comments below, the exception is caught in the first step, thrown in the last step
202
203 # C++ -> Python
204 with capture:
205 m.try_catch(m.MyException5, throw_myex5)
206 assert str(capture).startswith("MyException5: nested error 5")
207
208 # Python -> C++ -> Python
209 with pytest.raises(m.MyException) as excinfo:
210 m.try_catch(m.MyException5, throw_myex)
211 assert str(excinfo.value) == "nested error"
212
213 def pycatch(exctype, f, *args):
214 try:
215 f(*args)
216 except m.MyException as e:
217 print(e)
218
219 # C++ -> Python -> C++ -> Python
220 with capture:
221 m.try_catch(
222 m.MyException5,
223 pycatch,
224 m.MyException,
225 m.try_catch,
226 m.MyException,
227 throw_myex5,
228 )
229 assert str(capture).startswith("MyException5: nested error 5")
230
231 # C++ -> Python -> C++
232 with capture:
233 m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4)
234 assert capture == "this error is rethrown"
235
236 # Python -> C++ -> Python -> C++
237 with pytest.raises(m.MyException5) as excinfo:
238 m.try_catch(m.MyException, pycatch, m.MyException, m.throws5)
239 assert str(excinfo.value) == "this is a helper-defined translated exception"
240
241
242@pytest.mark.skipif("env.PY2")
244 with pytest.raises(RuntimeError) as excinfo:
245 m.throw_nested_exception()
246 assert str(excinfo.value) == "Outer Exception"
247 assert str(excinfo.value.__cause__) == "Inner Exception"
248
249
250# This can often happen if you wrap a pybind11 class in a Python wrapper
252 class MyRepr(object):
253 def __repr__(self):
254 raise AttributeError("Example error")
255
256 with pytest.raises(TypeError):
257 m.simple_bool_passthrough(MyRepr())
258
259
261 """Tests that a local translator works and that the local translator from
262 the cross module is not applied"""
263 with pytest.raises(RuntimeError) as excinfo:
264 m.throws6()
265 assert msg(excinfo.value) == "MyException6 only handled in this module"
266
267 with pytest.raises(RuntimeError) as excinfo:
268 m.throws_local_error()
269 assert not isinstance(excinfo.value, KeyError)
270 assert msg(excinfo.value) == "never caught"
271
272 with pytest.raises(Exception) as excinfo:
273 m.throws_local_simple_error()
274 assert not isinstance(excinfo.value, cm.LocalSimpleException)
275 assert msg(excinfo.value) == "this mod"
\rst Holds a reference to a Python object (with reference counting)
Definition: pytypes.h:259
Definition: pytypes.h:1200
bool hasattr(handle obj, handle name)
Definition: pytypes.h:517
bool isinstance(handle obj)
\rst Return true if obj is an instance of T.
Definition: pytypes.h:489
def test_local_translator(msg)
def test_raise_from(msg)
def ignore_pytest_unraisable_warning(f)
def test_exception_matches()
def test_throw_nested_exception()
def test_cross_module_exceptions(msg)
def test_raise_from_already_set(msg)
def test_python_alreadyset_in_destructor(monkeypatch, capsys)
def test_python_call_in_catch()
def test_std_exception(msg)
def test_cross_module_exception_translator()
def test_error_already_set(msg)
def test_nested_throws(capture)