mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-20 14:59:27 +00:00
Add and document py::error_already_set::discard_as_unraisable()
To deal with exceptions that hit destructors or other noexcept functions. Includes fixes to support Python 2.7 and extends documentation on error handling. @virtuald and @YannickJadoul both contributed to this PR.
This commit is contained in:
committed by
Ralf W. Grosse-Kunstleve
parent
a876aac2cf
commit
3618bea2aa
@@ -65,6 +65,25 @@ struct PythonCallInDestructor {
|
||||
py::dict d;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct PythonAlreadySetInDestructor {
|
||||
PythonAlreadySetInDestructor(const py::str &s) : s(s) {}
|
||||
~PythonAlreadySetInDestructor() {
|
||||
py::dict foo;
|
||||
try {
|
||||
// Assign to a py::object to force read access of nonexistent dict entry
|
||||
py::object o = foo["bar"];
|
||||
}
|
||||
catch (py::error_already_set& ex) {
|
||||
ex.discard_as_unraisable(s);
|
||||
}
|
||||
}
|
||||
|
||||
py::str s;
|
||||
};
|
||||
|
||||
|
||||
TEST_SUBMODULE(exceptions, m) {
|
||||
m.def("throw_std_exception", []() {
|
||||
throw std::runtime_error("This exception was intentionally thrown.");
|
||||
@@ -183,6 +202,11 @@ TEST_SUBMODULE(exceptions, m) {
|
||||
return false;
|
||||
});
|
||||
|
||||
m.def("python_alreadyset_in_destructor", [](py::str s) {
|
||||
PythonAlreadySetInDestructor alreadyset_in_destructor(s);
|
||||
return true;
|
||||
});
|
||||
|
||||
// test_nested_throws
|
||||
m.def("try_catch", [m](py::object exc_type, py::function f, py::args args) {
|
||||
try { f(*args); }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import exceptions as m
|
||||
@@ -48,6 +50,33 @@ def test_python_call_in_catch():
|
||||
assert d["good"] is True
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_exception_matches():
|
||||
assert m.exception_matches()
|
||||
assert m.exception_matches_base()
|
||||
|
||||
Reference in New Issue
Block a user