test_exceptions.cpp revision 11986
1/*
2    tests/test_custom-exceptions.cpp -- exception translation
3
4    Copyright (c) 2016 Pim Schellart <P.Schellart@princeton.edu>
5
6    All rights reserved. Use of this source code is governed by a
7    BSD-style license that can be found in the LICENSE file.
8*/
9
10#include "pybind11_tests.h"
11
12// A type that should be raised as an exeption in Python
13class MyException : public std::exception {
14public:
15    explicit MyException(const char * m) : message{m} {}
16    virtual const char * what() const noexcept override {return message.c_str();}
17private:
18    std::string message = "";
19};
20
21// A type that should be translated to a standard Python exception
22class MyException2 : public std::exception {
23public:
24    explicit MyException2(const char * m) : message{m} {}
25    virtual const char * what() const noexcept override {return message.c_str();}
26private:
27    std::string message = "";
28};
29
30// A type that is not derived from std::exception (and is thus unknown)
31class MyException3 {
32public:
33    explicit MyException3(const char * m) : message{m} {}
34    virtual const char * what() const noexcept {return message.c_str();}
35private:
36    std::string message = "";
37};
38
39// A type that should be translated to MyException
40// and delegated to its exception translator
41class MyException4 : public std::exception {
42public:
43    explicit MyException4(const char * m) : message{m} {}
44    virtual const char * what() const noexcept override {return message.c_str();}
45private:
46    std::string message = "";
47};
48
49
50// Like the above, but declared via the helper function
51class MyException5 : public std::logic_error {
52public:
53    explicit MyException5(const std::string &what) : std::logic_error(what) {}
54};
55
56// Inherits from MyException5
57class MyException5_1 : public MyException5 {
58    using MyException5::MyException5;
59};
60
61void throws1() {
62    throw MyException("this error should go to a custom type");
63}
64
65void throws2() {
66    throw MyException2("this error should go to a standard Python exception");
67}
68
69void throws3() {
70    throw MyException3("this error cannot be translated");
71}
72
73void throws4() {
74    throw MyException4("this error is rethrown");
75}
76
77void throws5() {
78    throw MyException5("this is a helper-defined translated exception");
79}
80
81void throws5_1() {
82    throw MyException5_1("MyException5 subclass");
83}
84
85void throws_logic_error() {
86    throw std::logic_error("this error should fall through to the standard handler");
87}
88
89struct PythonCallInDestructor {
90    PythonCallInDestructor(const py::dict &d) : d(d) {}
91    ~PythonCallInDestructor() { d["good"] = true; }
92
93    py::dict d;
94};
95
96test_initializer custom_exceptions([](py::module &m) {
97    // make a new custom exception and use it as a translation target
98    static py::exception<MyException> ex(m, "MyException");
99    py::register_exception_translator([](std::exception_ptr p) {
100        try {
101            if (p) std::rethrow_exception(p);
102        } catch (const MyException &e) {
103            // Set MyException as the active python error
104            ex(e.what());
105        }
106    });
107
108    // register new translator for MyException2
109    // no need to store anything here because this type will
110    // never by visible from Python
111    py::register_exception_translator([](std::exception_ptr p) {
112        try {
113            if (p) std::rethrow_exception(p);
114        } catch (const MyException2 &e) {
115            // Translate this exception to a standard RuntimeError
116            PyErr_SetString(PyExc_RuntimeError, e.what());
117        }
118    });
119
120    // register new translator for MyException4
121    // which will catch it and delegate to the previously registered
122    // translator for MyException by throwing a new exception
123    py::register_exception_translator([](std::exception_ptr p) {
124        try {
125            if (p) std::rethrow_exception(p);
126        } catch (const MyException4 &e) {
127            throw MyException(e.what());
128        }
129    });
130
131    // A simple exception translation:
132    auto ex5 = py::register_exception<MyException5>(m, "MyException5");
133    // A slightly more complicated one that declares MyException5_1 as a subclass of MyException5
134    py::register_exception<MyException5_1>(m, "MyException5_1", ex5.ptr());
135
136    m.def("throws1", &throws1);
137    m.def("throws2", &throws2);
138    m.def("throws3", &throws3);
139    m.def("throws4", &throws4);
140    m.def("throws5", &throws5);
141    m.def("throws5_1", &throws5_1);
142    m.def("throws_logic_error", &throws_logic_error);
143
144    m.def("throw_already_set", [](bool err) {
145        if (err)
146            PyErr_SetString(PyExc_ValueError, "foo");
147        try {
148            throw py::error_already_set();
149        } catch (const std::runtime_error& e) {
150            if ((err && e.what() != std::string("ValueError: foo")) ||
151                (!err && e.what() != std::string("Unknown internal error occurred")))
152            {
153                PyErr_Clear();
154                throw std::runtime_error("error message mismatch");
155            }
156        }
157        PyErr_Clear();
158        if (err)
159            PyErr_SetString(PyExc_ValueError, "foo");
160        throw py::error_already_set();
161    });
162
163    m.def("python_call_in_destructor", [](py::dict d) {
164        try {
165            PythonCallInDestructor set_dict_in_destructor(d);
166            PyErr_SetString(PyExc_ValueError, "foo");
167            throw py::error_already_set();
168        } catch (const py::error_already_set&) {
169            return true;
170        }
171        return false;
172    });
173});
174