Skip to content

Add tests for exception leaking. #1679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/testing/exceptiontest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Python.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
Expand Down Expand Up @@ -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;

}
}
}


Expand Down
75 changes: 75 additions & 0 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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')