diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 3fc29bde2..e7bb345f8 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -31,8 +31,9 @@ internal CLRObject(Object ob, IntPtr tp) : base() this.gcHandle = gc; inst = ob; - // Fix the BaseException args slot if wrapping a CLR exception - Exceptions.SetArgs(py); + // Fix the BaseException args (and __cause__ in case of Python 3) + // slot if wrapping a CLR exception + Exceptions.SetArgsAndCause(py); } @@ -70,4 +71,4 @@ internal static IntPtr GetInstHandle(Object ob) return co.pyHandle; } } -} \ No newline at end of file +} diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index ec1b82175..48f40958d 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -144,7 +144,7 @@ internal static void Shutdown() /// /// A CLR exception /// The python object wrapping - internal static void SetArgs(IntPtr ob) + internal static void SetArgsAndCause(IntPtr ob) { var e = ExceptionClassObject.ToException(ob); if (e == null) @@ -163,6 +163,14 @@ internal static void SetArgs(IntPtr ob) } Marshal.WriteIntPtr(ob, ExceptionOffset.args, args); + +#if !(PYTHON25 || PYTHON26 || PYTHON27) + if (e.InnerException != null) + { + IntPtr cause = CLRObject.GetInstHandle(e.InnerException); + Marshal.WriteIntPtr(ob, ExceptionOffset.cause, cause); + } +#endif } /// @@ -434,4 +442,4 @@ puplic static variables on the Exceptions class filled in from //PyAPI_DATA(PyObject *) PyExc_BytesWarning; #endif } -} \ No newline at end of file +} diff --git a/src/testing/exceptiontest.cs b/src/testing/exceptiontest.cs index b38182857..414c25d82 100644 --- a/src/testing/exceptiontest.cs +++ b/src/testing/exceptiontest.cs @@ -1,6 +1,5 @@ using System; - namespace Python.Test { //======================================================================== @@ -55,6 +54,25 @@ public static bool ThrowException() { throw new OverflowException("error"); } + + public static void ThrowChainedExceptions() + { + try + { + try + { + throw new Exception("Innermost exception"); + } + catch (Exception exc) + { + throw new Exception("Inner exception", exc); + } + } + catch (Exception exc2) + { + throw new Exception("Outer exception", exc2); + } + } } diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index 892d6f062..eedf2aa61 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -345,6 +345,27 @@ def testPicklingExceptions(self): self.assertEqual(exc.args, loaded.args) + def testChainedExceptions(self): + if six.PY3: + from Python.Test import ExceptionTest + + try: + ExceptionTest.ThrowChainedExceptions() + except Exception as exc: + msgs = [ + "Outer exception", + "Inner exception", + "Innermost exception" + ] + + for msg in msgs: + self.assertEqual(exc.Message, msg) + self.assertEqual(exc.__cause__, exc.InnerException) + exc = exc.__cause__ + + else: + self.fail("Test should raise an exception") + def test_suite(): return unittest.makeSuite(ExceptionTests)