Skip to content

Commit bcfdcc7

Browse files
committed
Test for class object on crossed domain
1 parent 08fad26 commit bcfdcc7

File tree

1 file changed

+216
-112
lines changed

1 file changed

+216
-112
lines changed

src/embed_tests/TestDomainReload.cs

Lines changed: 216 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NUnit.Framework;
66
using Python.Runtime;
77

8+
using PyRuntime = Python.Runtime.Runtime;
89
//
910
// This test case is disabled on .NET Standard because it doesn't have all the
1011
// APIs we use. We could work around that, but .NET Core doesn't implement
@@ -17,6 +18,12 @@ namespace Python.EmbeddingTest
1718
{
1819
class TestDomainReload
1920
{
21+
abstract class CrossCaller : MarshalByRefObject
22+
{
23+
public abstract ValueType Execte(ValueType arg);
24+
}
25+
26+
2027
/// <summary>
2128
/// Test that the python runtime can survive a C# domain reload without crashing.
2229
///
@@ -53,71 +60,198 @@ public static void DomainReloadAndGC()
5360
{
5461
Assert.IsFalse(PythonEngine.IsInitialized);
5562
RunAssemblyAndUnload("test1");
56-
Assert.That(Runtime.Runtime.Py_IsInitialized() != 0,
63+
Assert.That(PyRuntime.Py_IsInitialized() != 0,
5764
"On soft-shutdown mode, Python runtime should still running");
5865

5966
RunAssemblyAndUnload("test2");
60-
Assert.That(Runtime.Runtime.Py_IsInitialized() != 0,
67+
Assert.That(PyRuntime.Py_IsInitialized() != 0,
6168
"On soft-shutdown mode, Python runtime should still running");
6269

6370
if (PythonEngine.DefaultShutdownMode == ShutdownMode.Normal)
6471
{
6572
// The default mode is a normal mode,
6673
// it should shutdown the Python VM avoiding influence other tests.
67-
Runtime.Runtime.PyGILState_Ensure();
68-
Runtime.Runtime.Py_Finalize();
74+
PyRuntime.PyGILState_Ensure();
75+
PyRuntime.Py_Finalize();
6976
}
7077
}
7178

72-
[Test]
73-
public static void CrossDomainObject()
79+
#region CrossDomainObject
80+
81+
class CrossDomianObjectStep1 : CrossCaller
7482
{
75-
IntPtr handle = IntPtr.Zero;
76-
Type type = typeof(Proxy);
83+
public override ValueType Execte(ValueType arg)
7784
{
78-
AppDomain domain = CreateDomain("test_domain_reload");
7985
try
8086
{
81-
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
82-
type.Assembly.FullName,
83-
type.FullName);
84-
theProxy.Call("InitPython", ShutdownMode.Reload);
85-
handle = (IntPtr)theProxy.Call("GetTestObject");
86-
theProxy.Call("ShutdownPython");
87+
Type type = typeof(Python.EmbeddingTest.Domain.MyClass);
88+
string code = string.Format(@"
89+
import clr
90+
clr.AddReference('{0}')
91+
92+
from Python.EmbeddingTest.Domain import MyClass
93+
obj = MyClass()
94+
obj.Method()
95+
obj.StaticMethod()
96+
obj.Property = 1
97+
obj.Field = 10
98+
", Assembly.GetExecutingAssembly().FullName);
99+
100+
using (Py.GIL())
101+
using (var scope = Py.CreateScope())
102+
{
103+
scope.Exec(code);
104+
using (PyObject obj = scope.Get("obj"))
105+
{
106+
Debug.Assert(obj.AsManagedObject(type).GetType() == type);
107+
// We only needs its Python handle
108+
PyRuntime.XIncref(obj.Handle);
109+
return obj.Handle;
110+
}
111+
}
87112
}
88-
finally
113+
catch (Exception e)
89114
{
90-
AppDomain.Unload(domain);
115+
Debug.WriteLine(e);
116+
throw;
91117
}
92118
}
119+
}
93120

121+
122+
class CrossDomianObjectStep2 : CrossCaller
123+
{
124+
public override ValueType Execte(ValueType arg)
94125
{
95-
AppDomain domain = CreateDomain("test_domain_reload");
126+
// handle refering a clr object created in previous domain,
127+
// it should had been deserialized and became callable agian.
128+
IntPtr handle = (IntPtr)arg;
96129
try
97130
{
98-
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
99-
type.Assembly.FullName,
100-
type.FullName);
101-
theProxy.Call("InitPython", ShutdownMode.Reload);
131+
using (Py.GIL())
132+
{
133+
IntPtr tp = Runtime.Runtime.PyObject_TYPE(handle);
134+
IntPtr tp_clear = Marshal.ReadIntPtr(tp, TypeOffset.tp_clear);
102135

103-
// handle refering a clr object created in previous domain,
104-
// it should had been deserialized and became callable agian.
105-
theProxy.Call("RunTestObject", handle);
106-
theProxy.Call("ShutdownPythonCompletely");
136+
using (PyObject obj = new PyObject(handle))
137+
{
138+
obj.InvokeMethod("Method");
139+
obj.InvokeMethod("StaticMethod");
140+
141+
using (var scope = Py.CreateScope())
142+
{
143+
scope.Set("obj", obj);
144+
scope.Exec(@"
145+
obj.Method()
146+
obj.StaticMethod()
147+
obj.Property += 1
148+
obj.Field += 10
149+
");
150+
}
151+
var clrObj = obj.As<Domain.MyClass>();
152+
Assert.AreEqual(clrObj.Property, 2);
153+
Assert.AreEqual(clrObj.Field, 20);
154+
}
155+
}
107156
}
108-
finally
157+
catch (Exception e)
109158
{
110-
AppDomain.Unload(domain);
159+
Debug.WriteLine(e);
160+
throw;
111161
}
162+
return 0;
112163
}
113-
if (PythonEngine.DefaultShutdownMode == ShutdownMode.Normal)
164+
}
165+
166+
[Test]
167+
public static void CrossDomainObject()
168+
{
169+
RunDomainReloadSteps<CrossDomianObjectStep1, CrossDomianObjectStep2>();
170+
}
171+
172+
#endregion
173+
174+
#region TestClassReference
175+
176+
class ReloadClassRefStep1 : CrossCaller
177+
{
178+
public override ValueType Execte(ValueType arg)
179+
{
180+
const string code = @"
181+
from Python.EmbeddingTest.Domain import MyClass
182+
183+
def test_obj_call():
184+
obj = MyClass()
185+
obj.Method()
186+
obj.StaticMethod()
187+
obj.Property = 1
188+
obj.Field = 10
189+
190+
test_obj_call()
191+
";
192+
const string name = "test_domain_reload_mod";
193+
using (Py.GIL())
194+
{
195+
IntPtr module = PyRuntime.PyModule_New(name);
196+
Assert.That(module != IntPtr.Zero);
197+
IntPtr globals = PyRuntime.PyObject_GetAttrString(module, "__dict__");
198+
Assert.That(globals != IntPtr.Zero);
199+
try
200+
{
201+
int res = PyRuntime.PyDict_SetItemString(globals, "__builtins__",
202+
PyRuntime.PyEval_GetBuiltins());
203+
PythonException.ThrowIfIsNotZero(res);
204+
205+
PythonEngine.Exec(code, globals);
206+
IntPtr modules = PyRuntime.PyImport_GetModuleDict();
207+
res = PyRuntime.PyDict_SetItemString(modules, name, modules);
208+
PythonException.ThrowIfIsNotZero(res);
209+
}
210+
catch
211+
{
212+
PyRuntime.XDecref(module);
213+
throw;
214+
}
215+
finally
216+
{
217+
PyRuntime.XDecref(globals);
218+
}
219+
return module;
220+
}
221+
}
222+
}
223+
224+
class ReloadClassRefStep2 : CrossCaller
225+
{
226+
public override ValueType Execte(ValueType arg)
114227
{
115-
Assert.IsTrue(Runtime.Runtime.Py_IsInitialized() == 0);
228+
var module = (IntPtr)arg;
229+
using (Py.GIL())
230+
{
231+
var test_obj_call = PyRuntime.PyObject_GetAttrString(module, "test_obj_call");
232+
PythonException.ThrowIfIsNull(test_obj_call);
233+
var args = PyRuntime.PyTuple_New(0);
234+
var res = PyRuntime.PyObject_CallObject(test_obj_call, args);
235+
PythonException.ThrowIfIsNull(res);
236+
237+
PyRuntime.XDecref(args);
238+
PyRuntime.XDecref(res);
239+
}
240+
return 0;
116241
}
117242
}
118243

119244

245+
[Test]
246+
public void TestClassReference()
247+
{
248+
RunDomainReloadSteps<ReloadClassRefStep1, ReloadClassRefStep2>();
249+
}
250+
251+
#endregion
252+
120253
#region Tempary tests
254+
121255
// https://github.com/pythonnet/pythonnet/pull/1074#issuecomment-596139665
122256
[Test]
123257
public void CrossReleaseBuiltinType()
@@ -274,6 +408,58 @@ static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
274408

275409
return null;
276410
}
411+
412+
static void RunDomainReloadSteps<T1, T2>() where T1 : CrossCaller where T2 : CrossCaller
413+
{
414+
ValueType arg = null;
415+
Type type = typeof(Proxy);
416+
{
417+
AppDomain domain = CreateDomain("test_domain_reload");
418+
try
419+
{
420+
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
421+
type.Assembly.FullName,
422+
type.FullName);
423+
theProxy.Call("InitPython", ShutdownMode.Reload);
424+
425+
var caller = (T1)domain.CreateInstanceAndUnwrap(
426+
typeof(T1).Assembly.FullName,
427+
typeof(T1).FullName);
428+
arg = caller.Execte(arg);
429+
430+
theProxy.Call("ShutdownPython");
431+
}
432+
finally
433+
{
434+
AppDomain.Unload(domain);
435+
}
436+
}
437+
438+
{
439+
AppDomain domain = CreateDomain("test_domain_reload");
440+
try
441+
{
442+
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
443+
type.Assembly.FullName,
444+
type.FullName);
445+
theProxy.Call("InitPython", ShutdownMode.Reload);
446+
447+
var caller = (T2)domain.CreateInstanceAndUnwrap(
448+
typeof(T2).Assembly.FullName,
449+
typeof(T2).FullName);
450+
caller.Execte(arg);
451+
theProxy.Call("ShutdownPythonCompletely");
452+
}
453+
finally
454+
{
455+
AppDomain.Unload(domain);
456+
}
457+
}
458+
if (PythonEngine.DefaultShutdownMode == ShutdownMode.Normal)
459+
{
460+
Assert.IsTrue(PyRuntime.Py_IsInitialized() == 0);
461+
}
462+
}
277463
}
278464

