Skip to content

Commit 1da4bf0

Browse files
committed
Add unit tests showing basic exception as class workflow
1 parent f8743f8 commit 1da4bf0

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

tests/test_exceptions.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ struct PythonCallInDestructor {
6666
};
6767

6868

69-
7069
struct PythonAlreadySetInDestructor {
7170
PythonAlreadySetInDestructor(const py::str &s) : s(s) {}
7271
~PythonAlreadySetInDestructor() {
@@ -84,7 +83,30 @@ struct PythonAlreadySetInDestructor {
8483
};
8584

8685

86+
// An Exception that is going to be bound as a py:class_
87+
class BoundException : public std::exception {
88+
public:
89+
BoundException(const std::string& m, int e) : message{m}, m_errorCode(e) {}
90+
virtual const char * what() const noexcept override {return message.c_str();}
91+
int errorCode() const noexcept { return m_errorCode;}
92+
private:
93+
std::string message;
94+
int m_errorCode;
95+
};
96+
97+
98+
8799
TEST_SUBMODULE(exceptions, m) {
100+
// Provide a class binding for BoundException. This is providing the
101+
// dynamic_attr because the parent type (PyExc_Exception) permits dynamic
102+
// attributes. One improvement might be to make is_except force
103+
// dynamic_attr
104+
auto PyBoundException = py::class_< BoundException>(m, "BoundException", py::is_except(), py::dynamic_attr())
105+
.def(py::init< std::string, int>())
106+
.def("getErrorCode", &BoundException::errorCode)
107+
.def("getMessage", &BoundException::what)
108+
.def_property_readonly("message", &BoundException::what);
109+
88110
m.def("throw_std_exception", []() {
89111
throw std::runtime_error("This exception was intentionally thrown.");
90112
});
@@ -128,14 +150,29 @@ TEST_SUBMODULE(exceptions, m) {
128150
// A slightly more complicated one that declares MyException5_1 as a subclass of MyException5
129151
py::register_exception<MyException5_1>(m, "MyException5_1", ex5.ptr());
130152

153+
// An exception translator for the exception that is bound as a class
154+
// This is using PyErr_SetObject instead of PyErr_SetString.
155+
py::register_exception_translator([](std::exception_ptr p) {
156+
try {
157+
if (p) {
158+
std::rethrow_exception(p);
159+
}
160+
} catch (const BoundException &e) {
161+
auto err = py::cast(e);
162+
auto errType = err.get_type().ptr();
163+
PyErr_SetObject(errType, err.ptr());
164+
}
165+
});
166+
131167
m.def("throws1", []() { throw MyException("this error should go to a custom type"); });
132168
m.def("throws2", []() { throw MyException2("this error should go to a standard Python exception"); });
133169
m.def("throws3", []() { throw MyException3("this error cannot be translated"); });
134170
m.def("throws4", []() { throw MyException4("this error is rethrown"); });
135171
m.def("throws5", []() { throw MyException5("this is a helper-defined translated exception"); });
136172
m.def("throws5_1", []() { throw MyException5_1("MyException5 subclass"); });
137173
m.def("throws_logic_error", []() { throw std::logic_error("this error should fall through to the standard handler"); });
138-
m.def("throws_overflow_error", []() {throw std::overflow_error(""); });
174+
m.def("throws_overflow_error", []() { throw std::overflow_error(""); });
175+
m.def("throws_bound_exception", []() { throw BoundException("this error is a class", 42); });
139176
m.def("exception_matches", []() {
140177
py::dict foo;
141178
try {

tests/test_exceptions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,18 @@ def __repr__(self):
189189

190190
with pytest.raises(TypeError):
191191
m.simple_bool_passthrough(MyRepr())
192+
193+
194+
def test_bound_exceptions():
195+
"""Tests throwing/catching an exception bound as a class"""
196+
try:
197+
m.throws_bound_exception()
198+
except m.BoundException as ex:
199+
assert ex.message == "this error is a class"
200+
assert ex.getErrorCode() == 42
201+
202+
try:
203+
raise m.BoundException("raising from python", 14)
204+
except m.BoundException as ex:
205+
assert ex.message == "raising from python"
206+
assert ex.getErrorCode() == 14

0 commit comments

Comments
 (0)