From 8f5a8503d16cc0703e6fef189fd26e9d33b55019 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 Sep 2023 10:09:19 +0200 Subject: [PATCH 1/6] Skip garbage collection on process shutdown --- src/runtime/Runtime.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index beb577e45..5e0685000 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -294,20 +294,26 @@ internal static void Shutdown() DisposeLazyObject(hexCallable); PyObjectConversions.Reset(); - PyGC_Collect(); - bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown, - forceBreakLoops: true); - Debug.Assert(everythingSeemsCollected); + if (!ProcessIsTerminating) + { + PyGC_Collect(); + bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown, + forceBreakLoops: true); + Debug.Assert(everythingSeemsCollected); + } + ResetPyMembers(); Finalizer.Shutdown(); InternString.Shutdown(); - ResetPyMembers(); - if (!HostedInPython) { - GC.Collect(); - GC.WaitForPendingFinalizers(); + if (!ProcessIsTerminating) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + PyGILState_Release(state); // Then release the GIL for good, if there is somehting to release // Use the unchecked version as the checked version calls `abort()` From 85f74bbf6b645a8922e3de0d79f6bbba420917ad Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 Sep 2023 10:20:14 +0200 Subject: [PATCH 2/6] Shut down by default without allowing reload, throw on attempt --- src/runtime/PythonEngine.cs | 11 ++++++++--- src/runtime/Runtime.cs | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 4ed45b9e9..af5c2fb4c 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -309,13 +309,13 @@ static void LoadMixins(BorrowedReference targetModuleDict) static void OnDomainUnload(object _, EventArgs __) { - Shutdown(); + Shutdown(allowReload: true); } static void OnProcessExit(object _, EventArgs __) { Runtime.ProcessIsTerminating = true; - Shutdown(); + Shutdown(allowReload: false); } /// @@ -366,6 +366,11 @@ private static void AllowLeaksDuringShutdown(object sender, Finalizer.ErrorArgs /// after calling the Shutdown method. /// public static void Shutdown() + { + Shutdown(allowReload: false); + } + + public static void Shutdown(bool allowReload) { if (!initialized) { @@ -389,7 +394,7 @@ public static void Shutdown() ExecuteShutdownHandlers(); // Remember to shut down the runtime. - Runtime.Shutdown(); + Runtime.Shutdown(allowReload); initialized = false; diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 5e0685000..ac95e80f2 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -96,8 +96,9 @@ internal static int GetRun() return runNumber; } - internal static bool HostedInPython; - internal static bool ProcessIsTerminating; + internal static bool HostedInPython = false; + internal static bool ProcessIsTerminating = false; + internal static bool ShutdownWithoutReload = false; /// /// Initialize the runtime... @@ -112,6 +113,14 @@ internal static void Initialize(bool initSigs = false) } _isInitialized = true; + if (ShutdownWithoutReload) + { + throw new Exception( + "Runtime was shut down without allowReload: true, can " + + "not be restarted in this process" + ); + } + bool interpreterAlreadyInitialized = TryUsingDll( () => Py_IsInitialized() != 0 ); @@ -253,17 +262,18 @@ private static void InitPyMembers() return Util.ReadPtr(pyType.Borrow(), TypeOffset.tp_iternext); } - internal static void Shutdown() + internal static void Shutdown(bool allowReload) { if (Py_IsInitialized() == 0 || !_isInitialized) { return; } _isInitialized = false; + ShutdownWithoutReload = !allowReload; var state = PyGILState_Ensure(); - if (!HostedInPython && !ProcessIsTerminating) + if (!HostedInPython && !ProcessIsTerminating && allowReload) { // avoid saving dead objects TryCollectingGarbage(runs: 3); From 8e4a197f4a1ced61fa25f0f33fce8778d188caec Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 Sep 2023 13:38:24 +0200 Subject: [PATCH 3/6] Refactor embed tests to use common fixture --- src/embed_tests/BaseFixture.cs | 20 + src/embed_tests/CallableObject.cs | 126 +-- src/embed_tests/ClassManagerTests.cs | 43 +- src/embed_tests/CodecGroups.cs | 219 +++-- src/embed_tests/Codecs.cs | 856 +++++++++--------- src/embed_tests/Events.cs | 15 +- src/embed_tests/ExtensionTypes.cs | 16 +- src/embed_tests/GlobalTestsSetup.cs | 48 +- src/embed_tests/Inheritance.cs | 329 ++++--- src/embed_tests/Inspect.cs | 72 +- src/embed_tests/Modules.cs | 630 +++++++------ src/embed_tests/NumPyTests.cs | 132 ++- src/embed_tests/References.cs | 61 +- src/embed_tests/TestCallbacks.cs | 40 +- src/embed_tests/TestConverter.cs | 311 +++---- src/embed_tests/TestCustomMarshal.cs | 38 +- src/embed_tests/TestFinalizer.cs | 400 ++++---- src/embed_tests/TestGILState.cs | 41 +- src/embed_tests/TestInstanceWrapping.cs | 93 +- src/embed_tests/TestInterrupt.cs | 122 ++- src/embed_tests/TestNamedArguments.cs | 69 +- src/embed_tests/TestNativeTypeOffset.cs | 50 +- src/embed_tests/TestOperator.cs | 562 ++++++------ src/embed_tests/TestPyBuffer.cs | 227 +++-- src/embed_tests/TestPyFloat.cs | 231 +++-- src/embed_tests/TestPyInt.cs | 361 ++++---- src/embed_tests/TestPyIter.cs | 38 +- src/embed_tests/TestPyList.cs | 257 +++--- src/embed_tests/TestPyNumber.cs | 39 +- src/embed_tests/TestPyObject.cs | 133 ++- src/embed_tests/TestPySequence.cs | 161 ++-- src/embed_tests/TestPyString.cs | 203 ++--- src/embed_tests/TestPyTuple.cs | 313 +++---- src/embed_tests/TestPyType.cs | 54 +- src/embed_tests/TestPyWith.cs | 84 +- src/embed_tests/TestPythonEngineProperties.cs | 347 ++++--- src/embed_tests/TestPythonException.cs | 263 +++--- src/embed_tests/TestRuntime.cs | 195 ++-- src/embed_tests/dynamic.cs | 247 +++-- src/embed_tests/pyimport.cs | 159 ++-- src/embed_tests/pyinitialize.cs | 248 +++-- src/embed_tests/pyrunstring.cs | 96 +- 42 files changed, 3764 insertions(+), 4185 deletions(-) create mode 100644 src/embed_tests/BaseFixture.cs diff --git a/src/embed_tests/BaseFixture.cs b/src/embed_tests/BaseFixture.cs new file mode 100644 index 000000000..10605d00a --- /dev/null +++ b/src/embed_tests/BaseFixture.cs @@ -0,0 +1,20 @@ +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest; + +public class BaseFixture +{ + [OneTimeSetUp] + public void BaseSetup() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void BaseTearDown() + { + PyObjectConversions.Reset(); + PythonEngine.Shutdown(allowReload: true); + } +} diff --git a/src/embed_tests/CallableObject.cs b/src/embed_tests/CallableObject.cs index 8466f5ad8..716a75259 100644 --- a/src/embed_tests/CallableObject.cs +++ b/src/embed_tests/CallableObject.cs @@ -5,83 +5,85 @@ using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class CallableObject : BaseFixture { - public class CallableObject + IPythonBaseTypeProvider _provider; + + [OneTimeSetUp] + public void SetUp() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - using var locals = new PyDict(); - PythonEngine.Exec(CallViaInheritance.BaseClassSource, locals: locals); - CustomBaseTypeProvider.BaseClass = new PyType(locals[CallViaInheritance.BaseClassName]); - PythonEngine.InteropConfiguration.PythonBaseTypeProviders.Add(new CustomBaseTypeProvider()); - } + using var locals = new PyDict(); + PythonEngine.Exec(CallViaInheritance.BaseClassSource, locals: locals); + CustomBaseTypeProvider.BaseClass = new PyType(locals[CallViaInheritance.BaseClassName]); + _provider = new CustomBaseTypeProvider(); + PythonEngine.InteropConfiguration.PythonBaseTypeProviders.Add(_provider); + } - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] - public void CallMethodMakesObjectCallable() - { - var doubler = new DerivedDoubler(); - dynamic applyObjectTo21 = PythonEngine.Eval("lambda o: o(21)"); - Assert.AreEqual(doubler.__call__(21), (int)applyObjectTo21(doubler.ToPython())); - } - [Test] - public void CallMethodCanBeInheritedFromPython() - { - var callViaInheritance = new CallViaInheritance(); - dynamic applyObjectTo14 = PythonEngine.Eval("lambda o: o(14)"); - Assert.AreEqual(callViaInheritance.Call(14), (int)applyObjectTo14(callViaInheritance.ToPython())); - } + [OneTimeTearDown] + public void TearDown() + { + PythonEngine.InteropConfiguration.PythonBaseTypeProviders.Remove(_provider); + } - [Test] - public void CanOverwriteCall() - { - var callViaInheritance = new CallViaInheritance(); - using var scope = Py.CreateScope(); - scope.Set("o", callViaInheritance); - scope.Exec("orig_call = o.Call"); - scope.Exec("o.Call = lambda a: orig_call(a*7)"); - int result = scope.Eval("o.Call(5)"); - Assert.AreEqual(105, result); - } + [Test] + public void CallMethodMakesObjectCallable() + { + var doubler = new DerivedDoubler(); + dynamic applyObjectTo21 = PythonEngine.Eval("lambda o: o(21)"); + Assert.AreEqual(doubler.__call__(21), (int)applyObjectTo21(doubler.ToPython())); + } + [Test] + public void CallMethodCanBeInheritedFromPython() + { + var callViaInheritance = new CallViaInheritance(); + dynamic applyObjectTo14 = PythonEngine.Eval("lambda o: o(14)"); + Assert.AreEqual(callViaInheritance.Call(14), (int)applyObjectTo14(callViaInheritance.ToPython())); + } - class Doubler - { - public int __call__(int arg) => 2 * arg; - } + [Test] + public void CanOverwriteCall() + { + var callViaInheritance = new CallViaInheritance(); + using var scope = Py.CreateScope(); + scope.Set("o", callViaInheritance); + scope.Exec("orig_call = o.Call"); + scope.Exec("o.Call = lambda a: orig_call(a*7)"); + int result = scope.Eval("o.Call(5)"); + Assert.AreEqual(105, result); + } + + class Doubler + { + public int __call__(int arg) => 2 * arg; + } - class DerivedDoubler : Doubler { } + class DerivedDoubler : Doubler { } - class CallViaInheritance - { - public const string BaseClassName = "Forwarder"; - public static readonly string BaseClassSource = $@" + class CallViaInheritance + { + public const string BaseClassName = "Forwarder"; + public static readonly string BaseClassSource = $@" class MyCallableBase: def __call__(self, val): return self.Call(val) class {BaseClassName}(MyCallableBase): pass "; - public int Call(int arg) => 3 * arg; - } + public int Call(int arg) => 3 * arg; + } - class CustomBaseTypeProvider : IPythonBaseTypeProvider - { - internal static PyType BaseClass; + class CustomBaseTypeProvider : IPythonBaseTypeProvider + { + internal static PyType BaseClass; - public IEnumerable GetBaseTypes(Type type, IList existingBases) - { - Assert.Greater(BaseClass.Refcount, 0); - return type != typeof(CallViaInheritance) - ? existingBases - : new[] { BaseClass }; - } + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + Assert.Greater(BaseClass.Refcount, 0); + return type != typeof(CallViaInheritance) + ? existingBases + : new[] { BaseClass }; } } } diff --git a/src/embed_tests/ClassManagerTests.cs b/src/embed_tests/ClassManagerTests.cs index 72025a28b..a2d3cb2bb 100644 --- a/src/embed_tests/ClassManagerTests.cs +++ b/src/embed_tests/ClassManagerTests.cs @@ -2,39 +2,26 @@ using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class ClassManagerTests : BaseFixture { - public class ClassManagerTests + [Test] + public void NestedClassDerivingFromParent() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void NestedClassDerivingFromParent() - { - var f = new NestedTestContainer().ToPython(); - f.GetAttr(nameof(NestedTestContainer.Bar)); - } + var f = new NestedTestContainer().ToPython(); + f.GetAttr(nameof(NestedTestContainer.Bar)); } +} - public class NestedTestParent +public class NestedTestParent +{ + public class Nested : NestedTestParent { - public class Nested : NestedTestParent - { - } } +} - public class NestedTestContainer - { - public NestedTestParent Bar = new NestedTestParent.Nested(); - } +public class NestedTestContainer +{ + public NestedTestParent Bar = new NestedTestParent.Nested(); } diff --git a/src/embed_tests/CodecGroups.cs b/src/embed_tests/CodecGroups.cs index 689e5b24c..492516594 100644 --- a/src/embed_tests/CodecGroups.cs +++ b/src/embed_tests/CodecGroups.cs @@ -1,132 +1,119 @@ -namespace Python.EmbeddingTest -{ - using System; - using System.Linq; - using NUnit.Framework; - using Python.Runtime; - using Python.Runtime.Codecs; - - public class CodecGroups - { - [Test] - public void GetEncodersByType() - { - var encoder1 = new ObjectToEncoderInstanceEncoder(); - var encoder2 = new ObjectToEncoderInstanceEncoder(); - var group = new EncoderGroup { - new ObjectToEncoderInstanceEncoder>(), - encoder1, - encoder2, - }; +using System; +using System.Linq; +using NUnit.Framework; +using Python.Runtime; +using Python.Runtime.Codecs; - var got = group.GetEncoders(typeof(Uri)).ToArray(); - CollectionAssert.AreEqual(new[]{encoder1, encoder2}, got); - } +namespace Python.EmbeddingTest; - [Test] - public void CanEncode() - { - var group = new EncoderGroup { - new ObjectToEncoderInstanceEncoder>(), - new ObjectToEncoderInstanceEncoder(), - }; +public class CodecGroups : BaseFixture +{ + [Test] + public void GetEncodersByType() + { + var encoder1 = new ObjectToEncoderInstanceEncoder(); + var encoder2 = new ObjectToEncoderInstanceEncoder(); + var group = new EncoderGroup { + new ObjectToEncoderInstanceEncoder>(), + encoder1, + encoder2, + }; - Assert.IsTrue(group.CanEncode(typeof(Tuple))); - Assert.IsTrue(group.CanEncode(typeof(Uri))); - Assert.IsFalse(group.CanEncode(typeof(string))); - } + var got = group.GetEncoders(typeof(Uri)).ToArray(); + CollectionAssert.AreEqual(new[]{encoder1, encoder2}, got); + } - [Test] - public void Encodes() - { - var encoder0 = new ObjectToEncoderInstanceEncoder>(); - var encoder1 = new ObjectToEncoderInstanceEncoder(); - var encoder2 = new ObjectToEncoderInstanceEncoder(); - var group = new EncoderGroup { - encoder0, - encoder1, - encoder2, - }; + [Test] + public void CanEncode() + { + var group = new EncoderGroup { + new ObjectToEncoderInstanceEncoder>(), + new ObjectToEncoderInstanceEncoder(), + }; - var uri = group.TryEncode(new Uri("data:")); - var clrObject = (CLRObject)ManagedType.GetManagedObject(uri); - Assert.AreSame(encoder1, clrObject.inst); - Assert.AreNotSame(encoder2, clrObject.inst); + Assert.IsTrue(group.CanEncode(typeof(Tuple))); + Assert.IsTrue(group.CanEncode(typeof(Uri))); + Assert.IsFalse(group.CanEncode(typeof(string))); + } - var tuple = group.TryEncode(Tuple.Create(1)); - clrObject = (CLRObject)ManagedType.GetManagedObject(tuple); - Assert.AreSame(encoder0, clrObject.inst); - } + [Test] + public void Encodes() + { + var encoder0 = new ObjectToEncoderInstanceEncoder>(); + var encoder1 = new ObjectToEncoderInstanceEncoder(); + var encoder2 = new ObjectToEncoderInstanceEncoder(); + var group = new EncoderGroup { + encoder0, + encoder1, + encoder2, + }; - [Test] - public void GetDecodersByTypes() - { - var pyint = new PyInt(10).GetPythonType(); - var pyfloat = new PyFloat(10).GetPythonType(); - var pystr = new PyString("world").GetPythonType(); - var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); - var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); - var group = new DecoderGroup { - decoder1, - decoder2, - }; + var uri = group.TryEncode(new Uri("data:")); + var clrObject = (CLRObject)ManagedType.GetManagedObject(uri); + Assert.AreSame(encoder1, clrObject.inst); + Assert.AreNotSame(encoder2, clrObject.inst); - var decoder = group.GetDecoder(pyfloat, typeof(string)); - Assert.AreSame(decoder2, decoder); - decoder = group.GetDecoder(pystr, typeof(string)); - Assert.IsNull(decoder); - decoder = group.GetDecoder(pyint, typeof(long)); - Assert.AreSame(decoder1, decoder); - } - [Test] - public void CanDecode() - { - var pyint = new PyInt(10).GetPythonType(); - var pyfloat = new PyFloat(10).GetPythonType(); - var pystr = new PyString("world").GetPythonType(); - var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); - var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); - var group = new DecoderGroup { - decoder1, - decoder2, - }; + var tuple = group.TryEncode(Tuple.Create(1)); + clrObject = (CLRObject)ManagedType.GetManagedObject(tuple); + Assert.AreSame(encoder0, clrObject.inst); + } - Assert.IsTrue(group.CanDecode(pyint, typeof(long))); - Assert.IsFalse(group.CanDecode(pyint, typeof(int))); - Assert.IsTrue(group.CanDecode(pyfloat, typeof(string))); - Assert.IsFalse(group.CanDecode(pystr, typeof(string))); - } + [Test] + public void GetDecodersByTypes() + { + var pyint = new PyInt(10).GetPythonType(); + var pyfloat = new PyFloat(10).GetPythonType(); + var pystr = new PyString("world").GetPythonType(); + var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); + var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); + var group = new DecoderGroup { + decoder1, + decoder2, + }; - [Test] - public void Decodes() - { - var pyint = new PyInt(10).GetPythonType(); - var pyfloat = new PyFloat(10).GetPythonType(); - var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); - var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); - var group = new DecoderGroup { - decoder1, - decoder2, - }; + var decoder = group.GetDecoder(pyfloat, typeof(string)); + Assert.AreSame(decoder2, decoder); + decoder = group.GetDecoder(pystr, typeof(string)); + Assert.IsNull(decoder); + decoder = group.GetDecoder(pyint, typeof(long)); + Assert.AreSame(decoder1, decoder); + } + [Test] + public void CanDecode() + { + var pyint = new PyInt(10).GetPythonType(); + var pyfloat = new PyFloat(10).GetPythonType(); + var pystr = new PyString("world").GetPythonType(); + var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); + var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); + var group = new DecoderGroup { + decoder1, + decoder2, + }; - Assert.IsTrue(group.TryDecode(new PyInt(10), out long longResult)); - Assert.AreEqual(42, longResult); - Assert.IsTrue(group.TryDecode(new PyFloat(10), out string strResult)); - Assert.AreSame("atad:", strResult); + Assert.IsTrue(group.CanDecode(pyint, typeof(long))); + Assert.IsFalse(group.CanDecode(pyint, typeof(int))); + Assert.IsTrue(group.CanDecode(pyfloat, typeof(string))); + Assert.IsFalse(group.CanDecode(pystr, typeof(string))); + } - Assert.IsFalse(group.TryDecode(new PyInt(10), out int _)); - } + [Test] + public void Decodes() + { + var pyint = new PyInt(10).GetPythonType(); + var pyfloat = new PyFloat(10).GetPythonType(); + var decoder1 = new DecoderReturningPredefinedValue(pyint, decodeResult: 42); + var decoder2 = new DecoderReturningPredefinedValue(pyfloat, decodeResult: "atad:"); + var group = new DecoderGroup { + decoder1, + decoder2, + }; - [SetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + Assert.IsTrue(group.TryDecode(new PyInt(10), out long longResult)); + Assert.AreEqual(42, longResult); + Assert.IsTrue(group.TryDecode(new PyFloat(10), out string strResult)); + Assert.AreSame("atad:", strResult); - [TearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + Assert.IsFalse(group.TryDecode(new PyInt(10), out int _)); } } diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 9b764d43f..7d8048578 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -1,294 +1,283 @@ -namespace Python.EmbeddingTest { - using System; - using System.Collections.Generic; - using System.Linq; - using NUnit.Framework; - using Python.Runtime; - using Python.Runtime.Codecs; - - public class Codecs +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Python.Runtime; +using Python.Runtime.Codecs; + +namespace Python.EmbeddingTest; + +public class Codecs : BaseFixture +{ + [Test] + public void TupleConversionsGeneric() { - [SetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + TupleConversionsGeneric, ValueTuple>(); + } - [TearDown] - public void Dispose() + static void TupleConversionsGeneric() + { + TupleCodec.Register(); + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + T restored = default; + using (var scope = Py.CreateScope()) { - PythonEngine.Shutdown(); + void Accept(T value) => restored = value; + using var accept = new Action(Accept).ToPython(); + scope.Set(nameof(tuple), tuple); + scope.Set(nameof(accept), accept); + scope.Exec($"{nameof(accept)}({nameof(tuple)})"); + Assert.AreEqual(expected: tuple, actual: restored); } + } - [Test] - public void TupleConversionsGeneric() + [Test] + public void TupleConversionsObject() + { + TupleConversionsObject, ValueTuple>(); + } + static void TupleConversionsObject() + { + TupleCodec.Register(); + var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); + T restored = default; + using (var scope = Py.CreateScope()) { - TupleConversionsGeneric, ValueTuple>(); + void Accept(object value) => restored = (T)value; + using var accept = new Action(Accept).ToPython(); + scope.Set(nameof(tuple), tuple); + scope.Set(nameof(accept), accept); + scope.Exec($"{nameof(accept)}({nameof(tuple)})"); + Assert.AreEqual(expected: tuple, actual: restored); } + } - static void TupleConversionsGeneric() - { - TupleCodec.Register(); - var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); - T restored = default; - using (var scope = Py.CreateScope()) - { - void Accept(T value) => restored = value; - using var accept = new Action(Accept).ToPython(); - scope.Set(nameof(tuple), tuple); - scope.Set(nameof(accept), accept); - scope.Exec($"{nameof(accept)}({nameof(tuple)})"); - Assert.AreEqual(expected: tuple, actual: restored); - } - } + [Test] + public void TupleRoundtripObject() + { + TupleRoundtripObject, ValueTuple>(); + } + static void TupleRoundtripObject() + { + var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); + using var pyTuple = TupleCodec.Instance.TryEncode(tuple); + Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); + Assert.AreEqual(expected: tuple, actual: restored); + } - [Test] - public void TupleConversionsObject() - { - TupleConversionsObject, ValueTuple>(); - } - static void TupleConversionsObject() - { - TupleCodec.Register(); - var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); - T restored = default; - using (var scope = Py.CreateScope()) - { - void Accept(object value) => restored = (T)value; - using var accept = new Action(Accept).ToPython(); - scope.Set(nameof(tuple), tuple); - scope.Set(nameof(accept), accept); - scope.Exec($"{nameof(accept)}({nameof(tuple)})"); - Assert.AreEqual(expected: tuple, actual: restored); - } - } + [Test] + public void TupleRoundtripGeneric() + { + TupleRoundtripGeneric, ValueTuple>(); + } - [Test] - public void TupleRoundtripObject() - { - TupleRoundtripObject, ValueTuple>(); - } - static void TupleRoundtripObject() - { - var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); - using var pyTuple = TupleCodec.Instance.TryEncode(tuple); - Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); - Assert.AreEqual(expected: tuple, actual: restored); - } + static void TupleRoundtripGeneric() + { + var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); + using var pyTuple = TupleCodec.Instance.TryEncode(tuple); + Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out T restored)); + Assert.AreEqual(expected: tuple, actual: restored); + } - [Test] - public void TupleRoundtripGeneric() - { - TupleRoundtripGeneric, ValueTuple>(); - } + static PyObject GetPythonIterable() => PythonEngine.Eval("map(lambda x: x, [1,2,3])"); - static void TupleRoundtripGeneric() - { - var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); - using var pyTuple = TupleCodec.Instance.TryEncode(tuple); - Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out T restored)); - Assert.AreEqual(expected: tuple, actual: restored); - } + [Test] + public void ListDecoderTest() + { + var codec = ListDecoder.Instance; + var items = new List() { new PyInt(1), new PyInt(2), new PyInt(3) }; + + using var pyList = new PyList(items.ToArray()); + + using var pyListType = pyList.GetPythonType(); + Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList))); + Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList))); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable))); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(IEnumerable))); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection))); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool))); + + //we'd have to copy into a list instance to do this, it would not be lossless. + //lossy converters can be implemented outside of the python.net core library + Assert.IsFalse(codec.CanDecode(pyListType, typeof(List))); + + //convert to list of int + IList intList = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intList); }); + CollectionAssert.AreEqual(intList, new List { 1, 2, 3 }); + + //convert to list of string. This will not work. + //The ListWrapper class will throw a python exception when it tries to access any element. + //TryDecode is a lossless conversion so there will be no exception at that point + //interestingly, since the size of the python list can be queried without any conversion, + //the IList will report a Count of 3. + IList stringList = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringList); }); + Assert.AreEqual(stringList.Count, 3); + Assert.Throws(typeof(InvalidCastException), () => { var x = stringList[0]; }); + + //can't convert python iterable to list (this will require a copy which isn't lossless) + using var foo = GetPythonIterable(); + using var fooType = foo.GetPythonType(); + Assert.IsFalse(codec.CanDecode(fooType, typeof(IList))); + } - static PyObject GetPythonIterable() => PythonEngine.Eval("map(lambda x: x, [1,2,3])"); + [Test] + public void SequenceDecoderTest() + { + var codec = SequenceDecoder.Instance; + var items = new List() { new PyInt(1), new PyInt(2), new PyInt(3) }; + + //SequenceConverter can only convert to any ICollection + using var pyList = new PyList(items.ToArray()); + using var listType = pyList.GetPythonType(); + //it can convert a PyList, since PyList satisfies the python sequence protocol + + Assert.IsFalse(codec.CanDecode(listType, typeof(bool))); + Assert.IsFalse(codec.CanDecode(listType, typeof(IList))); + Assert.IsFalse(codec.CanDecode(listType, typeof(System.Collections.IEnumerable))); + Assert.IsFalse(codec.CanDecode(listType, typeof(IEnumerable))); + + Assert.IsTrue(codec.CanDecode(listType, typeof(ICollection))); + Assert.IsTrue(codec.CanDecode(listType, typeof(ICollection))); + Assert.IsTrue(codec.CanDecode(listType, typeof(ICollection))); + + //convert to collection of int + ICollection intCollection = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intCollection); }); + CollectionAssert.AreEqual(intCollection, new List { 1, 2, 3 }); + + //no python exception should have occurred during the above conversion and check + Runtime.Runtime.CheckExceptionOccurred(); + + //convert to collection of string. This will not work. + //The SequenceWrapper class will throw a python exception when it tries to access any element. + //TryDecode is a lossless conversion so there will be no exception at that point + //interestingly, since the size of the python sequence can be queried without any conversion, + //the IList will report a Count of 3. + ICollection stringCollection = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringCollection); }); + Assert.AreEqual(3, stringCollection.Count()); + Assert.Throws(typeof(InvalidCastException), () => { + string[] array = new string[3]; + stringCollection.CopyTo(array, 0); + }); + + Runtime.Runtime.CheckExceptionOccurred(); + + //can't convert python iterable to collection (this will require a copy which isn't lossless) + //python iterables do not satisfy the python sequence protocol + var foo = GetPythonIterable(); + var fooType = foo.GetPythonType(); + Assert.IsFalse(codec.CanDecode(fooType, typeof(ICollection))); + + //python tuples do satisfy the python sequence protocol + var pyTuple = new PyTuple(items.ToArray()); + var pyTupleType = pyTuple.GetPythonType(); + + Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection))); + Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection))); + Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection))); + + //convert to collection of int + ICollection intCollection2 = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out intCollection2); }); + CollectionAssert.AreEqual(intCollection2, new List { 1, 2, 3 }); + + //no python exception should have occurred during the above conversion and check + Runtime.Runtime.CheckExceptionOccurred(); + + //convert to collection of string. This will not work. + //The SequenceWrapper class will throw a python exception when it tries to access any element. + //TryDecode is a lossless conversion so there will be no exception at that point + //interestingly, since the size of the python sequence can be queried without any conversion, + //the IList will report a Count of 3. + ICollection stringCollection2 = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out stringCollection2); }); + Assert.AreEqual(3, stringCollection2.Count()); + Assert.Throws(typeof(InvalidCastException), () => { + string[] array = new string[3]; + stringCollection2.CopyTo(array, 0); + }); + + Runtime.Runtime.CheckExceptionOccurred(); - [Test] - public void ListDecoderTest() - { - var codec = ListDecoder.Instance; - var items = new List() { new PyInt(1), new PyInt(2), new PyInt(3) }; - - using var pyList = new PyList(items.ToArray()); - - using var pyListType = pyList.GetPythonType(); - Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList))); - Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList))); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable))); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(IEnumerable))); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection))); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool))); - - //we'd have to copy into a list instance to do this, it would not be lossless. - //lossy converters can be implemented outside of the python.net core library - Assert.IsFalse(codec.CanDecode(pyListType, typeof(List))); - - //convert to list of int - IList intList = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intList); }); - CollectionAssert.AreEqual(intList, new List { 1, 2, 3 }); - - //convert to list of string. This will not work. - //The ListWrapper class will throw a python exception when it tries to access any element. - //TryDecode is a lossless conversion so there will be no exception at that point - //interestingly, since the size of the python list can be queried without any conversion, - //the IList will report a Count of 3. - IList stringList = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringList); }); - Assert.AreEqual(stringList.Count, 3); - Assert.Throws(typeof(InvalidCastException), () => { var x = stringList[0]; }); - - //can't convert python iterable to list (this will require a copy which isn't lossless) - using var foo = GetPythonIterable(); - using var fooType = foo.GetPythonType(); - Assert.IsFalse(codec.CanDecode(fooType, typeof(IList))); - } + } - [Test] - public void SequenceDecoderTest() - { - var codec = SequenceDecoder.Instance; - var items = new List() { new PyInt(1), new PyInt(2), new PyInt(3) }; - - //SequenceConverter can only convert to any ICollection - using var pyList = new PyList(items.ToArray()); - using var listType = pyList.GetPythonType(); - //it can convert a PyList, since PyList satisfies the python sequence protocol - - Assert.IsFalse(codec.CanDecode(listType, typeof(bool))); - Assert.IsFalse(codec.CanDecode(listType, typeof(IList))); - Assert.IsFalse(codec.CanDecode(listType, typeof(System.Collections.IEnumerable))); - Assert.IsFalse(codec.CanDecode(listType, typeof(IEnumerable))); - - Assert.IsTrue(codec.CanDecode(listType, typeof(ICollection))); - Assert.IsTrue(codec.CanDecode(listType, typeof(ICollection))); - Assert.IsTrue(codec.CanDecode(listType, typeof(ICollection))); - - //convert to collection of int - ICollection intCollection = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intCollection); }); - CollectionAssert.AreEqual(intCollection, new List { 1, 2, 3 }); - - //no python exception should have occurred during the above conversion and check - Runtime.CheckExceptionOccurred(); - - //convert to collection of string. This will not work. - //The SequenceWrapper class will throw a python exception when it tries to access any element. - //TryDecode is a lossless conversion so there will be no exception at that point - //interestingly, since the size of the python sequence can be queried without any conversion, - //the IList will report a Count of 3. - ICollection stringCollection = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringCollection); }); - Assert.AreEqual(3, stringCollection.Count()); - Assert.Throws(typeof(InvalidCastException), () => { - string[] array = new string[3]; - stringCollection.CopyTo(array, 0); - }); - - Runtime.CheckExceptionOccurred(); - - //can't convert python iterable to collection (this will require a copy which isn't lossless) - //python iterables do not satisfy the python sequence protocol - var foo = GetPythonIterable(); - var fooType = foo.GetPythonType(); - Assert.IsFalse(codec.CanDecode(fooType, typeof(ICollection))); - - //python tuples do satisfy the python sequence protocol - var pyTuple = new PyTuple(items.ToArray()); - var pyTupleType = pyTuple.GetPythonType(); - - Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection))); - Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection))); - Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection))); - - //convert to collection of int - ICollection intCollection2 = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out intCollection2); }); - CollectionAssert.AreEqual(intCollection2, new List { 1, 2, 3 }); - - //no python exception should have occurred during the above conversion and check - Runtime.CheckExceptionOccurred(); - - //convert to collection of string. This will not work. - //The SequenceWrapper class will throw a python exception when it tries to access any element. - //TryDecode is a lossless conversion so there will be no exception at that point - //interestingly, since the size of the python sequence can be queried without any conversion, - //the IList will report a Count of 3. - ICollection stringCollection2 = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out stringCollection2); }); - Assert.AreEqual(3, stringCollection2.Count()); - Assert.Throws(typeof(InvalidCastException), () => { - string[] array = new string[3]; - stringCollection2.CopyTo(array, 0); - }); - - Runtime.CheckExceptionOccurred(); + [Test] + public void IterableDecoderTest() + { + var codec = IterableDecoder.Instance; + var items = new List() { new PyInt(1), new PyInt(2), new PyInt(3) }; - } + var pyList = new PyList(items.ToArray()); + var pyListType = pyList.GetPythonType(); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(IList))); + Assert.IsTrue(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable))); + Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection))); + Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool))); - [Test] - public void IterableDecoderTest() - { - var codec = IterableDecoder.Instance; - var items = new List() { new PyInt(1), new PyInt(2), new PyInt(3) }; - - var pyList = new PyList(items.ToArray()); - var pyListType = pyList.GetPythonType(); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(IList))); - Assert.IsTrue(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable))); - Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection))); - Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool))); - - //ensure a PyList can be converted to a plain IEnumerable - System.Collections.IEnumerable plainEnumerable1 = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable1); }); - CollectionAssert.AreEqual(plainEnumerable1.Cast().Select(i => i.ToInt32()), new List { 1, 2, 3 }); - - //can convert to any generic ienumerable. If the type is not assignable from the python element - //it will lead to an empty iterable when decoding. TODO - should it throw? - Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); - Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); - Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); - - IEnumerable intEnumerable = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); }); - CollectionAssert.AreEqual(intEnumerable, new List { 1, 2, 3 }); - - Runtime.CheckExceptionOccurred(); - - IEnumerable doubleEnumerable = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out doubleEnumerable); }); - CollectionAssert.AreEqual(doubleEnumerable, new List { 1, 2, 3 }); - - Runtime.CheckExceptionOccurred(); - - IEnumerable stringEnumerable = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringEnumerable); }); - - Assert.Throws(typeof(InvalidCastException), () => { - foreach (string item in stringEnumerable) - { - var x = item; - } - }); - Assert.Throws(typeof(InvalidCastException), () => { - stringEnumerable.Count(); - }); - - Runtime.CheckExceptionOccurred(); - - //ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable - var foo = GetPythonIterable(); - var fooType = foo.GetPythonType(); - System.Collections.IEnumerable plainEnumerable2 = null; - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); }); - CollectionAssert.AreEqual(plainEnumerable2.Cast().Select(i => i.ToInt32()), new List { 1, 2, 3 }); - - //can convert to any generic ienumerable. If the type is not assignable from the python element - //it will be an exception during TryDecode - Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable))); - Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable))); - Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable))); - - Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); }); - CollectionAssert.AreEqual(intEnumerable, new List { 1, 2, 3 }); - } + //ensure a PyList can be converted to a plain IEnumerable + System.Collections.IEnumerable plainEnumerable1 = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable1); }); + CollectionAssert.AreEqual(plainEnumerable1.Cast().Select(i => i.ToInt32()), new List { 1, 2, 3 }); - // regression for https://github.com/pythonnet/pythonnet/issues/1427 - [Test] - public void PythonRegisteredDecoder_NoStackOverflowOnSystemType() - { - const string PyCode = @" + //can convert to any generic ienumerable. If the type is not assignable from the python element + //it will lead to an empty iterable when decoding. TODO - should it throw? + Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); + Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); + Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable))); + + IEnumerable intEnumerable = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); }); + CollectionAssert.AreEqual(intEnumerable, new List { 1, 2, 3 }); + + Runtime.Runtime.CheckExceptionOccurred(); + + IEnumerable doubleEnumerable = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out doubleEnumerable); }); + CollectionAssert.AreEqual(doubleEnumerable, new List { 1, 2, 3 }); + + Runtime.Runtime.CheckExceptionOccurred(); + + IEnumerable stringEnumerable = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringEnumerable); }); + + Assert.Throws(typeof(InvalidCastException), () => { + foreach (string item in stringEnumerable) + { + var x = item; + } + }); + Assert.Throws(typeof(InvalidCastException), () => { + stringEnumerable.Count(); + }); + + Runtime.Runtime.CheckExceptionOccurred(); + + //ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable + var foo = GetPythonIterable(); + var fooType = foo.GetPythonType(); + System.Collections.IEnumerable plainEnumerable2 = null; + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); }); + CollectionAssert.AreEqual(plainEnumerable2.Cast().Select(i => i.ToInt32()), new List { 1, 2, 3 }); + + //can convert to any generic ienumerable. If the type is not assignable from the python element + //it will be an exception during TryDecode + Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable))); + Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable))); + Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable))); + + Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); }); + CollectionAssert.AreEqual(intEnumerable, new List { 1, 2, 3 }); + } + + // regression for https://github.com/pythonnet/pythonnet/issues/1427 + [Test] + public void PythonRegisteredDecoder_NoStackOverflowOnSystemType() + { + const string PyCode = @" import clr import System from Python.Runtime import PyObjectConversions @@ -306,44 +295,44 @@ def CanEncode(self, clr_type): system_type = list_encoder.GetType()"; - PythonEngine.Exec(PyCode); - } + PythonEngine.Exec(PyCode); + } - const string TestExceptionMessage = "Hello World!"; - [Test] - public void ExceptionEncoded() - { - PyObjectConversions.RegisterEncoder(new ValueErrorCodec()); - void CallMe() => throw new ValueErrorWrapper(TestExceptionMessage); - var callMeAction = new Action(CallMe); - using var scope = Py.CreateScope(); - scope.Exec(@" + const string TestExceptionMessage = "Hello World!"; + [Test] + public void ExceptionEncoded() + { + PyObjectConversions.RegisterEncoder(new ValueErrorCodec()); + void CallMe() => throw new ValueErrorWrapper(TestExceptionMessage); + var callMeAction = new Action(CallMe); + using var scope = Py.CreateScope(); + scope.Exec(@" def call(func): try: func() except ValueError as e: return str(e) "); - var callFunc = scope.Get("call"); - string message = callFunc.Invoke(callMeAction.ToPython()).As(); - Assert.AreEqual(TestExceptionMessage, message); - } + var callFunc = scope.Get("call"); + string message = callFunc.Invoke(callMeAction.ToPython()).As(); + Assert.AreEqual(TestExceptionMessage, message); + } - [Test] - public void ExceptionDecoded() - { - PyObjectConversions.RegisterDecoder(new ValueErrorCodec()); - using var scope = Py.CreateScope(); - var error = Assert.Throws(() - => PythonEngine.Exec($"raise ValueError('{TestExceptionMessage}')")); - Assert.AreEqual(TestExceptionMessage, error.Message); - } + [Test] + public void ExceptionDecoded() + { + PyObjectConversions.RegisterDecoder(new ValueErrorCodec()); + using var scope = Py.CreateScope(); + var error = Assert.Throws(() + => PythonEngine.Exec($"raise ValueError('{TestExceptionMessage}')")); + Assert.AreEqual(TestExceptionMessage, error.Message); + } - [Test] - public void DateTimeDecoded() - { - using var scope = Py.CreateScope(); - scope.Exec(@" + [Test] + public void DateTimeDecoded() + { + using var scope = Py.CreateScope(); + scope.Exec(@" import clr from datetime import datetime @@ -352,173 +341,172 @@ from datetime import datetime DateTimeDecoder.Setup() "); - scope.Exec("Codecs.AcceptsDateTime(datetime(2021, 1, 22))"); - } + scope.Exec("Codecs.AcceptsDateTime(datetime(2021, 1, 22))"); + } - [Test] - public void FloatDerivedDecoded() - { - using var scope = Py.CreateScope(); - scope.Exec(@"class FloatDerived(float): pass"); - using var floatDerived = scope.Eval("FloatDerived"); - var decoder = new DecoderReturningPredefinedValue(floatDerived, 42); - PyObjectConversions.RegisterDecoder(decoder); - using var result = scope.Eval("FloatDerived()"); - object decoded = result.As(); - Assert.AreEqual(42, decoded); - } + [Test] + public void FloatDerivedDecoded() + { + using var scope = Py.CreateScope(); + scope.Exec(@"class FloatDerived(float): pass"); + using var floatDerived = scope.Eval("FloatDerived"); + var decoder = new DecoderReturningPredefinedValue(floatDerived, 42); + PyObjectConversions.RegisterDecoder(decoder); + using var result = scope.Eval("FloatDerived()"); + object decoded = result.As(); + Assert.AreEqual(42, decoded); + } - [Test] - public void ExceptionDecodedNoInstance() - { - PyObjectConversions.RegisterDecoder(new InstancelessExceptionDecoder()); - using var scope = Py.CreateScope(); - var error = Assert.Throws(() => PythonEngine.Exec( - $"[].__iter__().__next__()")); - Assert.AreEqual(TestExceptionMessage, error.Message); - } + [Test] + public void ExceptionDecodedNoInstance() + { + PyObjectConversions.RegisterDecoder(new InstancelessExceptionDecoder()); + using var scope = Py.CreateScope(); + var error = Assert.Throws(() => PythonEngine.Exec( + $"[].__iter__().__next__()")); + Assert.AreEqual(TestExceptionMessage, error.Message); + } - public static void AcceptsDateTime(DateTime v) {} + public static void AcceptsDateTime(DateTime v) {} - [Test] - public void As_Object_AffectedByDecoders() - { - var everythingElseToSelf = new EverythingElseToSelfDecoder(); - PyObjectConversions.RegisterDecoder(everythingElseToSelf); + [Test] + public void As_Object_AffectedByDecoders() + { + var everythingElseToSelf = new EverythingElseToSelfDecoder(); + PyObjectConversions.RegisterDecoder(everythingElseToSelf); - var pyObj = PythonEngine.Eval("iter"); - var decoded = pyObj.As(); - Assert.AreSame(everythingElseToSelf, decoded); - } + var pyObj = PythonEngine.Eval("iter"); + var decoded = pyObj.As(); + Assert.AreSame(everythingElseToSelf, decoded); + } - public class EverythingElseToSelfDecoder : IPyObjectDecoder + public class EverythingElseToSelfDecoder : IPyObjectDecoder + { + public bool CanDecode(PyType objectType, Type targetType) { - public bool CanDecode(PyType objectType, Type targetType) - { - return targetType.IsAssignableFrom(typeof(EverythingElseToSelfDecoder)); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - value = (T)(object)this; - return true; - } + return targetType.IsAssignableFrom(typeof(EverythingElseToSelfDecoder)); } - class ValueErrorWrapper : Exception + public bool TryDecode(PyObject pyObj, out T value) { - public ValueErrorWrapper(string message) : base(message) { } + value = (T)(object)this; + return true; } + } - class ValueErrorCodec : IPyObjectEncoder, IPyObjectDecoder - { - public bool CanDecode(PyType objectType, Type targetType) - => this.CanEncode(targetType) - && PythonReferenceComparer.Instance.Equals(objectType, PythonEngine.Eval("ValueError")); + class ValueErrorWrapper : Exception + { + public ValueErrorWrapper(string message) : base(message) { } + } - public bool CanEncode(Type type) => type == typeof(ValueErrorWrapper) - || typeof(ValueErrorWrapper).IsSubclassOf(type); + class ValueErrorCodec : IPyObjectEncoder, IPyObjectDecoder + { + public bool CanDecode(PyType objectType, Type targetType) + => this.CanEncode(targetType) + && PythonReferenceComparer.Instance.Equals(objectType, PythonEngine.Eval("ValueError")); - public bool TryDecode(PyObject pyObj, out T value) - { - var message = pyObj.GetAttr("args")[0].As(); - value = (T)(object)new ValueErrorWrapper(message); - return true; - } + public bool CanEncode(Type type) => type == typeof(ValueErrorWrapper) + || typeof(ValueErrorWrapper).IsSubclassOf(type); - public PyObject TryEncode(object value) - { - var error = (ValueErrorWrapper)value; - return PythonEngine.Eval("ValueError").Invoke(error.Message.ToPython()); - } + public bool TryDecode(PyObject pyObj, out T value) + { + var message = pyObj.GetAttr("args")[0].As(); + value = (T)(object)new ValueErrorWrapper(message); + return true; } - class InstancelessExceptionDecoder : IPyObjectDecoder, IDisposable + public PyObject TryEncode(object value) { - readonly PyObject PyErr = Py.Import("clr.interop").GetAttr("PyErr"); - - public bool CanDecode(PyType objectType, Type targetType) - => PythonReferenceComparer.Instance.Equals(PyErr, objectType); - - public void Dispose() - { - PyErr.Dispose(); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj.HasAttr("value")) - { - value = default; - return false; - } - - value = (T)(object)new ValueErrorWrapper(TestExceptionMessage); - return true; - } + var error = (ValueErrorWrapper)value; + return PythonEngine.Eval("ValueError").Invoke(error.Message.ToPython()); } } - /// - /// "Decodes" only objects of exact type . - /// Result is just the raw proxy to the encoder instance itself. - /// - class ObjectToEncoderInstanceEncoder : IPyObjectEncoder + class InstancelessExceptionDecoder : IPyObjectDecoder, IDisposable { - public bool CanEncode(Type type) => type == typeof(T); - public PyObject TryEncode(object value) => PyObject.FromManagedObject(this); - } + readonly PyObject PyErr = Py.Import("clr.interop").GetAttr("PyErr"); - /// - /// Decodes object of specified Python type to the predefined value - /// - /// Type of the - class DecoderReturningPredefinedValue : IPyObjectDecoder - { - public PyObject TheOnlySupportedSourceType { get; } - public TTarget DecodeResult { get; } + public bool CanDecode(PyType objectType, Type targetType) + => PythonReferenceComparer.Instance.Equals(PyErr, objectType); - public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult) + public void Dispose() { - this.TheOnlySupportedSourceType = objectType; - this.DecodeResult = decodeResult; + PyErr.Dispose(); } - public bool CanDecode(PyType objectType, Type targetType) - => PythonReferenceComparer.Instance.Equals(objectType, TheOnlySupportedSourceType) - && targetType == typeof(TTarget); public bool TryDecode(PyObject pyObj, out T value) { - if (typeof(T) != typeof(TTarget)) - throw new ArgumentException(nameof(T)); - value = (T)(object)DecodeResult; + if (pyObj.HasAttr("value")) + { + value = default; + return false; + } + + value = (T)(object)new ValueErrorWrapper(TestExceptionMessage); return true; } } +} - public class DateTimeDecoder : IPyObjectDecoder +/// +/// "Decodes" only objects of exact type . +/// Result is just the raw proxy to the encoder instance itself. +/// +class ObjectToEncoderInstanceEncoder : IPyObjectEncoder +{ + public bool CanEncode(Type type) => type == typeof(T); + public PyObject TryEncode(object value) => PyObject.FromManagedObject(this); +} + +/// +/// Decodes object of specified Python type to the predefined value +/// +/// Type of the +class DecoderReturningPredefinedValue : IPyObjectDecoder +{ + public PyObject TheOnlySupportedSourceType { get; } + public TTarget DecodeResult { get; } + + public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult) { - public static void Setup() - { - PyObjectConversions.RegisterDecoder(new DateTimeDecoder()); - } + this.TheOnlySupportedSourceType = objectType; + this.DecodeResult = decodeResult; + } - public bool CanDecode(PyType objectType, Type targetType) - { - return targetType == typeof(DateTime); - } + public bool CanDecode(PyType objectType, Type targetType) + => PythonReferenceComparer.Instance.Equals(objectType, TheOnlySupportedSourceType) + && targetType == typeof(TTarget); + public bool TryDecode(PyObject pyObj, out T value) + { + if (typeof(T) != typeof(TTarget)) + throw new ArgumentException(nameof(T)); + value = (T)(object)DecodeResult; + return true; + } +} - public bool TryDecode(PyObject pyObj, out T value) - { - var dt = new DateTime( - pyObj.GetAttr("year").As(), - pyObj.GetAttr("month").As(), - pyObj.GetAttr("day").As(), - pyObj.GetAttr("hour").As(), - pyObj.GetAttr("minute").As(), - pyObj.GetAttr("second").As()); - value = (T)(object)dt; - return true; - } +public class DateTimeDecoder : IPyObjectDecoder +{ + public static void Setup() + { + PyObjectConversions.RegisterDecoder(new DateTimeDecoder()); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return targetType == typeof(DateTime); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + var dt = new DateTime( + pyObj.GetAttr("year").As(), + pyObj.GetAttr("month").As(), + pyObj.GetAttr("day").As(), + pyObj.GetAttr("hour").As(), + pyObj.GetAttr("minute").As(), + pyObj.GetAttr("second").As()); + value = (T)(object)dt; + return true; } } diff --git a/src/embed_tests/Events.cs b/src/embed_tests/Events.cs index c216f4214..74259ed42 100644 --- a/src/embed_tests/Events.cs +++ b/src/embed_tests/Events.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Threading; using NUnit.Framework; @@ -8,20 +7,8 @@ namespace Python.EmbeddingTest; -public class Events +public class Events : BaseFixture { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void UsingDoesNotLeak() { diff --git a/src/embed_tests/ExtensionTypes.cs b/src/embed_tests/ExtensionTypes.cs index 803845960..3e744dc75 100644 --- a/src/embed_tests/ExtensionTypes.cs +++ b/src/embed_tests/ExtensionTypes.cs @@ -1,25 +1,11 @@ using System; - using NUnit.Framework; - using Python.Runtime; namespace Python.EmbeddingTest; -public class ExtensionTypes +public class ExtensionTypes : BaseFixture { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void WeakrefIsNone_AfterBoundMethodIsGone() { diff --git a/src/embed_tests/GlobalTestsSetup.cs b/src/embed_tests/GlobalTestsSetup.cs index dff58b978..e405241cd 100644 --- a/src/embed_tests/GlobalTestsSetup.cs +++ b/src/embed_tests/GlobalTestsSetup.cs @@ -1,37 +1,37 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest -{ +namespace Python.EmbeddingTest; - // As the SetUpFixture, the OneTimeTearDown of this class is executed after - // all tests have run. - [SetUpFixture] - public partial class GlobalTestsSetup +// As the SetUpFixture, the OneTimeTearDown of this class is executed after +// all tests have run. +[SetUpFixture] +public partial class GlobalTestsSetup +{ + [OneTimeSetUp] + public void GlobalSetup() { - [OneTimeSetUp] - public void GlobalSetup() - { - Finalizer.Instance.ErrorHandler += FinalizerErrorHandler; - } + PythonEngine.Initialize(); + Finalizer.Instance.ErrorHandler += FinalizerErrorHandler; + } - private void FinalizerErrorHandler(object sender, Finalizer.ErrorArgs e) + private void FinalizerErrorHandler(object sender, Finalizer.ErrorArgs e) + { + if (e.Error is RuntimeShutdownException) { - if (e.Error is RuntimeShutdownException) - { - // allow objects to leak after the python runtime run - // they were created in is gone - e.Handled = true; - } + // allow objects to leak after the python runtime run + // they were created in is gone + e.Handled = true; } + } - [OneTimeTearDown] - public void FinalCleanup() + [OneTimeTearDown] + public void FinalCleanup() + { + PyObjectConversions.Reset(); + if (PythonEngine.IsInitialized) { - if (PythonEngine.IsInitialized) - { - PythonEngine.Shutdown(); - } + PythonEngine.Shutdown(); } } } diff --git a/src/embed_tests/Inheritance.cs b/src/embed_tests/Inheritance.cs index ebbc24dc4..eb9dc6b0d 100644 --- a/src/embed_tests/Inheritance.cs +++ b/src/embed_tests/Inheritance.cs @@ -5,159 +5,157 @@ using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class Inheritance : BaseFixture { - public class Inheritance + [OneTimeSetUp] + public void SetUp() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - using var locals = new PyDict(); - PythonEngine.Exec(InheritanceTestBaseClassWrapper.ClassSourceCode, locals: locals); - ExtraBaseTypeProvider.ExtraBase = new PyType(locals[InheritanceTestBaseClassWrapper.ClassName]); - var baseTypeProviders = PythonEngine.InteropConfiguration.PythonBaseTypeProviders; - baseTypeProviders.Add(new ExtraBaseTypeProvider()); - baseTypeProviders.Add(new NoEffectBaseTypeProvider()); - } - - [OneTimeTearDown] - public void Dispose() - { - ExtraBaseTypeProvider.ExtraBase.Dispose(); - PythonEngine.Shutdown(); - } - - [Test] - public void ExtraBase_PassesInstanceCheck() - { - var inherited = new Inherited(); - bool properlyInherited = PyIsInstance(inherited, ExtraBaseTypeProvider.ExtraBase); - Assert.IsTrue(properlyInherited); - } - - static dynamic PyIsInstance => PythonEngine.Eval("isinstance"); + using var locals = new PyDict(); + PythonEngine.Exec(InheritanceTestBaseClassWrapper.ClassSourceCode, locals: locals); + ExtraBaseTypeProvider.ExtraBase = new PyType(locals[InheritanceTestBaseClassWrapper.ClassName]); + var baseTypeProviders = PythonEngine.InteropConfiguration.PythonBaseTypeProviders; + baseTypeProviders.Add(new ExtraBaseTypeProvider()); + baseTypeProviders.Add(new NoEffectBaseTypeProvider()); + } - [Test] - public void InheritingWithExtraBase_CreatesNewClass() - { - PyObject a = ExtraBaseTypeProvider.ExtraBase; - var inherited = new Inherited(); - PyObject inheritedClass = inherited.ToPython().GetAttr("__class__"); - Assert.IsFalse(PythonReferenceComparer.Instance.Equals(a, inheritedClass)); - } + [OneTimeTearDown] + public void Dispose() + { + ExtraBaseTypeProvider.ExtraBase.Dispose(); + } - [Test] - public void InheritedFromInheritedClassIsSelf() - { - using var scope = Py.CreateScope(); - scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); - scope.Exec($"class B({nameof(Inherited)}): pass"); - PyObject b = scope.Eval("B"); - PyObject bInstance = b.Invoke(); - PyObject bInstanceClass = bInstance.GetAttr("__class__"); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(b, bInstanceClass)); - } + [Test] + public void ExtraBase_PassesInstanceCheck() + { + var inherited = new Inherited(); + bool properlyInherited = PyIsInstance(inherited, ExtraBaseTypeProvider.ExtraBase); + Assert.IsTrue(properlyInherited); + } - // https://github.com/pythonnet/pythonnet/issues/1420 - [Test] - public void CallBaseMethodFromContainerInNestedClass() - { - using var nested = new ContainerClass.InnerClass().ToPython(); - nested.InvokeMethod(nameof(ContainerClass.BaseMethod)); - } + static dynamic PyIsInstance => PythonEngine.Eval("isinstance"); - [Test] - public void Grandchild_PassesExtraBaseInstanceCheck() - { - using var scope = Py.CreateScope(); - scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); - scope.Exec($"class B({nameof(Inherited)}): pass"); - PyObject b = scope.Eval("B"); - PyObject bInst = b.Invoke(); - bool properlyInherited = PyIsInstance(bInst, ExtraBaseTypeProvider.ExtraBase); - Assert.IsTrue(properlyInherited); - } + [Test] + public void InheritingWithExtraBase_CreatesNewClass() + { + PyObject a = ExtraBaseTypeProvider.ExtraBase; + var inherited = new Inherited(); + PyObject inheritedClass = inherited.ToPython().GetAttr("__class__"); + Assert.IsFalse(PythonReferenceComparer.Instance.Equals(a, inheritedClass)); + } - [Test] - public void CallInheritedClrMethod_WithExtraPythonBase() - { - var instance = new Inherited().ToPython(); - string result = instance.InvokeMethod(nameof(PythonWrapperBase.WrapperBaseMethod)).As(); - Assert.AreEqual(result, nameof(PythonWrapperBase.WrapperBaseMethod)); - } + [Test] + public void InheritedFromInheritedClassIsSelf() + { + using var scope = Py.CreateScope(); + scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); + scope.Exec($"class B({nameof(Inherited)}): pass"); + PyObject b = scope.Eval("B"); + PyObject bInstance = b.Invoke(); + PyObject bInstanceClass = bInstance.GetAttr("__class__"); + Assert.IsTrue(PythonReferenceComparer.Instance.Equals(b, bInstanceClass)); + } - [Test] - public void CallExtraBaseMethod() - { - var instance = new Inherited(); - using var scope = Py.CreateScope(); - scope.Set(nameof(instance), instance); - int actual = instance.ToPython().InvokeMethod("callVirt").As(); - Assert.AreEqual(expected: Inherited.OverridenVirtValue, actual); - } + // https://github.com/pythonnet/pythonnet/issues/1420 + [Test] + public void CallBaseMethodFromContainerInNestedClass() + { + using var nested = new ContainerClass.InnerClass().ToPython(); + nested.InvokeMethod(nameof(ContainerClass.BaseMethod)); + } - [Test] - public void SetAdHocAttributes_WhenExtraBasePresent() - { - var instance = new Inherited(); - using var scope = Py.CreateScope(); - scope.Set(nameof(instance), instance); - scope.Exec($"super({nameof(instance)}.__class__, {nameof(instance)}).set_x_to_42()"); - int actual = scope.Eval($"{nameof(instance)}.{nameof(Inherited.XProp)}"); - Assert.AreEqual(expected: Inherited.X, actual); - } + [Test] + public void Grandchild_PassesExtraBaseInstanceCheck() + { + using var scope = Py.CreateScope(); + scope.Exec($"from {typeof(Inherited).Namespace} import {nameof(Inherited)}"); + scope.Exec($"class B({nameof(Inherited)}): pass"); + PyObject b = scope.Eval("B"); + PyObject bInst = b.Invoke(); + bool properlyInherited = PyIsInstance(bInst, ExtraBaseTypeProvider.ExtraBase); + Assert.IsTrue(properlyInherited); + } - // https://github.com/pythonnet/pythonnet/issues/1476 - [Test] - public void BaseClearIsCalled() - { - using var scope = Py.CreateScope(); - scope.Set("exn", new Exception("42")); - var msg = scope.Eval("exn.args[0]"); - Assert.AreEqual(2, msg.Refcount); - scope.Set("exn", null); - Assert.AreEqual(1, msg.Refcount); - } + [Test] + public void CallInheritedClrMethod_WithExtraPythonBase() + { + var instance = new Inherited().ToPython(); + string result = instance.InvokeMethod(nameof(PythonWrapperBase.WrapperBaseMethod)).As(); + Assert.AreEqual(result, nameof(PythonWrapperBase.WrapperBaseMethod)); + } - // https://github.com/pythonnet/pythonnet/issues/1455 - [Test] - public void PropertyAccessorOverridden() - { - using var derived = new PropertyAccessorDerived().ToPython(); - derived.SetAttr(nameof(PropertyAccessorDerived.VirtualProp), "hi".ToPython()); - Assert.AreEqual("HI", derived.GetAttr(nameof(PropertyAccessorDerived.VirtualProp)).As()); - } + [Test] + public void CallExtraBaseMethod() + { + var instance = new Inherited(); + using var scope = Py.CreateScope(); + scope.Set(nameof(instance), instance); + int actual = instance.ToPython().InvokeMethod("callVirt").As(); + Assert.AreEqual(expected: Inherited.OverridenVirtValue, actual); } - class ExtraBaseTypeProvider : IPythonBaseTypeProvider + [Test] + public void SetAdHocAttributes_WhenExtraBasePresent() { - internal static PyType ExtraBase; - public IEnumerable GetBaseTypes(Type type, IList existingBases) - { - if (type == typeof(InheritanceTestBaseClassWrapper)) - { - return new[] { PyType.Get(type.BaseType), ExtraBase }; - } - return existingBases; - } + var instance = new Inherited(); + using var scope = Py.CreateScope(); + scope.Set(nameof(instance), instance); + scope.Exec($"super({nameof(instance)}.__class__, {nameof(instance)}).set_x_to_42()"); + int actual = scope.Eval($"{nameof(instance)}.{nameof(Inherited.XProp)}"); + Assert.AreEqual(expected: Inherited.X, actual); } - class NoEffectBaseTypeProvider : IPythonBaseTypeProvider + // https://github.com/pythonnet/pythonnet/issues/1476 + [Test] + public void BaseClearIsCalled() { - public IEnumerable GetBaseTypes(Type type, IList existingBases) - => existingBases; + using var scope = Py.CreateScope(); + scope.Set("exn", new Exception("42")); + var msg = scope.Eval("exn.args[0]"); + Assert.AreEqual(2, msg.Refcount); + scope.Set("exn", null); + Assert.AreEqual(1, msg.Refcount); } - public class PythonWrapperBase + // https://github.com/pythonnet/pythonnet/issues/1455 + [Test] + public void PropertyAccessorOverridden() { - public string WrapperBaseMethod() => nameof(WrapperBaseMethod); + using var derived = new PropertyAccessorDerived().ToPython(); + derived.SetAttr(nameof(PropertyAccessorDerived.VirtualProp), "hi".ToPython()); + Assert.AreEqual("HI", derived.GetAttr(nameof(PropertyAccessorDerived.VirtualProp)).As()); } +} - public class InheritanceTestBaseClassWrapper : PythonWrapperBase +class ExtraBaseTypeProvider : IPythonBaseTypeProvider +{ + internal static PyType ExtraBase; + public IEnumerable GetBaseTypes(Type type, IList existingBases) { - public const string ClassName = "InheritanceTestBaseClass"; - public const string ClassSourceCode = "class " + ClassName + + if (type == typeof(InheritanceTestBaseClassWrapper)) + { + return new[] { PyType.Get(type.BaseType), ExtraBase }; + } + return existingBases; + } +} + +class NoEffectBaseTypeProvider : IPythonBaseTypeProvider +{ + public IEnumerable GetBaseTypes(Type type, IList existingBases) + => existingBases; +} + +public class PythonWrapperBase +{ + public string WrapperBaseMethod() => nameof(WrapperBaseMethod); +} + +public class InheritanceTestBaseClassWrapper : PythonWrapperBase +{ + public const string ClassName = "InheritanceTestBaseClass"; + public const string ClassSourceCode = "class " + ClassName + @": def virt(self): return 42 @@ -170,56 +168,55 @@ def __getattr__(self, name): def __setattr__(self, name, value): value[name] = name " + ClassName + " = " + ClassName + "\n"; - } +} - public class Inherited : InheritanceTestBaseClassWrapper +public class Inherited : InheritanceTestBaseClassWrapper +{ + public const int OverridenVirtValue = -42; + public const int X = 42; + readonly Dictionary extras = new Dictionary(); + public int virt() => OverridenVirtValue; + public int XProp { - public const int OverridenVirtValue = -42; - public const int X = 42; - readonly Dictionary extras = new Dictionary(); - public int virt() => OverridenVirtValue; - public int XProp + get { - get + using (var scope = Py.CreateScope()) { - using (var scope = Py.CreateScope()) + scope.Set("this", this); + try + { + return scope.Eval($"super(this.__class__, this).{nameof(XProp)}"); + } + catch (PythonException ex) when (PythonReferenceComparer.Instance.Equals(ex.Type, Exceptions.AttributeError)) { - scope.Set("this", this); - try - { - return scope.Eval($"super(this.__class__, this).{nameof(XProp)}"); - } - catch (PythonException ex) when (PythonReferenceComparer.Instance.Equals(ex.Type, Exceptions.AttributeError)) - { - if (this.extras.TryGetValue(nameof(this.XProp), out object value)) - return (int)value; - throw; - } + if (this.extras.TryGetValue(nameof(this.XProp), out object value)) + return (int)value; + throw; } } - set => this.extras[nameof(this.XProp)] = value; } + set => this.extras[nameof(this.XProp)] = value; } +} - public class PropertyAccessorBase - { - public virtual string VirtualProp { get; set; } - } +public class PropertyAccessorBase +{ + public virtual string VirtualProp { get; set; } +} - public class PropertyAccessorIntermediate: PropertyAccessorBase { } +public class PropertyAccessorIntermediate: PropertyAccessorBase { } - public class PropertyAccessorDerived: PropertyAccessorIntermediate - { - public override string VirtualProp { set => base.VirtualProp = value.ToUpperInvariant(); } - } +public class PropertyAccessorDerived: PropertyAccessorIntermediate +{ + public override string VirtualProp { set => base.VirtualProp = value.ToUpperInvariant(); } +} - public class ContainerClass - { - public void BaseMethod() { } +public class ContainerClass +{ + public void BaseMethod() { } - public class InnerClass: ContainerClass - { + public class InnerClass: ContainerClass + { - } } } diff --git a/src/embed_tests/Inspect.cs b/src/embed_tests/Inspect.cs index 8ff94e02c..36131e393 100644 --- a/src/embed_tests/Inspect.cs +++ b/src/embed_tests/Inspect.cs @@ -1,59 +1,43 @@ using System; -using System.Collections.Generic; - using NUnit.Framework; - using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class Inspect : BaseFixture { - public class Inspect + [Test] + public void InstancePropertiesVisibleOnClass() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + var uri = new Uri("http://example.org").ToPython(); + var uriClass = uri.GetPythonType(); + var property = uriClass.GetAttr(nameof(Uri.AbsoluteUri)); + var pyProp = (PropertyObject)ManagedType.GetManagedObject(property.Reference); + Assert.AreEqual(nameof(Uri.AbsoluteUri), pyProp.info.Value.Name); + } - [OneTimeTearDown] - public void Dispose() + [Test] + public void BoundMethodsAreInspectable() + { + using var scope = Py.CreateScope(); + try { - PythonEngine.Shutdown(); + scope.Import("inspect"); } - - [Test] - public void InstancePropertiesVisibleOnClass() + catch (PythonException) { - var uri = new Uri("http://example.org").ToPython(); - var uriClass = uri.GetPythonType(); - var property = uriClass.GetAttr(nameof(Uri.AbsoluteUri)); - var pyProp = (PropertyObject)ManagedType.GetManagedObject(property.Reference); - Assert.AreEqual(nameof(Uri.AbsoluteUri), pyProp.info.Value.Name); + Assert.Inconclusive("Python build does not include inspect module"); + return; } - [Test] - public void BoundMethodsAreInspectable() - { - using var scope = Py.CreateScope(); - try - { - scope.Import("inspect"); - } - catch (PythonException) - { - Assert.Inconclusive("Python build does not include inspect module"); - return; - } - - var obj = new Class(); - scope.Set(nameof(obj), obj); - using var spec = scope.Eval($"inspect.getfullargspec({nameof(obj)}.{nameof(Class.Method)})"); - } + var obj = new Class(); + scope.Set(nameof(obj), obj); + using var spec = scope.Eval($"inspect.getfullargspec({nameof(obj)}.{nameof(Class.Method)})"); + } - class Class - { - public void Method(int a, int b = 10) { } - public void Method(int a, object b) { } - } + class Class + { + public void Method(int a, int b = 10) { } + public void Method(int a, object b) { } } } diff --git a/src/embed_tests/Modules.cs b/src/embed_tests/Modules.cs index a88ab8552..99efa95f5 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -1,405 +1,391 @@ -using System; using System.Threading; using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class Modules : BaseFixture { - public class Modules - { - private PyModule ps; + private PyModule ps; - [SetUp] - public void SetUp() + [SetUp] + public void SetUp() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps = Py.CreateScope("test"); - } + ps = Py.CreateScope("test"); } + } - [TearDown] - public void Dispose() + [TearDown] + public void Dispose() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Dispose(); - ps = null; - } + ps.Dispose(); + ps = null; } + } - [OneTimeSetUp] - public void OneTimeSetUp() + /// + /// Eval a Python expression and obtain its return value. + /// + [Test] + public void TestEval() + { + using (Py.GIL()) { - PythonEngine.Initialize(); + ps.Set("a", 1); + var result = ps.Eval("a + 2"); + Assert.AreEqual(3, result); } + } - [OneTimeTearDown] - public void OneTimeTearDown() + /// + /// Exec Python statements and obtain the variables created. + /// + [Test] + public void TestExec() + { + using (Py.GIL()) { - PythonEngine.Shutdown(); + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable + ps.Exec("aa = bb + cc + 3"); + var result = ps.Get("aa"); + Assert.AreEqual(113, result); } + } - /// - /// Eval a Python expression and obtain its return value. - /// - [Test] - public void TestEval() + /// + /// Compile an expression into an ast object; + /// Execute the ast and obtain its return value. + /// + [Test] + public void TestCompileExpression() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Set("a", 1); - var result = ps.Eval("a + 2"); - Assert.AreEqual(3, result); - } + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable + PyObject script = PythonEngine.Compile("bb + cc + 3", "", RunFlagType.Eval); + var result = ps.Execute(script); + Assert.AreEqual(113, result); } + } - /// - /// Exec Python statements and obtain the variables created. - /// - [Test] - public void TestExec() + /// + /// Compile Python statements into an ast object; + /// Execute the ast; + /// Obtain the local variables created. + /// + [Test] + public void TestCompileStatements() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Set("bb", 100); //declare a global variable - ps.Set("cc", 10); //declare a local variable - ps.Exec("aa = bb + cc + 3"); - var result = ps.Get("aa"); - Assert.AreEqual(113, result); - } + ps.Set("bb", 100); //declare a global variable + ps.Set("cc", 10); //declare a local variable + PyObject script = PythonEngine.Compile("aa = bb + cc + 3", "", RunFlagType.File); + ps.Execute(script); + var result = ps.Get("aa"); + Assert.AreEqual(113, result); } + } - /// - /// Compile an expression into an ast object; - /// Execute the ast and obtain its return value. - /// - [Test] - public void TestCompileExpression() + /// + /// Create a function in the scope, then the function can read variables in the scope. + /// It cannot write the variables unless it uses the 'global' keyword. + /// + [Test] + public void TestScopeFunction() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Set("bb", 100); //declare a global variable - ps.Set("cc", 10); //declare a local variable - PyObject script = PythonEngine.Compile("bb + cc + 3", "", RunFlagType.Eval); - var result = ps.Execute(script); - Assert.AreEqual(113, result); - } + ps.Set("bb", 100); + ps.Set("cc", 10); + ps.Exec( + "def func1():\n" + + " bb = cc + 10\n"); + dynamic func1 = ps.Get("func1"); + func1(); //call the function, it can be called any times + var result = ps.Get("bb"); + Assert.AreEqual(100, result); + + ps.Set("bb", 100); + ps.Set("cc", 10); + ps.Exec( + "def func2():\n" + + " global bb\n" + + " bb = cc + 10\n"); + dynamic func2 = ps.Get("func2"); + func2(); + result = ps.Get("bb"); + Assert.AreEqual(20, result); } + } - /// - /// Compile Python statements into an ast object; - /// Execute the ast; - /// Obtain the local variables created. - /// - [Test] - public void TestCompileStatements() + /// + /// Create a class in the scope, the class can read variables in the scope. + /// Its methods can write the variables with the help of 'global' keyword. + /// + [Test] + public void TestScopeClass() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Set("bb", 100); //declare a global variable - ps.Set("cc", 10); //declare a local variable - PyObject script = PythonEngine.Compile("aa = bb + cc + 3", "", RunFlagType.File); - ps.Execute(script); - var result = ps.Get("aa"); - Assert.AreEqual(113, result); - } + dynamic _ps = ps; + _ps.bb = 100; + ps.Exec( + "class Class1():\n" + + " def __init__(self, value):\n" + + " self.value = value\n" + + " def call(self, arg):\n" + + " return self.value + bb + arg\n" + //use scope variables + " def update(self, arg):\n" + + " global bb\n" + + " bb = self.value + arg\n" //update scope variable + ); + dynamic obj1 = _ps.Class1(20); + var result = obj1.call(10).As(); + Assert.AreEqual(130, result); + + obj1.update(10); + result = ps.Get("bb"); + Assert.AreEqual(30, result); } + } - /// - /// Create a function in the scope, then the function can read variables in the scope. - /// It cannot write the variables unless it uses the 'global' keyword. - /// - [Test] - public void TestScopeFunction() + /// + /// Import a python module into the session. + /// Equivalent to the Python "import" statement. + /// + [Test] + public void TestImportModule() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Set("bb", 100); - ps.Set("cc", 10); - ps.Exec( - "def func1():\n" + - " bb = cc + 10\n"); - dynamic func1 = ps.Get("func1"); - func1(); //call the function, it can be called any times - var result = ps.Get("bb"); - Assert.AreEqual(100, result); - - ps.Set("bb", 100); - ps.Set("cc", 10); - ps.Exec( - "def func2():\n" + - " global bb\n" + - " bb = cc + 10\n"); - dynamic func2 = ps.Get("func2"); - func2(); - result = ps.Get("bb"); - Assert.AreEqual(20, result); - } + dynamic sys = ps.Import("sys"); + Assert.IsTrue(ps.Contains("sys")); + + ps.Exec("sys.attr1 = 2"); + var value1 = ps.Eval("sys.attr1"); + var value2 = sys.attr1.As(); + Assert.AreEqual(2, value1); + Assert.AreEqual(2, value2); + + //import as + ps.Import("sys", "sys1"); + Assert.IsTrue(ps.Contains("sys1")); } + } - /// - /// Create a class in the scope, the class can read variables in the scope. - /// Its methods can write the variables with the help of 'global' keyword. - /// - [Test] - public void TestScopeClass() + /// + /// Create a scope and import variables from a scope, + /// exec Python statements in the scope then discard it. + /// + [Test] + public void TestImportScope() + { + using (Py.GIL()) { - using (Py.GIL()) - { - dynamic _ps = ps; - _ps.bb = 100; - ps.Exec( - "class Class1():\n" + - " def __init__(self, value):\n" + - " self.value = value\n" + - " def call(self, arg):\n" + - " return self.value + bb + arg\n" + //use scope variables - " def update(self, arg):\n" + - " global bb\n" + - " bb = self.value + arg\n" //update scope variable - ); - dynamic obj1 = _ps.Class1(20); - var result = obj1.call(10).As(); - Assert.AreEqual(130, result); + ps.Set("bb", 100); + ps.Set("cc", 10); - obj1.update(10); - result = ps.Get("bb"); - Assert.AreEqual(30, result); + using (var scope = Py.CreateScope()) + { + scope.Import(ps, "ps"); + scope.Exec("aa = ps.bb + ps.cc + 3"); + var result = scope.Get("aa"); + Assert.AreEqual(113, result); } + + Assert.IsFalse(ps.Contains("aa")); } + } - /// - /// Import a python module into the session. - /// Equivalent to the Python "import" statement. - /// - [Test] - public void TestImportModule() + /// + /// Create a scope and import variables from a scope, + /// exec Python statements in the scope then discard it. + /// + [Test] + public void TestImportAllFromScope() + { + using (Py.GIL()) { - using (Py.GIL()) + ps.Set("bb", 100); + ps.Set("cc", 10); + + using (var scope = ps.NewScope()) { - dynamic sys = ps.Import("sys"); - Assert.IsTrue(ps.Contains("sys")); - - ps.Exec("sys.attr1 = 2"); - var value1 = ps.Eval("sys.attr1"); - var value2 = sys.attr1.As(); - Assert.AreEqual(2, value1); - Assert.AreEqual(2, value2); - - //import as - ps.Import("sys", "sys1"); - Assert.IsTrue(ps.Contains("sys1")); + scope.Exec("aa = bb + cc + 3"); + var result = scope.Get("aa"); + Assert.AreEqual(113, result); } + + Assert.IsFalse(ps.Contains("aa")); } + } - /// - /// Create a scope and import variables from a scope, - /// exec Python statements in the scope then discard it. - /// - [Test] - public void TestImportScope() + /// + /// Create a scope and import variables from a scope, + /// call the function imported. + /// + [Test] + public void TestImportScopeFunction() + { + using (Py.GIL()) { - using (Py.GIL()) + ps.Set("bb", 100); + ps.Set("cc", 10); + ps.Exec( + "def func1():\n" + + " return cc + bb\n"); + + using (var scope = ps.NewScope()) { - ps.Set("bb", 100); - ps.Set("cc", 10); + //'func1' is imported from the origion scope + scope.Exec( + "def func2():\n" + + " return func1() - cc - bb\n"); + dynamic func2 = scope.Get("func2"); - using (var scope = Py.CreateScope()) - { - scope.Import(ps, "ps"); - scope.Exec("aa = ps.bb + ps.cc + 3"); - var result = scope.Get("aa"); - Assert.AreEqual(113, result); - } + var result1 = func2().As(); + Assert.AreEqual(0, result1); + + scope.Set("cc", 20);//it has no effect on the globals of 'func1' + var result2 = func2().As(); + Assert.AreEqual(-10, result2); + scope.Set("cc", 10); //rollback - Assert.IsFalse(ps.Contains("aa")); + ps.Set("cc", 20); + var result3 = func2().As(); + Assert.AreEqual(10, result3); + ps.Set("cc", 10); //rollback } } + } - /// - /// Create a scope and import variables from a scope, - /// exec Python statements in the scope then discard it. - /// - [Test] - public void TestImportAllFromScope() + /// + /// Use the locals() and globals() method just like in python module + /// + [Test] + public void TestVariables() + { + using (Py.GIL()) { - using (Py.GIL()) - { - ps.Set("bb", 100); - ps.Set("cc", 10); + (ps.Variables() as dynamic)["ee"] = new PyInt(200); + var a0 = ps.Get("ee"); + Assert.AreEqual(200, a0); - using (var scope = ps.NewScope()) - { - scope.Exec("aa = bb + cc + 3"); - var result = scope.Get("aa"); - Assert.AreEqual(113, result); - } + ps.Exec("locals()['ee'] = 210"); + var a1 = ps.Get("ee"); + Assert.AreEqual(210, a1); - Assert.IsFalse(ps.Contains("aa")); - } - } + ps.Exec("globals()['ee'] = 220"); + var a2 = ps.Get("ee"); + Assert.AreEqual(220, a2); - /// - /// Create a scope and import variables from a scope, - /// call the function imported. - /// - [Test] - public void TestImportScopeFunction() - { - using (Py.GIL()) + using (var item = ps.Variables()) { - ps.Set("bb", 100); - ps.Set("cc", 10); - ps.Exec( - "def func1():\n" + - " return cc + bb\n"); - - using (var scope = ps.NewScope()) - { - //'func1' is imported from the origion scope - scope.Exec( - "def func2():\n" + - " return func1() - cc - bb\n"); - dynamic func2 = scope.Get("func2"); - - var result1 = func2().As(); - Assert.AreEqual(0, result1); - - scope.Set("cc", 20);//it has no effect on the globals of 'func1' - var result2 = func2().As(); - Assert.AreEqual(-10, result2); - scope.Set("cc", 10); //rollback - - ps.Set("cc", 20); - var result3 = func2().As(); - Assert.AreEqual(10, result3); - ps.Set("cc", 10); //rollback - } + item["ee"] = new PyInt(230); } + var a3 = ps.Get("ee"); + Assert.AreEqual(230, a3); } + } - /// - /// Use the locals() and globals() method just like in python module - /// - [Test] - public void TestVariables() + /// + /// Share a pyscope by multiple threads. + /// + [Test] + public void TestThread() + { + //After the proposal here https://github.com/pythonnet/pythonnet/pull/419 complished, + //the BeginAllowThreads statement blow and the last EndAllowThreads statement + //should be removed. + dynamic _ps = ps; + var ts = PythonEngine.BeginAllowThreads(); + try { using (Py.GIL()) - { - (ps.Variables() as dynamic)["ee"] = new PyInt(200); - var a0 = ps.Get("ee"); - Assert.AreEqual(200, a0); - - ps.Exec("locals()['ee'] = 210"); - var a1 = ps.Get("ee"); - Assert.AreEqual(210, a1); - - ps.Exec("globals()['ee'] = 220"); - var a2 = ps.Get("ee"); - Assert.AreEqual(220, a2); - - using (var item = ps.Variables()) - { - item["ee"] = new PyInt(230); - } - var a3 = ps.Get("ee"); - Assert.AreEqual(230, a3); + { + _ps.res = 0; + _ps.bb = 100; + _ps.th_cnt = 0; + //add function to the scope + //can be call many times, more efficient than ast + ps.Exec( + "import threading\n"+ + "lock = threading.Lock()\n"+ + "def update():\n" + + " global res, th_cnt\n" + + " with lock:\n" + + " res += bb + 1\n" + + " th_cnt += 1\n" + ); } - } - - /// - /// Share a pyscope by multiple threads. - /// - [Test] - public void TestThread() - { - //After the proposal here https://github.com/pythonnet/pythonnet/pull/419 complished, - //the BeginAllowThreads statement blow and the last EndAllowThreads statement - //should be removed. - dynamic _ps = ps; - var ts = PythonEngine.BeginAllowThreads(); - try + int th_cnt = 100; + for (int i = 0; i < th_cnt; i++) { - using (Py.GIL()) - { - _ps.res = 0; - _ps.bb = 100; - _ps.th_cnt = 0; - //add function to the scope - //can be call many times, more efficient than ast - ps.Exec( - "import threading\n"+ - "lock = threading.Lock()\n"+ - "def update():\n" + - " global res, th_cnt\n" + - " with lock:\n" + - " res += bb + 1\n" + - " th_cnt += 1\n" - ); - } - int th_cnt = 100; - for (int i = 0; i < th_cnt; i++) - { - System.Threading.Thread th = new System.Threading.Thread(() => - { - using (Py.GIL()) - { - //ps.GetVariable("update")(); //call the scope function dynamicly - _ps.update(); - } - }); - th.Start(); - } - //equivalent to Thread.Join, make the main thread join the GIL competition - int cnt = 0; - while (cnt != th_cnt) + System.Threading.Thread th = new System.Threading.Thread(() => { using (Py.GIL()) { - cnt = ps.Get("th_cnt"); + //ps.GetVariable("update")(); //call the scope function dynamicly + _ps.update(); } - Thread.Yield(); - } + }); + th.Start(); + } + //equivalent to Thread.Join, make the main thread join the GIL competition + int cnt = 0; + while (cnt != th_cnt) + { using (Py.GIL()) { - var result = ps.Get("res"); - Assert.AreEqual(101 * th_cnt, result); + cnt = ps.Get("th_cnt"); } + Thread.Yield(); } - finally + using (Py.GIL()) { - PythonEngine.EndAllowThreads(ts); + var result = ps.Get("res"); + Assert.AreEqual(101 * th_cnt, result); } } - - [Test] - public void TestCreate() + finally { - using var scope = Py.CreateScope(); + PythonEngine.EndAllowThreads(ts); + } + } - Assert.IsFalse(PyModule.SysModules.HasKey("testmod")); + [Test] + public void TestCreate() + { + using var scope = Py.CreateScope(); - PyModule testmod = new PyModule("testmod"); + Assert.IsFalse(PyModule.SysModules.HasKey("testmod")); - testmod.SetAttr("testattr1", "True".ToPython()); + PyModule testmod = new PyModule("testmod"); - PyModule.SysModules.SetItem("testmod", testmod); + testmod.SetAttr("testattr1", "True".ToPython()); - using PyObject code = PythonEngine.Compile( - "import testmod\n" + - "x = testmod.testattr1" - ); - scope.Execute(code); + PyModule.SysModules.SetItem("testmod", testmod); - Assert.IsTrue(scope.TryGet("x", out dynamic x)); - Assert.AreEqual("True", x.ToString()); - } + using PyObject code = PythonEngine.Compile( + "import testmod\n" + + "x = testmod.testattr1" + ); + scope.Execute(code); - [Test] - public void ImportClrNamespace() - { - Py.Import(GetType().Namespace); - } + Assert.IsTrue(scope.TryGet("x", out dynamic x)); + Assert.AreEqual("True", x.ToString()); + } + + [Test] + public void ImportClrNamespace() + { + Py.Import(GetType().Namespace); } } diff --git a/src/embed_tests/NumPyTests.cs b/src/embed_tests/NumPyTests.cs index e102ddb99..05b9539a2 100644 --- a/src/embed_tests/NumPyTests.cs +++ b/src/embed_tests/NumPyTests.cs @@ -6,92 +6,84 @@ using Python.Runtime; using Python.Runtime.Codecs; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class NumPyTests : BaseFixture { - public class NumPyTests + [OneTimeSetUp] + public void SetUp() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - TupleCodec.Register(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + TupleCodec.Register(); + } - [Test] - public void TestReadme() - { - Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString()); + [Test] + public void TestReadme() + { + Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString()); - dynamic sin = np.sin; - StringAssert.StartsWith("-0.95892", sin(5).ToString()); + dynamic sin = np.sin; + StringAssert.StartsWith("-0.95892", sin(5).ToString()); - double c = (double)(np.cos(5) + sin(5)); - Assert.AreEqual(-0.675262, c, 0.01); + double c = (double)(np.cos(5) + sin(5)); + Assert.AreEqual(-0.675262, c, 0.01); - dynamic a = np.array(new List { 1, 2, 3 }); - Assert.AreEqual("float64", a.dtype.ToString()); + dynamic a = np.array(new List { 1, 2, 3 }); + Assert.AreEqual("float64", a.dtype.ToString()); - dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); - Assert.AreEqual("int32", b.dtype.ToString()); + dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); + Assert.AreEqual("int32", b.dtype.ToString()); - Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString().Replace(" ", " ")); - } + Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString().Replace(" ", " ")); + } - [Test] - public void MultidimensionalNumPyArray() - { - var array = new[,] { { 1, 2 }, { 3, 4 } }; - var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); - Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); - Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); - } + [Test] + public void MultidimensionalNumPyArray() + { + var array = new[,] { { 1, 2 }, { 3, 4 } }; + var ndarray = np.InvokeMethod("asarray", array.ToPython()); + Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); + Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); + Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); + } - [Test] - public void Int64Array() - { - var array = new long[,] { { 1, 2 }, { 3, 4 } }; - var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); - Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); - Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); - } + [Test] + public void Int64Array() + { + var array = new long[,] { { 1, 2 }, { 3, 4 } }; + var ndarray = np.InvokeMethod("asarray", array.ToPython()); + Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); + Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); + Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); + } - [Test] - public void VarArg() - { - dynamic zX = np.array(new[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 8, 9, 0 } }); - dynamic grad = np.gradient(zX, 4.0, 5.0); - dynamic grad2 = np.InvokeMethod("gradient", new PyObject[] {zX, new PyFloat(4.0), new PyFloat(5.0)}); - - Assert.AreEqual(4.125, grad[0].sum().__float__().As(), 0.001); - Assert.AreEqual(-1.2, grad[1].sum().__float__().As(), 0.001); - Assert.AreEqual(4.125, grad2[0].sum().__float__().As(), 0.001); - Assert.AreEqual(-1.2, grad2[1].sum().__float__().As(), 0.001); - } + [Test] + public void VarArg() + { + dynamic zX = np.array(new[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 8, 9, 0 } }); + dynamic grad = np.gradient(zX, 4.0, 5.0); + dynamic grad2 = np.InvokeMethod("gradient", new PyObject[] {zX, new PyFloat(4.0), new PyFloat(5.0)}); + + Assert.AreEqual(4.125, grad[0].sum().__float__().As(), 0.001); + Assert.AreEqual(-1.2, grad[1].sum().__float__().As(), 0.001); + Assert.AreEqual(4.125, grad2[0].sum().__float__().As(), 0.001); + Assert.AreEqual(-1.2, grad2[1].sum().__float__().As(), 0.001); + } #pragma warning disable IDE1006 - dynamic np + dynamic np + { + get { - get + try { - try - { - return Py.Import("numpy"); - } - catch (PythonException) - { - Assert.Inconclusive("Numpy or dependency not installed"); - return null; - } + return Py.Import("numpy"); + } + catch (PythonException) + { + Assert.Inconclusive("Numpy or dependency not installed"); + return null; } } - } + } diff --git a/src/embed_tests/References.cs b/src/embed_tests/References.cs index c416c5ebe..eeca08ae6 100644 --- a/src/embed_tests/References.cs +++ b/src/embed_tests/References.cs @@ -1,47 +1,34 @@ -namespace Python.EmbeddingTest -{ - using NUnit.Framework; - using Python.Runtime; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest; - public class References +public class References : BaseFixture +{ + [Test] + public void MoveToPyObject_SetsNull() { - [OneTimeSetUp] - public void SetUp() + using var dict = new PyDict(); + NewReference reference = Runtime.Runtime.PyDict_Items(dict.Reference); + try { - PythonEngine.Initialize(); - } + Assert.IsFalse(reference.IsNull()); - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); + using (reference.MoveToPyObject()) + Assert.IsTrue(reference.IsNull()); } - - [Test] - public void MoveToPyObject_SetsNull() + finally { - var dict = new PyDict(); - NewReference reference = Runtime.PyDict_Items(dict.Reference); - try - { - Assert.IsFalse(reference.IsNull()); - - using (reference.MoveToPyObject()) - Assert.IsTrue(reference.IsNull()); - } - finally - { - reference.Dispose(); - } + reference.Dispose(); } + } - [Test] - public void CanBorrowFromNewReference() - { - var dict = new PyDict(); - using NewReference reference = Runtime.PyDict_Items(dict.Reference); - BorrowedReference borrowed = reference.BorrowOrThrow(); - PythonException.ThrowIfIsNotZero(Runtime.PyList_Reverse(borrowed)); - } + [Test] + public void CanBorrowFromNewReference() + { + using var dict = new PyDict(); + using NewReference reference = Runtime.Runtime.PyDict_Items(dict.Reference); + BorrowedReference borrowed = reference.BorrowOrThrow(); + PythonException.ThrowIfIsNotZero(Runtime.Runtime.PyList_Reverse(borrowed)); } } diff --git a/src/embed_tests/TestCallbacks.cs b/src/embed_tests/TestCallbacks.cs index 88b84d0c3..a9b057142 100644 --- a/src/embed_tests/TestCallbacks.cs +++ b/src/embed_tests/TestCallbacks.cs @@ -3,31 +3,23 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest { - public class TestCallbacks { - [OneTimeSetUp] - public void SetUp() { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() { - PythonEngine.Shutdown(); - } +namespace Python.EmbeddingTest; - [Test] - public void TestNoOverloadException() { - int passed = 0; - var aFunctionThatCallsIntoPython = new Action(value => passed = value); - using (Py.GIL()) { - using dynamic callWith42 = PythonEngine.Eval("lambda f: f([42])"); - using var pyFunc = aFunctionThatCallsIntoPython.ToPython(); - var error = Assert.Throws(() => callWith42(pyFunc)); - Assert.AreEqual("TypeError", error.Type.Name); - string expectedArgTypes = "()"; - StringAssert.EndsWith(expectedArgTypes, error.Message); - error.Traceback.Dispose(); - } +public class TestCallbacks : BaseFixture +{ + [Test] + public void TestNoOverloadException() + { + int passed = 0; + var aFunctionThatCallsIntoPython = new Action(value => passed = value); + using (Py.GIL()) { + using dynamic callWith42 = PythonEngine.Eval("lambda f: f([42])"); + using var pyFunc = aFunctionThatCallsIntoPython.ToPython(); + var error = Assert.Throws(() => callWith42(pyFunc)); + Assert.AreEqual("TypeError", error.Type.Name); + string expectedArgTypes = "()"; + StringAssert.EndsWith(expectedArgTypes, error.Message); + error.Traceback.Dispose(); } } } diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 0686d528b..7570d9498 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -9,206 +9,193 @@ using PyRuntime = Python.Runtime.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestConverter : BaseFixture { - public class TestConverter + static readonly Type[] _numTypes = new Type[] { - static readonly Type[] _numTypes = new Type[] - { - typeof(short), - typeof(ushort), - typeof(int), - typeof(uint), - typeof(long), - typeof(ulong) - }; + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong) + }; + + [Test] + public void TestConvertSingleToManaged( + [Values(float.PositiveInfinity, float.NegativeInfinity, float.MinValue, float.MaxValue, float.NaN, + float.Epsilon)] float testValue) + { + var pyFloat = new PyFloat(testValue); - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + object convertedValue; + var converted = Converter.ToManaged(pyFloat, typeof(float), out convertedValue, false); - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + Assert.IsTrue(converted); + Assert.IsTrue(((float) convertedValue).Equals(testValue)); + } - [Test] - public void TestConvertSingleToManaged( - [Values(float.PositiveInfinity, float.NegativeInfinity, float.MinValue, float.MaxValue, float.NaN, - float.Epsilon)] float testValue) - { - var pyFloat = new PyFloat(testValue); + [Test] + public void TestConvertDoubleToManaged( + [Values(double.PositiveInfinity, double.NegativeInfinity, double.MinValue, double.MaxValue, double.NaN, + double.Epsilon)] double testValue) + { + var pyFloat = new PyFloat(testValue); - object convertedValue; - var converted = Converter.ToManaged(pyFloat, typeof(float), out convertedValue, false); + object convertedValue; + var converted = Converter.ToManaged(pyFloat, typeof(double), out convertedValue, false); - Assert.IsTrue(converted); - Assert.IsTrue(((float) convertedValue).Equals(testValue)); - } + Assert.IsTrue(converted); + Assert.IsTrue(((double) convertedValue).Equals(testValue)); + } - [Test] - public void TestConvertDoubleToManaged( - [Values(double.PositiveInfinity, double.NegativeInfinity, double.MinValue, double.MaxValue, double.NaN, - double.Epsilon)] double testValue) + [Test] + public void CovertTypeError() + { + Type[] floatTypes = new Type[] { - var pyFloat = new PyFloat(testValue); - - object convertedValue; - var converted = Converter.ToManaged(pyFloat, typeof(double), out convertedValue, false); - - Assert.IsTrue(converted); - Assert.IsTrue(((double) convertedValue).Equals(testValue)); - } - - [Test] - public void CovertTypeError() + typeof(float), + typeof(double) + }; + using (var s = new PyString("abc")) { - Type[] floatTypes = new Type[] + foreach (var type in _numTypes.Union(floatTypes)) { - typeof(float), - typeof(double) - }; - using (var s = new PyString("abc")) - { - foreach (var type in _numTypes.Union(floatTypes)) + object value; + try { - object value; - try - { - bool res = Converter.ToManaged(s, type, out value, true); - Assert.IsFalse(res); - var bo = Exceptions.ExceptionMatches(Exceptions.TypeError); - Assert.IsTrue(Exceptions.ExceptionMatches(Exceptions.TypeError) - || Exceptions.ExceptionMatches(Exceptions.ValueError)); - } - finally - { - Exceptions.Clear(); - } + bool res = Converter.ToManaged(s, type, out value, true); + Assert.IsFalse(res); + var bo = Exceptions.ExceptionMatches(Exceptions.TypeError); + Assert.IsTrue(Exceptions.ExceptionMatches(Exceptions.TypeError) + || Exceptions.ExceptionMatches(Exceptions.ValueError)); + } + finally + { + Exceptions.Clear(); } } } + } - [Test] - public void ConvertOverflow() + [Test] + public void ConvertOverflow() + { + using (var num = new PyInt(ulong.MaxValue)) { - using (var num = new PyInt(ulong.MaxValue)) + using var largeNum = PyRuntime.PyNumber_Add(num, num); + try { - using var largeNum = PyRuntime.PyNumber_Add(num, num); - try - { - object value; - foreach (var type in _numTypes) - { - bool res = Converter.ToManaged(largeNum.BorrowOrThrow(), type, out value, true); - Assert.IsFalse(res); - Assert.IsTrue(Exceptions.ExceptionMatches(Exceptions.OverflowError)); - Exceptions.Clear(); - } - } - finally + object value; + foreach (var type in _numTypes) { + bool res = Converter.ToManaged(largeNum.BorrowOrThrow(), type, out value, true); + Assert.IsFalse(res); + Assert.IsTrue(Exceptions.ExceptionMatches(Exceptions.OverflowError)); Exceptions.Clear(); } } + finally + { + Exceptions.Clear(); + } } + } - [Test] - public void NoImplicitConversionToBool() - { - var pyObj = new PyList(items: new[] { 1.ToPython(), 2.ToPython() }).ToPython(); - Assert.Throws(() => pyObj.As()); - } + [Test] + public void NoImplicitConversionToBool() + { + var pyObj = new PyList(items: new[] { 1.ToPython(), 2.ToPython() }).ToPython(); + Assert.Throws(() => pyObj.As()); + } - [Test] - public void ToNullable() - { - const int Const = 42; - var i = new PyInt(Const); - var ni = i.As(); - Assert.AreEqual(Const, ni); - } + [Test] + public void ToNullable() + { + const int Const = 42; + var i = new PyInt(Const); + var ni = i.As(); + Assert.AreEqual(Const, ni); + } - [Test] - public void BigIntExplicit() - { - BigInteger val = 42; - var i = new PyInt(val); - var ni = i.As(); - Assert.AreEqual(val, ni); - var nullable = i.As(); - Assert.AreEqual(val, nullable); - } + [Test] + public void BigIntExplicit() + { + BigInteger val = 42; + var i = new PyInt(val); + var ni = i.As(); + Assert.AreEqual(val, ni); + var nullable = i.As(); + Assert.AreEqual(val, nullable); + } - [Test] - public void PyIntImplicit() - { - var i = new PyInt(1); - var ni = (PyObject)i.As(); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(i, ni)); - } + [Test] + public void PyIntImplicit() + { + var i = new PyInt(1); + var ni = (PyObject)i.As(); + Assert.IsTrue(PythonReferenceComparer.Instance.Equals(i, ni)); + } - [Test] - public void ToPyList() - { - var list = new PyList(); - list.Append("hello".ToPython()); - list.Append("world".ToPython()); - var back = list.ToPython().As(); - Assert.AreEqual(list.Length(), back.Length()); - } + [Test] + public void ToPyList() + { + var list = new PyList(); + list.Append("hello".ToPython()); + list.Append("world".ToPython()); + var back = list.ToPython().As(); + Assert.AreEqual(list.Length(), back.Length()); + } - [Test] - public void RawListProxy() - { - var list = new List {"hello", "world"}; - var listProxy = PyObject.FromManagedObject(list); - var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy); - Assert.AreSame(list, clrObject.inst); - } + [Test] + public void RawListProxy() + { + var list = new List {"hello", "world"}; + var listProxy = PyObject.FromManagedObject(list); + var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy); + Assert.AreSame(list, clrObject.inst); + } - [Test] - public void RawPyObjectProxy() - { - var pyObject = "hello world!".ToPython(); - var pyObjectProxy = PyObject.FromManagedObject(pyObject); - var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy); - Assert.AreSame(pyObject, clrObject.inst); + [Test] + public void RawPyObjectProxy() + { + var pyObject = "hello world!".ToPython(); + var pyObjectProxy = PyObject.FromManagedObject(pyObject); + var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy); + Assert.AreSame(pyObject, clrObject.inst); #pragma warning disable CS0612 // Type or member is obsolete - const string handlePropertyName = nameof(PyObject.Handle); + const string handlePropertyName = nameof(PyObject.Handle); #pragma warning restore CS0612 // Type or member is obsolete - var proxiedHandle = pyObjectProxy.GetAttr(handlePropertyName).As(); - Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); - } + var proxiedHandle = pyObjectProxy.GetAttr(handlePropertyName).As(); + Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); + } - // regression for https://github.com/pythonnet/pythonnet/issues/451 - [Test] - public void CanGetListFromDerivedClass() - { - using var scope = Py.CreateScope(); - scope.Import(typeof(GetListImpl).Namespace, asname: "test"); - scope.Exec(@" + // regression for https://github.com/pythonnet/pythonnet/issues/451 + [Test] + public void CanGetListFromDerivedClass() + { + using var scope = Py.CreateScope(); + scope.Import(typeof(GetListImpl).Namespace, asname: "test"); + scope.Exec(@" class PyGetListImpl(test.GetListImpl): pass "); - var pyImpl = scope.Get("PyGetListImpl"); - dynamic inst = pyImpl.Invoke(); - List result = inst.GetList(); - CollectionAssert.AreEqual(new[] { "testing" }, result); - } + var pyImpl = scope.Get("PyGetListImpl"); + dynamic inst = pyImpl.Invoke(); + List result = inst.GetList(); + CollectionAssert.AreEqual(new[] { "testing" }, result); } +} - public interface IGetList - { - List GetList(); - } +public interface IGetList +{ + List GetList(); +} - public class GetListImpl : IGetList - { - public List GetList() => new() { "testing" }; - } +public class GetListImpl : IGetList +{ + public List GetList() => new() { "testing" }; } diff --git a/src/embed_tests/TestCustomMarshal.cs b/src/embed_tests/TestCustomMarshal.cs index 312863d0c..0b00147b3 100644 --- a/src/embed_tests/TestCustomMarshal.cs +++ b/src/embed_tests/TestCustomMarshal.cs @@ -1,35 +1,21 @@ -using System; using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestCustomMarshal : BaseFixture { - public class TestCustomMarshal + [Test] + public static void GetManagedStringTwice() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public static void GetManagedStringTwice() - { - const string expected = "FooBar"; + const string expected = "FooBar"; - using var op = Runtime.Runtime.PyString_FromString(expected); - string s1 = Runtime.Runtime.GetManagedString(op.BorrowOrThrow()); - string s2 = Runtime.Runtime.GetManagedString(op.Borrow()); + using var op = Runtime.Runtime.PyString_FromString(expected); + string s1 = Runtime.Runtime.GetManagedString(op.BorrowOrThrow()); + string s2 = Runtime.Runtime.GetManagedString(op.Borrow()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); - Assert.AreEqual(expected, s1); - Assert.AreEqual(expected, s2); - } + Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); + Assert.AreEqual(expected, s1); + Assert.AreEqual(expected, s2); } } diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index b748a2244..ded49b240 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -1,244 +1,244 @@ using NUnit.Framework; + using Python.Runtime; + using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestFinalizer { - public class TestFinalizer + private int _oldThreshold; + + [SetUp] + public void SetUp() { - private int _oldThreshold; + _oldThreshold = Finalizer.Instance.Threshold; + PythonEngine.Initialize(); + Exceptions.Clear(); + } + + [TearDown] + public void TearDown() + { + Finalizer.Instance.Threshold = _oldThreshold; + PythonEngine.Shutdown(); + } + + private static void FullGCCollect() + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } - [SetUp] - public void SetUp() + [Test] + [Obsolete("GC tests are not guaranteed")] + public void CollectBasicObject() + { + Assert.IsTrue(Finalizer.Instance.Enable); + + Finalizer.Instance.Threshold = 1; + bool called = false; + var objectCount = 0; + EventHandler handler = (s, e) => { - _oldThreshold = Finalizer.Instance.Threshold; - PythonEngine.Initialize(); - Exceptions.Clear(); - } + objectCount = e.ObjectCount; + called = true; + }; + + Assert.IsFalse(called, "The event handler was called before it was installed"); + Finalizer.Instance.BeforeCollect += handler; + + IntPtr pyObj = MakeAGarbage(out var shortWeak, out var longWeak); + FullGCCollect(); + // The object has been resurrected + Warn.If( + shortWeak.IsAlive, + "The referenced object is alive although it should have been collected", + shortWeak + ); + Assert.IsTrue( + longWeak.IsAlive, + "The reference object is not alive although it should still be", + longWeak + ); - [TearDown] - public void TearDown() { - Finalizer.Instance.Threshold = _oldThreshold; - PythonEngine.Shutdown(); + var garbage = Finalizer.Instance.GetCollectedObjects(); + Assert.NotZero(garbage.Count, "There should still be garbage around"); + Warn.Unless( + garbage.Contains(pyObj), + $"The {nameof(longWeak)} reference doesn't show up in the garbage list", + garbage + ); } - - private static void FullGCCollect() + try { - GC.Collect(); - GC.WaitForPendingFinalizers(); + Finalizer.Instance.Collect(); } - - [Test] - [Obsolete("GC tests are not guaranteed")] - public void CollectBasicObject() + finally { - Assert.IsTrue(Finalizer.Instance.Enable); - - Finalizer.Instance.Threshold = 1; - bool called = false; - var objectCount = 0; - EventHandler handler = (s, e) => - { - objectCount = e.ObjectCount; - called = true; - }; - - Assert.IsFalse(called, "The event handler was called before it was installed"); - Finalizer.Instance.BeforeCollect += handler; - - IntPtr pyObj = MakeAGarbage(out var shortWeak, out var longWeak); - FullGCCollect(); - // The object has been resurrected - Warn.If( - shortWeak.IsAlive, - "The referenced object is alive although it should have been collected", - shortWeak - ); - Assert.IsTrue( - longWeak.IsAlive, - "The reference object is not alive although it should still be", - longWeak - ); - - { - var garbage = Finalizer.Instance.GetCollectedObjects(); - Assert.NotZero(garbage.Count, "There should still be garbage around"); - Warn.Unless( - garbage.Contains(pyObj), - $"The {nameof(longWeak)} reference doesn't show up in the garbage list", - garbage - ); - } - try - { - Finalizer.Instance.Collect(); - } - finally - { - Finalizer.Instance.BeforeCollect -= handler; - } - Assert.IsTrue(called, "The event handler was not called during finalization"); - Assert.GreaterOrEqual(objectCount, 1); + Finalizer.Instance.BeforeCollect -= handler; } + Assert.IsTrue(called, "The event handler was not called during finalization"); + Assert.GreaterOrEqual(objectCount, 1); + } - [Test] - [Obsolete("GC tests are not guaranteed")] - public void CollectOnShutdown() + [Test] + [Obsolete("GC tests are not guaranteed")] + public void CollectOnShutdown() + { + IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); + FullGCCollect(); + Assert.IsFalse(shortWeak.IsAlive); + List garbage = Finalizer.Instance.GetCollectedObjects(); + Assert.IsNotEmpty(garbage, "The garbage object should be collected"); + Assert.IsTrue(garbage.Contains(op), + "Garbage should contains the collected object"); + + PythonEngine.Shutdown(); + garbage = Finalizer.Instance.GetCollectedObjects(); + + if (garbage.Count > 0) { - IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); - FullGCCollect(); - Assert.IsFalse(shortWeak.IsAlive); - List garbage = Finalizer.Instance.GetCollectedObjects(); - Assert.IsNotEmpty(garbage, "The garbage object should be collected"); - Assert.IsTrue(garbage.Contains(op), - "Garbage should contains the collected object"); - - PythonEngine.Shutdown(); - garbage = Finalizer.Instance.GetCollectedObjects(); - - if (garbage.Count > 0) + PythonEngine.Initialize(); + string objects = string.Join("\n", garbage.Select(ob => { - PythonEngine.Initialize(); - string objects = string.Join("\n", garbage.Select(ob => - { - var obj = new PyObject(new BorrowedReference(ob)); - return $"{obj} [{obj.GetPythonType()}@{obj.Handle}]"; - })); - Assert.Fail("Garbage is not empty:\n" + objects); - } + var obj = new PyObject(new BorrowedReference(ob)); + return $"{obj} [{obj.GetPythonType()}@{obj.Handle}]"; + })); + Assert.Fail("Garbage is not empty:\n" + objects); } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] // ensure lack of references to obj + [Obsolete("GC tests are not guaranteed")] + private static IntPtr MakeAGarbage(out WeakReference shortWeak, out WeakReference longWeak) + { + IntPtr handle = IntPtr.Zero; + WeakReference @short = null, @long = null; + // must create Python object in the thread where we have GIL + IntPtr val = Runtime.Runtime.PyLong_FromLongLong(1024).DangerousMoveToPointerOrNull(); + // must create temp object in a different thread to ensure it is not present + // when conservatively scanning stack for GC roots. + // see https://xamarin.github.io/bugzilla-archives/17/17593/bug.html + var garbageGen = new Thread(() => + { + var obj = new PyObject(val, skipCollect: true); + @short = new WeakReference(obj); + @long = new WeakReference(obj, true); + handle = obj.Handle; + }); + garbageGen.Start(); + Assert.IsTrue(garbageGen.Join(TimeSpan.FromSeconds(5)), "Garbage creation timed out"); + shortWeak = @short; + longWeak = @long; + return handle; + } - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] // ensure lack of references to obj - [Obsolete("GC tests are not guaranteed")] - private static IntPtr MakeAGarbage(out WeakReference shortWeak, out WeakReference longWeak) + private static long CompareWithFinalizerOn(PyObject pyCollect, bool enbale) + { + // Must larger than 512 bytes make sure Python use + string str = new string('1', 1024); + Finalizer.Instance.Enable = true; + FullGCCollect(); + FullGCCollect(); + pyCollect.Invoke(); + Finalizer.Instance.Collect(); + Finalizer.Instance.Enable = enbale; + + // Estimate unmanaged memory size + long before = Environment.WorkingSet - GC.GetTotalMemory(true); + for (int i = 0; i < 10000; i++) { - IntPtr handle = IntPtr.Zero; - WeakReference @short = null, @long = null; - // must create Python object in the thread where we have GIL - IntPtr val = Runtime.Runtime.PyLong_FromLongLong(1024).DangerousMoveToPointerOrNull(); - // must create temp object in a different thread to ensure it is not present - // when conservatively scanning stack for GC roots. - // see https://xamarin.github.io/bugzilla-archives/17/17593/bug.html - var garbageGen = new Thread(() => - { - var obj = new PyObject(val, skipCollect: true); - @short = new WeakReference(obj); - @long = new WeakReference(obj, true); - handle = obj.Handle; - }); - garbageGen.Start(); - Assert.IsTrue(garbageGen.Join(TimeSpan.FromSeconds(5)), "Garbage creation timed out"); - shortWeak = @short; - longWeak = @long; - return handle; + // Memory will leak when disable Finalizer + new PyString(str); } - - private static long CompareWithFinalizerOn(PyObject pyCollect, bool enbale) + FullGCCollect(); + FullGCCollect(); + pyCollect.Invoke(); + if (enbale) { - // Must larger than 512 bytes make sure Python use - string str = new string('1', 1024); - Finalizer.Instance.Enable = true; - FullGCCollect(); - FullGCCollect(); - pyCollect.Invoke(); Finalizer.Instance.Collect(); - Finalizer.Instance.Enable = enbale; - - // Estimate unmanaged memory size - long before = Environment.WorkingSet - GC.GetTotalMemory(true); - for (int i = 0; i < 10000; i++) - { - // Memory will leak when disable Finalizer - new PyString(str); - } - FullGCCollect(); - FullGCCollect(); - pyCollect.Invoke(); - if (enbale) - { - Finalizer.Instance.Collect(); - } + } - FullGCCollect(); - FullGCCollect(); - long after = Environment.WorkingSet - GC.GetTotalMemory(true); - return after - before; + FullGCCollect(); + FullGCCollect(); + long after = Environment.WorkingSet - GC.GetTotalMemory(true); + return after - before; - } + } - /// - /// Because of two vms both have their memory manager, - /// this test only prove the finalizer has take effect. - /// - [Test] - [Ignore("Too many uncertainties, only manual on when debugging")] - public void SimpleTestMemory() + /// + /// Because of two vms both have their memory manager, + /// this test only prove the finalizer has take effect. + /// + [Test] + [Ignore("Too many uncertainties, only manual on when debugging")] + public void SimpleTestMemory() + { + bool oldState = Finalizer.Instance.Enable; + try { - bool oldState = Finalizer.Instance.Enable; - try + using (PyObject gcModule = PyModule.Import("gc")) + using (PyObject pyCollect = gcModule.GetAttr("collect")) { - using (PyObject gcModule = PyModule.Import("gc")) - using (PyObject pyCollect = gcModule.GetAttr("collect")) - { - long span1 = CompareWithFinalizerOn(pyCollect, false); - long span2 = CompareWithFinalizerOn(pyCollect, true); - Assert.Less(span2, span1); - } - } - finally - { - Finalizer.Instance.Enable = oldState; + long span1 = CompareWithFinalizerOn(pyCollect, false); + long span2 = CompareWithFinalizerOn(pyCollect, true); + Assert.Less(span2, span1); } } + finally + { + Finalizer.Instance.Enable = oldState; + } + } - [Test] - public void ValidateRefCount() + [Test] + public void ValidateRefCount() + { + if (!Finalizer.Instance.RefCountValidationEnabled) { - if (!Finalizer.Instance.RefCountValidationEnabled) - { - Assert.Ignore("Only run with FINALIZER_CHECK"); - } - IntPtr ptr = IntPtr.Zero; - bool called = false; - Finalizer.IncorrectRefCntHandler handler = (s, e) => - { - called = true; - Assert.AreEqual(ptr, e.Handle); - Assert.AreEqual(2, e.ImpactedObjects.Count); - // Fix for this test, don't do this on general environment + Assert.Ignore("Only run with FINALIZER_CHECK"); + } + IntPtr ptr = IntPtr.Zero; + bool called = false; + Finalizer.IncorrectRefCntHandler handler = (s, e) => + { + called = true; + Assert.AreEqual(ptr, e.Handle); + Assert.AreEqual(2, e.ImpactedObjects.Count); + // Fix for this test, don't do this on general environment #pragma warning disable CS0618 // Type or member is obsolete - Runtime.Runtime.XIncref(e.Reference); + Runtime.Runtime.XIncref(e.Reference); #pragma warning restore CS0618 // Type or member is obsolete - return false; - }; - Finalizer.Instance.IncorrectRefCntResolver += handler; - try - { - ptr = CreateStringGarbage(); - FullGCCollect(); - Assert.Throws(() => Finalizer.Instance.Collect()); - Assert.IsTrue(called); - } - finally - { - Finalizer.Instance.IncorrectRefCntResolver -= handler; - } + return false; + }; + Finalizer.Instance.IncorrectRefCntResolver += handler; + try + { + ptr = CreateStringGarbage(); + FullGCCollect(); + Assert.Throws(() => Finalizer.Instance.Collect()); + Assert.IsTrue(called); } - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] // ensure lack of references to s1 and s2 - private static IntPtr CreateStringGarbage() + finally { - PyString s1 = new PyString("test_string"); - // s2 steal a reference from s1 - IntPtr address = s1.Reference.DangerousGetAddress(); - PyString s2 = new (StolenReference.DangerousFromPointer(address)); - return address; + Finalizer.Instance.IncorrectRefCntResolver -= handler; } } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] // ensure lack of references to s1 and s2 + private static IntPtr CreateStringGarbage() + { + PyString s1 = new PyString("test_string"); + // s2 steal a reference from s1 + IntPtr address = s1.Reference.DangerousGetAddress(); + PyString s2 = new (StolenReference.DangerousFromPointer(address)); + return address; + } } diff --git a/src/embed_tests/TestGILState.cs b/src/embed_tests/TestGILState.cs index bf6f02dc6..1b121cdcf 100644 --- a/src/embed_tests/TestGILState.cs +++ b/src/embed_tests/TestGILState.cs @@ -1,33 +1,20 @@ -namespace Python.EmbeddingTest -{ - using NUnit.Framework; - using Python.Runtime; +using NUnit.Framework; +using Python.Runtime; - public class TestGILState - { - /// - /// Ensure, that calling multiple times is safe - /// - [Test] - public void CanDisposeMultipleTimes() - { - using (var gilState = Py.GIL()) - { - for(int i = 0; i < 50; i++) - gilState.Dispose(); - } - } +namespace Python.EmbeddingTest; - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() +public class TestGILState : BaseFixture +{ + /// + /// Ensure, that calling multiple times is safe + /// + [Test] + public void CanDisposeMultipleTimes() + { + using (var gilState = Py.GIL()) { - PythonEngine.Shutdown(); + for(int i = 0; i < 50; i++) + gilState.Dispose(); } } } diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs index 0a441c823..7ec9f43ff 100644 --- a/src/embed_tests/TestInstanceWrapping.cs +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -3,66 +3,53 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestInstanceWrapping : BaseFixture { - public class TestInstanceWrapping + // regression test for https://github.com/pythonnet/pythonnet/issues/811 + [Test] + public void OverloadResolution_UnknownToObject() { - [OneTimeSetUp] - public void SetUp() + var overloaded = new Overloaded(); + using (Py.GIL()) { - PythonEngine.Initialize(); - } + var o = overloaded.ToPython(); - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); + dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())"); + callWithSelf(o); + Assert.AreEqual(Overloaded.Object, overloaded.Value); } + } - // regression test for https://github.com/pythonnet/pythonnet/issues/811 - [Test] - public void OverloadResolution_UnknownToObject() - { - var overloaded = new Overloaded(); - using (Py.GIL()) - { - var o = overloaded.ToPython(); - - dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())"); - callWithSelf(o); - Assert.AreEqual(Overloaded.Object, overloaded.Value); - } - } - - [Test] - public void WeakRefIsNone_AfterObjectIsGone() - { - using var makeref = Py.Import("weakref").GetAttr("ref"); - var ub = new UriBuilder().ToPython(); - using var weakref = makeref.Invoke(ub); - ub.Dispose(); - Assert.IsTrue(weakref.Invoke().IsNone()); - } - - class Base {} - class Derived: Base { } - - class Overloaded: Derived - { - public int Value { get; set; } - public void IntOrStr(int arg) => this.Value = arg; - public void IntOrStr(string arg) => - this.Value = int.Parse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture); + [Test] + public void WeakRefIsNone_AfterObjectIsGone() + { + using var makeref = Py.Import("weakref").GetAttr("ref"); + var ub = new UriBuilder().ToPython(); + using var weakref = makeref.Invoke(ub); + ub.Dispose(); + Assert.IsTrue(weakref.Invoke().IsNone()); + } - public const int Object = 1; - public const int ConcreteClass = 2; - public void ObjOrClass(object _) => this.Value = Object; - public void ObjOrClass(Overloaded _) => this.Value = ConcreteClass; + class Base {} + class Derived: Base { } - public const int Base = ConcreteClass + 1; - public const int Derived = Base + 1; - public void BaseOrDerived(Base _) => this.Value = Base; - public void BaseOrDerived(Derived _) => this.Value = Derived; - } + class Overloaded: Derived + { + public int Value { get; set; } + public void IntOrStr(int arg) => this.Value = arg; + public void IntOrStr(string arg) => + this.Value = int.Parse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture); + + public const int Object = 1; + public const int ConcreteClass = 2; + public void ObjOrClass(object _) => this.Value = Object; + public void ObjOrClass(Overloaded _) => this.Value = ConcreteClass; + + public const int Base = ConcreteClass + 1; + public const int Derived = Base + 1; + public void BaseOrDerived(Base _) => this.Value = Base; + public void BaseOrDerived(Derived _) => this.Value = Derived; } } diff --git a/src/embed_tests/TestInterrupt.cs b/src/embed_tests/TestInterrupt.cs index e6546adb2..7abf177cd 100644 --- a/src/embed_tests/TestInterrupt.cs +++ b/src/embed_tests/TestInterrupt.cs @@ -7,65 +7,64 @@ using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestInterrupt : BaseFixture { - public class TestInterrupt + PyObject threading; + + [OneTimeSetUp] + public void SetUp() { - PyObject threading; - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - // workaround for assert tlock.locked() warning - threading = Py.Import("threading"); - } + // workaround for assert tlock.locked() warning + threading = Py.Import("threading"); + } - [OneTimeTearDown] - public void Dispose() - { - threading.Dispose(); - PythonEngine.Shutdown(); - } + [OneTimeTearDown] + public void Dispose() + { + threading.Dispose(); + } - [Test] - public void PythonThreadIDStable() + [Test] + public void PythonThreadIDStable() + { + long pythonThreadID = 0; + long pythonThreadID2 = 0; + var asyncCall = Task.Factory.StartNew(() => { - long pythonThreadID = 0; - long pythonThreadID2 = 0; - var asyncCall = Task.Factory.StartNew(() => - { - using (Py.GIL()) - { - Interlocked.Exchange(ref pythonThreadID, (long)PythonEngine.GetPythonThreadID()); - Interlocked.Exchange(ref pythonThreadID2, (long)PythonEngine.GetPythonThreadID()); - } - }); - - var timeout = Stopwatch.StartNew(); - - IntPtr threadState = PythonEngine.BeginAllowThreads(); - while (Interlocked.Read(ref pythonThreadID) == 0 || Interlocked.Read(ref pythonThreadID2) == 0) + using (Py.GIL()) { - Assert.Less(timeout.Elapsed, TimeSpan.FromSeconds(5), "thread IDs were not assigned in time"); + Interlocked.Exchange(ref pythonThreadID, (long)PythonEngine.GetPythonThreadID()); + Interlocked.Exchange(ref pythonThreadID2, (long)PythonEngine.GetPythonThreadID()); } - PythonEngine.EndAllowThreads(threadState); + }); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread has not finished in time"); + var timeout = Stopwatch.StartNew(); - Assert.AreEqual(pythonThreadID, pythonThreadID2); - Assert.NotZero(pythonThreadID); + IntPtr threadState = PythonEngine.BeginAllowThreads(); + while (Interlocked.Read(ref pythonThreadID) == 0 || Interlocked.Read(ref pythonThreadID2) == 0) + { + Assert.Less(timeout.Elapsed, TimeSpan.FromSeconds(5), "thread IDs were not assigned in time"); } + PythonEngine.EndAllowThreads(threadState); + + Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread has not finished in time"); - [Test] - public void InterruptTest() + Assert.AreEqual(pythonThreadID, pythonThreadID2); + Assert.NotZero(pythonThreadID); + } + + [Test] + public void InterruptTest() + { + long pythonThreadID = 0; + var asyncCall = Task.Factory.StartNew(() => { - long pythonThreadID = 0; - var asyncCall = Task.Factory.StartNew(() => + using (Py.GIL()) { - using (Py.GIL()) - { - Interlocked.Exchange(ref pythonThreadID, (long)PythonEngine.GetPythonThreadID()); - return PythonEngine.RunSimpleString(@" + Interlocked.Exchange(ref pythonThreadID, (long)PythonEngine.GetPythonThreadID()); + return PythonEngine.RunSimpleString(@" try: import time @@ -73,26 +72,25 @@ import time time.sleep(0.2) except KeyboardInterrupt: pass"); - } - }); + } + }); - var timeout = Stopwatch.StartNew(); + var timeout = Stopwatch.StartNew(); - IntPtr threadState = PythonEngine.BeginAllowThreads(); - while (Interlocked.Read(ref pythonThreadID) == 0) - { - Assert.Less(timeout.Elapsed, TimeSpan.FromSeconds(5), "thread ID was not assigned in time"); - } - PythonEngine.EndAllowThreads(threadState); + IntPtr threadState = PythonEngine.BeginAllowThreads(); + while (Interlocked.Read(ref pythonThreadID) == 0) + { + Assert.Less(timeout.Elapsed, TimeSpan.FromSeconds(5), "thread ID was not assigned in time"); + } + PythonEngine.EndAllowThreads(threadState); - int interruptReturnValue = PythonEngine.Interrupt((ulong)Interlocked.Read(ref pythonThreadID)); - Assert.AreEqual(1, interruptReturnValue); + int interruptReturnValue = PythonEngine.Interrupt((ulong)Interlocked.Read(ref pythonThreadID)); + Assert.AreEqual(1, interruptReturnValue); - threadState = PythonEngine.BeginAllowThreads(); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread was not interrupted in time"); - PythonEngine.EndAllowThreads(threadState); + threadState = PythonEngine.BeginAllowThreads(); + Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread was not interrupted in time"); + PythonEngine.EndAllowThreads(threadState); - Assert.AreEqual(0, asyncCall.Result); - } + Assert.AreEqual(0, asyncCall.Result); } } diff --git a/src/embed_tests/TestNamedArguments.cs b/src/embed_tests/TestNamedArguments.cs index c86302038..ecd0ae7cd 100644 --- a/src/embed_tests/TestNamedArguments.cs +++ b/src/embed_tests/TestNamedArguments.cs @@ -2,54 +2,42 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestNamedArguments : BaseFixture { - public class TestNamedArguments + /// + /// Test named arguments support through Py.kw method + /// + [Test] + public void TestKeywordArgs() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - /// - /// Test named arguments support through Py.kw method - /// - [Test] - public void TestKeywordArgs() - { - dynamic a = CreateTestClass(); - var result = (int)a.Test3(2, Py.kw("a4", 8)); + dynamic a = CreateTestClass(); + var result = (int)a.Test3(2, Py.kw("a4", 8)); - Assert.AreEqual(12, result); - } + Assert.AreEqual(12, result); + } - /// - /// Test keyword arguments with .net named arguments - /// - [Test] - public void TestNamedArgs() - { - dynamic a = CreateTestClass(); - var result = (int)a.Test3(2, a4: 8); + /// + /// Test keyword arguments with .net named arguments + /// + [Test] + public void TestNamedArgs() + { + dynamic a = CreateTestClass(); + var result = (int)a.Test3(2, a4: 8); - Assert.AreEqual(12, result); - } + Assert.AreEqual(12, result); + } - private static PyObject CreateTestClass() - { - var locals = new PyDict(); + private static PyObject CreateTestClass() + { + var locals = new PyDict(); - PythonEngine.Exec(@" + PythonEngine.Exec(@" class cmTest3: def Test3(self, a1 = 1, a2 = 1, a3 = 1, a4 = 1): return a1 + a2 + a3 + a4 @@ -57,8 +45,7 @@ def Test3(self, a1 = 1, a2 = 1, a3 = 1, a4 = 1): a = cmTest3() ", null, locals); - return locals.GetItem("a"); - } - + return locals.GetItem("a"); } + } diff --git a/src/embed_tests/TestNativeTypeOffset.cs b/src/embed_tests/TestNativeTypeOffset.cs index d692c24e6..ff3a72baa 100644 --- a/src/embed_tests/TestNativeTypeOffset.cs +++ b/src/embed_tests/TestNativeTypeOffset.cs @@ -1,45 +1,25 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - using NUnit.Framework; - using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestNativeTypeOffset : BaseFixture { - public class TestNativeTypeOffset + /// + /// Tests that installation has generated code for NativeTypeOffset and that it can be loaded. + /// + [Test] + public void LoadNativeTypeOffsetClass() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - /// - /// Tests that installation has generated code for NativeTypeOffset and that it can be loaded. - /// - [Test] - public void LoadNativeTypeOffsetClass() + PyObject sys = Py.Import("sys"); + // We can safely ignore the "m" abi flag + var abiflags = sys.HasAttr("abiflags") ? sys.GetAttr("abiflags").ToString() : ""; + abiflags = abiflags.Replace("m", ""); + if (!string.IsNullOrEmpty(abiflags)) { - PyObject sys = Py.Import("sys"); - // We can safely ignore the "m" abi flag - var abiflags = sys.HasAttr("abiflags") ? sys.GetAttr("abiflags").ToString() : ""; - abiflags = abiflags.Replace("m", ""); - if (!string.IsNullOrEmpty(abiflags)) - { - string typeName = "Python.Runtime.NativeTypeOffset, Python.Runtime"; - Assert.NotNull(Type.GetType(typeName), $"{typeName} does not exist and sys.abiflags={abiflags}"); - } + string typeName = "Python.Runtime.NativeTypeOffset, Python.Runtime"; + Assert.NotNull(Type.GetType(typeName), $"{typeName} does not exist and sys.abiflags={abiflags}"); } } } diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index a5713274a..34df538ba 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -4,279 +4,266 @@ using Python.Runtime.Codecs; using System; -using System.Linq; using System.Reflection; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestOperator : BaseFixture { - public class TestOperator + public class OperableObject { - [OneTimeSetUp] - public void SetUp() + public int Num { get; set; } + + public override int GetHashCode() { - PythonEngine.Initialize(); + return unchecked(159832395 + Num.GetHashCode()); } - [OneTimeTearDown] - public void Dispose() + public override bool Equals(object obj) { - PythonEngine.Shutdown(); + return obj is OperableObject @object && + Num == @object.Num; } - public class OperableObject + public OperableObject(int num) { - public int Num { get; set; } - - public override int GetHashCode() - { - return unchecked(159832395 + Num.GetHashCode()); - } - - public override bool Equals(object obj) - { - return obj is OperableObject @object && - Num == @object.Num; - } - - public OperableObject(int num) - { - Num = num; - } - - public static OperableObject operator ~(OperableObject a) - { - return new OperableObject(~a.Num); - } + Num = num; + } - public static OperableObject operator +(OperableObject a) - { - return new OperableObject(+a.Num); - } + public static OperableObject operator ~(OperableObject a) + { + return new OperableObject(~a.Num); + } - public static OperableObject operator -(OperableObject a) - { - return new OperableObject(-a.Num); - } + public static OperableObject operator +(OperableObject a) + { + return new OperableObject(+a.Num); + } - public static OperableObject operator +(int a, OperableObject b) - { - return new OperableObject(a + b.Num); - } - public static OperableObject operator +(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num + b.Num); - } - public static OperableObject operator +(OperableObject a, int b) - { - return new OperableObject(a.Num + b); - } + public static OperableObject operator -(OperableObject a) + { + return new OperableObject(-a.Num); + } - public static OperableObject operator -(int a, OperableObject b) - { - return new OperableObject(a - b.Num); - } - public static OperableObject operator -(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num - b.Num); - } - public static OperableObject operator -(OperableObject a, int b) - { - return new OperableObject(a.Num - b); - } + public static OperableObject operator +(int a, OperableObject b) + { + return new OperableObject(a + b.Num); + } + public static OperableObject operator +(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num + b.Num); + } + public static OperableObject operator +(OperableObject a, int b) + { + return new OperableObject(a.Num + b); + } - public static OperableObject operator *(int a, OperableObject b) - { - return new OperableObject(a * b.Num); - } - public static OperableObject operator *(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num * b.Num); - } - public static OperableObject operator *(OperableObject a, int b) - { - return new OperableObject(a.Num * b); - } + public static OperableObject operator -(int a, OperableObject b) + { + return new OperableObject(a - b.Num); + } + public static OperableObject operator -(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num - b.Num); + } + public static OperableObject operator -(OperableObject a, int b) + { + return new OperableObject(a.Num - b); + } - public static OperableObject operator /(int a, OperableObject b) - { - return new OperableObject(a / b.Num); - } - public static OperableObject operator /(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num / b.Num); - } - public static OperableObject operator /(OperableObject a, int b) - { - return new OperableObject(a.Num / b); - } + public static OperableObject operator *(int a, OperableObject b) + { + return new OperableObject(a * b.Num); + } + public static OperableObject operator *(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num * b.Num); + } + public static OperableObject operator *(OperableObject a, int b) + { + return new OperableObject(a.Num * b); + } - public static OperableObject operator %(int a, OperableObject b) - { - return new OperableObject(a % b.Num); - } - public static OperableObject operator %(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num % b.Num); - } - public static OperableObject operator %(OperableObject a, int b) - { - return new OperableObject(a.Num % b); - } + public static OperableObject operator /(int a, OperableObject b) + { + return new OperableObject(a / b.Num); + } + public static OperableObject operator /(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num / b.Num); + } + public static OperableObject operator /(OperableObject a, int b) + { + return new OperableObject(a.Num / b); + } - public static OperableObject operator &(int a, OperableObject b) - { - return new OperableObject(a & b.Num); - } - public static OperableObject operator &(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num & b.Num); - } - public static OperableObject operator &(OperableObject a, int b) - { - return new OperableObject(a.Num & b); - } + public static OperableObject operator %(int a, OperableObject b) + { + return new OperableObject(a % b.Num); + } + public static OperableObject operator %(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num % b.Num); + } + public static OperableObject operator %(OperableObject a, int b) + { + return new OperableObject(a.Num % b); + } - public static OperableObject operator |(int a, OperableObject b) - { - return new OperableObject(a | b.Num); - } - public static OperableObject operator |(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num | b.Num); - } - public static OperableObject operator |(OperableObject a, int b) - { - return new OperableObject(a.Num | b); - } + public static OperableObject operator &(int a, OperableObject b) + { + return new OperableObject(a & b.Num); + } + public static OperableObject operator &(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num & b.Num); + } + public static OperableObject operator &(OperableObject a, int b) + { + return new OperableObject(a.Num & b); + } - public static OperableObject operator ^(int a, OperableObject b) - { - return new OperableObject(a ^ b.Num); - } - public static OperableObject operator ^(OperableObject a, OperableObject b) - { - return new OperableObject(a.Num ^ b.Num); - } - public static OperableObject operator ^(OperableObject a, int b) - { - return new OperableObject(a.Num ^ b); - } + public static OperableObject operator |(int a, OperableObject b) + { + return new OperableObject(a | b.Num); + } + public static OperableObject operator |(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num | b.Num); + } + public static OperableObject operator |(OperableObject a, int b) + { + return new OperableObject(a.Num | b); + } - public static bool operator ==(int a, OperableObject b) - { - return (a == b.Num); - } - public static bool operator ==(OperableObject a, OperableObject b) - { - return (a.Num == b.Num); - } - public static bool operator ==(OperableObject a, int b) - { - return (a.Num == b); - } + public static OperableObject operator ^(int a, OperableObject b) + { + return new OperableObject(a ^ b.Num); + } + public static OperableObject operator ^(OperableObject a, OperableObject b) + { + return new OperableObject(a.Num ^ b.Num); + } + public static OperableObject operator ^(OperableObject a, int b) + { + return new OperableObject(a.Num ^ b); + } - public static bool operator !=(int a, OperableObject b) - { - return (a != b.Num); - } - public static bool operator !=(OperableObject a, OperableObject b) - { - return (a.Num != b.Num); - } - public static bool operator !=(OperableObject a, int b) - { - return (a.Num != b); - } + public static bool operator ==(int a, OperableObject b) + { + return (a == b.Num); + } + public static bool operator ==(OperableObject a, OperableObject b) + { + return (a.Num == b.Num); + } + public static bool operator ==(OperableObject a, int b) + { + return (a.Num == b); + } - public static bool operator <=(int a, OperableObject b) - { - return (a <= b.Num); - } - public static bool operator <=(OperableObject a, OperableObject b) - { - return (a.Num <= b.Num); - } - public static bool operator <=(OperableObject a, int b) - { - return (a.Num <= b); - } + public static bool operator !=(int a, OperableObject b) + { + return (a != b.Num); + } + public static bool operator !=(OperableObject a, OperableObject b) + { + return (a.Num != b.Num); + } + public static bool operator !=(OperableObject a, int b) + { + return (a.Num != b); + } - public static bool operator >=(int a, OperableObject b) - { - return (a >= b.Num); - } - public static bool operator >=(OperableObject a, OperableObject b) - { - return (a.Num >= b.Num); - } - public static bool operator >=(OperableObject a, int b) - { - return (a.Num >= b); - } + public static bool operator <=(int a, OperableObject b) + { + return (a <= b.Num); + } + public static bool operator <=(OperableObject a, OperableObject b) + { + return (a.Num <= b.Num); + } + public static bool operator <=(OperableObject a, int b) + { + return (a.Num <= b); + } - public static bool operator >=(OperableObject a, (int, int) b) - { - using (Py.GIL()) - { - int bNum = b.Item1; - return a.Num >= bNum; - } - } - public static bool operator <=(OperableObject a, (int, int) b) - { - using (Py.GIL()) - { - int bNum = b.Item1; - return a.Num <= bNum; - } - } + public static bool operator >=(int a, OperableObject b) + { + return (a >= b.Num); + } + public static bool operator >=(OperableObject a, OperableObject b) + { + return (a.Num >= b.Num); + } + public static bool operator >=(OperableObject a, int b) + { + return (a.Num >= b); + } - public static bool operator <(int a, OperableObject b) - { - return (a < b.Num); - } - public static bool operator <(OperableObject a, OperableObject b) + public static bool operator >=(OperableObject a, (int, int) b) + { + using (Py.GIL()) { - return (a.Num < b.Num); + int bNum = b.Item1; + return a.Num >= bNum; } - public static bool operator <(OperableObject a, int b) + } + public static bool operator <=(OperableObject a, (int, int) b) + { + using (Py.GIL()) { - return (a.Num < b); + int bNum = b.Item1; + return a.Num <= bNum; } + } - public static bool operator >(int a, OperableObject b) - { - return (a > b.Num); - } - public static bool operator >(OperableObject a, OperableObject b) - { - return (a.Num > b.Num); - } - public static bool operator >(OperableObject a, int b) - { - return (a.Num > b); - } + public static bool operator <(int a, OperableObject b) + { + return (a < b.Num); + } + public static bool operator <(OperableObject a, OperableObject b) + { + return (a.Num < b.Num); + } + public static bool operator <(OperableObject a, int b) + { + return (a.Num < b); + } - public static OperableObject operator <<(OperableObject a, int offset) - { - return new OperableObject(a.Num << offset); - } + public static bool operator >(int a, OperableObject b) + { + return (a > b.Num); + } + public static bool operator >(OperableObject a, OperableObject b) + { + return (a.Num > b.Num); + } + public static bool operator >(OperableObject a, int b) + { + return (a.Num > b); + } - public static OperableObject operator >>(OperableObject a, int offset) - { - return new OperableObject(a.Num >> offset); - } + public static OperableObject operator <<(OperableObject a, int offset) + { + return new OperableObject(a.Num << offset); } - [Test] - public void SymmetricalOperatorOverloads() + public static OperableObject operator >>(OperableObject a, int offset) { - string name = string.Format("{0}.{1}", - typeof(OperableObject).DeclaringType.Name, - typeof(OperableObject).Name); - string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + return new OperableObject(a.Num >> offset); + } + } - PythonEngine.Exec($@" + [Test] + public void SymmetricalOperatorOverloads() + { + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + + PythonEngine.Exec($@" from {module} import * cls = {name} a = cls(-2) @@ -333,43 +320,43 @@ public void SymmetricalOperatorOverloads() c = a > b assert c == (a.Num > b.Num) "); - } + } - [Test] - public void EnumOperator() - { - PythonEngine.Exec($@" + [Test] + public void EnumOperator() + { + PythonEngine.Exec($@" from System.IO import FileAccess c = FileAccess.Read | FileAccess.Write"); - } + } - [Test] - public void OperatorOverloadMissingArgument() - { - string name = string.Format("{0}.{1}", - typeof(OperableObject).DeclaringType.Name, - typeof(OperableObject).Name); - string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + [Test] + public void OperatorOverloadMissingArgument() + { + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; - Assert.Throws(() => - PythonEngine.Exec($@" + Assert.Throws(() => + PythonEngine.Exec($@" from {module} import * cls = {name} a = cls(2) b = cls(10) a.op_Addition() ")); - } + } - [Test] - public void ForwardOperatorOverloads() - { - string name = string.Format("{0}.{1}", - typeof(OperableObject).DeclaringType.Name, - typeof(OperableObject).Name); - string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + [Test] + public void ForwardOperatorOverloads() + { + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; - PythonEngine.Exec($@" + PythonEngine.Exec($@" from {module} import * cls = {name} a = cls(2) @@ -416,17 +403,17 @@ public void ForwardOperatorOverloads() c = a > b assert c == (a.Num > b) "); - } + } - [Test] - public void TupleComparisonOperatorOverloads() - { - TupleCodec.Register(); - string name = string.Format("{0}.{1}", - typeof(OperableObject).DeclaringType.Name, - typeof(OperableObject).Name); - string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; - PythonEngine.Exec($@" + [Test] + public void TupleComparisonOperatorOverloads() + { + TupleCodec.Register(); + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + PythonEngine.Exec($@" from {module} import * cls = {name} a = cls(2) @@ -444,18 +431,18 @@ public void TupleComparisonOperatorOverloads() c = b <= a assert c == (b[0] <= a.Num) "); - } + } - [Test] - public void ReverseOperatorOverloads() - { - string name = string.Format("{0}.{1}", - typeof(OperableObject).DeclaringType.Name, - typeof(OperableObject).Name); - string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + [Test] + public void ReverseOperatorOverloads() + { + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; - PythonEngine.Exec($@" + PythonEngine.Exec($@" from {module} import * cls = {name} a = 2 @@ -504,16 +491,16 @@ public void ReverseOperatorOverloads() assert c == (a > b.Num) "); - } - [Test] - public void ShiftOperatorOverloads() - { - string name = string.Format("{0}.{1}", - typeof(OperableObject).DeclaringType.Name, - typeof(OperableObject).Name); - string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + } + [Test] + public void ShiftOperatorOverloads() + { + string name = string.Format("{0}.{1}", + typeof(OperableObject).DeclaringType.Name, + typeof(OperableObject).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; - PythonEngine.Exec($@" + PythonEngine.Exec($@" from {module} import * cls = {name} a = cls(2) @@ -525,6 +512,5 @@ public void ShiftOperatorOverloads() c = a >> b.Num assert c.Num == a.Num >> b.Num "); - } } } diff --git a/src/embed_tests/TestPyBuffer.cs b/src/embed_tests/TestPyBuffer.cs index 1b4e28d12..06bd529b0 100644 --- a/src/embed_tests/TestPyBuffer.cs +++ b/src/embed_tests/TestPyBuffer.cs @@ -5,156 +5,149 @@ using Python.Runtime; using Python.Runtime.Codecs; -namespace Python.EmbeddingTest { - class TestPyBuffer - { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - TupleCodec.Register(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void TestBufferWrite() - { - string bufferTestString = "hello world! !$%&/()=?"; - string bufferTestString2 = "h llo world! !$%&/()=?"; +namespace Python.EmbeddingTest; - using var _ = Py.GIL(); +public class TestPyBuffer : BaseFixture +{ + [OneTimeSetUp] + public void SetUp() + { + TupleCodec.Register(); + } - using var pythonArray = ByteArrayFromAsciiString(bufferTestString); + [Test] + public void TestBufferWrite() + { + string bufferTestString = "hello world! !$%&/()=?"; + string bufferTestString2 = "h llo world! !$%&/()=?"; - using (PyBuffer buf = pythonArray.GetBuffer(PyBUF.WRITABLE)) - { - byte[] managedArray = { (byte)' ' }; - buf.Write(managedArray, 0, managedArray.Length, 1); - } + using var _ = Py.GIL(); - string result = pythonArray.InvokeMethod("decode", "utf-8".ToPython()).As(); - Assert.IsTrue(result == bufferTestString2); - } + using var pythonArray = ByteArrayFromAsciiString(bufferTestString); - [Test] - public void TestBufferRead() + using (PyBuffer buf = pythonArray.GetBuffer(PyBUF.WRITABLE)) { - string bufferTestString = "hello world! !$%&/()=?"; + byte[] managedArray = { (byte)' ' }; + buf.Write(managedArray, 0, managedArray.Length, 1); + } - using var _ = Py.GIL(); + string result = pythonArray.InvokeMethod("decode", "utf-8".ToPython()).As(); + Assert.IsTrue(result == bufferTestString2); + } - using var pythonArray = ByteArrayFromAsciiString(bufferTestString); - byte[] managedArray = new byte[bufferTestString.Length]; + [Test] + public void TestBufferRead() + { + string bufferTestString = "hello world! !$%&/()=?"; - using (PyBuffer buf = pythonArray.GetBuffer()) - { - managedArray[0] = (byte)' '; - buf.Read(managedArray, 1, managedArray.Length - 1, 1); - } + using var _ = Py.GIL(); - string result = new UTF8Encoding().GetString(managedArray); - Assert.IsTrue(result == " " + bufferTestString.Substring(1)); - } + using var pythonArray = ByteArrayFromAsciiString(bufferTestString); + byte[] managedArray = new byte[bufferTestString.Length]; - [Test] - public void ArrayHasBuffer() + using (PyBuffer buf = pythonArray.GetBuffer()) { - var array = new[,] {{1, 2}, {3,4}}; - var memoryView = PythonEngine.Eval("memoryview"); - var mem = memoryView.Invoke(array.ToPython()); - Assert.AreEqual(1, mem[(0, 0).ToPython()].As()); - Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As()); + managedArray[0] = (byte)' '; + buf.Read(managedArray, 1, managedArray.Length - 1, 1); } - [Test] - public void RefCount() - { - using var _ = Py.GIL(); - using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); + string result = new UTF8Encoding().GetString(managedArray); + Assert.IsTrue(result == " " + bufferTestString.Substring(1)); + } - Assert.AreEqual(1, arr.Refcount); + [Test] + public void ArrayHasBuffer() + { + var array = new[,] {{1, 2}, {3,4}}; + var memoryView = PythonEngine.Eval("memoryview"); + var mem = memoryView.Invoke(array.ToPython()); + Assert.AreEqual(1, mem[(0, 0).ToPython()].As()); + Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As()); + } - using (PyBuffer buf = arr.GetBuffer()) - { - Assert.AreEqual(2, arr.Refcount); - } + [Test] + public void RefCount() + { + using var _ = Py.GIL(); + using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); - } + Assert.AreEqual(1, arr.Refcount); - [Test] - public void Finalization() + using (PyBuffer buf = arr.GetBuffer()) { - if (Type.GetType("Mono.Runtime") is not null) - { - Assert.Inconclusive("test unreliable in Mono"); - return; - } + Assert.AreEqual(2, arr.Refcount); + } - using var _ = Py.GIL(); - using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); + Assert.AreEqual(1, arr.Refcount); + } - Assert.AreEqual(1, arr.Refcount); + [Test] + public void Finalization() + { + if (Type.GetType("Mono.Runtime") is not null) + { + Assert.Inconclusive("test unreliable in Mono"); + return; + } - MakeBufAndLeak(arr); + using var _ = Py.GIL(); + using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Finalizer.Instance.Collect(); + Assert.AreEqual(1, arr.Refcount); - Assert.AreEqual(1, arr.Refcount); - } + MakeBufAndLeak(arr); - [Test] - public void MultidimensionalNumPyArray() - { - var ndarray = np.arange(24).reshape(1,2,3,4).T; - PyObject ndim = ndarray.ndim; - PyObject shape = ndarray.shape; - PyObject strides = ndarray.strides; - PyObject contiguous = ndarray.flags["C_CONTIGUOUS"]; + GC.Collect(); + GC.WaitForPendingFinalizers(); + Finalizer.Instance.Collect(); - using PyBuffer buf = ndarray.GetBuffer(PyBUF.STRIDED); + Assert.AreEqual(1, arr.Refcount); + } - Assert.Multiple(() => - { - Assert.That(buf.Dimensions, Is.EqualTo(ndim.As())); - Assert.That(buf.Shape, Is.EqualTo(shape.As())); - Assert.That(buf.Strides, Is.EqualTo(strides.As())); - Assert.That(buf.IsContiguous(BufferOrderStyle.C), Is.EqualTo(contiguous.As())); - }); - } + [Test] + public void MultidimensionalNumPyArray() + { + var ndarray = np.arange(24).reshape(1,2,3,4).T; + PyObject ndim = ndarray.ndim; + PyObject shape = ndarray.shape; + PyObject strides = ndarray.strides; + PyObject contiguous = ndarray.flags["C_CONTIGUOUS"]; - [MethodImpl(MethodImplOptions.NoInlining)] - static void MakeBufAndLeak(PyObject bufProvider) - { - PyBuffer buf = bufProvider.GetBuffer(); - } + using PyBuffer buf = ndarray.GetBuffer(PyBUF.STRIDED); - static PyObject ByteArrayFromAsciiString(string str) + Assert.Multiple(() => { - using var scope = Py.CreateScope(); - return Runtime.Runtime.PyByteArray_FromStringAndSize(str).MoveToPyObject(); - } + Assert.That(buf.Dimensions, Is.EqualTo(ndim.As())); + Assert.That(buf.Shape, Is.EqualTo(shape.As())); + Assert.That(buf.Strides, Is.EqualTo(strides.As())); + Assert.That(buf.IsContiguous(BufferOrderStyle.C), Is.EqualTo(contiguous.As())); + }); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void MakeBufAndLeak(PyObject bufProvider) + { + PyBuffer buf = bufProvider.GetBuffer(); + } - dynamic np + static PyObject ByteArrayFromAsciiString(string str) + { + using var scope = Py.CreateScope(); + return Runtime.Runtime.PyByteArray_FromStringAndSize(str).MoveToPyObject(); + } + + dynamic np + { + get { - get + try + { + return Py.Import("numpy"); + } + catch (PythonException) { - try - { - return Py.Import("numpy"); - } - catch (PythonException) - { - Assert.Inconclusive("Numpy or dependency not installed"); - return null; - } + Assert.Inconclusive("Numpy or dependency not installed"); + return null; } } } diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index 36531cb6a..1cc971dbd 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -2,129 +2,116 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +/// +/// PyFloat implementation isn't complete, thus tests aren't complete. +/// +public class TestPyFloat : BaseFixture { - /// - /// PyFloat implementation isn't complete, thus tests aren't complete. - /// - public class TestPyFloat + [Test] + public void FloatCtor() + { + const float a = 4.5F; + var i = new PyFloat(a); + Assert.True(PyFloat.IsFloatType(i)); + // Assert.Assert.AreEqual(i, a.ToInt32()); + } + + [Test] + public void PyObjectCtorGood() + { + var i = new PyFloat(5); + var a = new PyFloat(i); + Assert.True(PyFloat.IsFloatType(a)); + // Assert.Assert.AreEqual(i, a.ToInt32()); + } + + [Test] + public void PyObjectCtorBad() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void FloatCtor() - { - const float a = 4.5F; - var i = new PyFloat(a); - Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); - } - - [Test] - public void PyObjectCtorGood() - { - var i = new PyFloat(5); - var a = new PyFloat(i); - Assert.True(PyFloat.IsFloatType(a)); - // Assert.Assert.AreEqual(i, a.ToInt32()); - } - - [Test] - public void PyObjectCtorBad() - { - var i = new PyString("Foo"); - PyFloat a = null; - - var ex = Assert.Throws(() => a = new PyFloat(i)); - - StringAssert.StartsWith("object is not a float", ex.Message); - Assert.IsNull(a); - } - - [Test] - public void DoubleCtor() - { - const double a = 4.5; - var i = new PyFloat(a); - Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); - } - - [Test] - public void StringIntCtor() - { - const string a = "5"; - var i = new PyFloat(a); - Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); - } - - [Test] - public void StringDoubleCtor() - { - const string a = "4.5"; - var i = new PyFloat(a); - Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); - } - - [Test] - public void StringBadCtor() - { - const string i = "Foo"; - PyFloat a = null; - - var ex = Assert.Throws(() => a = new PyFloat(i)); - - StringAssert.StartsWith("could not convert string to float", ex.Message); - Assert.IsNull(a); - } - - [Test] - public void IsFloatTrue() - { - const double a = 4.5; - var i = new PyFloat(a); - Assert.True(PyFloat.IsFloatType(i)); - } - - [Test] - public void IsFloatFalse() - { - var i = new PyString("Foo"); - Assert.False(PyFloat.IsFloatType(i)); - } - - [Test] - public void AsFloatGood() - { - const double a = 4.5; - var i = new PyFloat(a); - PyFloat s = PyFloat.AsFloat(i); - - Assert.True(PyFloat.IsFloatType(s)); - // Assert.Assert.AreEqual(i, a.ToInt32()); - } - - [Test] - public void AsFloatBad() - { - var s = new PyString("Foo"); - PyFloat a = null; - - var ex = Assert.Throws(() => a = PyFloat.AsFloat(s)); - StringAssert.StartsWith("could not convert string to float", ex.Message); - Assert.IsNull(a); - } + var i = new PyString("Foo"); + PyFloat a = null; + + var ex = Assert.Throws(() => a = new PyFloat(i)); + + StringAssert.StartsWith("object is not a float", ex.Message); + Assert.IsNull(a); + } + + [Test] + public void DoubleCtor() + { + const double a = 4.5; + var i = new PyFloat(a); + Assert.True(PyFloat.IsFloatType(i)); + // Assert.Assert.AreEqual(i, a.ToInt32()); + } + + [Test] + public void StringIntCtor() + { + const string a = "5"; + var i = new PyFloat(a); + Assert.True(PyFloat.IsFloatType(i)); + // Assert.Assert.AreEqual(i, a.ToInt32()); + } + + [Test] + public void StringDoubleCtor() + { + const string a = "4.5"; + var i = new PyFloat(a); + Assert.True(PyFloat.IsFloatType(i)); + // Assert.Assert.AreEqual(i, a.ToInt32()); + } + + [Test] + public void StringBadCtor() + { + const string i = "Foo"; + PyFloat a = null; + + var ex = Assert.Throws(() => a = new PyFloat(i)); + + StringAssert.StartsWith("could not convert string to float", ex.Message); + Assert.IsNull(a); + } + + [Test] + public void IsFloatTrue() + { + const double a = 4.5; + var i = new PyFloat(a); + Assert.True(PyFloat.IsFloatType(i)); + } + + [Test] + public void IsFloatFalse() + { + var i = new PyString("Foo"); + Assert.False(PyFloat.IsFloatType(i)); + } + + [Test] + public void AsFloatGood() + { + const double a = 4.5; + var i = new PyFloat(a); + PyFloat s = PyFloat.AsFloat(i); + + Assert.True(PyFloat.IsFloatType(s)); + // Assert.Assert.AreEqual(i, a.ToInt32()); + } + + [Test] + public void AsFloatBad() + { + var s = new PyString("Foo"); + PyFloat a = null; + + var ex = Assert.Throws(() => a = PyFloat.AsFloat(s)); + StringAssert.StartsWith("could not convert string to float", ex.Message); + Assert.IsNull(a); } } diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index c147e074b..837287b81 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -6,219 +6,206 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyInt : BaseFixture { - public class TestPyInt + [Test] + public void TestCtorInt() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + const int i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + [Test] + public void TestCtorUInt() + { + const uint i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorInt() - { - const int i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorLong() + { + const long i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorUInt() - { - const uint i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorULong() + { + const ulong i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorLong() - { - const long i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorShort() + { + const short i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorULong() - { - const ulong i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorUShort() + { + const ushort i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorShort() - { - const short i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorByte() + { + const byte i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorUShort() - { - const ushort i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorSByte() + { + const sbyte i = 5; + var a = new PyInt(i); + Assert.AreEqual(i, a.ToInt32()); + } - [Test] - public void TestCtorByte() - { - const byte i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorPyObject() + { + var i = new PyInt(5); + var a = new PyInt(i); + Assert.AreEqual(5, a.ToInt32()); + } - [Test] - public void TestCtorSByte() - { - const sbyte i = 5; - var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); - } + [Test] + public void TestCtorBadPyObject() + { + var i = new PyString("Foo"); + PyInt a = null; - [Test] - public void TestCtorPyObject() - { - var i = new PyInt(5); - var a = new PyInt(i); - Assert.AreEqual(5, a.ToInt32()); - } + var ex = Assert.Throws(() => a = new PyInt(i)); - [Test] - public void TestCtorBadPyObject() - { - var i = new PyString("Foo"); - PyInt a = null; + StringAssert.StartsWith("object is not an int", ex.Message); + Assert.IsNull(a); + } - var ex = Assert.Throws(() => a = new PyInt(i)); + [Test] + public void TestCtorString() + { + const string i = "5"; + var a = new PyInt(i); + Assert.AreEqual(5, a.ToInt32()); + } - StringAssert.StartsWith("object is not an int", ex.Message); - Assert.IsNull(a); - } + [Test] + public void TestCtorBadString() + { + const string i = "Foo"; + PyInt a = null; - [Test] - public void TestCtorString() - { - const string i = "5"; - var a = new PyInt(i); - Assert.AreEqual(5, a.ToInt32()); - } + var ex = Assert.Throws(() => a = new PyInt(i)); - [Test] - public void TestCtorBadString() - { - const string i = "Foo"; - PyInt a = null; + StringAssert.StartsWith("invalid literal for int", ex.Message); + Assert.IsNull(a); + } - var ex = Assert.Throws(() => a = new PyInt(i)); + [Test] + public void TestIsIntTypeTrue() + { + var i = new PyInt(5); + Assert.True(PyInt.IsIntType(i)); + } - StringAssert.StartsWith("invalid literal for int", ex.Message); - Assert.IsNull(a); - } + [Test] + public void TestIsIntTypeFalse() + { + var s = new PyString("Foo"); + Assert.False(PyInt.IsIntType(s)); + } - [Test] - public void TestIsIntTypeTrue() - { - var i = new PyInt(5); - Assert.True(PyInt.IsIntType(i)); - } + [Test] + public void TestAsIntGood() + { + var i = new PyInt(5); + var a = PyInt.AsInt(i); + Assert.AreEqual(5, a.ToInt32()); + } - [Test] - public void TestIsIntTypeFalse() - { - var s = new PyString("Foo"); - Assert.False(PyInt.IsIntType(s)); - } + [Test] + public void TestAsIntBad() + { + var s = new PyString("Foo"); + PyInt a = null; - [Test] - public void TestAsIntGood() - { - var i = new PyInt(5); - var a = PyInt.AsInt(i); - Assert.AreEqual(5, a.ToInt32()); - } + var ex = Assert.Throws(() => a = PyInt.AsInt(s)); + StringAssert.StartsWith("invalid literal for int", ex.Message); + Assert.IsNull(a); + } - [Test] - public void TestAsIntBad() - { - var s = new PyString("Foo"); - PyInt a = null; + [Test] + public void TestConvertToInt32() + { + var a = new PyInt(5); + Assert.IsInstanceOf(typeof(int), a.ToInt32()); + Assert.AreEqual(5, a.ToInt32()); + } - var ex = Assert.Throws(() => a = PyInt.AsInt(s)); - StringAssert.StartsWith("invalid literal for int", ex.Message); - Assert.IsNull(a); - } + [Test] + public void TestConvertToInt16() + { + var a = new PyInt(5); + Assert.IsInstanceOf(typeof(short), a.ToInt16()); + Assert.AreEqual(5, a.ToInt16()); + } - [Test] - public void TestConvertToInt32() - { - var a = new PyInt(5); - Assert.IsInstanceOf(typeof(int), a.ToInt32()); - Assert.AreEqual(5, a.ToInt32()); - } + [Test] + public void TestConvertToInt64() + { + long val = 5 + (long)int.MaxValue; + var a = new PyInt(val); + Assert.IsInstanceOf(typeof(long), a.ToInt64()); + Assert.AreEqual(val, a.ToInt64()); + } - [Test] - public void TestConvertToInt16() - { - var a = new PyInt(5); - Assert.IsInstanceOf(typeof(short), a.ToInt16()); - Assert.AreEqual(5, a.ToInt16()); - } + [Test] + public void ToBigInteger() + { + int[] simpleValues = + { + 0, 1, 2, + 0x10, + 0x79, + 0x80, + 0x81, + 0xFF, + 0x123, + 0x8000, + 0x1234, + 0x8001, + 0x4000, + 0xFF, + }; + simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray(); + + var expected = simpleValues.Select(v => new BigInteger(v)).ToArray(); + var actual = simpleValues.Select(v => new PyInt(v).ToBigInteger()).ToArray(); + + CollectionAssert.AreEqual(expected, actual); + } - [Test] - public void TestConvertToInt64() - { - long val = 5 + (long)int.MaxValue; - var a = new PyInt(val); - Assert.IsInstanceOf(typeof(long), a.ToInt64()); - Assert.AreEqual(val, a.ToInt64()); - } - - [Test] - public void ToBigInteger() - { - int[] simpleValues = - { - 0, 1, 2, - 0x10, - 0x79, - 0x80, - 0x81, - 0xFF, - 0x123, - 0x8000, - 0x1234, - 0x8001, - 0x4000, - 0xFF, - }; - simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray(); - - var expected = simpleValues.Select(v => new BigInteger(v)).ToArray(); - var actual = simpleValues.Select(v => new PyInt(v).ToBigInteger()).ToArray(); - - CollectionAssert.AreEqual(expected, actual); - } - - [Test] - public void ToBigIntegerLarge() - { - BigInteger val = BigInteger.Pow(2, 1024) + 3; - var pyInt = new PyInt(val); - Assert.AreEqual(val, pyInt.ToBigInteger()); - val = -val; - pyInt = new PyInt(val); - Assert.AreEqual(val, pyInt.ToBigInteger()); - } + [Test] + public void ToBigIntegerLarge() + { + BigInteger val = BigInteger.Pow(2, 1024) + 3; + var pyInt = new PyInt(val); + Assert.AreEqual(val, pyInt.ToBigInteger()); + val = -val; + pyInt = new PyInt(val); + Assert.AreEqual(val, pyInt.ToBigInteger()); } } diff --git a/src/embed_tests/TestPyIter.cs b/src/embed_tests/TestPyIter.cs index 7428da979..8805e96f4 100644 --- a/src/embed_tests/TestPyIter.cs +++ b/src/embed_tests/TestPyIter.cs @@ -1,37 +1,21 @@ using System.Linq; -using System.Text; - using NUnit.Framework; - using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyIter : BaseFixture { - class TestPyIter + [Test] + public void KeepOldObjects() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void KeepOldObjects() + using (Py.GIL()) + using (var testString = new PyString("hello world! !$%&/()=?")) { - using (Py.GIL()) - using (var testString = new PyString("hello world! !$%&/()=?")) - { - PyObject[] chars = testString.ToArray(); - Assert.IsTrue(chars.Length > 1); - string reconstructed = string.Concat(chars.Select(c => c.As())); - Assert.AreEqual(testString.As(), reconstructed); - } + PyObject[] chars = testString.ToArray(); + Assert.IsTrue(chars.Length > 1); + string reconstructed = string.Concat(chars.Select(c => c.As())); + Assert.AreEqual(testString.As(), reconstructed); } } } diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index eee129f2d..5ea00b4e0 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -3,170 +3,157 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyList : BaseFixture { - public class TestPyList + [Test] + public void TestStringIsListType() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + var s = new PyString("foo"); + Assert.False(PyList.IsListType(s)); + } - [Test] - public void TestStringIsListType() - { - var s = new PyString("foo"); - Assert.False(PyList.IsListType(s)); - } + [Test] + public void TestListIsListType() + { + var s = new PyList(); + Assert.True(PyList.IsListType(s)); + } - [Test] - public void TestListIsListType() - { - var s = new PyList(); - Assert.True(PyList.IsListType(s)); - } + [Test] + public void TestStringAsListType() + { + var i = new PyInt(5); + PyList t = null; - [Test] - public void TestStringAsListType() - { - var i = new PyInt(5); - PyList t = null; + var ex = Assert.Throws(() => t = PyList.AsList(i)); - var ex = Assert.Throws(() => t = PyList.AsList(i)); + Assert.AreEqual("'int' object is not iterable", ex.Message); + Assert.IsNull(t); + } - Assert.AreEqual("'int' object is not iterable", ex.Message); - Assert.IsNull(t); - } + [Test] + public void TestListAsListType() + { + var l = new PyList(); + PyList t = PyList.AsList(l); - [Test] - public void TestListAsListType() - { - var l = new PyList(); - PyList t = PyList.AsList(l); + Assert.IsNotNull(t); + Assert.IsInstanceOf(typeof(PyList), t); + } - Assert.IsNotNull(t); - Assert.IsInstanceOf(typeof(PyList), t); - } + [Test] + public void TestEmptyCtor() + { + var s = new PyList(); - [Test] - public void TestEmptyCtor() - { - var s = new PyList(); + Assert.IsInstanceOf(typeof(PyList), s); + Assert.AreEqual(0, s.Length()); + } - Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(0, s.Length()); - } + [Test] + public void TestPyObjectArrayCtor() + { + var ai = new PyObject[] {new PyInt(3), new PyInt(2), new PyInt(1) }; + var s = new PyList(ai); + + Assert.IsInstanceOf(typeof(PyList), s); + Assert.AreEqual(3, s.Length()); + Assert.AreEqual("3", s[0].ToString()); + Assert.AreEqual("2", s[1].ToString()); + Assert.AreEqual("1", s[2].ToString()); + } - [Test] - public void TestPyObjectArrayCtor() - { - var ai = new PyObject[] {new PyInt(3), new PyInt(2), new PyInt(1) }; - var s = new PyList(ai); - - Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("3", s[0].ToString()); - Assert.AreEqual("2", s[1].ToString()); - Assert.AreEqual("1", s[2].ToString()); - } + [Test] + public void TestPyObjectCtor() + { + var a = new PyList(); + var s = new PyList(a); - [Test] - public void TestPyObjectCtor() - { - var a = new PyList(); - var s = new PyList(a); + Assert.IsInstanceOf(typeof(PyList), s); + Assert.AreEqual(0, s.Length()); + } - Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(0, s.Length()); - } + [Test] + public void TestBadPyObjectCtor() + { + var i = new PyInt(5); + PyList t = null; - [Test] - public void TestBadPyObjectCtor() - { - var i = new PyInt(5); - PyList t = null; + var ex = Assert.Throws(() => t = new PyList(i)); - var ex = Assert.Throws(() => t = new PyList(i)); + Assert.AreEqual("object is not a list", ex.Message); + Assert.IsNull(t); + } - Assert.AreEqual("object is not a list", ex.Message); - Assert.IsNull(t); - } + [Test] + public void TestAppend() + { + var ai = new PyObject[] { new PyInt(3), new PyInt(2), new PyInt(1) }; + var s = new PyList(ai); + s.Append(new PyInt(4)); - [Test] - public void TestAppend() - { - var ai = new PyObject[] { new PyInt(3), new PyInt(2), new PyInt(1) }; - var s = new PyList(ai); - s.Append(new PyInt(4)); + Assert.AreEqual(4, s.Length()); + Assert.AreEqual("4", s[3].ToString()); + } - Assert.AreEqual(4, s.Length()); - Assert.AreEqual("4", s[3].ToString()); - } + [Test] + public void TestInsert() + { + var ai = new PyObject[] { new PyInt(3), new PyInt(2), new PyInt(1) }; + var s = new PyList(ai); + s.Insert(0, new PyInt(4)); - [Test] - public void TestInsert() - { - var ai = new PyObject[] { new PyInt(3), new PyInt(2), new PyInt(1) }; - var s = new PyList(ai); - s.Insert(0, new PyInt(4)); + Assert.AreEqual(4, s.Length()); + Assert.AreEqual("4", s[0].ToString()); + } - Assert.AreEqual(4, s.Length()); - Assert.AreEqual("4", s[0].ToString()); - } + [Test] + public void TestReverse() + { + var ai = new PyObject[] { new PyInt(3), new PyInt(1), new PyInt(2) }; + var s = new PyList(ai); - [Test] - public void TestReverse() - { - var ai = new PyObject[] { new PyInt(3), new PyInt(1), new PyInt(2) }; - var s = new PyList(ai); + s.Reverse(); - s.Reverse(); + Assert.AreEqual(3, s.Length()); + Assert.AreEqual("2", s[0].ToString()); + Assert.AreEqual("1", s[1].ToString()); + Assert.AreEqual("3", s[2].ToString()); + } - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("2", s[0].ToString()); - Assert.AreEqual("1", s[1].ToString()); - Assert.AreEqual("3", s[2].ToString()); - } + [Test] + public void TestSort() + { + var ai = new PyObject[] { new PyInt(3), new PyInt(1), new PyInt(2) }; + var s = new PyList(ai); - [Test] - public void TestSort() - { - var ai = new PyObject[] { new PyInt(3), new PyInt(1), new PyInt(2) }; - var s = new PyList(ai); + s.Sort(); - s.Sort(); + Assert.AreEqual(3, s.Length()); + Assert.AreEqual("1", s[0].ToString()); + Assert.AreEqual("2", s[1].ToString()); + Assert.AreEqual("3", s[2].ToString()); + } - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("1", s[0].ToString()); - Assert.AreEqual("2", s[1].ToString()); - Assert.AreEqual("3", s[2].ToString()); - } + [Test] + public void TestOnPyList() + { + var list = new PyList(); - [Test] - public void TestOnPyList() + list.Append(new PyString("foo")); + list.Append(new PyString("bar")); + list.Append(new PyString("baz")); + var result = new List(); + foreach (PyObject item in list) { - var list = new PyList(); - - list.Append(new PyString("foo")); - list.Append(new PyString("bar")); - list.Append(new PyString("baz")); - var result = new List(); - foreach (PyObject item in list) - { - result.Add(item.ToString()); - } - - Assert.AreEqual(3, result.Count); - Assert.AreEqual("foo", result[0]); - Assert.AreEqual("bar", result[1]); - Assert.AreEqual("baz", result[2]); + result.Add(item.ToString()); } + + Assert.AreEqual(3, result.Count); + Assert.AreEqual("foo", result[0]); + Assert.AreEqual("bar", result[1]); + Assert.AreEqual("baz", result[2]); } } diff --git a/src/embed_tests/TestPyNumber.cs b/src/embed_tests/TestPyNumber.cs index 0261c15c1..5ea7543d4 100644 --- a/src/embed_tests/TestPyNumber.cs +++ b/src/embed_tests/TestPyNumber.cs @@ -2,34 +2,21 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyNumber : BaseFixture { - public class TestPyNumber + [Test] + public void IsNumberTypeTrue() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void IsNumberTypeTrue() - { - var i = new PyInt(1); - Assert.True(PyNumber.IsNumberType(i)); - } + var i = new PyInt(1); + Assert.True(PyNumber.IsNumberType(i)); + } - [Test] - public void IsNumberTypeFalse() - { - var s = new PyString("Foo"); - Assert.False(PyNumber.IsNumberType(s)); - } + [Test] + public void IsNumberTypeFalse() + { + var s = new PyString("Foo"); + Assert.False(PyNumber.IsNumberType(s)); } } diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index 2f27eba1b..0d25b7d0e 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -4,36 +4,24 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyObject : BaseFixture { - public class TestPyObject + [Test] + public void TestGetDynamicMemberNames() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void TestGetDynamicMemberNames() + List expectedMemberNames = new List { - List expectedMemberNames = new List - { - "add", - "getNumber", - "member1", - "member2" - }; + "add", + "getNumber", + "member1", + "member2" + }; - PyDict locals = new PyDict(); + PyDict locals = new PyDict(); - PythonEngine.Exec(@" + PythonEngine.Exec(@" class MemberNamesTest(object): def __init__(self): self.member1 = 123 @@ -48,64 +36,63 @@ def add(self, x, y): a = MemberNamesTest() ", null, locals); - PyObject a = locals.GetItem("a"); + PyObject a = locals.GetItem("a"); - IEnumerable memberNames = a.GetDynamicMemberNames(); + IEnumerable memberNames = a.GetDynamicMemberNames(); - foreach (string expectedName in expectedMemberNames) - { - Assert.IsTrue(memberNames.Contains(expectedName), "Could not find member '{0}'.", expectedName); - } - } - - [Test] - public void InvokeNull() + foreach (string expectedName in expectedMemberNames) { - var list = PythonEngine.Eval("list"); - Assert.Throws(() => list.Invoke(new PyObject[] {null})); + Assert.IsTrue(memberNames.Contains(expectedName), "Could not find member '{0}'.", expectedName); } + } - [Test] - public void AsManagedObjectInvalidCast() - { - var list = PythonEngine.Eval("list"); - Assert.Throws(() => list.AsManagedObject(typeof(int))); - } + [Test] + public void InvokeNull() + { + var list = PythonEngine.Eval("list"); + Assert.Throws(() => list.Invoke(new PyObject[] {null})); + } - [Test] - public void UnaryMinus_ThrowsOnBadType() - { - dynamic list = new PyList(); - var error = Assert.Throws(() => list = -list); - Assert.AreEqual("TypeError", error.Type.Name); - } + [Test] + public void AsManagedObjectInvalidCast() + { + var list = PythonEngine.Eval("list"); + Assert.Throws(() => list.AsManagedObject(typeof(int))); + } - [Test] - [Obsolete] - public void GetAttrDefault_IgnoresAttributeErrorOnly() - { - var ob = new PyObjectTestMethods().ToPython(); - using var fallback = new PyList(); - var attrErrResult = ob.GetAttr(nameof(PyObjectTestMethods.RaisesAttributeError), fallback); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(fallback, attrErrResult)); - - var typeErrResult = Assert.Throws( - () => ob.GetAttr(nameof(PyObjectTestMethods.RaisesTypeError), fallback) - ); - Assert.AreEqual(Exceptions.TypeError, typeErrResult.Type); - } + [Test] + public void UnaryMinus_ThrowsOnBadType() + { + dynamic list = new PyList(); + var error = Assert.Throws(() => list = -list); + Assert.AreEqual("TypeError", error.Type.Name); + } - // regression test from https://github.com/pythonnet/pythonnet/issues/1642 - [Test] - public void InheritedMethodsAutoacquireGIL() - { - PythonEngine.Exec("from System import String\nString.Format('{0},{1}', 1, 2)"); - } + [Test] + [Obsolete] + public void GetAttrDefault_IgnoresAttributeErrorOnly() + { + var ob = new PyObjectTestMethods().ToPython(); + using var fallback = new PyList(); + var attrErrResult = ob.GetAttr(nameof(PyObjectTestMethods.RaisesAttributeError), fallback); + Assert.IsTrue(PythonReferenceComparer.Instance.Equals(fallback, attrErrResult)); + + var typeErrResult = Assert.Throws( + () => ob.GetAttr(nameof(PyObjectTestMethods.RaisesTypeError), fallback) + ); + Assert.AreEqual(Exceptions.TypeError, typeErrResult.Type); } - public class PyObjectTestMethods + // regression test from https://github.com/pythonnet/pythonnet/issues/1642 + [Test] + public void InheritedMethodsAutoacquireGIL() { - public string RaisesAttributeError => throw new PythonException(new PyType(Exceptions.AttributeError), value: null, traceback: null); - public string RaisesTypeError => throw new PythonException(new PyType(Exceptions.TypeError), value: null, traceback: null); + PythonEngine.Exec("from System import String\nString.Format('{0},{1}', 1, 2)"); } } + +public class PyObjectTestMethods +{ + public string RaisesAttributeError => throw new PythonException(new PyType(Exceptions.AttributeError), value: null, traceback: null); + public string RaisesTypeError => throw new PythonException(new PyType(Exceptions.TypeError), value: null, traceback: null); +} diff --git a/src/embed_tests/TestPySequence.cs b/src/embed_tests/TestPySequence.cs index dc35a2633..43c5480f0 100644 --- a/src/embed_tests/TestPySequence.cs +++ b/src/embed_tests/TestPySequence.cs @@ -2,94 +2,81 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPySequence : BaseFixture { - public class TestPySequence + [Test] + public void TestIsSequenceTrue() + { + var t = new PyString("FooBar"); + Assert.True(PySequence.IsSequenceType(t)); + } + + [Test] + public void TestIsSequenceFalse() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void TestIsSequenceTrue() - { - var t = new PyString("FooBar"); - Assert.True(PySequence.IsSequenceType(t)); - } - - [Test] - public void TestIsSequenceFalse() - { - var t = new PyInt(5); - Assert.False(PySequence.IsSequenceType(t)); - } - - [Test] - public void TestGetSlice() - { - var t = new PyString("FooBar"); - - PyObject s = t.GetSlice(0, 3); - Assert.AreEqual("Foo", s.ToString()); - - PyObject s2 = t.GetSlice(3, 6); - Assert.AreEqual("Bar", s2.ToString()); - - PyObject s3 = t.GetSlice(0, 6); - Assert.AreEqual("FooBar", s3.ToString()); - - PyObject s4 = t.GetSlice(0, 12); - Assert.AreEqual("FooBar", s4.ToString()); - } - - [Test] - public void TestConcat() - { - var t1 = new PyString("Foo"); - var t2 = new PyString("Bar"); - - PyObject actual = t1.Concat(t2); - - Assert.AreEqual("FooBar", actual.ToString()); - } - - [Test] - public void TestRepeat() - { - var t1 = new PyString("Foo"); - - PyObject actual = t1.Repeat(3); - Assert.AreEqual("FooFooFoo", actual.ToString()); - - actual = t1.Repeat(-3); - Assert.AreEqual("", actual.ToString()); - } - - [Test] - public void TestContains() - { - var t1 = new PyString("FooBar"); - - Assert.True(t1.Contains(new PyString("a"))); - Assert.False(t1.Contains(new PyString("z"))); - } - - [Test] - public void TestIndex() - { - var t1 = new PyString("FooBar"); - - Assert.AreEqual(4, t1.Index32(new PyString("a"))); - Assert.AreEqual(5L, t1.Index64(new PyString("r"))); - Assert.AreEqual(-(nint)1, t1.Index(new PyString("z"))); - } + var t = new PyInt(5); + Assert.False(PySequence.IsSequenceType(t)); + } + + [Test] + public void TestGetSlice() + { + var t = new PyString("FooBar"); + + PyObject s = t.GetSlice(0, 3); + Assert.AreEqual("Foo", s.ToString()); + + PyObject s2 = t.GetSlice(3, 6); + Assert.AreEqual("Bar", s2.ToString()); + + PyObject s3 = t.GetSlice(0, 6); + Assert.AreEqual("FooBar", s3.ToString()); + + PyObject s4 = t.GetSlice(0, 12); + Assert.AreEqual("FooBar", s4.ToString()); + } + + [Test] + public void TestConcat() + { + var t1 = new PyString("Foo"); + var t2 = new PyString("Bar"); + + PyObject actual = t1.Concat(t2); + + Assert.AreEqual("FooBar", actual.ToString()); + } + + [Test] + public void TestRepeat() + { + var t1 = new PyString("Foo"); + + PyObject actual = t1.Repeat(3); + Assert.AreEqual("FooFooFoo", actual.ToString()); + + actual = t1.Repeat(-3); + Assert.AreEqual("", actual.ToString()); + } + + [Test] + public void TestContains() + { + var t1 = new PyString("FooBar"); + + Assert.True(t1.Contains(new PyString("a"))); + Assert.False(t1.Contains(new PyString("z"))); + } + + [Test] + public void TestIndex() + { + var t1 = new PyString("FooBar"); + + Assert.AreEqual(4, t1.Index32(new PyString("a"))); + Assert.AreEqual(5L, t1.Index64(new PyString("r"))); + Assert.AreEqual(-(nint)1, t1.Index(new PyString("z"))); } } diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index b12e08c23..9b0116711 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -2,115 +2,102 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyString : BaseFixture { - public class TestPyString + [Test] + public void TestStringCtor() + { + const string expected = "foo"; + var actual = new PyString(expected); + Assert.AreEqual(expected, actual.ToString()); + } + + [Test] + public void TestEmptyStringCtor() + { + const string expected = ""; + var actual = new PyString(expected); + Assert.AreEqual(expected, actual.ToString()); + } + + [Test] + [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] + public void TestPyObjectCtor() + { + const string expected = "Foo"; + + var t = new PyString(expected); + var actual = new PyString(t); + + Assert.AreEqual(expected, actual.ToString()); + } + + [Test] + public void TestBadPyObjectCtor() + { + var t = new PyInt(5); + PyString actual = null; + + var ex = Assert.Throws(() => actual = new PyString(t)); + + StringAssert.StartsWith("object is not a string", ex.Message); + Assert.IsNull(actual); + } + + [Test] + public void TestCtorBorrowed() + { + const string expected = "foo"; + + var t = new PyString(expected); + var actual = new PyString(t.Reference); + + Assert.AreEqual(expected, actual.ToString()); + } + + [Test] + [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] + public void IsStringTrue() + { + var t = new PyString("foo"); + + Assert.True(PyString.IsStringType(t)); + } + + [Test] + public void IsStringFalse() + { + var t = new PyInt(5); + + Assert.False(PyString.IsStringType(t)); + } + + [Test] + public void TestUnicode() + { + const string expected = "foo\u00e9"; + PyObject actual = new PyString(expected); + Assert.AreEqual(expected, actual.ToString()); + } + + [Test] + public void TestUnicodeSurrogateToString() + { + var expected = "foo\ud83d\udc3c"; + var actual = PythonEngine.Eval("'foo\ud83d\udc3c'"); + Assert.AreEqual(4, actual.Length()); + Assert.AreEqual(expected, actual.ToString()); + } + + [Test] + public void TestUnicodeSurrogate() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void TestStringCtor() - { - const string expected = "foo"; - var actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); - } - - [Test] - public void TestEmptyStringCtor() - { - const string expected = ""; - var actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); - } - - [Test] - [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] - public void TestPyObjectCtor() - { - const string expected = "Foo"; - - var t = new PyString(expected); - var actual = new PyString(t); - - Assert.AreEqual(expected, actual.ToString()); - } - - [Test] - public void TestBadPyObjectCtor() - { - var t = new PyInt(5); - PyString actual = null; - - var ex = Assert.Throws(() => actual = new PyString(t)); - - StringAssert.StartsWith("object is not a string", ex.Message); - Assert.IsNull(actual); - } - - [Test] - public void TestCtorBorrowed() - { - const string expected = "foo"; - - var t = new PyString(expected); - var actual = new PyString(t.Reference); - - Assert.AreEqual(expected, actual.ToString()); - } - - [Test] - [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] - public void IsStringTrue() - { - var t = new PyString("foo"); - - Assert.True(PyString.IsStringType(t)); - } - - [Test] - public void IsStringFalse() - { - var t = new PyInt(5); - - Assert.False(PyString.IsStringType(t)); - } - - [Test] - public void TestUnicode() - { - const string expected = "foo\u00e9"; - PyObject actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); - } - - [Test] - public void TestUnicodeSurrogateToString() - { - var expected = "foo\ud83d\udc3c"; - var actual = PythonEngine.Eval("'foo\ud83d\udc3c'"); - Assert.AreEqual(4, actual.Length()); - Assert.AreEqual(expected, actual.ToString()); - } - - [Test] - public void TestUnicodeSurrogate() - { - const string expected = "foo\ud83d\udc3c"; // "foo🐼" - PyObject actual = new PyString(expected); - // python treats "foo🐼" as 4 characters, dotnet as 5 - Assert.AreEqual(4, actual.Length()); - Assert.AreEqual(expected, actual.ToString()); - } + const string expected = "foo\ud83d\udc3c"; // "foo🐼" + PyObject actual = new PyString(expected); + // python treats "foo🐼" as 4 characters, dotnet as 5 + Assert.AreEqual(4, actual.Length()); + Assert.AreEqual(expected, actual.ToString()); } } diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 5d76116aa..42e9379ea 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -2,170 +2,157 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyTuple : BaseFixture { - public class TestPyTuple + /// + /// Test IsTupleType without having to Initialize a tuple. + /// PyTuple constructor use IsTupleType. This decouples the tests. + /// + [Test] + public void TestStringIsTupleType() + { + var s = new PyString("foo"); + Assert.False(PyTuple.IsTupleType(s)); + } + + /// + /// Test IsTupleType with Tuple. + /// + [Test] + public void TestPyTupleIsTupleType() + { + var t = new PyTuple(); + Assert.True(PyTuple.IsTupleType(t)); + } + + [Test] + public void TestPyTupleEmpty() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - /// - /// Test IsTupleType without having to Initialize a tuple. - /// PyTuple constructor use IsTupleType. This decouples the tests. - /// - [Test] - public void TestStringIsTupleType() - { - var s = new PyString("foo"); - Assert.False(PyTuple.IsTupleType(s)); - } - - /// - /// Test IsTupleType with Tuple. - /// - [Test] - public void TestPyTupleIsTupleType() - { - var t = new PyTuple(); - Assert.True(PyTuple.IsTupleType(t)); - } - - [Test] - public void TestPyTupleEmpty() - { - var t = new PyTuple(); - Assert.AreEqual(0, t.Length()); - } - - [Test] - public void TestPyTupleBadCtor() - { - var i = new PyInt(5); - PyTuple t = null; - - var ex = Assert.Throws(() => t = new PyTuple(i)); - - Assert.AreEqual("object is not a tuple", ex.Message); - Assert.IsNull(t); - } - - [Test] - public void TestPyTupleCtorEmptyArray() - { - var a = new PyObject[] { }; - var t = new PyTuple(a); - - Assert.AreEqual(0, t.Length()); - } - - [Test] - public void TestPyTupleCtorArrayPyIntEmpty() - { - var a = new PyInt[] { }; - var t = new PyTuple(a); - - Assert.AreEqual(0, t.Length()); - } - - [Test] - public void TestPyTupleCtorArray() - { - var a = new PyObject[] { new PyInt(1), new PyString("Foo") }; - var t = new PyTuple(a); - - Assert.AreEqual(2, t.Length()); - } - - /// - /// Test PyTuple.Concat(...) doesn't let invalid appends happen - /// and throws and exception. - /// - /// - /// Test has second purpose. Currently it generated an Exception - /// that the GC failed to remove often and caused AppDomain unload - /// errors at the end of tests. See GH#397 for more info. - /// - /// Curious, on PY27 it gets a Unicode on the ex.Message. On PY3+ its string. - /// - [Test] - public void TestPyTupleInvalidAppend() - { - PyObject s = new PyString("foo"); - var t = new PyTuple(); - - var ex = Assert.Throws(() => t.Concat(s)); - - StringAssert.StartsWith("can only concatenate tuple", ex.Message); - Assert.AreEqual(0, t.Length()); - Assert.IsEmpty(t); - } - - [Test] - public void TestPyTupleValidAppend() - { - var t0 = new PyTuple(); - var t = new PyTuple(); - t.Concat(t0); - - Assert.IsNotNull(t); - Assert.IsInstanceOf(typeof(PyTuple), t); - } - - [Test] - public void TestPyTupleStringConvert() - { - PyObject s = new PyString("foo"); - PyTuple t = PyTuple.AsTuple(s); - - Assert.IsNotNull(t); - Assert.IsInstanceOf(typeof(PyTuple), t); - Assert.AreEqual("f", t[0].ToString()); - Assert.AreEqual("o", t[1].ToString()); - Assert.AreEqual("o", t[2].ToString()); - } - - [Test] - public void TestPyTupleValidConvert() - { - var l = new PyList(); - PyTuple t = PyTuple.AsTuple(l); - - Assert.IsNotNull(t); - Assert.IsInstanceOf(typeof(PyTuple), t); - } - - [Test] - public void TestNewPyTupleFromPyTuple() - { - var t0 = new PyTuple(); - var t = new PyTuple(t0); - - Assert.IsNotNull(t); - Assert.IsInstanceOf(typeof(PyTuple), t); - } - - /// - /// TODO: Should this throw ArgumentError instead? - /// - [Test] - public void TestInvalidAsTuple() - { - var i = new PyInt(5); - PyTuple t = null; - - var ex = Assert.Throws(() => t = PyTuple.AsTuple(i)); - - Assert.AreEqual("'int' object is not iterable", ex.Message); - Assert.IsNull(t); - } + var t = new PyTuple(); + Assert.AreEqual(0, t.Length()); + } + + [Test] + public void TestPyTupleBadCtor() + { + var i = new PyInt(5); + PyTuple t = null; + + var ex = Assert.Throws(() => t = new PyTuple(i)); + + Assert.AreEqual("object is not a tuple", ex.Message); + Assert.IsNull(t); + } + + [Test] + public void TestPyTupleCtorEmptyArray() + { + var a = new PyObject[] { }; + var t = new PyTuple(a); + + Assert.AreEqual(0, t.Length()); + } + + [Test] + public void TestPyTupleCtorArrayPyIntEmpty() + { + var a = new PyInt[] { }; + var t = new PyTuple(a); + + Assert.AreEqual(0, t.Length()); + } + + [Test] + public void TestPyTupleCtorArray() + { + var a = new PyObject[] { new PyInt(1), new PyString("Foo") }; + var t = new PyTuple(a); + + Assert.AreEqual(2, t.Length()); + } + + /// + /// Test PyTuple.Concat(...) doesn't let invalid appends happen + /// and throws and exception. + /// + /// + /// Test has second purpose. Currently it generated an Exception + /// that the GC failed to remove often and caused AppDomain unload + /// errors at the end of tests. See GH#397 for more info. + /// + /// Curious, on PY27 it gets a Unicode on the ex.Message. On PY3+ its string. + /// + [Test] + public void TestPyTupleInvalidAppend() + { + PyObject s = new PyString("foo"); + var t = new PyTuple(); + + var ex = Assert.Throws(() => t.Concat(s)); + + StringAssert.StartsWith("can only concatenate tuple", ex.Message); + Assert.AreEqual(0, t.Length()); + Assert.IsEmpty(t); + } + + [Test] + public void TestPyTupleValidAppend() + { + var t0 = new PyTuple(); + var t = new PyTuple(); + t.Concat(t0); + + Assert.IsNotNull(t); + Assert.IsInstanceOf(typeof(PyTuple), t); + } + + [Test] + public void TestPyTupleStringConvert() + { + PyObject s = new PyString("foo"); + PyTuple t = PyTuple.AsTuple(s); + + Assert.IsNotNull(t); + Assert.IsInstanceOf(typeof(PyTuple), t); + Assert.AreEqual("f", t[0].ToString()); + Assert.AreEqual("o", t[1].ToString()); + Assert.AreEqual("o", t[2].ToString()); + } + + [Test] + public void TestPyTupleValidConvert() + { + var l = new PyList(); + PyTuple t = PyTuple.AsTuple(l); + + Assert.IsNotNull(t); + Assert.IsInstanceOf(typeof(PyTuple), t); + } + + [Test] + public void TestNewPyTupleFromPyTuple() + { + var t0 = new PyTuple(); + var t = new PyTuple(t0); + + Assert.IsNotNull(t); + Assert.IsInstanceOf(typeof(PyTuple), t); + } + + /// + /// TODO: Should this throw ArgumentError instead? + /// + [Test] + public void TestInvalidAsTuple() + { + var i = new PyInt(5); + PyTuple t = null; + + var ex = Assert.Throws(() => t = PyTuple.AsTuple(i)); + + Assert.AreEqual("'int' object is not iterable", ex.Message); + Assert.IsNull(t); } } diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index 34645747d..c92462a62 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using System.Text; using NUnit.Framework; @@ -6,42 +5,29 @@ using Python.Runtime; using Python.Runtime.Native; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyType : BaseFixture { - public class TestPyType + [Test] + public void CanCreateHeapType() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void CanCreateHeapType() - { - const string name = "nÁmæ"; - const string docStr = "dÁcæ"; + const string name = "nÁmæ"; + const string docStr = "dÁcæ"; - using var doc = new StrPtr(docStr, Encoding.UTF8); - var spec = new TypeSpec( - name: name, - basicSize: Util.ReadInt32(Runtime.Runtime.PyBaseObjectType, TypeOffset.tp_basicsize), - slots: new TypeSpec.Slot[] { - new (TypeSlotID.tp_doc, doc.RawPointer), - }, - TypeFlags.Default | TypeFlags.HeapType - ); + using var doc = new StrPtr(docStr, Encoding.UTF8); + var spec = new TypeSpec( + name: name, + basicSize: Util.ReadInt32(Runtime.Runtime.PyBaseObjectType, TypeOffset.tp_basicsize), + slots: new TypeSpec.Slot[] { + new (TypeSlotID.tp_doc, doc.RawPointer), + }, + TypeFlags.Default | TypeFlags.HeapType + ); - using var type = new PyType(spec); - Assert.AreEqual(name, type.GetAttr("__name__").As()); - Assert.AreEqual(name, type.Name); - Assert.AreEqual(docStr, type.GetAttr("__doc__").As()); - } + using var type = new PyType(spec); + Assert.AreEqual(name, type.GetAttr("__name__").As()); + Assert.AreEqual(name, type.Name); + Assert.AreEqual(docStr, type.GetAttr("__doc__").As()); } } diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs index d1c9aac28..d695fdb9c 100644 --- a/src/embed_tests/TestPyWith.cs +++ b/src/embed_tests/TestPyWith.cs @@ -1,32 +1,19 @@ -using System; using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPyWith : BaseFixture { - public class TestPyWith + /// + /// Test that exception is raised in context manager that ignores it. + /// + [Test] + public void TestWithPositive() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + var locals = new PyDict(); - /// - /// Test that exception is raised in context manager that ignores it. - /// - [Test] - public void TestWithPositive() - { - var locals = new PyDict(); - - PythonEngine.Exec(@" + PythonEngine.Exec(@" class CmTest: def __enter__(self): return self @@ -39,32 +26,32 @@ def fail(self): a = CmTest() ", null, locals); - var a = locals.GetItem("a"); + var a = locals.GetItem("a"); - try - { - Py.With(a, cmTest => - { - cmTest.fail(); - }); - } - catch (PythonException e) + try + { + Py.With(a, cmTest => { - TestContext.Out.WriteLine(e.Message); - Assert.IsTrue(e.Type.Name == "ZeroDivisionError"); - } + cmTest.fail(); + }); + } + catch (PythonException e) + { + TestContext.Out.WriteLine(e.Message); + Assert.IsTrue(e.Type.Name == "ZeroDivisionError"); } + } - /// - /// Test that exception is not raised in context manager that handles it - /// - [Test] - public void TestWithNegative() - { - var locals = new PyDict(); + /// + /// Test that exception is not raised in context manager that handles it + /// + [Test] + public void TestWithNegative() + { + var locals = new PyDict(); - PythonEngine.Exec(@" + PythonEngine.Exec(@" class CmTest: def __enter__(self): print('Enter') @@ -78,11 +65,10 @@ def fail(self): a = CmTest() ", null, locals); - var a = locals.GetItem("a"); - Py.With(a, cmTest => - { - cmTest.fail(); - }); - } + var a = locals.GetItem("a"); + Py.With(a, cmTest => + { + cmTest.fail(); + }); } } diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index be91d7f45..1cdebdbfa 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -2,233 +2,232 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPythonEngineProperties : BaseFixture { - public class TestPythonEngineProperties + [Test] + public static void GetBuildinfoDoesntCrash() { - [Test] - public static void GetBuildinfoDoesntCrash() + PythonEngine.Initialize(); + using (Py.GIL()) { - PythonEngine.Initialize(); - using (Py.GIL()) - { - string s = PythonEngine.BuildInfo; - - Assert.True(s.Length > 5); - Assert.True(s.Contains(",")); - } - } + string s = PythonEngine.BuildInfo; - [Test] - public static void GetCompilerDoesntCrash() - { - PythonEngine.Initialize(); - using (Py.GIL()) - { - string s = PythonEngine.Compiler; - - Assert.True(s.Length > 0); - Assert.True(s.Contains("[")); - Assert.True(s.Contains("]")); - } + Assert.True(s.Length > 5); + Assert.True(s.Contains(",")); } + } - [Test] - public static void GetCopyrightDoesntCrash() + [Test] + public static void GetCompilerDoesntCrash() + { + PythonEngine.Initialize(); + using (Py.GIL()) { - PythonEngine.Initialize(); - using (Py.GIL()) - { - string s = PythonEngine.Copyright; - - Assert.True(s.Length > 0); - Assert.True(s.Contains("Python Software Foundation")); - } - } + string s = PythonEngine.Compiler; - [Test] - public static void GetPlatformDoesntCrash() - { - PythonEngine.Initialize(); - using (Py.GIL()) - { - string s = PythonEngine.Platform; - - Assert.True(s.Length > 0); - Assert.True(s.Contains("x") || s.Contains("win")); - } + Assert.True(s.Length > 0); + Assert.True(s.Contains("[")); + Assert.True(s.Contains("]")); } + } - [Test] - public static void GetVersionDoesntCrash() + [Test] + public static void GetCopyrightDoesntCrash() + { + PythonEngine.Initialize(); + using (Py.GIL()) { - PythonEngine.Initialize(); - using (Py.GIL()) - { - string s = PythonEngine.Version; - - Assert.True(s.Length > 0); - Assert.True(s.Contains(",")); - } + string s = PythonEngine.Copyright; + + Assert.True(s.Length > 0); + Assert.True(s.Contains("Python Software Foundation")); } + } - [Test] - public static void GetPythonPathDefault() + [Test] + public static void GetPlatformDoesntCrash() + { + PythonEngine.Initialize(); + using (Py.GIL()) { - PythonEngine.Initialize(); - string s = PythonEngine.PythonPath; + string s = PythonEngine.Platform; - StringAssert.Contains("python", s.ToLower()); - PythonEngine.Shutdown(); + Assert.True(s.Length > 0); + Assert.True(s.Contains("x") || s.Contains("win")); } + } - [Test] - public static void GetProgramNameDefault() + [Test] + public static void GetVersionDoesntCrash() + { + PythonEngine.Initialize(); + using (Py.GIL()) { - PythonEngine.Initialize(); - string s = PythonEngine.ProgramName; + string s = PythonEngine.Version; - Assert.NotNull(s); - PythonEngine.Shutdown(); + Assert.True(s.Length > 0); + Assert.True(s.Contains(",")); } + } - /// - /// Test default behavior of PYTHONHOME. If ENVVAR is set it will - /// return the same value. If not, returns EmptyString. - /// - [Test] - public static void GetPythonHomeDefault() - { - string envPythonHome = Environment.GetEnvironmentVariable("PYTHONHOME") ?? ""; + [Test] + public static void GetPythonPathDefault() + { + PythonEngine.Initialize(); + string s = PythonEngine.PythonPath; - PythonEngine.Initialize(); - string enginePythonHome = PythonEngine.PythonHome; + StringAssert.Contains("python", s.ToLower()); + PythonEngine.Shutdown(); + } - Assert.AreEqual(envPythonHome, enginePythonHome); - PythonEngine.Shutdown(); - } + [Test] + public static void GetProgramNameDefault() + { + PythonEngine.Initialize(); + string s = PythonEngine.ProgramName; - [Test] - public void SetPythonHome() - { - PythonEngine.Initialize(); - var pythonHomeBackup = PythonEngine.PythonHome; - PythonEngine.Shutdown(); + Assert.NotNull(s); + PythonEngine.Shutdown(); + } - if (pythonHomeBackup == "") - Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); + /// + /// Test default behavior of PYTHONHOME. If ENVVAR is set it will + /// return the same value. If not, returns EmptyString. + /// + [Test] + public static void GetPythonHomeDefault() + { + string envPythonHome = Environment.GetEnvironmentVariable("PYTHONHOME") ?? ""; - var pythonHome = "/dummypath/"; + PythonEngine.Initialize(); + string enginePythonHome = PythonEngine.PythonHome; - PythonEngine.PythonHome = pythonHome; - PythonEngine.Initialize(); + Assert.AreEqual(envPythonHome, enginePythonHome); + PythonEngine.Shutdown(); + } - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); - PythonEngine.Shutdown(); + [Test] + public void SetPythonHome() + { + PythonEngine.Initialize(); + var pythonHomeBackup = PythonEngine.PythonHome; + PythonEngine.Shutdown(); - // Restoring valid pythonhome. - PythonEngine.PythonHome = pythonHomeBackup; - } + if (pythonHomeBackup == "") + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); - [Test] - public void SetPythonHomeTwice() - { - PythonEngine.Initialize(); - var pythonHomeBackup = PythonEngine.PythonHome; - PythonEngine.Shutdown(); + var pythonHome = "/dummypath/"; - if (pythonHomeBackup == "") - Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); + PythonEngine.PythonHome = pythonHome; + PythonEngine.Initialize(); - var pythonHome = "/dummypath/"; + Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + PythonEngine.Shutdown(); - PythonEngine.PythonHome = "/dummypath2/"; - PythonEngine.PythonHome = pythonHome; - PythonEngine.Initialize(); + // Restoring valid pythonhome. + PythonEngine.PythonHome = pythonHomeBackup; + } - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); - PythonEngine.Shutdown(); + [Test] + public void SetPythonHomeTwice() + { + PythonEngine.Initialize(); + var pythonHomeBackup = PythonEngine.PythonHome; + PythonEngine.Shutdown(); - PythonEngine.PythonHome = pythonHomeBackup; - } + if (pythonHomeBackup == "") + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); - [Test] - [Ignore("Currently buggy in Python")] - public void SetPythonHomeEmptyString() - { - PythonEngine.Initialize(); + var pythonHome = "/dummypath/"; + + PythonEngine.PythonHome = "/dummypath2/"; + PythonEngine.PythonHome = pythonHome; + PythonEngine.Initialize(); - var backup = PythonEngine.PythonHome; - if (backup == "") - { - PythonEngine.Shutdown(); - Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); - } - PythonEngine.PythonHome = ""; + Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + PythonEngine.Shutdown(); - Assert.AreEqual("", PythonEngine.PythonHome); + PythonEngine.PythonHome = pythonHomeBackup; + } + + [Test] + [Ignore("Currently buggy in Python")] + public void SetPythonHomeEmptyString() + { + PythonEngine.Initialize(); - PythonEngine.PythonHome = backup; + var backup = PythonEngine.PythonHome; + if (backup == "") + { PythonEngine.Shutdown(); + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); } + PythonEngine.PythonHome = ""; - [Test] - public void SetProgramName() + Assert.AreEqual("", PythonEngine.PythonHome); + + PythonEngine.PythonHome = backup; + PythonEngine.Shutdown(); + } + + [Test] + public void SetProgramName() + { + if (PythonEngine.IsInitialized) { - if (PythonEngine.IsInitialized) - { - PythonEngine.Shutdown(); - } + PythonEngine.Shutdown(); + } - var programNameBackup = PythonEngine.ProgramName; + var programNameBackup = PythonEngine.ProgramName; - var programName = "FooBar"; + var programName = "FooBar"; - PythonEngine.ProgramName = programName; - PythonEngine.Initialize(); + PythonEngine.ProgramName = programName; + PythonEngine.Initialize(); - Assert.AreEqual(programName, PythonEngine.ProgramName); - PythonEngine.Shutdown(); + Assert.AreEqual(programName, PythonEngine.ProgramName); + PythonEngine.Shutdown(); - PythonEngine.ProgramName = programNameBackup; - } + PythonEngine.ProgramName = programNameBackup; + } + + [Test] + public void SetPythonPath() + { + PythonEngine.Initialize(); - [Test] - public void SetPythonPath() + const string moduleName = "pytest"; + bool importShouldSucceed; + try { - PythonEngine.Initialize(); - - const string moduleName = "pytest"; - bool importShouldSucceed; - try - { - Py.Import(moduleName); - importShouldSucceed = true; - } - catch - { - importShouldSucceed = false; - } - - string[] paths = Py.Import("sys").GetAttr("path").As(); - string path = string.Join(System.IO.Path.PathSeparator.ToString(), paths); - - // path should not be set to PythonEngine.PythonPath here. - // PythonEngine.PythonPath gets the default module search path, not the full search path. - // The list sys.path is initialized with this value on interpreter startup; - // it can be (and usually is) modified later to change the search path for loading modules. - // See https://docs.python.org/3/c-api/init.html#c.Py_GetPath - // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path. + Py.Import(moduleName); + importShouldSucceed = true; + } + catch + { + importShouldSucceed = false; + } - PythonEngine.Shutdown(); + string[] paths = Py.Import("sys").GetAttr("path").As(); + string path = string.Join(System.IO.Path.PathSeparator.ToString(), paths); - PythonEngine.PythonPath = path; - PythonEngine.Initialize(); + // path should not be set to PythonEngine.PythonPath here. + // PythonEngine.PythonPath gets the default module search path, not the full search path. + // The list sys.path is initialized with this value on interpreter startup; + // it can be (and usually is) modified later to change the search path for loading modules. + // See https://docs.python.org/3/c-api/init.html#c.Py_GetPath + // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path. - Assert.AreEqual(path, PythonEngine.PythonPath); - if (importShouldSucceed) Py.Import(moduleName); + PythonEngine.Shutdown(); - PythonEngine.Shutdown(); - } + PythonEngine.PythonPath = path; + PythonEngine.Initialize(); + + Assert.AreEqual(path, PythonEngine.PythonPath); + if (importShouldSucceed) Py.Import(moduleName); + + PythonEngine.Shutdown(); } } diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a248b6a1f..7d33389e7 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -2,179 +2,166 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestPythonException : BaseFixture { - public class TestPythonException + [Test] + public void TestMessage() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + var list = new PyList(); + PyObject foo = null; - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + var ex = Assert.Throws(() => foo = list[0]); - [Test] - public void TestMessage() - { - var list = new PyList(); - PyObject foo = null; - - var ex = Assert.Throws(() => foo = list[0]); - - Assert.AreEqual("list index out of range", ex.Message); - Assert.IsNull(foo); - } + Assert.AreEqual("list index out of range", ex.Message); + Assert.IsNull(foo); + } - [Test] - public void TestType() - { - var list = new PyList(); - PyObject foo = null; + [Test] + public void TestType() + { + var list = new PyList(); + PyObject foo = null; - var ex = Assert.Throws(() => foo = list[0]); + var ex = Assert.Throws(() => foo = list[0]); - Assert.AreEqual("IndexError", ex.Type.Name); - Assert.IsNull(foo); - } + Assert.AreEqual("IndexError", ex.Type.Name); + Assert.IsNull(foo); + } - [Test] - public void TestNoError() - { - // There is no PyErr to fetch - Assert.Throws(() => PythonException.FetchCurrentRaw()); - var currentError = PythonException.FetchCurrentOrNullRaw(); - Assert.IsNull(currentError); - } + [Test] + public void TestNoError() + { + // There is no PyErr to fetch + Assert.Throws(() => PythonException.FetchCurrentRaw()); + var currentError = PythonException.FetchCurrentOrNullRaw(); + Assert.IsNull(currentError); + } - [Test] - public void TestNestedExceptions() + [Test] + public void TestNestedExceptions() + { + try { - try - { - PythonEngine.Exec(@" + PythonEngine.Exec(@" try: raise Exception('inner') except Exception as ex: raise Exception('outer') from ex "); - } - catch (PythonException ex) - { - Assert.That(ex.InnerException, Is.InstanceOf()); - Assert.That(ex.InnerException.Message, Is.EqualTo("inner")); - } } - - [Test] - public void InnerIsEmptyWithNoCause() + catch (PythonException ex) { - var list = new PyList(); - PyObject foo = null; + Assert.That(ex.InnerException, Is.InstanceOf()); + Assert.That(ex.InnerException.Message, Is.EqualTo("inner")); + } + } - var ex = Assert.Throws(() => foo = list[0]); + [Test] + public void InnerIsEmptyWithNoCause() + { + var list = new PyList(); + PyObject foo = null; - Assert.IsNull(ex.InnerException); - } + var ex = Assert.Throws(() => foo = list[0]); + + Assert.IsNull(ex.InnerException); + } - [Test] - public void TestPythonExceptionFormat() + [Test] + public void TestPythonExceptionFormat() + { + try { - try - { - PythonEngine.Exec("raise ValueError('Error!')"); - Assert.Fail("Exception should have been raised"); - } - catch (PythonException ex) - { - // Console.WriteLine($"Format: {ex.Format()}"); - // Console.WriteLine($"Stacktrace: {ex.StackTrace}"); - Assert.That( - ex.Format(), - Does.Contain("Traceback") - .And.Contains("(most recent call last):") - .And.Contains("ValueError: Error!") - ); - - // Check that the stacktrace is properly formatted - Assert.That( - ex.StackTrace, - Does.Not.StartWith("[") - .And.Not.Contain("\\n") - ); - } + PythonEngine.Exec("raise ValueError('Error!')"); + Assert.Fail("Exception should have been raised"); } + catch (PythonException ex) + { + // Console.WriteLine($"Format: {ex.Format()}"); + // Console.WriteLine($"Stacktrace: {ex.StackTrace}"); + Assert.That( + ex.Format(), + Does.Contain("Traceback") + .And.Contains("(most recent call last):") + .And.Contains("ValueError: Error!") + ); + + // Check that the stacktrace is properly formatted + Assert.That( + ex.StackTrace, + Does.Not.StartWith("[") + .And.Not.Contain("\\n") + ); + } + } - [Test] - public void TestPythonExceptionFormatNoTraceback() + [Test] + public void TestPythonExceptionFormatNoTraceback() + { + try + { + var module = PyModule.Import("really____unknown___module"); + Assert.Fail("Unknown module should not be loaded"); + } + catch (PythonException ex) { - try - { - var module = PyModule.Import("really____unknown___module"); - Assert.Fail("Unknown module should not be loaded"); - } - catch (PythonException ex) - { - // ImportError/ModuleNotFoundError do not have a traceback when not running in a script - Assert.AreEqual(ex.StackTrace, ex.Format()); - } + // ImportError/ModuleNotFoundError do not have a traceback when not running in a script + Assert.AreEqual(ex.StackTrace, ex.Format()); } + } - [Test] - public void TestPythonExceptionFormatNormalized() + [Test] + public void TestPythonExceptionFormatNormalized() + { + try { - try - { - PythonEngine.Exec("a=b\n"); - Assert.Fail("Exception should have been raised"); - } - catch (PythonException ex) - { - Assert.AreEqual("Traceback (most recent call last):\n File \"\", line 1, in \nNameError: name 'b' is not defined\n", ex.Format()); - } + PythonEngine.Exec("a=b\n"); + Assert.Fail("Exception should have been raised"); } + catch (PythonException ex) + { + Assert.AreEqual("Traceback (most recent call last):\n File \"\", line 1, in \nNameError: name 'b' is not defined\n", ex.Format()); + } + } - [Test] - public void TestPythonException_PyErr_NormalizeException() + [Test] + public void TestPythonException_PyErr_NormalizeException() + { + using (var scope = Py.CreateScope()) { - using (var scope = Py.CreateScope()) - { - scope.Exec(@" + scope.Exec(@" class TestException(NameError): def __init__(self, val): super().__init__(val) x = int(val)"); - Assert.IsTrue(scope.TryGet("TestException", out PyObject type)); - - PyObject str = "dummy string".ToPython(); - var typePtr = new NewReference(type.Reference); - var strPtr = new NewReference(str.Reference); - var tbPtr = new NewReference(Runtime.Runtime.None.Reference); - Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr); - - using var typeObj = typePtr.MoveToPyObject(); - using var strObj = strPtr.MoveToPyObject(); - using var tbObj = tbPtr.MoveToPyObject(); - // the type returned from PyErr_NormalizeException should not be the same type since a new - // exception was raised by initializing the exception - Assert.IsFalse(PythonReferenceComparer.Instance.Equals(type, typeObj)); - // the message should now be the string from the throw exception during normalization - Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); - } + Assert.IsTrue(scope.TryGet("TestException", out PyObject type)); + + PyObject str = "dummy string".ToPython(); + var typePtr = new NewReference(type.Reference); + var strPtr = new NewReference(str.Reference); + var tbPtr = new NewReference(Runtime.Runtime.None.Reference); + Runtime.Runtime.PyErr_NormalizeException(ref typePtr, ref strPtr, ref tbPtr); + + using var typeObj = typePtr.MoveToPyObject(); + using var strObj = strPtr.MoveToPyObject(); + using var tbObj = tbPtr.MoveToPyObject(); + // the type returned from PyErr_NormalizeException should not be the same type since a new + // exception was raised by initializing the exception + Assert.IsFalse(PythonReferenceComparer.Instance.Equals(type, typeObj)); + // the message should now be the string from the throw exception during normalization + Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); } + } - [Test] - public void TestPythonException_Normalize_ThrowsWhenErrorSet() - { - Exceptions.SetError(Exceptions.TypeError, "Error!"); - var pythonException = PythonException.FetchCurrentRaw(); - Exceptions.SetError(Exceptions.TypeError, "Another error"); - Assert.Throws(() => pythonException.Normalize()); - Exceptions.Clear(); - } + [Test] + public void TestPythonException_Normalize_ThrowsWhenErrorSet() + { + Exceptions.SetError(Exceptions.TypeError, "Error!"); + var pythonException = PythonException.FetchCurrentRaw(); + Exceptions.SetError(Exceptions.TypeError, "Another error"); + Assert.Throws(() => pythonException.Normalize()); + Exceptions.Clear(); } } diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 77696fd96..62958d0de 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -1,130 +1,117 @@ -using System; -using System.Collections.Generic; using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class TestRuntime { - public class TestRuntime + [Test] + public static void Py_IsInitializedValue() { - [OneTimeSetUp] - public void SetUp() + if (Runtime.Runtime.Py_IsInitialized() == 1) { - // We needs to ensure that no any engines are running. - if (PythonEngine.IsInitialized) - { - PythonEngine.Shutdown(); - } - } - - [Test] - public static void Py_IsInitializedValue() - { - if (Runtime.Runtime.Py_IsInitialized() == 1) - { - Runtime.Runtime.PyGILState_Ensure(); - } - Runtime.Runtime.Py_Finalize(); - Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); - Runtime.Runtime.Py_Initialize(); - Assert.AreEqual(1, Runtime.Runtime.Py_IsInitialized()); - Runtime.Runtime.Py_Finalize(); - Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + Runtime.Runtime.PyGILState_Ensure(); } + Runtime.Runtime.Py_Finalize(); + Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + Runtime.Runtime.Py_Initialize(); + Assert.AreEqual(1, Runtime.Runtime.Py_IsInitialized()); + Runtime.Runtime.Py_Finalize(); + Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + } - [Test] - public static void RefCountTest() - { - Runtime.Runtime.Py_Initialize(); - using var op = Runtime.Runtime.PyString_FromString("FooBar"); + [Test] + public static void RefCountTest() + { + Runtime.Runtime.Py_Initialize(); + using var op = Runtime.Runtime.PyString_FromString("FooBar"); - // New object RefCount should be one - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.BorrowOrThrow())); + // New object RefCount should be one + Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.BorrowOrThrow())); - // Checking refcount didn't change refcount - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); + // Checking refcount didn't change refcount + Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); - // Borrowing a reference doesn't increase refcount - BorrowedReference p = op.Borrow(); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + // Borrowing a reference doesn't increase refcount + BorrowedReference p = op.Borrow(); + Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); - // Py_IncRef/Py_DecRef increase and decrease RefCount - Runtime.Runtime.Py_IncRef(op.Borrow()); - Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); - Runtime.Runtime.Py_DecRef(StolenReference.DangerousFromPointer(op.DangerousGetAddress())); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + // Py_IncRef/Py_DecRef increase and decrease RefCount + Runtime.Runtime.Py_IncRef(op.Borrow()); + Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); + Runtime.Runtime.Py_DecRef(StolenReference.DangerousFromPointer(op.DangerousGetAddress())); + Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); - // XIncref/XDecref increase and decrease RefCount + // XIncref/XDecref increase and decrease RefCount #pragma warning disable CS0618 // Type or member is obsolete. We are testing corresponding members - Runtime.Runtime.XIncref(p); - Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); - Runtime.Runtime.XDecref(op.Steal()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Runtime.Runtime.XIncref(p); + Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); + Runtime.Runtime.XDecref(op.Steal()); + Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); #pragma warning restore CS0618 // Type or member is obsolete - op.Dispose(); + op.Dispose(); - Runtime.Runtime.Py_Finalize(); - } + Runtime.Runtime.Py_Finalize(); + } + + [Test] + public static void PyCheck_Iter_PyObject_IsIterable_Test() + { + Runtime.Runtime.Py_Initialize(); + + Runtime.Native.ABI.Initialize(Runtime.Runtime.PyVersion); - [Test] - public static void PyCheck_Iter_PyObject_IsIterable_Test() + // Tests that a python list is an iterable, but not an iterator + using (var pyListNew = Runtime.Runtime.PyList_New(0)) { - Runtime.Runtime.Py_Initialize(); - - Runtime.Native.ABI.Initialize(Runtime.Runtime.PyVersion); - - // Tests that a python list is an iterable, but not an iterator - using (var pyListNew = Runtime.Runtime.PyList_New(0)) - { - BorrowedReference pyList = pyListNew.BorrowOrThrow(); - Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyList)); - Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyList)); - - // Tests that a python list iterator is both an iterable and an iterator - using var pyListIter = Runtime.Runtime.PyObject_GetIter(pyList); - Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyListIter.BorrowOrThrow())); - Assert.IsTrue(Runtime.Runtime.PyIter_Check(pyListIter.Borrow())); - } - - // Tests that a python float is neither an iterable nor an iterator - using (var pyFloat = Runtime.Runtime.PyFloat_FromDouble(2.73)) - { - Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(pyFloat.BorrowOrThrow())); - Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyFloat.Borrow())); - } + BorrowedReference pyList = pyListNew.BorrowOrThrow(); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyList)); + Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyList)); + + // Tests that a python list iterator is both an iterable and an iterator + using var pyListIter = Runtime.Runtime.PyObject_GetIter(pyList); + Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyListIter.BorrowOrThrow())); + Assert.IsTrue(Runtime.Runtime.PyIter_Check(pyListIter.Borrow())); + } - Runtime.Runtime.Py_Finalize(); + // Tests that a python float is neither an iterable nor an iterator + using (var pyFloat = Runtime.Runtime.PyFloat_FromDouble(2.73)) + { + Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(pyFloat.BorrowOrThrow())); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyFloat.Borrow())); } - [Test] - public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test() + Runtime.Runtime.Py_Finalize(); + } + + [Test] + public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test() + { + Runtime.Runtime.Py_Initialize(); + + Runtime.Native.ABI.Initialize(Runtime.Runtime.PyVersion); + + try { - Runtime.Runtime.Py_Initialize(); - - Runtime.Native.ABI.Initialize(Runtime.Runtime.PyVersion); - - try - { - // Create an instance of threading.Lock, which is one of the very few types that does not have the - // TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check. - using var threading = Runtime.Runtime.PyImport_ImportModule("threading"); - BorrowedReference threadingDict = Runtime.Runtime.PyModule_GetDict(threading.BorrowOrThrow()); - Exceptions.ErrorCheck(threadingDict); - BorrowedReference lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock"); - if (lockType.IsNull) - throw PythonException.ThrowLastAsClrException(); - - using var args = Runtime.Runtime.PyTuple_New(0); - using var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, args.Borrow()); - - Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance.BorrowOrThrow())); - Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance.Borrow())); - } - finally - { - Runtime.Runtime.Py_Finalize(); - } + // Create an instance of threading.Lock, which is one of the very few types that does not have the + // TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check. + using var threading = Runtime.Runtime.PyImport_ImportModule("threading"); + BorrowedReference threadingDict = Runtime.Runtime.PyModule_GetDict(threading.BorrowOrThrow()); + Exceptions.ErrorCheck(threadingDict); + BorrowedReference lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock"); + if (lockType.IsNull) + throw PythonException.ThrowLastAsClrException(); + + using var args = Runtime.Runtime.PyTuple_New(0); + using var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, args.Borrow()); + + Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance.BorrowOrThrow())); + Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance.Borrow())); + } + finally + { + Runtime.Runtime.Py_Finalize(); } } } diff --git a/src/embed_tests/dynamic.cs b/src/embed_tests/dynamic.cs index 6e3bfc4cb..f23a96718 100644 --- a/src/embed_tests/dynamic.cs +++ b/src/embed_tests/dynamic.cs @@ -4,136 +4,124 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class DynamicTest : BaseFixture { - public class DynamicTest + /// + /// Set the attribute of a PyObject with a .NET object. + /// + [Test] + public void AssignObject() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } + var stream = new StringBuilder(); + dynamic sys = Py.Import("sys"); + sys.testattr = stream; + // Check whether there are the same object. + dynamic _stream = sys.testattr.AsManagedObject(typeof(StringBuilder)); + Assert.AreEqual(_stream, stream); + + PythonEngine.RunSimpleString( + "import sys\n" + + "sys.testattr.Append('Hello!')\n"); + Assert.AreEqual(stream.ToString(), "Hello!"); + } - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + /// + /// Set the attribute of a PyObject to null. + /// + [Test] + public void AssignNone() + { + dynamic sys = Py.Import("sys"); + sys.testattr = new StringBuilder(); + Assert.IsNotNull(sys.testattr); - /// - /// Set the attribute of a PyObject with a .NET object. - /// - [Test] - public void AssignObject() - { - var stream = new StringBuilder(); - dynamic sys = Py.Import("sys"); - sys.testattr = stream; - // Check whether there are the same object. - dynamic _stream = sys.testattr.AsManagedObject(typeof(StringBuilder)); - Assert.AreEqual(_stream, stream); - - PythonEngine.RunSimpleString( - "import sys\n" + - "sys.testattr.Append('Hello!')\n"); - Assert.AreEqual(stream.ToString(), "Hello!"); - } + sys.testattr = null; + Assert.IsNull(sys.testattr); + } - /// - /// Set the attribute of a PyObject to null. - /// - [Test] - public void AssignNone() + /// + /// Check whether we can get the attr of a python object when the + /// value of attr is a PyObject. + /// + /// + /// FIXME: Issue on Travis PY27: Error : Python.EmbeddingTest.dynamicTest.AssignPyObject + /// Python.Runtime.PythonException : ImportError : /home/travis/virtualenv/python2.7.9/lib/python2.7/lib-dynload/_io.so: undefined symbol: _PyLong_AsInt + /// + [Test] + public void AssignPyObject() + { + if (Environment.GetEnvironmentVariable("TRAVIS") == "true" && + Environment.GetEnvironmentVariable("TRAVIS_PYTHON_VERSION") == "2.7") { - dynamic sys = Py.Import("sys"); - sys.testattr = new StringBuilder(); - Assert.IsNotNull(sys.testattr); - - sys.testattr = null; - Assert.IsNull(sys.testattr); + Assert.Ignore("Fails on Travis/PY27: ImportError: ... undefined symbol: _PyLong_AsInt"); } - /// - /// Check whether we can get the attr of a python object when the - /// value of attr is a PyObject. - /// - /// - /// FIXME: Issue on Travis PY27: Error : Python.EmbeddingTest.dynamicTest.AssignPyObject - /// Python.Runtime.PythonException : ImportError : /home/travis/virtualenv/python2.7.9/lib/python2.7/lib-dynload/_io.so: undefined symbol: _PyLong_AsInt - /// - [Test] - public void AssignPyObject() - { - if (Environment.GetEnvironmentVariable("TRAVIS") == "true" && - Environment.GetEnvironmentVariable("TRAVIS_PYTHON_VERSION") == "2.7") - { - Assert.Ignore("Fails on Travis/PY27: ImportError: ... undefined symbol: _PyLong_AsInt"); - } - - dynamic sys = Py.Import("sys"); - dynamic io = Py.Import("io"); - sys.testattr = io.StringIO(); - dynamic bb = sys.testattr; // Get the PyObject - bb.write("Hello!"); - Assert.AreEqual(bb.getvalue().ToString(), "Hello!"); - } + dynamic sys = Py.Import("sys"); + dynamic io = Py.Import("io"); + sys.testattr = io.StringIO(); + dynamic bb = sys.testattr; // Get the PyObject + bb.write("Hello!"); + Assert.AreEqual(bb.getvalue().ToString(), "Hello!"); + } - /// - /// Pass the .NET object in Python side. - /// - [Test] - public void PassObjectInPython() - { - var stream = new StringBuilder(); - dynamic sys = Py.Import("sys"); - sys.testattr1 = stream; - - // Pass the .NET object in Python side - PythonEngine.RunSimpleString( - "import sys\n" + - "sys.testattr2 = sys.testattr1\n" - ); - - // Compare in Python - PythonEngine.RunSimpleString( - "import sys\n" + - "sys.testattr3 = sys.testattr1 is sys.testattr2\n" - ); - Assert.AreEqual(sys.testattr3.ToString(), "True"); - - // Compare in .NET - Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); - } + /// + /// Pass the .NET object in Python side. + /// + [Test] + public void PassObjectInPython() + { + var stream = new StringBuilder(); + dynamic sys = Py.Import("sys"); + sys.testattr1 = stream; + + // Pass the .NET object in Python side + PythonEngine.RunSimpleString( + "import sys\n" + + "sys.testattr2 = sys.testattr1\n" + ); + + // Compare in Python + PythonEngine.RunSimpleString( + "import sys\n" + + "sys.testattr3 = sys.testattr1 is sys.testattr2\n" + ); + Assert.AreEqual(sys.testattr3.ToString(), "True"); + + // Compare in .NET + Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); + } - /// - /// Pass the PyObject in .NET side - /// - [Test] - public void PassPyObjectInNet() - { - var stream = new StringBuilder(); - dynamic sys = Py.Import("sys"); - sys.testattr1 = stream; - sys.testattr2 = sys.testattr1; + /// + /// Pass the PyObject in .NET side + /// + [Test] + public void PassPyObjectInNet() + { + var stream = new StringBuilder(); + dynamic sys = Py.Import("sys"); + sys.testattr1 = stream; + sys.testattr2 = sys.testattr1; - // Compare in Python - PythonEngine.RunSimpleString( - "import sys\n" + - "sys.testattr3 = sys.testattr1 is sys.testattr2\n" - ); + // Compare in Python + PythonEngine.RunSimpleString( + "import sys\n" + + "sys.testattr3 = sys.testattr1 is sys.testattr2\n" + ); - Assert.AreEqual(sys.testattr3.ToString(), "True"); + Assert.AreEqual(sys.testattr3.ToString(), "True"); - // Compare in .NET - Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); - } + // Compare in .NET + Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); + } - // regression test for https://github.com/pythonnet/pythonnet/issues/1848 - [Test] - public void EnumEquality() - { - using var scope = Py.CreateScope(); - scope.Exec(@" + // regression test for https://github.com/pythonnet/pythonnet/issues/1848 + [Test] + public void EnumEquality() + { + using var scope = Py.CreateScope(); + scope.Exec(@" import enum class MyEnum(enum.IntEnum): @@ -141,23 +129,22 @@ class MyEnum(enum.IntEnum): ERROR = 2 def get_status(): - return MyEnum.OK + return MyEnum.OK " ); - dynamic MyEnum = scope.Get("MyEnum"); - dynamic status = scope.Get("get_status").Invoke(); - Assert.IsTrue(status == MyEnum.OK); - } + dynamic MyEnum = scope.Get("MyEnum"); + dynamic status = scope.Get("get_status").Invoke(); + Assert.IsTrue(status == MyEnum.OK); + } - // regression test for https://github.com/pythonnet/pythonnet/issues/1680 - [Test] - public void ForEach() - { - dynamic pyList = PythonEngine.Eval("[1,2,3]"); - var list = new List(); - foreach (int item in pyList) - list.Add(item); - } + // regression test for https://github.com/pythonnet/pythonnet/issues/1680 + [Test] + public void ForEach() + { + dynamic pyList = PythonEngine.Eval("[1,2,3]"); + var list = new List(); + foreach (int item in pyList) + list.Add(item); } } diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index b828d5315..f8caa944c 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -5,99 +5,96 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +/// +/// Test Import unittests and regressions +/// +/// +/// The required directory structure was added to .\pythonnet\src\embed_tests\fixtures\ directory: +/// + PyImportTest/ +/// | - __init__.py +/// | + test/ +/// | | - __init__.py +/// | | - one.py +/// +public class PyImportTest : BaseFixture { - /// - /// Test Import unittests and regressions - /// - /// - /// Keeping in old-style SetUp/TearDown due to required SetUp. - /// The required directory structure was added to .\pythonnet\src\embed_tests\fixtures\ directory: - /// + PyImportTest/ - /// | - __init__.py - /// | + test/ - /// | | - __init__.py - /// | | - one.py - /// - public class PyImportTest + [OneTimeSetUp] + public void SetUp() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - - /* Append the tests directory to sys.path - * using reflection to circumvent the private - * modifiers placed on most Runtime methods. */ - string testPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); - TestContext.Out.WriteLine(testPath); + /* Append the tests directory to sys.path + * using reflection to circumvent the private + * modifiers placed on most Runtime methods. */ + string testPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); + TestContext.Out.WriteLine(testPath); - using var str = Runtime.Runtime.PyString_FromString(testPath); - Assert.IsFalse(str.IsNull()); - BorrowedReference path = Runtime.Runtime.PySys_GetObject("path"); - Assert.IsFalse(path.IsNull); - Runtime.Runtime.PyList_Append(path, str.Borrow()); - } + using var str = Runtime.Runtime.PyString_FromString(testPath); + Assert.IsFalse(str.IsNull()); + BorrowedReference path = Runtime.Runtime.PySys_GetObject("path"); + Assert.IsFalse(path.IsNull); + Runtime.Runtime.PyList_Append(path, str.Borrow()); + } - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } + [OneTimeTearDown] + public void Dispose() + { + using var path = new PyList(Runtime.Runtime.PySys_GetObject("path")); + path.InvokeMethod("pop"); + } - /// - /// Test subdirectory import - /// - [Test] - public void TestDottedName() - { - var module = PyModule.Import("PyImportTest.test.one"); - Assert.IsNotNull(module); - } + /// + /// Test subdirectory import + /// + [Test] + public void TestDottedName() + { + var module = PyModule.Import("PyImportTest.test.one"); + Assert.IsNotNull(module); + } - /// - /// Tests that sys.args is set. If it wasn't exception would be raised. - /// - [Test] - public void TestSysArgsImportException() - { - var module = PyModule.Import("PyImportTest.sysargv"); - Assert.IsNotNull(module); - } + /// + /// Tests that sys.args is set. If it wasn't exception would be raised. + /// + [Test] + public void TestSysArgsImportException() + { + var module = PyModule.Import("PyImportTest.sysargv"); + Assert.IsNotNull(module); + } - /// - /// Test Global Variable casting. GH#420 - /// - [Test] - public void TestCastGlobalVar() - { - dynamic foo = Py.Import("PyImportTest.cast_global_var"); - Assert.AreEqual("1", foo.FOO.ToString()); - Assert.AreEqual("1", foo.test_foo().ToString()); + /// + /// Test Global Variable casting. GH#420 + /// + [Test] + public void TestCastGlobalVar() + { + dynamic foo = Py.Import("PyImportTest.cast_global_var"); + Assert.AreEqual("1", foo.FOO.ToString()); + Assert.AreEqual("1", foo.test_foo().ToString()); - foo.FOO = 2; - Assert.AreEqual("2", foo.FOO.ToString()); - Assert.AreEqual("2", foo.test_foo().ToString()); - } + foo.FOO = 2; + Assert.AreEqual("2", foo.FOO.ToString()); + Assert.AreEqual("2", foo.test_foo().ToString()); + } - [Test] - public void BadAssembly() + [Test] + public void BadAssembly() + { + string path = Runtime.Runtime.PythonDLL; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - string path = Runtime.Runtime.PythonDLL; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - path = @"C:\Windows\System32\kernel32.dll"; - } + path = @"C:\Windows\System32\kernel32.dll"; + } - Assert.IsTrue(File.Exists(path), $"Test DLL {path} does not exist!"); + Assert.IsTrue(File.Exists(path), $"Test DLL {path} does not exist!"); - string code = $@" + string code = $@" import clr clr.AddReference('{path}') "; - Assert.Throws(() => PythonEngine.Exec(code)); - } + Assert.Throws(() => PythonEngine.Exec(code)); } } @@ -105,9 +102,9 @@ import clr // initialize fails if a class derived from IEnumerable is in global namespace public class PublicEnumerator : System.Collections.IEnumerable { - public System.Collections.IEnumerator GetEnumerator() - { - return null; - } +public System.Collections.IEnumerator GetEnumerator() +{ + return null; +} } diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index 25dafb686..0489e77c5 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -1,157 +1,155 @@ -using System; using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class PyInitializeTest { - public class PyInitializeTest + /// + /// Tests issue with multiple simple Initialize/Shutdowns. + /// Fixed by #343 + /// + [Test] + public static void StartAndStopTwice() { - /// - /// Tests issue with multiple simple Initialize/Shutdowns. - /// Fixed by #343 - /// - [Test] - public static void StartAndStopTwice() - { - PythonEngine.Initialize(); - PythonEngine.Shutdown(); + PythonEngine.Initialize(); + PythonEngine.Shutdown(); - PythonEngine.Initialize(); - PythonEngine.Shutdown(); - } + PythonEngine.Initialize(); + PythonEngine.Shutdown(); + } - [Test] - public static void LoadDefaultArgs() + [Test] + public static void LoadDefaultArgs() + { + using (new PythonEngine()) { - using (new PythonEngine()) + using(var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) { - using(var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) - { - Assert.AreNotEqual(0, argv.Length()); - } + Assert.AreNotEqual(0, argv.Length()); } } + } - [Test] - public static void LoadSpecificArgs() + [Test] + public static void LoadSpecificArgs() + { + var args = new[] { "test1", "test2" }; + using (new PythonEngine(args)) { - var args = new[] { "test1", "test2" }; - using (new PythonEngine(args)) + using (var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) { - using (var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) - { - using var v0 = argv[0]; - using var v1 = argv[1]; - Assert.AreEqual(args[0], v0.ToString()); - Assert.AreEqual(args[1], v1.ToString()); - } + using var v0 = argv[0]; + using var v1 = argv[1]; + Assert.AreEqual(args[0], v0.ToString()); + Assert.AreEqual(args[1], v1.ToString()); } } + } - // regression test for https://github.com/pythonnet/pythonnet/issues/1561 - [Test] - public void ImportClassShutdownRefcount() - { - PythonEngine.Initialize(); + // regression test for https://github.com/pythonnet/pythonnet/issues/1561 + [Test] + public void ImportClassShutdownRefcount() + { + PythonEngine.Initialize(); - PyObject ns = Py.Import(typeof(ImportClassShutdownRefcountClass).Namespace); - PyObject cls = ns.GetAttr(nameof(ImportClassShutdownRefcountClass)); - BorrowedReference clsRef = cls.Reference; + PyObject ns = Py.Import(typeof(ImportClassShutdownRefcountClass).Namespace); + PyObject cls = ns.GetAttr(nameof(ImportClassShutdownRefcountClass)); + BorrowedReference clsRef = cls.Reference; #pragma warning disable CS0618 // Type or member is obsolete - cls.Leak(); + cls.Leak(); #pragma warning restore CS0618 // Type or member is obsolete - ns.Dispose(); + ns.Dispose(); - Assert.Less(Runtime.Runtime.Refcount32(clsRef), 256); + Assert.Less(Runtime.Runtime.Refcount32(clsRef), 256); - PythonEngine.Shutdown(); - Assert.Greater(Runtime.Runtime.Refcount32(clsRef), 0); - } + PythonEngine.Shutdown(); + Assert.Greater(Runtime.Runtime.Refcount32(clsRef), 0); + } - /// - /// Failing test demonstrating current issue with OverflowException (#376) - /// and ArgumentException issue after that one is fixed. - /// More complex version of StartAndStopTwice test - /// - [Test] - [Ignore("GH#376: System.OverflowException : Arithmetic operation resulted in an overflow")] - //[Ignore("System.ArgumentException : Cannot pass a GCHandle across AppDomains")] - public void ReInitialize() + /// + /// Failing test demonstrating current issue with OverflowException (#376) + /// and ArgumentException issue after that one is fixed. + /// More complex version of StartAndStopTwice test + /// + [Test] + [Ignore("GH#376: System.OverflowException : Arithmetic operation resulted in an overflow")] + //[Ignore("System.ArgumentException : Cannot pass a GCHandle across AppDomains")] + public void ReInitialize() + { + var code = "from System import Int32\n"; + PythonEngine.Initialize(); + using (Py.GIL()) { - var code = "from System import Int32\n"; - PythonEngine.Initialize(); - using (Py.GIL()) - { - // Import any class or struct from .NET - PythonEngine.RunSimpleString(code); - } - PythonEngine.Shutdown(); - - PythonEngine.Initialize(); - using (Py.GIL()) - { - // Import a class/struct from .NET - // This class/struct must be imported during the first initialization. - PythonEngine.RunSimpleString(code); - // Create an instance of the class/struct - // System.OverflowException Exception will be raised here. - // If replacing int with Int64, OverflowException will be replaced with AppDomain exception. - PythonEngine.RunSimpleString("Int32(1)"); - } - PythonEngine.Shutdown(); + // Import any class or struct from .NET + PythonEngine.RunSimpleString(code); } + PythonEngine.Shutdown(); - /// - /// Helper for testing the shutdown handlers. - /// - int shutdown_count = 0; - void OnShutdownIncrement() - { - shutdown_count++; - } - void OnShutdownDouble() + PythonEngine.Initialize(); + using (Py.GIL()) { - shutdown_count *= 2; + // Import a class/struct from .NET + // This class/struct must be imported during the first initialization. + PythonEngine.RunSimpleString(code); + // Create an instance of the class/struct + // System.OverflowException Exception will be raised here. + // If replacing int with Int64, OverflowException will be replaced with AppDomain exception. + PythonEngine.RunSimpleString("Int32(1)"); } + PythonEngine.Shutdown(); + } - /// - /// Test the shutdown handlers. - /// - [Test] - public void ShutdownHandlers() - { - // Test we can run one shutdown handler. - shutdown_count = 0; - PythonEngine.Initialize(); - PythonEngine.AddShutdownHandler(OnShutdownIncrement); - PythonEngine.Shutdown(); - Assert.That(shutdown_count, Is.EqualTo(1)); - - // Test we can run multiple shutdown handlers in the right order. - shutdown_count = 4; - PythonEngine.Initialize(); - PythonEngine.AddShutdownHandler(OnShutdownIncrement); - PythonEngine.AddShutdownHandler(OnShutdownDouble); - PythonEngine.Shutdown(); - // Correct: 4 * 2 + 1 = 9 - // Wrong: (4 + 1) * 2 = 10 - Assert.That(shutdown_count, Is.EqualTo(9)); - - // Test we can remove shutdown handlers, handling duplicates. - shutdown_count = 4; - PythonEngine.Initialize(); - PythonEngine.AddShutdownHandler(OnShutdownIncrement); - PythonEngine.AddShutdownHandler(OnShutdownIncrement); - PythonEngine.AddShutdownHandler(OnShutdownDouble); - PythonEngine.AddShutdownHandler(OnShutdownIncrement); - PythonEngine.AddShutdownHandler(OnShutdownDouble); - PythonEngine.RemoveShutdownHandler(OnShutdownDouble); - PythonEngine.Shutdown(); - // Correct: (4 + 1) * 2 + 1 + 1 = 12 - // Wrong: (4 * 2) + 1 + 1 + 1 = 11 - Assert.That(shutdown_count, Is.EqualTo(12)); - } + /// + /// Helper for testing the shutdown handlers. + /// + int shutdown_count = 0; + void OnShutdownIncrement() + { + shutdown_count++; } + void OnShutdownDouble() + { + shutdown_count *= 2; + } + + /// + /// Test the shutdown handlers. + /// + [Test] + public void ShutdownHandlers() + { + // Test we can run one shutdown handler. + shutdown_count = 0; + PythonEngine.Initialize(); + PythonEngine.AddShutdownHandler(OnShutdownIncrement); + PythonEngine.Shutdown(); + Assert.That(shutdown_count, Is.EqualTo(1)); - public class ImportClassShutdownRefcountClass { } + // Test we can run multiple shutdown handlers in the right order. + shutdown_count = 4; + PythonEngine.Initialize(); + PythonEngine.AddShutdownHandler(OnShutdownIncrement); + PythonEngine.AddShutdownHandler(OnShutdownDouble); + PythonEngine.Shutdown(); + // Correct: 4 * 2 + 1 = 9 + // Wrong: (4 + 1) * 2 = 10 + Assert.That(shutdown_count, Is.EqualTo(9)); + + // Test we can remove shutdown handlers, handling duplicates. + shutdown_count = 4; + PythonEngine.Initialize(); + PythonEngine.AddShutdownHandler(OnShutdownIncrement); + PythonEngine.AddShutdownHandler(OnShutdownIncrement); + PythonEngine.AddShutdownHandler(OnShutdownDouble); + PythonEngine.AddShutdownHandler(OnShutdownIncrement); + PythonEngine.AddShutdownHandler(OnShutdownDouble); + PythonEngine.RemoveShutdownHandler(OnShutdownDouble); + PythonEngine.Shutdown(); + // Correct: (4 + 1) * 2 + 1 + 1 = 12 + // Wrong: (4 * 2) + 1 + 1 + 1 = 11 + Assert.That(shutdown_count, Is.EqualTo(12)); + } } + +public class ImportClassShutdownRefcountClass { } diff --git a/src/embed_tests/pyrunstring.cs b/src/embed_tests/pyrunstring.cs index 57c133c00..ab3cf0907 100644 --- a/src/embed_tests/pyrunstring.cs +++ b/src/embed_tests/pyrunstring.cs @@ -1,65 +1,52 @@ -using System; using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest; + +public class RunStringTest : BaseFixture { - public class RunStringTest + [Test] + public void TestRunSimpleString() { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - - [Test] - public void TestRunSimpleString() - { - int aa = PythonEngine.RunSimpleString("import sys"); - Assert.AreEqual(0, aa); + int aa = PythonEngine.RunSimpleString("import sys"); + Assert.AreEqual(0, aa); - int bb = PythonEngine.RunSimpleString("import 1234"); - Assert.AreEqual(-1, bb); - } - - [Test] - public void TestEval() - { - dynamic sys = Py.Import("sys"); - sys.attr1 = 100; - var locals = new PyDict(); - locals.SetItem("sys", sys); - locals.SetItem("a", new PyInt(10)); - - object b = PythonEngine.Eval("sys.attr1 + a + 1", null, locals) - .AsManagedObject(typeof(int)); - Assert.AreEqual(111, b); - } + int bb = PythonEngine.RunSimpleString("import 1234"); + Assert.AreEqual(-1, bb); + } - [Test] - public void TestExec() - { - dynamic sys = Py.Import("sys"); - sys.attr1 = 100; - var locals = new PyDict(); - locals.SetItem("sys", sys); - locals.SetItem("a", new PyInt(10)); + [Test] + public void TestEval() + { + dynamic sys = Py.Import("sys"); + sys.attr1 = 100; + var locals = new PyDict(); + locals.SetItem("sys", sys); + locals.SetItem("a", new PyInt(10)); + + object b = PythonEngine.Eval("sys.attr1 + a + 1", null, locals) + .AsManagedObject(typeof(int)); + Assert.AreEqual(111, b); + } - PythonEngine.Exec("c = sys.attr1 + a + 1", null, locals); - object c = locals.GetItem("c").AsManagedObject(typeof(int)); - Assert.AreEqual(111, c); - } + [Test] + public void TestExec() + { + dynamic sys = Py.Import("sys"); + sys.attr1 = 100; + var locals = new PyDict(); + locals.SetItem("sys", sys); + locals.SetItem("a", new PyInt(10)); + + PythonEngine.Exec("c = sys.attr1 + a + 1", null, locals); + object c = locals.GetItem("c").AsManagedObject(typeof(int)); + Assert.AreEqual(111, c); + } - [Test] - public void TestExec2() - { - string code = @" + [Test] + public void TestExec2() + { + string code = @" class Test1(): pass @@ -68,7 +55,6 @@ def __init__(self): Test1() Test2()"; - PythonEngine.Exec(code); - } + PythonEngine.Exec(code); } } From df53d68fc387937aeef1aacecd789ed1f43159d7 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 Sep 2023 14:18:46 +0200 Subject: [PATCH 4/6] Fix accidental move of Reset call --- src/runtime/Runtime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index ac95e80f2..0d65c9dc8 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -312,9 +312,9 @@ internal static void Shutdown(bool allowReload) Debug.Assert(everythingSeemsCollected); } - ResetPyMembers(); Finalizer.Shutdown(); InternString.Shutdown(); + ResetPyMembers(); if (!HostedInPython) { From 4fe3757636be91e9fe52c3a7029c089f79146e12 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 Sep 2023 14:19:06 +0200 Subject: [PATCH 5/6] Fix refactored tests --- src/embed_tests/BaseFixture.cs | 2 +- src/embed_tests/Codecs.cs | 6 +++ src/embed_tests/TestDomainReload.cs | 5 +-- src/embed_tests/TestFinalizer.cs | 6 +-- src/embed_tests/TestPythonEngineProperties.cs | 26 ++++++------ src/embed_tests/pyinitialize.cs | 41 +++++++++---------- 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/embed_tests/BaseFixture.cs b/src/embed_tests/BaseFixture.cs index 10605d00a..e09172bce 100644 --- a/src/embed_tests/BaseFixture.cs +++ b/src/embed_tests/BaseFixture.cs @@ -14,7 +14,7 @@ public void BaseSetup() [OneTimeTearDown] public void BaseTearDown() { - PyObjectConversions.Reset(); PythonEngine.Shutdown(allowReload: true); + PyObjectConversions.Reset(); } } diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 7d8048578..a2149d0b2 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -9,6 +9,12 @@ namespace Python.EmbeddingTest; public class Codecs : BaseFixture { + [SetUp] + public void SetUp() + { + PyObjectConversions.Reset(); + } + [Test] public void TupleConversionsGeneric() { diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs index a0f9b63eb..712e26c92 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/TestDomainReload.cs @@ -367,14 +367,13 @@ public static void InitPython(string dllName) public static void ShutdownPython() { PythonEngine.EndAllowThreads(_state); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } public static void ShutdownPythonCompletely() { PythonEngine.EndAllowThreads(_state); - - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } static void OnDomainUnload(object sender, EventArgs e) diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index ded49b240..cf01c60f0 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -26,7 +26,7 @@ public void SetUp() public void TearDown() { Finalizer.Instance.Threshold = _oldThreshold; - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } private static void FullGCCollect() @@ -89,7 +89,7 @@ public void CollectBasicObject() } [Test] - [Obsolete("GC tests are not guaranteed")] + // [Obsolete("GC tests are not guaranteed")] public void CollectOnShutdown() { IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); @@ -100,7 +100,7 @@ public void CollectOnShutdown() Assert.IsTrue(garbage.Contains(op), "Garbage should contains the collected object"); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); garbage = Finalizer.Instance.GetCollectedObjects(); if (garbage.Count > 0) diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index 1cdebdbfa..63fa09e62 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -79,7 +79,7 @@ public static void GetPythonPathDefault() string s = PythonEngine.PythonPath; StringAssert.Contains("python", s.ToLower()); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } [Test] @@ -89,7 +89,7 @@ public static void GetProgramNameDefault() string s = PythonEngine.ProgramName; Assert.NotNull(s); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } /// @@ -105,7 +105,7 @@ public static void GetPythonHomeDefault() string enginePythonHome = PythonEngine.PythonHome; Assert.AreEqual(envPythonHome, enginePythonHome); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } [Test] @@ -113,7 +113,7 @@ public void SetPythonHome() { PythonEngine.Initialize(); var pythonHomeBackup = PythonEngine.PythonHome; - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); if (pythonHomeBackup == "") Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); @@ -124,7 +124,7 @@ public void SetPythonHome() PythonEngine.Initialize(); Assert.AreEqual(pythonHome, PythonEngine.PythonHome); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); // Restoring valid pythonhome. PythonEngine.PythonHome = pythonHomeBackup; @@ -135,7 +135,7 @@ public void SetPythonHomeTwice() { PythonEngine.Initialize(); var pythonHomeBackup = PythonEngine.PythonHome; - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); if (pythonHomeBackup == "") Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); @@ -147,7 +147,7 @@ public void SetPythonHomeTwice() PythonEngine.Initialize(); Assert.AreEqual(pythonHome, PythonEngine.PythonHome); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); PythonEngine.PythonHome = pythonHomeBackup; } @@ -161,7 +161,7 @@ public void SetPythonHomeEmptyString() var backup = PythonEngine.PythonHome; if (backup == "") { - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); } PythonEngine.PythonHome = ""; @@ -169,7 +169,7 @@ public void SetPythonHomeEmptyString() Assert.AreEqual("", PythonEngine.PythonHome); PythonEngine.PythonHome = backup; - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } [Test] @@ -177,7 +177,7 @@ public void SetProgramName() { if (PythonEngine.IsInitialized) { - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } var programNameBackup = PythonEngine.ProgramName; @@ -188,7 +188,7 @@ public void SetProgramName() PythonEngine.Initialize(); Assert.AreEqual(programName, PythonEngine.ProgramName); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); PythonEngine.ProgramName = programNameBackup; } @@ -220,7 +220,7 @@ public void SetPythonPath() // See https://docs.python.org/3/c-api/init.html#c.Py_GetPath // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path. - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); PythonEngine.PythonPath = path; PythonEngine.Initialize(); @@ -228,6 +228,6 @@ public void SetPythonPath() Assert.AreEqual(path, PythonEngine.PythonPath); if (importShouldSucceed) Py.Import(moduleName); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } } diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index 0489e77c5..e90bc1fd1 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -13,38 +13,36 @@ public class PyInitializeTest public static void StartAndStopTwice() { PythonEngine.Initialize(); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); PythonEngine.Initialize(); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } [Test] public static void LoadDefaultArgs() { - using (new PythonEngine()) + PythonEngine.Initialize(); + using (var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) { - using(var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) - { - Assert.AreNotEqual(0, argv.Length()); - } + Assert.AreNotEqual(0, argv.Length()); } + PythonEngine.Shutdown(allowReload: true); } [Test] public static void LoadSpecificArgs() { var args = new[] { "test1", "test2" }; - using (new PythonEngine(args)) + PythonEngine.Initialize(args); + using (var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) { - using (var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) - { - using var v0 = argv[0]; - using var v1 = argv[1]; - Assert.AreEqual(args[0], v0.ToString()); - Assert.AreEqual(args[1], v1.ToString()); - } + using var v0 = argv[0]; + using var v1 = argv[1]; + Assert.AreEqual(args[0], v0.ToString()); + Assert.AreEqual(args[1], v1.ToString()); } + PythonEngine.Shutdown(allowReload: true); } // regression test for https://github.com/pythonnet/pythonnet/issues/1561 @@ -63,7 +61,7 @@ public void ImportClassShutdownRefcount() Assert.Less(Runtime.Runtime.Refcount32(clsRef), 256); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); Assert.Greater(Runtime.Runtime.Refcount32(clsRef), 0); } @@ -73,7 +71,6 @@ public void ImportClassShutdownRefcount() /// More complex version of StartAndStopTwice test /// [Test] - [Ignore("GH#376: System.OverflowException : Arithmetic operation resulted in an overflow")] //[Ignore("System.ArgumentException : Cannot pass a GCHandle across AppDomains")] public void ReInitialize() { @@ -84,7 +81,7 @@ public void ReInitialize() // Import any class or struct from .NET PythonEngine.RunSimpleString(code); } - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); PythonEngine.Initialize(); using (Py.GIL()) @@ -97,7 +94,7 @@ public void ReInitialize() // If replacing int with Int64, OverflowException will be replaced with AppDomain exception. PythonEngine.RunSimpleString("Int32(1)"); } - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); } /// @@ -123,7 +120,7 @@ public void ShutdownHandlers() shutdown_count = 0; PythonEngine.Initialize(); PythonEngine.AddShutdownHandler(OnShutdownIncrement); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); Assert.That(shutdown_count, Is.EqualTo(1)); // Test we can run multiple shutdown handlers in the right order. @@ -131,7 +128,7 @@ public void ShutdownHandlers() PythonEngine.Initialize(); PythonEngine.AddShutdownHandler(OnShutdownIncrement); PythonEngine.AddShutdownHandler(OnShutdownDouble); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); // Correct: 4 * 2 + 1 = 9 // Wrong: (4 + 1) * 2 = 10 Assert.That(shutdown_count, Is.EqualTo(9)); @@ -145,7 +142,7 @@ public void ShutdownHandlers() PythonEngine.AddShutdownHandler(OnShutdownIncrement); PythonEngine.AddShutdownHandler(OnShutdownDouble); PythonEngine.RemoveShutdownHandler(OnShutdownDouble); - PythonEngine.Shutdown(); + PythonEngine.Shutdown(allowReload: true); // Correct: (4 + 1) * 2 + 1 + 1 = 12 // Wrong: (4 * 2) + 1 + 1 + 1 = 11 Assert.That(shutdown_count, Is.EqualTo(12)); From 7fc2e1d0af6d5e329bd0e0a8d73a1f3608488dea Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 Sep 2023 18:58:24 +0200 Subject: [PATCH 6/6] Only recover stashed data if the domain is actually changed --- src/embed_tests/TestDomainReload.cs | 8 +---- src/runtime/Runtime.cs | 24 ++++++++------ .../StateSerialization/PythonNetState.cs | 1 + src/runtime/StateSerialization/RuntimeData.cs | 32 +++++++++++++++---- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs index 712e26c92..b2b5fa85a 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/TestDomainReload.cs @@ -303,7 +303,7 @@ static void RunDomainReloadSteps() where T1 : CrossCaller where T2 : Cro var caller = CreateInstanceInstanceAndUnwrap(domain); caller.Execute(arg); - theProxy.Call("ShutdownPythonCompletely"); + theProxy.Call("ShutdownPython"); } finally { @@ -370,12 +370,6 @@ public static void ShutdownPython() PythonEngine.Shutdown(allowReload: true); } - public static void ShutdownPythonCompletely() - { - PythonEngine.EndAllowThreads(_state); - PythonEngine.Shutdown(allowReload: true); - } - static void OnDomainUnload(object sender, EventArgs e) { Console.WriteLine(string.Format("[{0} in .NET] unloading", AppDomain.CurrentDomain.FriendlyName)); diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 0d65c9dc8..6c1a480f5 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -66,6 +66,7 @@ private static string GetDefaultDllName(Version version) // .NET core: System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows) internal static bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; + internal static bool IsNetCore = RuntimeInformation.FrameworkDescription == ".NET" || RuntimeInformation.FrameworkDescription == ".NET Core"; internal static Version InteropVersion { get; } = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; @@ -113,14 +114,6 @@ internal static void Initialize(bool initSigs = false) } _isInitialized = true; - if (ShutdownWithoutReload) - { - throw new Exception( - "Runtime was shut down without allowReload: true, can " + - "not be restarted in this process" - ); - } - bool interpreterAlreadyInitialized = TryUsingDll( () => Py_IsInitialized() != 0 ); @@ -153,6 +146,18 @@ internal static void Initialize(bool initSigs = false) NewRun(); } } + + if (ShutdownWithoutReload && RuntimeData.StashedDataFromDifferentDomain()) + { + if (!HostedInPython) + Py_Finalize(); + + // Shutdown again + throw new Exception( + "Runtime was shut down with allowReload: false, can not be reloaded in different domain" + ); + } + MainManagedThreadId = Thread.CurrentThread.ManagedThreadId; Finalizer.Initialize(); @@ -172,6 +177,7 @@ internal static void Initialize(bool initSigs = false) // Initialize modules that depend on the runtime class. AssemblyManager.Initialize(); OperatorMethod.Initialize(); + if (RuntimeData.HasStashData()) { RuntimeData.RestoreRuntimeData(); @@ -273,7 +279,7 @@ internal static void Shutdown(bool allowReload) var state = PyGILState_Ensure(); - if (!HostedInPython && !ProcessIsTerminating && allowReload) + if (!HostedInPython && !ProcessIsTerminating && !IsNetCore && allowReload) { // avoid saving dead objects TryCollectingGarbage(runs: 3); diff --git a/src/runtime/StateSerialization/PythonNetState.cs b/src/runtime/StateSerialization/PythonNetState.cs index 771f92ecc..cb34ea3db 100644 --- a/src/runtime/StateSerialization/PythonNetState.cs +++ b/src/runtime/StateSerialization/PythonNetState.cs @@ -9,6 +9,7 @@ namespace Python.Runtime.StateSerialization; [Serializable] internal class PythonNetState { + public int DomainId { get; init; } public MetatypeState Metatype { get; init; } public SharedObjectsState SharedObjects { get; init; } public TypeManagerState Types { get; init; } diff --git a/src/runtime/StateSerialization/RuntimeData.cs b/src/runtime/StateSerialization/RuntimeData.cs index 204e15b5b..ccae6cdef 100644 --- a/src/runtime/StateSerialization/RuntimeData.cs +++ b/src/runtime/StateSerialization/RuntimeData.cs @@ -17,6 +17,10 @@ namespace Python.Runtime { public static class RuntimeData { + static readonly string DataName = "_pythonnet_data"; + static readonly string DomainIdName = "_pythonnet_domain_id"; + + private static Type? _formatterType; public static Type? FormatterType { @@ -34,11 +38,11 @@ public static Type? FormatterType public static ICLRObjectStorer? WrappersStorer { get; set; } /// - /// Clears the old "clr_data" entry if a previous one is present. + /// Clears the old DataName entry if a previous one is present. /// static void ClearCLRData () { - BorrowedReference capsule = PySys_GetObject("clr_data"); + BorrowedReference capsule = PySys_GetObject(DataName); if (!capsule.IsNull) { IntPtr oldData = PyCapsule_GetPointer(capsule, IntPtr.Zero); @@ -51,6 +55,7 @@ internal static void Stash() { var runtimeStorage = new PythonNetState { + DomainId = AppDomain.CurrentDomain.Id, Metatype = MetaType.SaveRuntimeData(), ImportHookState = ImportHook.SaveRuntimeData(), Types = TypeManager.SaveRuntimeData(), @@ -72,7 +77,10 @@ internal static void Stash() ClearCLRData(); using NewReference capsule = PyCapsule_New(mem, IntPtr.Zero, IntPtr.Zero); - int res = PySys_SetObject("clr_data", capsule.BorrowOrThrow()); + int res = PySys_SetObject(DataName, capsule.BorrowOrThrow()); + PythonException.ThrowIfIsNotZero(res); + using NewReference id = PyInt_FromInt32(AppDomain.CurrentDomain.Id); + res = PySys_SetObject(DomainIdName, id.BorrowOrThrow()); PythonException.ThrowIfIsNotZero(res); } @@ -90,7 +98,7 @@ internal static void RestoreRuntimeData() private static void RestoreRuntimeDataImpl() { - BorrowedReference capsule = PySys_GetObject("clr_data"); + BorrowedReference capsule = PySys_GetObject(DataName); if (capsule.IsNull) { return; @@ -115,12 +123,24 @@ private static void RestoreRuntimeDataImpl() public static bool HasStashData() { - return !PySys_GetObject("clr_data").IsNull; + return !PySys_GetObject(DataName).IsNull; + } + + public static bool StashedDataFromDifferentDomain() + { + var val = PySys_GetObject(DomainIdName); + if (!val.IsNull) + { + var n = PyLong_AsLongLong(val); + if (n != null) + return (int)n != AppDomain.CurrentDomain.Id; + } + return false; } public static void ClearStash() { - PySys_SetObject("clr_data", default); + PySys_SetObject(DataName, default); } static bool CheckSerializable (object o)