279465

@@ -358,88 +544,6 @@ public static void ShutdownPythonCompletely()
358544
PythonEngine.Shutdown();
359545
}
360546

361-
public static IntPtr GetTestObject()
362-
{
363-
try
364-
{
365-
Type type = typeof(Python.EmbeddingTest.Domain.MyClass);
366-
string code = string.Format(@"
367-
import clr
368-
clr.AddReference('{0}')
369-
370-
from Python.EmbeddingTest.Domain import MyClass
371-
obj = MyClass()
372-
obj.Method()
373-
obj.StaticMethod()
374-
obj.Property = 1
375-
obj.Field = 10
376-
", Assembly.GetExecutingAssembly().FullName);
377-
378-
using (Py.GIL())
379-
using (var scope = Py.CreateScope())
380-
{
381-
scope.Exec(code);
382-
using (PyObject obj = scope.Get("obj"))
383-
{
384-
Debug.Assert(obj.AsManagedObject(type).GetType() == type);
385-
// We only needs its Python handle
386-
Runtime.Runtime.XIncref(obj.Handle);
387-
return obj.Handle;
388-
}
389-
}
390-
}
391-
catch (Exception e)
392-
{
393-
Debug.WriteLine(e);
394-
throw;
395-
}
396-
}
397-
398-
public static void RunTestObject(IntPtr handle)
399-
{
400-
try
401-
{
402-
using (Py.GIL())
403-
{
404-
IntPtr tp = Runtime.Runtime.PyObject_TYPE(handle);
405-
IntPtr tp_clear = Marshal.ReadIntPtr(tp, TypeOffset.tp_clear);
406-
407-
using (PyObject obj = new PyObject(handle))
408-
{
409-
obj.InvokeMethod("Method");
410-
obj.InvokeMethod("StaticMethod");
411-
412-
using (var scope = Py.CreateScope())
413-
{
414-
scope.Set("obj", obj);
415-
scope.Exec(@"
416-
obj.Method()
417-
obj.StaticMethod()
418-
obj.Property += 1
419-
obj.Field += 10
420-
");
421-
}
422-
var clrObj = obj.As<Domain.MyClass>();
423-
Assert.AreEqual(clrObj.Property, 2);
424-
Assert.AreEqual(clrObj.Field, 20);
425-
}
426-
}
427-
}
428-
catch (Exception e)
429-
{
430-
Debug.WriteLine(e);
431-
throw;
432-
}
433-
}
434-
435-
public static void ReleaseTestObject(IntPtr handle)
436-
{
437-
using (Py.GIL())
438-
{
439-
Runtime.Runtime.XDecref(handle);
440-
}
441-
}
442-
443547
static void OnDomainUnload(object sender, EventArgs e)
444548
{
445549
Console.WriteLine(string.Format("[{0} in .NET] unloading", AppDomain.CurrentDomain.FriendlyName));

0 commit comments

Comments
 (0)