1# -*- coding: utf-8 -*- 2import sys 3 4import pytest 5 6from pybind11_tests import exceptions as m 7import pybind11_cross_module_tests as cm 8 9 10def test_std_exception(msg): 11 with pytest.raises(RuntimeError) as excinfo: 12 m.throw_std_exception() 13 assert msg(excinfo.value) == "This exception was intentionally thrown." 14 15 16def test_error_already_set(msg): 17 with pytest.raises(RuntimeError) as excinfo: 18 m.throw_already_set(False) 19 assert msg(excinfo.value) == "Unknown internal error occurred" 20 21 with pytest.raises(ValueError) as excinfo: 22 m.throw_already_set(True) 23 assert msg(excinfo.value) == "foo" 24 25 26def test_cross_module_exceptions(): 27 with pytest.raises(RuntimeError) as excinfo: 28 cm.raise_runtime_error() 29 assert str(excinfo.value) == "My runtime error" 30 31 with pytest.raises(ValueError) as excinfo: 32 cm.raise_value_error() 33 assert str(excinfo.value) == "My value error" 34 35 with pytest.raises(ValueError) as excinfo: 36 cm.throw_pybind_value_error() 37 assert str(excinfo.value) == "pybind11 value error" 38 39 with pytest.raises(TypeError) as excinfo: 40 cm.throw_pybind_type_error() 41 assert str(excinfo.value) == "pybind11 type error" 42 43 with pytest.raises(StopIteration) as excinfo: 44 cm.throw_stop_iteration() 45 46 47def test_python_call_in_catch(): 48 d = {} 49 assert m.python_call_in_destructor(d) is True 50 assert d["good"] is True 51 52 53def ignore_pytest_unraisable_warning(f): 54 unraisable = "PytestUnraisableExceptionWarning" 55 if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6 56 dec = pytest.mark.filterwarnings("ignore::pytest.{}".format(unraisable)) 57 return dec(f) 58 else: 59 return f 60 61 62@ignore_pytest_unraisable_warning 63def test_python_alreadyset_in_destructor(monkeypatch, capsys): 64 hooked = False 65 triggered = [False] # mutable, so Python 2.7 closure can modify it 66 67 if hasattr(sys, "unraisablehook"): # Python 3.8+ 68 hooked = True 69 # Don't take `sys.unraisablehook`, as that's overwritten by pytest 70 default_hook = sys.__unraisablehook__ 71 72 def hook(unraisable_hook_args): 73 exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args 74 if obj == "already_set demo": 75 triggered[0] = True 76 default_hook(unraisable_hook_args) 77 return 78 79 # Use monkeypatch so pytest can apply and remove the patch as appropriate 80 monkeypatch.setattr(sys, "unraisablehook", hook) 81 82 assert m.python_alreadyset_in_destructor("already_set demo") is True 83 if hooked: 84 assert triggered[0] is True 85 86 _, captured_stderr = capsys.readouterr() 87 # Error message is different in Python 2 and 3, check for words that appear in both 88 assert "ignored" in captured_stderr and "already_set demo" in captured_stderr 89 90 91def test_exception_matches(): 92 assert m.exception_matches() 93 assert m.exception_matches_base() 94 assert m.modulenotfound_exception_matches_base() 95 96 97def test_custom(msg): 98 # Can we catch a MyException? 99 with pytest.raises(m.MyException) as excinfo: 100 m.throws1() 101 assert msg(excinfo.value) == "this error should go to a custom type" 102 103 # Can we translate to standard Python exceptions? 104 with pytest.raises(RuntimeError) as excinfo: 105 m.throws2() 106 assert msg(excinfo.value) == "this error should go to a standard Python exception" 107 108 # Can we handle unknown exceptions? 109 with pytest.raises(RuntimeError) as excinfo: 110 m.throws3() 111 assert msg(excinfo.value) == "Caught an unknown exception!" 112 113 # Can we delegate to another handler by rethrowing? 114 with pytest.raises(m.MyException) as excinfo: 115 m.throws4() 116 assert msg(excinfo.value) == "this error is rethrown" 117 118 # Can we fall-through to the default handler? 119 with pytest.raises(RuntimeError) as excinfo: 120 m.throws_logic_error() 121 assert ( 122 msg(excinfo.value) == "this error should fall through to the standard handler" 123 ) 124 125 # OverFlow error translation. 126 with pytest.raises(OverflowError) as excinfo: 127 m.throws_overflow_error() 128 129 # Can we handle a helper-declared exception? 130 with pytest.raises(m.MyException5) as excinfo: 131 m.throws5() 132 assert msg(excinfo.value) == "this is a helper-defined translated exception" 133 134 # Exception subclassing: 135 with pytest.raises(m.MyException5) as excinfo: 136 m.throws5_1() 137 assert msg(excinfo.value) == "MyException5 subclass" 138 assert isinstance(excinfo.value, m.MyException5_1) 139 140 with pytest.raises(m.MyException5_1) as excinfo: 141 m.throws5_1() 142 assert msg(excinfo.value) == "MyException5 subclass" 143 144 with pytest.raises(m.MyException5) as excinfo: 145 try: 146 m.throws5() 147 except m.MyException5_1: 148 raise RuntimeError("Exception error: caught child from parent") 149 assert msg(excinfo.value) == "this is a helper-defined translated exception" 150 151 152def test_nested_throws(capture): 153 """Tests nested (e.g. C++ -> Python -> C++) exception handling""" 154 155 def throw_myex(): 156 raise m.MyException("nested error") 157 158 def throw_myex5(): 159 raise m.MyException5("nested error 5") 160 161 # In the comments below, the exception is caught in the first step, thrown in the last step 162 163 # C++ -> Python 164 with capture: 165 m.try_catch(m.MyException5, throw_myex5) 166 assert str(capture).startswith("MyException5: nested error 5") 167 168 # Python -> C++ -> Python 169 with pytest.raises(m.MyException) as excinfo: 170 m.try_catch(m.MyException5, throw_myex) 171 assert str(excinfo.value) == "nested error" 172 173 def pycatch(exctype, f, *args): 174 try: 175 f(*args) 176 except m.MyException as e: 177 print(e) 178 179 # C++ -> Python -> C++ -> Python 180 with capture: 181 m.try_catch( 182 m.MyException5, 183 pycatch, 184 m.MyException, 185 m.try_catch, 186 m.MyException, 187 throw_myex5, 188 ) 189 assert str(capture).startswith("MyException5: nested error 5") 190 191 # C++ -> Python -> C++ 192 with capture: 193 m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4) 194 assert capture == "this error is rethrown" 195 196 # Python -> C++ -> Python -> C++ 197 with pytest.raises(m.MyException5) as excinfo: 198 m.try_catch(m.MyException, pycatch, m.MyException, m.throws5) 199 assert str(excinfo.value) == "this is a helper-defined translated exception" 200 201 202# This can often happen if you wrap a pybind11 class in a Python wrapper 203def test_invalid_repr(): 204 class MyRepr(object): 205 def __repr__(self): 206 raise AttributeError("Example error") 207 208 with pytest.raises(TypeError): 209 m.simple_bool_passthrough(MyRepr()) 210