μ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.
1import sys
2
3import pytest
4
5import env
6import pybind11_cross_module_tests as cm
7import pybind11_tests # noqa: F401
8from pybind11_tests import exceptions as m
9
10
11def test_std_exception(msg):
12 with pytest.raises(RuntimeError) as excinfo:
13 m.throw_std_exception()
14 assert msg(excinfo.value) == "This exception was intentionally thrown."
15
16
17def test_error_already_set(msg):
18 with pytest.raises(RuntimeError) as excinfo:
19 m.throw_already_set(False)
20 assert (
21 msg(excinfo.value)
22 == "Internal error: pybind11::error_already_set called while Python error indicator not set."
23 )
24
25 with pytest.raises(ValueError) as excinfo:
26 m.throw_already_set(True)
27 assert msg(excinfo.value) == "foo"
28
29
30def test_raise_from(msg):
31 with pytest.raises(ValueError) as excinfo:
32 m.raise_from()
33 assert msg(excinfo.value) == "outer"
34 assert msg(excinfo.value.__cause__) == "inner"
35
36
37def test_raise_from_already_set(msg):
38 with pytest.raises(ValueError) as excinfo:
39 m.raise_from_already_set()
40 assert msg(excinfo.value) == "outer"
41 assert msg(excinfo.value.__cause__) == "inner"
42
43
44def test_cross_module_exceptions(msg):
45 with pytest.raises(RuntimeError) as excinfo:
46 cm.raise_runtime_error()
47 assert str(excinfo.value) == "My runtime error"
48
49 with pytest.raises(ValueError) as excinfo:
50 cm.raise_value_error()
51 assert str(excinfo.value) == "My value error"
52
53 with pytest.raises(ValueError) as excinfo:
54 cm.throw_pybind_value_error()
55 assert str(excinfo.value) == "pybind11 value error"
56
57 with pytest.raises(TypeError) as excinfo:
58 cm.throw_pybind_type_error()
59 assert str(excinfo.value) == "pybind11 type error"
60
61 with pytest.raises(StopIteration) as excinfo:
62 cm.throw_stop_iteration()
63
64 with pytest.raises(cm.LocalSimpleException) as excinfo:
65 cm.throw_local_simple_error()
66 assert msg(excinfo.value) == "external mod"
67
68 with pytest.raises(KeyError) as excinfo:
69 cm.throw_local_error()
70 # KeyError is a repr of the key, so it has an extra set of quotes
71 assert str(excinfo.value) == "'just local'"
72
73
74# TODO: FIXME
75@pytest.mark.xfail(
76 "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))",
77 raises=RuntimeError,
78 reason="See Issue #2847, PR #2999, PR #4324",
79)
80def test_cross_module_exception_translator():
81 with pytest.raises(KeyError):
82 # translator registered in cross_module_tests
83 m.throw_should_be_translated_to_key_error()
84
85
86def test_python_call_in_catch():
87 d = {}
88 assert m.python_call_in_destructor(d) is True
89 assert d["good"] is True
90
91
92def ignore_pytest_unraisable_warning(f):
93 unraisable = "PytestUnraisableExceptionWarning"
94 if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6
95 dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}")
96 return dec(f)
97 else:
98 return f
99
100
101# TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583
102@pytest.mark.xfail(env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False)
103@ignore_pytest_unraisable_warning
104def test_python_alreadyset_in_destructor(monkeypatch, capsys):
105 hooked = False
106 triggered = False
107
108 if hasattr(sys, "unraisablehook"): # Python 3.8+
109 hooked = True
110 # Don't take `sys.unraisablehook`, as that's overwritten by pytest
111 default_hook = sys.__unraisablehook__
112
113 def hook(unraisable_hook_args):
114 exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args
115 if obj == "already_set demo":
116 nonlocal triggered
117 triggered = True
118 default_hook(unraisable_hook_args)
119 return
120
121 # Use monkeypatch so pytest can apply and remove the patch as appropriate
122 monkeypatch.setattr(sys, "unraisablehook", hook)
123
124 assert m.python_alreadyset_in_destructor("already_set demo") is True
125 if hooked:
126 assert triggered is True
127
128 _, captured_stderr = capsys.readouterr()
129 assert captured_stderr.startswith("Exception ignored in: 'already_set demo'")
130 assert captured_stderr.rstrip().endswith("KeyError: 'bar'")
131
132
133def test_exception_matches():
134 assert m.exception_matches()
135 assert m.exception_matches_base()
136 assert m.modulenotfound_exception_matches_base()
137
138
139def test_custom(msg):
140 # Can we catch a MyException?
141 with pytest.raises(m.MyException) as excinfo:
142 m.throws1()
143 assert msg(excinfo.value) == "this error should go to a custom type"
144
145 # Can we translate to standard Python exceptions?
146 with pytest.raises(RuntimeError) as excinfo:
147 m.throws2()
148 assert msg(excinfo.value) == "this error should go to a standard Python exception"
149
150 # Can we handle unknown exceptions?
151 with pytest.raises(RuntimeError) as excinfo:
152 m.throws3()
153 assert msg(excinfo.value) == "Caught an unknown exception!"
154
155 # Can we delegate to another handler by rethrowing?
156 with pytest.raises(m.MyException) as excinfo:
157 m.throws4()
158 assert msg(excinfo.value) == "this error is rethrown"
159
160 # Can we fall-through to the default handler?
161 with pytest.raises(RuntimeError) as excinfo:
162 m.throws_logic_error()
163 assert (
164 msg(excinfo.value) == "this error should fall through to the standard handler"
165 )
166
167 # OverFlow error translation.
168 with pytest.raises(OverflowError) as excinfo:
169 m.throws_overflow_error()
170
171 # Can we handle a helper-declared exception?
172 with pytest.raises(m.MyException5) as excinfo:
173 m.throws5()
174 assert msg(excinfo.value) == "this is a helper-defined translated exception"
175
176 # Exception subclassing:
177 with pytest.raises(m.MyException5) as excinfo:
178 m.throws5_1()
179 assert msg(excinfo.value) == "MyException5 subclass"
180 assert isinstance(excinfo.value, m.MyException5_1)
181
182 with pytest.raises(m.MyException5_1) as excinfo:
183 m.throws5_1()
184 assert msg(excinfo.value) == "MyException5 subclass"
185
186 with pytest.raises(m.MyException5) as excinfo:
187 try:
188 m.throws5()
189 except m.MyException5_1 as err:
190 raise RuntimeError("Exception error: caught child from parent") from err
191 assert msg(excinfo.value) == "this is a helper-defined translated exception"
192
193
194def test_nested_throws(capture):
195 """Tests nested (e.g. C++ -> Python -> C++) exception handling"""
196
197 def throw_myex():
198 raise m.MyException("nested error")
199
200 def throw_myex5():
201 raise m.MyException5("nested error 5")
202
203 # In the comments below, the exception is caught in the first step, thrown in the last step
204
205 # C++ -> Python
206 with capture:
207 m.try_catch(m.MyException5, throw_myex5)
208 assert str(capture).startswith("MyException5: nested error 5")
209
210 # Python -> C++ -> Python
211 with pytest.raises(m.MyException) as excinfo:
212 m.try_catch(m.MyException5, throw_myex)
213 assert str(excinfo.value) == "nested error"
214
215 def pycatch(exctype, f, *args):
216 try:
217 f(*args)
218 except m.MyException as e:
219 print(e)
220
221 # C++ -> Python -> C++ -> Python
222 with capture:
223 m.try_catch(
224 m.MyException5,
225 pycatch,
226 m.MyException,
227 m.try_catch,
228 m.MyException,
229 throw_myex5,
230 )
231 assert str(capture).startswith("MyException5: nested error 5")
232
233 # C++ -> Python -> C++
234 with capture:
235 m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4)
236 assert capture == "this error is rethrown"
237
238 # Python -> C++ -> Python -> C++
239 with pytest.raises(m.MyException5) as excinfo:
240 m.try_catch(m.MyException, pycatch, m.MyException, m.throws5)
241 assert str(excinfo.value) == "this is a helper-defined translated exception"
242
243
244def test_throw_nested_exception():
245 with pytest.raises(RuntimeError) as excinfo:
246 m.throw_nested_exception()
247 assert str(excinfo.value) == "Outer Exception"
248 assert str(excinfo.value.__cause__) == "Inner Exception"
249
250
251# This can often happen if you wrap a pybind11 class in a Python wrapper
252def test_invalid_repr():
253 class MyRepr:
254 def __repr__(self):
255 raise AttributeError("Example error")
256
257 with pytest.raises(TypeError):
258 m.simple_bool_passthrough(MyRepr())
259
260
261def test_local_translator(msg):
262 """Tests that a local translator works and that the local translator from
263 the cross module is not applied"""
264 with pytest.raises(RuntimeError) as excinfo:
265 m.throws6()
266 assert msg(excinfo.value) == "MyException6 only handled in this module"
267
268 with pytest.raises(RuntimeError) as excinfo:
269 m.throws_local_error()
270 assert not isinstance(excinfo.value, KeyError)
271 assert msg(excinfo.value) == "never caught"
272
273 with pytest.raises(Exception) as excinfo:
274 m.throws_local_simple_error()
275 assert not isinstance(excinfo.value, cm.LocalSimpleException)
276 assert msg(excinfo.value) == "this mod"
277
278
280 assert m.error_already_set_what(RuntimeError, "\ud927") == (
281 "RuntimeError: \\ud927",
282 False,
283 )
284
285
287 assert m.error_already_set_what(RuntimeError, b"\x80") == (
288 "RuntimeError: b'\\x80'",
289 False,
290 )
291
292
293class FlakyException(Exception):
294 def __init__(self, failure_point):
295 if failure_point == "failure_point_init":
296 raise ValueError("triggered_failure_point_init")
297 self.failure_point = failure_point
298
299 def __str__(self):
300 if self.failure_point == "failure_point_str":
301 raise ValueError("triggered_failure_point_str")
302 return "FlakyException.__str__"
303
304
305@pytest.mark.parametrize(
306 "exc_type, exc_value, expected_what",
307 (
308 (ValueError, "plain_str", "ValueError: plain_str"),
309 (ValueError, ("tuple_elem",), "ValueError: tuple_elem"),
310 (FlakyException, ("happy",), "FlakyException: FlakyException.__str__"),
311 ),
312)
314 exc_type, exc_value, expected_what
315):
316 what, py_err_set_after_what = m.error_already_set_what(exc_type, exc_value)
317 assert not py_err_set_after_what
318 assert what == expected_what
319
320
321@pytest.mark.skipif("env.PYPY", reason="PyErr_NormalizeException Segmentation fault")
323 with pytest.raises(RuntimeError) as excinfo:
324 m.error_already_set_what(FlakyException, ("failure_point_init",))
325 lines = str(excinfo.value).splitlines()
326 # PyErr_NormalizeException replaces the original FlakyException with ValueError:
327 assert lines[:3] == [
328 "pybind11::error_already_set: MISMATCH of original and normalized active exception types:"
329 " ORIGINAL FlakyException REPLACED BY ValueError: triggered_failure_point_init",
330 "",
331 "At:",
332 ]
333 # Checking the first two lines of the traceback as formatted in error_string():
334 assert "test_exceptions.py(" in lines[3]
335 assert lines[3].endswith("): __init__")
336 assert lines[4].endswith("): test_flaky_exception_failure_point_init")
337
338
340 what, py_err_set_after_what = m.error_already_set_what(
341 FlakyException, ("failure_point_str",)
342 )
343 assert not py_err_set_after_what
344 lines = what.splitlines()
345 if env.PYPY and len(lines) == 3:
346 n = 3 # Traceback is missing.
347 else:
348 n = 5
349 assert (
350 lines[:n]
351 == [
352 "FlakyException: <MESSAGE UNAVAILABLE DUE TO ANOTHER EXCEPTION>",
353 "",
354 "MESSAGE UNAVAILABLE DUE TO EXCEPTION: ValueError: triggered_failure_point_str",
355 "",
356 "At:",
357 ][:n]
358 )
359
360
362 with pytest.raises(RuntimeError) as excinfo:
363 m.test_cross_module_interleaved_error_already_set()
364 assert str(excinfo.value) in (
365 "2nd error.", # Almost all platforms.
366 "RuntimeError: 2nd error.", # Some PyPy builds (seen under macOS).
367 )
368
369
371 m.test_error_already_set_double_restore(True) # dry_run
372 with pytest.raises(RuntimeError) as excinfo:
373 m.test_error_already_set_double_restore(False)
374 assert str(excinfo.value) == (
375 "Internal error: pybind11::detail::error_fetch_and_normalize::restore()"
376 " called a second time. ORIGINAL ERROR: ValueError: Random error."
377 )
378
379
381 # https://github.com/pybind/pybind11/issues/4075
382 what = m.test_pypy_oserror_normalization()
383 assert "this_filename_must_not_exist" in what
Definition: pytypes.h:1200
def __init__(self, failure_point)
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
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2002
def test_error_already_set_message_with_unicode_surrogate()
def test_cross_module_interleaved_error_already_set()
def test_error_already_set_what_with_happy_exceptions(exc_type, exc_value, expected_what)
def test_flaky_exception_failure_point_str()
def test_flaky_exception_failure_point_init()
def test_error_already_set_message_with_malformed_utf8()
def test_error_already_set_double_restore()
def test_pypy_oserror_normalization()