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.
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
index 053e7d4..83a46bf 100644
--- a/tests/test_exceptions.py
+++ b/tests/test_exceptions.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+import sys
+
import pytest
from pybind11_tests import exceptions as m
@@ -48,6 +50,33 @@
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()