5
5
using NUnit . Framework ;
6
6
using Python . Runtime ;
7
7
8
+ using PyRuntime = Python . Runtime . Runtime ;
8
9
//
9
10
// This test case is disabled on .NET Standard because it doesn't have all the
10
11
// APIs we use. We could work around that, but .NET Core doesn't implement
@@ -17,6 +18,12 @@ namespace Python.EmbeddingTest
17
18
{
18
19
class TestDomainReload
19
20
{
21
+ abstract class CrossCaller : MarshalByRefObject
22
+ {
23
+ public abstract ValueType Execte ( ValueType arg ) ;
24
+ }
25
+
26
+
20
27
/// <summary>
21
28
/// Test that the python runtime can survive a C# domain reload without crashing.
22
29
///
@@ -53,71 +60,198 @@ public static void DomainReloadAndGC()
53
60
{
54
61
Assert . IsFalse ( PythonEngine . IsInitialized ) ;
55
62
RunAssemblyAndUnload ( "test1" ) ;
56
- Assert . That ( Runtime . Runtime . Py_IsInitialized ( ) != 0 ,
63
+ Assert . That ( PyRuntime . Py_IsInitialized ( ) != 0 ,
57
64
"On soft-shutdown mode, Python runtime should still running" ) ;
58
65
59
66
RunAssemblyAndUnload ( "test2" ) ;
60
- Assert . That ( Runtime . Runtime . Py_IsInitialized ( ) != 0 ,
67
+ Assert . That ( PyRuntime . Py_IsInitialized ( ) != 0 ,
61
68
"On soft-shutdown mode, Python runtime should still running" ) ;
62
69
63
70
if ( PythonEngine . DefaultShutdownMode == ShutdownMode . Normal )
64
71
{
65
72
// The default mode is a normal mode,
66
73
// 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 ( ) ;
69
76
}
70
77
}
71
78
72
- [ Test ]
73
- public static void CrossDomainObject ( )
79
+ #region CrossDomainObject
80
+
81
+ class CrossDomianObjectStep1 : CrossCaller
74
82
{
75
- IntPtr handle = IntPtr . Zero ;
76
- Type type = typeof ( Proxy ) ;
83
+ public override ValueType Execte ( ValueType arg )
77
84
{
78
- AppDomain domain = CreateDomain ( "test_domain_reload" ) ;
79
85
try
80
86
{
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
+ }
87
112
}
88
- finally
113
+ catch ( Exception e )
89
114
{
90
- AppDomain . Unload ( domain ) ;
115
+ Debug . WriteLine ( e ) ;
116
+ throw ;
91
117
}
92
118
}
119
+ }
93
120
121
+
122
+ class CrossDomianObjectStep2 : CrossCaller
123
+ {
124
+ public override ValueType Execte ( ValueType arg )
94
125
{
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 ;
96
129
try
97
130
{
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 ) ;
102
135
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
+ }
107
156
}
108
- finally
157
+ catch ( Exception e )
109
158
{
110
- AppDomain . Unload ( domain ) ;
159
+ Debug . WriteLine ( e ) ;
160
+ throw ;
111
161
}
162
+ return 0 ;
112
163
}
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 )
114
227
{
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 ;
116
241
}
117
242
}
118
243
119
244
245
+ [ Test ]
246
+ public void TestClassReference ( )
247
+ {
248
+ RunDomainReloadSteps < ReloadClassRefStep1 , ReloadClassRefStep2 > ( ) ;
249
+ }
250
+
251
+ #endregion
252
+
120
253
#region Tempary tests
254
+
121
255
// https://github.com/pythonnet/pythonnet/pull/1074#issuecomment-596139665
122
256
[ Test ]
123
257
public void CrossReleaseBuiltinType ( )
@@ -274,6 +408,58 @@ static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
274
408
275
409
return null ;
276
410
}
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
+ }
277
463
}
278
464
279
465
@@ -358,88 +544,6 @@ public static void ShutdownPythonCompletely()
358
544
PythonEngine . Shutdown ( ) ;
359
545
}
360
546
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
-
443
547
static void OnDomainUnload ( object sender , EventArgs e )
444
548
{
445
549
Console . WriteLine ( string . Format ( "[{0} in .NET] unloading" , AppDomain . CurrentDomain . FriendlyName ) ) ;
0 commit comments