test_exceptions.py 5.84 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
3
import sys

Dean Moldovan's avatar
Dean Moldovan committed
4
5
import pytest

6
from pybind11_tests import exceptions as m
7
import pybind11_cross_module_tests as cm
Dean Moldovan's avatar
Dean Moldovan committed
8

9

10
def test_std_exception(msg):
11
    with pytest.raises(RuntimeError) as excinfo:
12
        m.throw_std_exception()
13
14
15
    assert msg(excinfo.value) == "This exception was intentionally thrown."


16
17
def test_error_already_set(msg):
    with pytest.raises(RuntimeError) as excinfo:
18
        m.throw_already_set(False)
19
20
21
    assert msg(excinfo.value) == "Unknown internal error occurred"

    with pytest.raises(ValueError) as excinfo:
22
        m.throw_already_set(True)
23
24
25
    assert msg(excinfo.value) == "foo"


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def test_cross_module_exceptions():
    with pytest.raises(RuntimeError) as excinfo:
        cm.raise_runtime_error()
    assert str(excinfo.value) == "My runtime error"

    with pytest.raises(ValueError) as excinfo:
        cm.raise_value_error()
    assert str(excinfo.value) == "My value error"

    with pytest.raises(ValueError) as excinfo:
        cm.throw_pybind_value_error()
    assert str(excinfo.value) == "pybind11 value error"

    with pytest.raises(TypeError) as excinfo:
        cm.throw_pybind_type_error()
    assert str(excinfo.value) == "pybind11 type error"

    with pytest.raises(StopIteration) as excinfo:
        cm.throw_stop_iteration()


47
48
def test_python_call_in_catch():
    d = {}
49
    assert m.python_call_in_destructor(d) is True
50
51
52
    assert d["good"] is True


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def test_python_alreadyset_in_destructor(monkeypatch, capsys):
    hooked = False
    triggered = [False]  # mutable, so Python 2.7 closure can modify it

    if hasattr(sys, 'unraisablehook'):  # Python 3.8+
        hooked = True
        default_hook = sys.unraisablehook

        def hook(unraisable_hook_args):
            exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args
            if obj == 'already_set demo':
                triggered[0] = True
            default_hook(unraisable_hook_args)
            return

        # Use monkeypatch so pytest can apply and remove the patch as appropriate
        monkeypatch.setattr(sys, 'unraisablehook', hook)

    assert m.python_alreadyset_in_destructor('already_set demo') is True
    if hooked:
        assert triggered[0] is True

    _, captured_stderr = capsys.readouterr()
    # Error message is different in Python 2 and 3, check for words that appear in both
    assert 'ignored' in captured_stderr and 'already_set demo' in captured_stderr


80
def test_exception_matches():
81
82
83
    assert m.exception_matches()
    assert m.exception_matches_base()
    assert m.modulenotfound_exception_matches_base()
84
85


Dean Moldovan's avatar
Dean Moldovan committed
86
def test_custom(msg):
87
88
89
    # Can we catch a MyException?
    with pytest.raises(m.MyException) as excinfo:
        m.throws1()
Dean Moldovan's avatar
Dean Moldovan committed
90
91
92
93
    assert msg(excinfo.value) == "this error should go to a custom type"

    # Can we translate to standard Python exceptions?
    with pytest.raises(RuntimeError) as excinfo:
94
        m.throws2()
Dean Moldovan's avatar
Dean Moldovan committed
95
96
97
98
    assert msg(excinfo.value) == "this error should go to a standard Python exception"

    # Can we handle unknown exceptions?
    with pytest.raises(RuntimeError) as excinfo:
99
        m.throws3()
Dean Moldovan's avatar
Dean Moldovan committed
100
101
102
    assert msg(excinfo.value) == "Caught an unknown exception!"

    # Can we delegate to another handler by rethrowing?
103
104
    with pytest.raises(m.MyException) as excinfo:
        m.throws4()
Dean Moldovan's avatar
Dean Moldovan committed
105
106
    assert msg(excinfo.value) == "this error is rethrown"

107
    # Can we fall-through to the default handler?
Dean Moldovan's avatar
Dean Moldovan committed
108
    with pytest.raises(RuntimeError) as excinfo:
109
        m.throws_logic_error()
Dean Moldovan's avatar
Dean Moldovan committed
110
    assert msg(excinfo.value) == "this error should fall through to the standard handler"
111

112
113
114
115
    # OverFlow error translation.
    with pytest.raises(OverflowError) as excinfo:
        m.throws_overflow_error()

116
    # Can we handle a helper-declared exception?
117
118
    with pytest.raises(m.MyException5) as excinfo:
        m.throws5()
119
120
121
    assert msg(excinfo.value) == "this is a helper-defined translated exception"

    # Exception subclassing:
122
123
    with pytest.raises(m.MyException5) as excinfo:
        m.throws5_1()
124
    assert msg(excinfo.value) == "MyException5 subclass"
125
    assert isinstance(excinfo.value, m.MyException5_1)
126

127
128
    with pytest.raises(m.MyException5_1) as excinfo:
        m.throws5_1()
129
130
    assert msg(excinfo.value) == "MyException5 subclass"

131
    with pytest.raises(m.MyException5) as excinfo:
132
        try:
133
134
            m.throws5()
        except m.MyException5_1:
135
136
            raise RuntimeError("Exception error: caught child from parent")
    assert msg(excinfo.value) == "this is a helper-defined translated exception"
Jason Rhinelander's avatar
Jason Rhinelander committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180


def test_nested_throws(capture):
    """Tests nested (e.g. C++ -> Python -> C++) exception handling"""

    def throw_myex():
        raise m.MyException("nested error")

    def throw_myex5():
        raise m.MyException5("nested error 5")

    # In the comments below, the exception is caught in the first step, thrown in the last step

    # C++ -> Python
    with capture:
        m.try_catch(m.MyException5, throw_myex5)
    assert str(capture).startswith("MyException5: nested error 5")

    # Python -> C++ -> Python
    with pytest.raises(m.MyException) as excinfo:
        m.try_catch(m.MyException5, throw_myex)
    assert str(excinfo.value) == "nested error"

    def pycatch(exctype, f, *args):
        try:
            f(*args)
        except m.MyException as e:
            print(e)

    # C++ -> Python -> C++ -> Python
    with capture:
        m.try_catch(
            m.MyException5, pycatch, m.MyException, m.try_catch, m.MyException, throw_myex5)
    assert str(capture).startswith("MyException5: nested error 5")

    # C++ -> Python -> C++
    with capture:
        m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4)
    assert capture == "this error is rethrown"

    # Python -> C++ -> Python -> C++
    with pytest.raises(m.MyException5) as excinfo:
        m.try_catch(m.MyException, pycatch, m.MyException, m.throws5)
    assert str(excinfo.value) == "this is a helper-defined translated exception"