Henry Schreiner | d8c7ee0 | 2020-07-20 13:35:21 -0400 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
James R. Barlow | 3618bea | 2020-08-08 03:07:14 -0700 | [diff] [blame] | 2 | import sys |
| 3 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 4 | import pytest |
| 5 | |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 6 | from pybind11_tests import exceptions as m |
Jason Rhinelander | d598172 | 2017-07-28 21:38:23 -0400 | [diff] [blame] | 7 | import pybind11_cross_module_tests as cm |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 8 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 9 | |
Dean Moldovan | 83e328f | 2017-06-09 00:44:49 +0200 | [diff] [blame] | 10 | def test_std_exception(msg): |
Dean Moldovan | 83e328f | 2017-06-09 00:44:49 +0200 | [diff] [blame] | 11 | with pytest.raises(RuntimeError) as excinfo: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 12 | m.throw_std_exception() |
Dean Moldovan | 83e328f | 2017-06-09 00:44:49 +0200 | [diff] [blame] | 13 | assert msg(excinfo.value) == "This exception was intentionally thrown." |
| 14 | |
| 15 | |
Ivan Smirnov | 67b5489 | 2016-09-07 21:10:16 +0100 | [diff] [blame] | 16 | def test_error_already_set(msg): |
Ivan Smirnov | 67b5489 | 2016-09-07 21:10:16 +0100 | [diff] [blame] | 17 | with pytest.raises(RuntimeError) as excinfo: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 18 | m.throw_already_set(False) |
Ivan Smirnov | 67b5489 | 2016-09-07 21:10:16 +0100 | [diff] [blame] | 19 | assert msg(excinfo.value) == "Unknown internal error occurred" |
| 20 | |
| 21 | with pytest.raises(ValueError) as excinfo: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 22 | m.throw_already_set(True) |
Ivan Smirnov | 67b5489 | 2016-09-07 21:10:16 +0100 | [diff] [blame] | 23 | assert msg(excinfo.value) == "foo" |
| 24 | |
| 25 | |
Jason Rhinelander | d598172 | 2017-07-28 21:38:23 -0400 | [diff] [blame] | 26 | def 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 | |
Dean Moldovan | 135ba8d | 2016-09-10 11:58:02 +0200 | [diff] [blame] | 47 | def test_python_call_in_catch(): |
Dean Moldovan | 135ba8d | 2016-09-10 11:58:02 +0200 | [diff] [blame] | 48 | d = {} |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 49 | assert m.python_call_in_destructor(d) is True |
Dean Moldovan | 135ba8d | 2016-09-10 11:58:02 +0200 | [diff] [blame] | 50 | assert d["good"] is True |
| 51 | |
| 52 | |
James R. Barlow | 3618bea | 2020-08-08 03:07:14 -0700 | [diff] [blame] | 53 | def test_python_alreadyset_in_destructor(monkeypatch, capsys): |
| 54 | hooked = False |
| 55 | triggered = [False] # mutable, so Python 2.7 closure can modify it |
| 56 | |
| 57 | if hasattr(sys, 'unraisablehook'): # Python 3.8+ |
| 58 | hooked = True |
| 59 | default_hook = sys.unraisablehook |
| 60 | |
| 61 | def hook(unraisable_hook_args): |
| 62 | exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args |
| 63 | if obj == 'already_set demo': |
| 64 | triggered[0] = True |
| 65 | default_hook(unraisable_hook_args) |
| 66 | return |
| 67 | |
| 68 | # Use monkeypatch so pytest can apply and remove the patch as appropriate |
| 69 | monkeypatch.setattr(sys, 'unraisablehook', hook) |
| 70 | |
| 71 | assert m.python_alreadyset_in_destructor('already_set demo') is True |
| 72 | if hooked: |
| 73 | assert triggered[0] is True |
| 74 | |
| 75 | _, captured_stderr = capsys.readouterr() |
| 76 | # Error message is different in Python 2 and 3, check for words that appear in both |
| 77 | assert 'ignored' in captured_stderr and 'already_set demo' in captured_stderr |
| 78 | |
| 79 | |
Roman Miroshnychenko | 83a8a97 | 2017-04-02 23:38:50 +0300 | [diff] [blame] | 80 | def test_exception_matches(): |
Yannick Jadoul | 97784da | 2019-05-12 23:35:49 +0200 | [diff] [blame] | 81 | assert m.exception_matches() |
| 82 | assert m.exception_matches_base() |
| 83 | assert m.modulenotfound_exception_matches_base() |
Roman Miroshnychenko | 83a8a97 | 2017-04-02 23:38:50 +0300 | [diff] [blame] | 84 | |
| 85 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 86 | def test_custom(msg): |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 87 | # Can we catch a MyException? |
| 88 | with pytest.raises(m.MyException) as excinfo: |
| 89 | m.throws1() |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 90 | assert msg(excinfo.value) == "this error should go to a custom type" |
| 91 | |
| 92 | # Can we translate to standard Python exceptions? |
| 93 | with pytest.raises(RuntimeError) as excinfo: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 94 | m.throws2() |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 95 | assert msg(excinfo.value) == "this error should go to a standard Python exception" |
| 96 | |
| 97 | # Can we handle unknown exceptions? |
| 98 | with pytest.raises(RuntimeError) as excinfo: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 99 | m.throws3() |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 100 | assert msg(excinfo.value) == "Caught an unknown exception!" |
| 101 | |
| 102 | # Can we delegate to another handler by rethrowing? |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 103 | with pytest.raises(m.MyException) as excinfo: |
| 104 | m.throws4() |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 105 | assert msg(excinfo.value) == "this error is rethrown" |
| 106 | |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 107 | # Can we fall-through to the default handler? |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 108 | with pytest.raises(RuntimeError) as excinfo: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 109 | m.throws_logic_error() |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 110 | assert msg(excinfo.value) == "this error should fall through to the standard handler" |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 111 | |
Francesco Biscani | deb3cb2 | 2019-11-14 08:56:58 +0100 | [diff] [blame] | 112 | # OverFlow error translation. |
| 113 | with pytest.raises(OverflowError) as excinfo: |
| 114 | m.throws_overflow_error() |
| 115 | |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 116 | # Can we handle a helper-declared exception? |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 117 | with pytest.raises(m.MyException5) as excinfo: |
| 118 | m.throws5() |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 119 | assert msg(excinfo.value) == "this is a helper-defined translated exception" |
| 120 | |
| 121 | # Exception subclassing: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 122 | with pytest.raises(m.MyException5) as excinfo: |
| 123 | m.throws5_1() |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 124 | assert msg(excinfo.value) == "MyException5 subclass" |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 125 | assert isinstance(excinfo.value, m.MyException5_1) |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 126 | |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 127 | with pytest.raises(m.MyException5_1) as excinfo: |
| 128 | m.throws5_1() |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 129 | assert msg(excinfo.value) == "MyException5 subclass" |
| 130 | |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 131 | with pytest.raises(m.MyException5) as excinfo: |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 132 | try: |
Jason Rhinelander | abcf43d | 2017-07-23 12:26:17 -0400 | [diff] [blame] | 133 | m.throws5() |
| 134 | except m.MyException5_1: |
Jason Rhinelander | b3794f1 | 2016-09-16 02:04:15 -0400 | [diff] [blame] | 135 | raise RuntimeError("Exception error: caught child from parent") |
| 136 | assert msg(excinfo.value) == "this is a helper-defined translated exception" |
Jason Rhinelander | 1682b67 | 2017-07-20 23:14:33 -0400 | [diff] [blame] | 137 | |
| 138 | |
| 139 | def test_nested_throws(capture): |
| 140 | """Tests nested (e.g. C++ -> Python -> C++) exception handling""" |
| 141 | |
| 142 | def throw_myex(): |
| 143 | raise m.MyException("nested error") |
| 144 | |
| 145 | def throw_myex5(): |
| 146 | raise m.MyException5("nested error 5") |
| 147 | |
| 148 | # In the comments below, the exception is caught in the first step, thrown in the last step |
| 149 | |
| 150 | # C++ -> Python |
| 151 | with capture: |
| 152 | m.try_catch(m.MyException5, throw_myex5) |
| 153 | assert str(capture).startswith("MyException5: nested error 5") |
| 154 | |
| 155 | # Python -> C++ -> Python |
| 156 | with pytest.raises(m.MyException) as excinfo: |
| 157 | m.try_catch(m.MyException5, throw_myex) |
| 158 | assert str(excinfo.value) == "nested error" |
| 159 | |
| 160 | def pycatch(exctype, f, *args): |
| 161 | try: |
| 162 | f(*args) |
| 163 | except m.MyException as e: |
| 164 | print(e) |
| 165 | |
| 166 | # C++ -> Python -> C++ -> Python |
| 167 | with capture: |
| 168 | m.try_catch( |
| 169 | m.MyException5, pycatch, m.MyException, m.try_catch, m.MyException, throw_myex5) |
| 170 | assert str(capture).startswith("MyException5: nested error 5") |
| 171 | |
| 172 | # C++ -> Python -> C++ |
| 173 | with capture: |
| 174 | m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4) |
| 175 | assert capture == "this error is rethrown" |
| 176 | |
| 177 | # Python -> C++ -> Python -> C++ |
| 178 | with pytest.raises(m.MyException5) as excinfo: |
| 179 | m.try_catch(m.MyException, pycatch, m.MyException, m.throws5) |
| 180 | assert str(excinfo.value) == "this is a helper-defined translated exception" |
Henry Schreiner | cf0a645 | 2020-08-18 07:14:34 -0400 | [diff] [blame^] | 181 | |
| 182 | |
| 183 | # This can often happen if you wrap a pybind11 class in a Python wrapper |
| 184 | def test_invalid_repr(): |
| 185 | |
| 186 | class MyRepr(object): |
| 187 | def __repr__(self): |
| 188 | raise AttributeError("Example error") |
| 189 | |
| 190 | with pytest.raises(TypeError): |
| 191 | m.simple_bool_passthrough(MyRepr()) |