55using NUnit . Framework ;
66using 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