File indexing completed on 2025-01-18 10:17:53
0001 import sys
0002
0003 import pytest
0004
0005 import env
0006 import pybind11_cross_module_tests as cm
0007 import pybind11_tests
0008 from pybind11_tests import exceptions as m
0009
0010
0011 def test_std_exception(msg):
0012 with pytest.raises(RuntimeError) as excinfo:
0013 m.throw_std_exception()
0014 assert msg(excinfo.value) == "This exception was intentionally thrown."
0015
0016
0017 def test_error_already_set(msg):
0018 with pytest.raises(RuntimeError) as excinfo:
0019 m.throw_already_set(False)
0020 assert (
0021 msg(excinfo.value)
0022 == "Internal error: pybind11::error_already_set called while Python error indicator not set."
0023 )
0024
0025 with pytest.raises(ValueError) as excinfo:
0026 m.throw_already_set(True)
0027 assert msg(excinfo.value) == "foo"
0028
0029
0030 def test_raise_from(msg):
0031 with pytest.raises(ValueError) as excinfo:
0032 m.raise_from()
0033 assert msg(excinfo.value) == "outer"
0034 assert msg(excinfo.value.__cause__) == "inner"
0035
0036
0037 def test_raise_from_already_set(msg):
0038 with pytest.raises(ValueError) as excinfo:
0039 m.raise_from_already_set()
0040 assert msg(excinfo.value) == "outer"
0041 assert msg(excinfo.value.__cause__) == "inner"
0042
0043
0044 def test_cross_module_exceptions(msg):
0045 with pytest.raises(RuntimeError) as excinfo:
0046 cm.raise_runtime_error()
0047 assert str(excinfo.value) == "My runtime error"
0048
0049 with pytest.raises(ValueError) as excinfo:
0050 cm.raise_value_error()
0051 assert str(excinfo.value) == "My value error"
0052
0053 with pytest.raises(ValueError) as excinfo:
0054 cm.throw_pybind_value_error()
0055 assert str(excinfo.value) == "pybind11 value error"
0056
0057 with pytest.raises(TypeError) as excinfo:
0058 cm.throw_pybind_type_error()
0059 assert str(excinfo.value) == "pybind11 type error"
0060
0061 with pytest.raises(StopIteration) as excinfo:
0062 cm.throw_stop_iteration()
0063
0064 with pytest.raises(cm.LocalSimpleException) as excinfo:
0065 cm.throw_local_simple_error()
0066 assert msg(excinfo.value) == "external mod"
0067
0068 with pytest.raises(KeyError) as excinfo:
0069 cm.throw_local_error()
0070
0071 assert str(excinfo.value) == "'just local'"
0072
0073
0074
0075 @pytest.mark.xfail(
0076 "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))",
0077 raises=RuntimeError,
0078 reason="See Issue #2847, PR #2999, PR #4324",
0079 )
0080 def test_cross_module_exception_translator():
0081 with pytest.raises(KeyError):
0082
0083 m.throw_should_be_translated_to_key_error()
0084
0085
0086 def test_python_call_in_catch():
0087 d = {}
0088 assert m.python_call_in_destructor(d) is True
0089 assert d["good"] is True
0090
0091
0092 def ignore_pytest_unraisable_warning(f):
0093 unraisable = "PytestUnraisableExceptionWarning"
0094 if hasattr(pytest, unraisable):
0095 dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}")
0096 return dec(f)
0097 else:
0098 return f
0099
0100
0101
0102 @pytest.mark.xfail(env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False)
0103 @ignore_pytest_unraisable_warning
0104 def test_python_alreadyset_in_destructor(monkeypatch, capsys):
0105 hooked = False
0106 triggered = False
0107
0108 if hasattr(sys, "unraisablehook"):
0109 hooked = True
0110
0111 default_hook = sys.__unraisablehook__
0112
0113 def hook(unraisable_hook_args):
0114 exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args
0115 if obj == "already_set demo":
0116 nonlocal triggered
0117 triggered = True
0118 default_hook(unraisable_hook_args)
0119 return
0120
0121
0122 monkeypatch.setattr(sys, "unraisablehook", hook)
0123
0124 assert m.python_alreadyset_in_destructor("already_set demo") is True
0125 if hooked:
0126 assert triggered is True
0127
0128 _, captured_stderr = capsys.readouterr()
0129 assert captured_stderr.startswith("Exception ignored in: 'already_set demo'")
0130 assert captured_stderr.rstrip().endswith("KeyError: 'bar'")
0131
0132
0133 def test_exception_matches():
0134 assert m.exception_matches()
0135 assert m.exception_matches_base()
0136 assert m.modulenotfound_exception_matches_base()
0137
0138
0139 def test_custom(msg):
0140
0141 with pytest.raises(m.MyException) as excinfo:
0142 m.throws1()
0143 assert msg(excinfo.value) == "this error should go to a custom type"
0144
0145
0146 with pytest.raises(RuntimeError) as excinfo:
0147 m.throws2()
0148 assert msg(excinfo.value) == "this error should go to a standard Python exception"
0149
0150
0151 with pytest.raises(RuntimeError) as excinfo:
0152 m.throws3()
0153 assert msg(excinfo.value) == "Caught an unknown exception!"
0154
0155
0156 with pytest.raises(m.MyException) as excinfo:
0157 m.throws4()
0158 assert msg(excinfo.value) == "this error is rethrown"
0159
0160
0161 with pytest.raises(RuntimeError) as excinfo:
0162 m.throws_logic_error()
0163 assert (
0164 msg(excinfo.value) == "this error should fall through to the standard handler"
0165 )
0166
0167
0168 with pytest.raises(OverflowError) as excinfo:
0169 m.throws_overflow_error()
0170
0171
0172 with pytest.raises(m.MyException5) as excinfo:
0173 m.throws5()
0174 assert msg(excinfo.value) == "this is a helper-defined translated exception"
0175
0176
0177 with pytest.raises(m.MyException5) as excinfo:
0178 m.throws5_1()
0179 assert msg(excinfo.value) == "MyException5 subclass"
0180 assert isinstance(excinfo.value, m.MyException5_1)
0181
0182 with pytest.raises(m.MyException5_1) as excinfo:
0183 m.throws5_1()
0184 assert msg(excinfo.value) == "MyException5 subclass"
0185
0186 with pytest.raises(m.MyException5) as excinfo:
0187 try:
0188 m.throws5()
0189 except m.MyException5_1 as err:
0190 raise RuntimeError("Exception error: caught child from parent") from err
0191 assert msg(excinfo.value) == "this is a helper-defined translated exception"
0192
0193
0194 def test_nested_throws(capture):
0195 """Tests nested (e.g. C++ -> Python -> C++) exception handling"""
0196
0197 def throw_myex():
0198 raise m.MyException("nested error")
0199
0200 def throw_myex5():
0201 raise m.MyException5("nested error 5")
0202
0203
0204
0205
0206 with capture:
0207 m.try_catch(m.MyException5, throw_myex5)
0208 assert str(capture).startswith("MyException5: nested error 5")
0209
0210
0211 with pytest.raises(m.MyException) as excinfo:
0212 m.try_catch(m.MyException5, throw_myex)
0213 assert str(excinfo.value) == "nested error"
0214
0215 def pycatch(exctype, f, *args):
0216 try:
0217 f(*args)
0218 except m.MyException as e:
0219 print(e)
0220
0221
0222 with capture:
0223 m.try_catch(
0224 m.MyException5,
0225 pycatch,
0226 m.MyException,
0227 m.try_catch,
0228 m.MyException,
0229 throw_myex5,
0230 )
0231 assert str(capture).startswith("MyException5: nested error 5")
0232
0233
0234 with capture:
0235 m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4)
0236 assert capture == "this error is rethrown"
0237
0238
0239 with pytest.raises(m.MyException5) as excinfo:
0240 m.try_catch(m.MyException, pycatch, m.MyException, m.throws5)
0241 assert str(excinfo.value) == "this is a helper-defined translated exception"
0242
0243
0244 def test_throw_nested_exception():
0245 with pytest.raises(RuntimeError) as excinfo:
0246 m.throw_nested_exception()
0247 assert str(excinfo.value) == "Outer Exception"
0248 assert str(excinfo.value.__cause__) == "Inner Exception"
0249
0250
0251
0252 def test_invalid_repr():
0253 class MyRepr:
0254 def __repr__(self):
0255 raise AttributeError("Example error")
0256
0257 with pytest.raises(TypeError):
0258 m.simple_bool_passthrough(MyRepr())
0259
0260
0261 def test_local_translator(msg):
0262 """Tests that a local translator works and that the local translator from
0263 the cross module is not applied"""
0264 with pytest.raises(RuntimeError) as excinfo:
0265 m.throws6()
0266 assert msg(excinfo.value) == "MyException6 only handled in this module"
0267
0268 with pytest.raises(RuntimeError) as excinfo:
0269 m.throws_local_error()
0270 assert not isinstance(excinfo.value, KeyError)
0271 assert msg(excinfo.value) == "never caught"
0272
0273 with pytest.raises(Exception) as excinfo:
0274 m.throws_local_simple_error()
0275 assert not isinstance(excinfo.value, cm.LocalSimpleException)
0276 assert msg(excinfo.value) == "this mod"
0277
0278
0279 def test_error_already_set_message_with_unicode_surrogate():
0280 assert m.error_already_set_what(RuntimeError, "\ud927") == (
0281 "RuntimeError: \\ud927",
0282 False,
0283 )
0284
0285
0286 def test_error_already_set_message_with_malformed_utf8():
0287 assert m.error_already_set_what(RuntimeError, b"\x80") == (
0288 "RuntimeError: b'\\x80'",
0289 False,
0290 )
0291
0292
0293 class FlakyException(Exception):
0294 def __init__(self, failure_point):
0295 if failure_point == "failure_point_init":
0296 raise ValueError("triggered_failure_point_init")
0297 self.failure_point = failure_point
0298
0299 def __str__(self):
0300 if self.failure_point == "failure_point_str":
0301 raise ValueError("triggered_failure_point_str")
0302 return "FlakyException.__str__"
0303
0304
0305 @pytest.mark.parametrize(
0306 "exc_type, exc_value, expected_what",
0307 (
0308 (ValueError, "plain_str", "ValueError: plain_str"),
0309 (ValueError, ("tuple_elem",), "ValueError: tuple_elem"),
0310 (FlakyException, ("happy",), "FlakyException: FlakyException.__str__"),
0311 ),
0312 )
0313 def test_error_already_set_what_with_happy_exceptions(
0314 exc_type, exc_value, expected_what
0315 ):
0316 what, py_err_set_after_what = m.error_already_set_what(exc_type, exc_value)
0317 assert not py_err_set_after_what
0318 assert what == expected_what
0319
0320
0321 @pytest.mark.skipif("env.PYPY", reason="PyErr_NormalizeException Segmentation fault")
0322 def test_flaky_exception_failure_point_init():
0323 with pytest.raises(RuntimeError) as excinfo:
0324 m.error_already_set_what(FlakyException, ("failure_point_init",))
0325 lines = str(excinfo.value).splitlines()
0326
0327 assert lines[:3] == [
0328 "pybind11::error_already_set: MISMATCH of original and normalized active exception types:"
0329 " ORIGINAL FlakyException REPLACED BY ValueError: triggered_failure_point_init",
0330 "",
0331 "At:",
0332 ]
0333
0334 assert "test_exceptions.py(" in lines[3]
0335 assert lines[3].endswith("): __init__")
0336 assert lines[4].endswith("): test_flaky_exception_failure_point_init")
0337
0338
0339 def test_flaky_exception_failure_point_str():
0340 what, py_err_set_after_what = m.error_already_set_what(
0341 FlakyException, ("failure_point_str",)
0342 )
0343 assert not py_err_set_after_what
0344 lines = what.splitlines()
0345 if env.PYPY and len(lines) == 3:
0346 n = 3
0347 else:
0348 n = 5
0349 assert (
0350 lines[:n]
0351 == [
0352 "FlakyException: <MESSAGE UNAVAILABLE DUE TO ANOTHER EXCEPTION>",
0353 "",
0354 "MESSAGE UNAVAILABLE DUE TO EXCEPTION: ValueError: triggered_failure_point_str",
0355 "",
0356 "At:",
0357 ][:n]
0358 )
0359
0360
0361 def test_cross_module_interleaved_error_already_set():
0362 with pytest.raises(RuntimeError) as excinfo:
0363 m.test_cross_module_interleaved_error_already_set()
0364 assert str(excinfo.value) in (
0365 "2nd error.",
0366 "RuntimeError: 2nd error.",
0367 )
0368
0369
0370 def test_error_already_set_double_restore():
0371 m.test_error_already_set_double_restore(True)
0372 with pytest.raises(RuntimeError) as excinfo:
0373 m.test_error_already_set_double_restore(False)
0374 assert str(excinfo.value) == (
0375 "Internal error: pybind11::detail::error_fetch_and_normalize::restore()"
0376 " called a second time. ORIGINAL ERROR: ValueError: Random error."
0377 )
0378
0379
0380 def test_pypy_oserror_normalization():
0381
0382 what = m.test_pypy_oserror_normalization()
0383 assert "this_filename_must_not_exist" in what