Skip to content

Commit ca1a72b

Browse files
BadSingletonlostmsu
authored andcommitted
Add tests for exception leaking.
Originally from PR #1402. The underlying bug is now fixed, but the tests are atill applicable.
1 parent b7fb03a commit ca1a72b

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

src/testing/exceptiontest.cs

+35
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Python.Runtime;
12
using System;
23
using System.Collections;
34
using System.Collections.Generic;
@@ -81,6 +82,40 @@ public static void ThrowChainedExceptions()
8182
throw new Exception("Outer exception", exc2);
8283
}
8384
}
85+
86+
public static IntPtr DoThrowSimple()
87+
{
88+
using (Py.GIL())
89+
{
90+
dynamic builtins = Py.Import("builtins");
91+
var typeErrorType = new PyType(builtins.TypeError);
92+
var pyerr = new PythonException(typeErrorType, value:null, traceback:null, "Type error, the first", innerException:null);
93+
throw new ArgumentException("Bogus bad parameter", pyerr);
94+
95+
}
96+
}
97+
98+
public static void DoThrowWithInner()
99+
{
100+
using(Py.GIL())
101+
{
102+
// create a TypeError
103+
dynamic builtins = Py.Import("builtins");
104+
var pyerrFirst = new PythonException(new PyType(builtins.TypeError), value:null, traceback:null, "Type error, the first", innerException:null);
105+
106+
// Create an ArgumentException, but as a python exception, with the previous type error as the inner exception
107+
var argExc = new ArgumentException("Bogus bad parameter", pyerrFirst);
108+
var argExcPyObj = argExc.ToPython();
109+
var pyArgExc = new PythonException(argExcPyObj.GetPythonType(), value:null, traceback:null, argExc.Message, innerException:argExc.InnerException);
110+
// This object must be disposed explicitly or else we get a false-positive leak.
111+
argExcPyObj.Dispose();
112+
113+
// Then throw a TypeError with the ArgumentException-as-python-error exception as inner.
114+
var pyerrSecond = new PythonException(new PyType(builtins.TypeError), value:null, traceback:null, "Type error, the second", innerException:pyArgExc);
115+
throw pyerrSecond;
116+
117+
}
118+
}
84119
}
85120

86121

tests/test_exceptions.py

+75
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,51 @@
88
import pytest
99
import pickle
1010

11+
# begin code from https://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects
12+
import gc
13+
# Recursively expand slist's objects
14+
# into olist, using seen to track
15+
# already processed objects.
16+
17+
def _getr(slist, olist, seen):
18+
for e in slist:
19+
if id(e) in seen:
20+
continue
21+
seen[id(e)] = None
22+
olist.append(e)
23+
tl = gc.get_referents(e)
24+
if tl:
25+
_getr(tl, olist, seen)
26+
27+
# The public function.
28+
def get_all_objects():
29+
gcl = gc.get_objects()
30+
olist = []
31+
seen = {}
32+
# Just in case:
33+
seen[id(gcl)] = None
34+
seen[id(olist)] = None
35+
seen[id(seen)] = None
36+
# _getr does the real work.
37+
_getr(gcl, olist, seen)
38+
return olist
39+
# end code from https://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects
40+
41+
def leak_check(func):
42+
def do_leak_check():
43+
func()
44+
gc.collect()
45+
exc = {x for x in get_all_objects() if isinstance(x, Exception) and not isinstance(x, pytest.PytestDeprecationWarning)}
46+
print(len(exc))
47+
if len(exc):
48+
for x in exc:
49+
print('-------')
50+
print(repr(x))
51+
print(gc.get_referrers(x))
52+
print(len(gc.get_referrers(x)))
53+
assert False
54+
gc.collect()
55+
return do_leak_check
1156

1257
def test_unified_exception_semantics():
1358
"""Test unified exception semantics."""
@@ -375,3 +420,33 @@ def test_iteration_innerexception():
375420
# after exception is thrown iterator is no longer valid
376421
with pytest.raises(StopIteration):
377422
next(val)
423+
424+
def leak_test(func):
425+
def do_test_leak():
426+
# PyTest leaks things, gather the current state
427+
orig_exc = {x for x in get_all_objects() if isinstance(x, Exception)}
428+
func()
429+
exc = {x for x in get_all_objects() if isinstance(x, Exception)}
430+
possibly_leaked = exc - orig_exc
431+
assert not possibly_leaked
432+
433+
return do_test_leak
434+
435+
@leak_test
436+
def test_dont_leak_exceptions_simple():
437+
from Python.Test import ExceptionTest
438+
439+
try:
440+
ExceptionTest.DoThrowSimple()
441+
except System.ArgumentException:
442+
print('type error, as expected')
443+
444+
@leak_test
445+
def test_dont_leak_exceptions_inner():
446+
from Python.Test import ExceptionTest
447+
try:
448+
ExceptionTest.DoThrowWithInner()
449+
except TypeError:
450+
print('type error, as expected')
451+
except System.ArgumentException:
452+
print('type error, also expected')

0 commit comments

Comments
 (0)