diff --git a/src/testing/exceptiontest.cs b/src/testing/exceptiontest.cs index 45acaa2cc..0b72e0014 100644 --- a/src/testing/exceptiontest.cs +++ b/src/testing/exceptiontest.cs @@ -1,3 +1,4 @@ +using Python.Runtime; using System; using System.Collections; using System.Collections.Generic; @@ -81,6 +82,40 @@ public static void ThrowChainedExceptions() throw new Exception("Outer exception", exc2); } } + + public static IntPtr DoThrowSimple() + { + using (Py.GIL()) + { + dynamic builtins = Py.Import("builtins"); + var typeErrorType = new PyType(builtins.TypeError); + var pyerr = new PythonException(typeErrorType, value:null, traceback:null, "Type error, the first", innerException:null); + throw new ArgumentException("Bogus bad parameter", pyerr); + + } + } + + public static void DoThrowWithInner() + { + using(Py.GIL()) + { + // create a TypeError + dynamic builtins = Py.Import("builtins"); + var pyerrFirst = new PythonException(new PyType(builtins.TypeError), value:null, traceback:null, "Type error, the first", innerException:null); + + // Create an ArgumentException, but as a python exception, with the previous type error as the inner exception + var argExc = new ArgumentException("Bogus bad parameter", pyerrFirst); + var argExcPyObj = argExc.ToPython(); + var pyArgExc = new PythonException(argExcPyObj.GetPythonType(), value:null, traceback:null, argExc.Message, innerException:argExc.InnerException); + // This object must be disposed explicitly or else we get a false-positive leak. + argExcPyObj.Dispose(); + + // Then throw a TypeError with the ArgumentException-as-python-error exception as inner. + var pyerrSecond = new PythonException(new PyType(builtins.TypeError), value:null, traceback:null, "Type error, the second", innerException:pyArgExc); + throw pyerrSecond; + + } + } } diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 7fafeebcb..469934fe5 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -8,6 +8,51 @@ import pytest import pickle +# begin code from https://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects +import gc +# Recursively expand slist's objects +# into olist, using seen to track +# already processed objects. + +def _getr(slist, olist, seen): + for e in slist: + if id(e) in seen: + continue + seen[id(e)] = None + olist.append(e) + tl = gc.get_referents(e) + if tl: + _getr(tl, olist, seen) + +# The public function. +def get_all_objects(): + gcl = gc.get_objects() + olist = [] + seen = {} + # Just in case: + seen[id(gcl)] = None + seen[id(olist)] = None + seen[id(seen)] = None + # _getr does the real work. + _getr(gcl, olist, seen) + return olist +# end code from https://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects + +def leak_check(func): + def do_leak_check(): + func() + gc.collect() + exc = {x for x in get_all_objects() if isinstance(x, Exception) and not isinstance(x, pytest.PytestDeprecationWarning)} + print(len(exc)) + if len(exc): + for x in exc: + print('-------') + print(repr(x)) + print(gc.get_referrers(x)) + print(len(gc.get_referrers(x))) + assert False + gc.collect() + return do_leak_check def test_unified_exception_semantics(): """Test unified exception semantics.""" @@ -375,3 +420,33 @@ def test_iteration_innerexception(): # after exception is thrown iterator is no longer valid with pytest.raises(StopIteration): next(val) + +def leak_test(func): + def do_test_leak(): + # PyTest leaks things, gather the current state + orig_exc = {x for x in get_all_objects() if isinstance(x, Exception)} + func() + exc = {x for x in get_all_objects() if isinstance(x, Exception)} + possibly_leaked = exc - orig_exc + assert not possibly_leaked + + return do_test_leak + +@leak_test +def test_dont_leak_exceptions_simple(): + from Python.Test import ExceptionTest + + try: + ExceptionTest.DoThrowSimple() + except System.ArgumentException: + print('type error, as expected') + +@leak_test +def test_dont_leak_exceptions_inner(): + from Python.Test import ExceptionTest + try: + ExceptionTest.DoThrowWithInner() + except TypeError: + print('type error, as expected') + except System.ArgumentException: + print('type error, also expected') \ No newline at end of file