diff --git a/pythonnet.15.sln b/pythonnet.15.sln
index ce863817f..3f41b7abe 100644
--- a/pythonnet.15.sln
+++ b/pythonnet.15.sln
@@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test.15", "src\testi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PerformanceTests", "src\perf_tests\Python.PerformanceTests.csproj", "{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.DomainReloadTests.15", "src\domain_tests\Python.DomainReloadTests.15.csproj", "{F2FB6DA3-318E-4F30-9A1F-932C667E38C5}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F4C6-4EE4-9AEE-315FD79BE2D5}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
@@ -388,6 +390,65 @@ Global
{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64
{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86
{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.ActiveCfg = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.Build.0 = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|Any CPU.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|Any CPU.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x64.ActiveCfg = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x64.Build.0 = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x86.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x86.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|Any CPU.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|Any CPU.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x64.ActiveCfg = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x64.Build.0 = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x86.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x86.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|Any CPU.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|Any CPU.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x64.ActiveCfg = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x64.Build.0 = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x86.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x86.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|Any CPU.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|Any CPU.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x64.ActiveCfg = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x64.Build.0 = Debug|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x86.ActiveCfg = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x86.Build.0 = Debug|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.ActiveCfg = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.Build.0 = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|Any CPU.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|Any CPU.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x64.ActiveCfg = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x64.Build.0 = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x86.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x86.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|Any CPU.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|Any CPU.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x64.ActiveCfg = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x64.Build.0 = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x86.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x86.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|Any CPU.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|Any CPU.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x64.ActiveCfg = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x64.Build.0 = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x86.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x86.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|Any CPU.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|Any CPU.Build.0 = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x64.ActiveCfg = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x64.Build.0 = Release|x64
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x86.ActiveCfg = Release|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/pythonnet.sln b/pythonnet.sln
index c5afd66c3..fdd140003 100644
--- a/pythonnet.sln
+++ b/pythonnet.sln
@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "src\console\Cons
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clrmodule", "src\clrmodule\clrmodule.csproj", "{86E834DE-1139-4511-96CC-69636A56E7AC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python.DomainReloadTests", "src\domain_tests\Python.DomainReloadTests.csproj", "{7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
DebugMono|x64 = DebugMono|x64
@@ -184,6 +186,30 @@ Global
{86E834DE-1139-4511-96CC-69636A56E7AC}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64
{86E834DE-1139-4511-96CC-69636A56E7AC}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86
{86E834DE-1139-4511-96CC-69636A56E7AC}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMono|x64.ActiveCfg = Debug|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMono|x86.ActiveCfg = Debug|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMonoPY3|x64.ActiveCfg = Debug|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMonoPY3|x86.ActiveCfg = Debug|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x64.ActiveCfg = Debug|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x64.Build.0 = Debug|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x86.ActiveCfg = Debug|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x86.Build.0 = Debug|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x64.ActiveCfg = Debug|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x64.Build.0 = Debug|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x86.ActiveCfg = Debug|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x86.Build.0 = Debug|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMono|x64.ActiveCfg = Release|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMono|x86.ActiveCfg = Release|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMon|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMon|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x64.ActiveCfg = Release|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x64.Build.0 = Release|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x86.ActiveCfg = Release|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x86.Build.0 = Release|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x64.ActiveCfg = Release|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x64.Build.0 = Release|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x86.ActiveCfg = Release|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/domain_tests/App.config b/src/domain_tests/App.config
new file mode 100644
index 000000000..56efbc7b5
--- /dev/null
+++ b/src/domain_tests/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/domain_tests/Python.DomainReloadTests.15.csproj b/src/domain_tests/Python.DomainReloadTests.15.csproj
new file mode 100644
index 000000000..a6953ca64
--- /dev/null
+++ b/src/domain_tests/Python.DomainReloadTests.15.csproj
@@ -0,0 +1,66 @@
+
+
+
+
+ net40;netcoreapp3.1
+ x64;x86
+ Debug;Release
+ Exe
+ false
+ Python.DomainReloadTests
+ Python.DomainReloadTests
+ Python.DomainReloadTests
+ 2.5.0
+ false
+ false
+ false
+ false
+ bin\
+ false
+ $(OutputPath)\$(AssemblyName).xml
+ $(OutputPath)\$(TargetFramework)\$(AssemblyName).xml
+ 1591
+ ..\..\
+ $(SolutionDir)\bin\
+ $(OutputPath)\$(TargetFramework)_publish
+ 7.3
+ prompt
+ XPLAT
+ $(DefineConstants);$(BaseDefineConstants);
+ $(DefineConstants);NETCOREAPP
+ $(DefineConstants);NETSTANDARD
+ $(DefineConstants);TRACE;DEBUG
+ $(NuGetPackageRoot)\microsoft.targetingpack.netframework.v4.5\1.0.1\lib\net45\
+
+
+ x86
+
+
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(TargetPath)
+ $(TargetDir)$(TargetName).pdb
+
+
+
diff --git a/src/domain_tests/Python.DomainReloadTests.csproj b/src/domain_tests/Python.DomainReloadTests.csproj
new file mode 100644
index 000000000..af454c89d
--- /dev/null
+++ b/src/domain_tests/Python.DomainReloadTests.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}
+ Exe
+ Python.DomainReloadTests
+ Python.DomainReloadTests
+ v4.0
+ bin\
+ 512
+ true
+ true
+
+
+ x86
+
+
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/domain_tests/TestRunner.cs b/src/domain_tests/TestRunner.cs
new file mode 100644
index 000000000..f8474d99b
--- /dev/null
+++ b/src/domain_tests/TestRunner.cs
@@ -0,0 +1,978 @@
+// We can't refer to or use Python.Runtime here.
+// We want it to be loaded only inside the subdomains
+using System;
+using Microsoft.CSharp;
+using System.CodeDom.Compiler;
+using System.IO;
+using System.Linq;
+
+namespace Python.DomainReloadTests
+{
+ ///
+ /// This class provides an executable that can run domain reload tests.
+ /// The setup is a bit complicated:
+ /// 1. pytest runs test_*.py in this directory.
+ /// 2. test_classname runs Python.DomainReloadTests.exe (this class) with an argument
+ /// 3. This class at runtime creates a directory that has both C# and
+ /// python code, and compiles the C#.
+ /// 4. This class then runs the C# code.
+ ///
+ /// But wait there's more indirection. The C# code that's run -- known as
+ /// the test runner --
+ /// This class compiles a DLL that contains the class which code will change
+ /// and a runner executable that will run Python code referencing the class.
+ /// Each test case:
+ /// * Compiles some code, loads it into a domain, runs python that refers to it.
+ /// * Unload the domain.
+ /// * Compile a new piece of code, load it into a domain, run a new piece of python that accesses the code.
+ /// * Unload the domain. Reload the domain, run the same python again.
+ /// This class gets built into an executable which takes one argument:
+ /// which test case to run. That's because pytest assumes we'll run
+ /// everything in one process, but we really want a clean process on each
+ /// test case to test the init/reload/teardown parts of the domain reload
+ /// code.
+ ///
+ class TestRunner
+ {
+ const string TestAssemblyName = "DomainTests";
+
+ class TestCase
+ {
+ ///
+ /// The key to pass as an argument to choose this test.
+ ///
+ public string Name;
+
+ ///
+ /// The C# code to run in the first domain.
+ ///
+ public string DotNetBefore;
+
+ ///
+ /// The C# code to run in the second domain.
+ ///
+ public string DotNetAfter;
+
+ ///
+ /// The Python code to run as a module that imports the C#.
+ /// It should have two functions: before() and after(). Before
+ /// will be called when DotNetBefore is loaded; after will be
+ /// called (twice) when DotNetAfter is loaded.
+ /// To make the test fail, have those functions raise exceptions.
+ ///
+ /// Make sure there's no leading spaces since Python cares.
+ ///
+ public string PythonCode;
+ }
+
+ static TestCase[] Cases = new TestCase[]
+ {
+ new TestCase
+ {
+ Name = "class_rename",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Before { }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class After { }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+import TestNamespace
+
+def before_reload():
+ sys.my_cls = TestNamespace.Before
+
+
+def after_reload():
+ assert sys.my_cls is not None
+ try:
+ foo = TestNamespace.Before
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "static_member_rename",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls { public static int Before() { return 5; } }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls { public static int After() { return 10; } }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+import TestNamespace
+
+def before_reload():
+ sys.my_cls = TestNamespace.Cls
+ sys.my_fn = TestNamespace.Cls.Before
+ sys.my_fn()
+ TestNamespace.Cls.Before()
+
+def after_reload():
+
+ # We should have reloaded the class so we can access the new function.
+ assert 10 == sys.my_cls.After()
+ assert True is True
+
+ try:
+ # We should have reloaded the class. The old function still exists, but is now invalid.
+ sys.my_cls.Before()
+ except AttributeError:
+ print('Caught expected TypeError')
+ else:
+ raise AssertionError('Failed to throw exception: expected TypeError calling class member that no longer exists')
+
+ assert sys.my_fn is not None
+
+ try:
+ # Unbound functions still exist. They will error out when called though.
+ sys.my_fn()
+ except TypeError:
+ print('Caught expected TypeError')
+ else:
+ raise AssertionError('Failed to throw exception: expected TypeError calling unbound .NET function that no longer exists')
+ ",
+ },
+
+
+ new TestCase
+ {
+ Name = "member_rename",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls { public int Before() { return 5; } }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls { public int After() { return 10; } }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+import TestNamespace
+
+def before_reload():
+ sys.my_cls = TestNamespace.Cls()
+ sys.my_fn = TestNamespace.Cls().Before
+ sys.my_fn()
+ TestNamespace.Cls().Before()
+
+def after_reload():
+
+ # We should have reloaded the class so we can access the new function.
+ assert 10 == sys.my_cls.After()
+ assert True is True
+
+ try:
+ # We should have reloaded the class. The old function still exists, but is now invalid.
+ sys.my_cls.Before()
+ except AttributeError:
+ print('Caught expected TypeError')
+ else:
+ raise AssertionError('Failed to throw exception: expected TypeError calling class member that no longer exists')
+
+ assert sys.my_fn is not None
+
+ try:
+ # Unbound functions still exist. They will error out when called though.
+ sys.my_fn()
+ except TypeError:
+ print('Caught expected TypeError')
+ else:
+ raise AssertionError('Failed to throw exception: expected TypeError calling unbound .NET function that no longer exists')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "field_rename",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ static public int Before = 2;
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ static public int After = 4;
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ sys.my_int = Cls.Before
+
+def after_reload():
+ print(sys.my_int)
+ try:
+ assert 2 == Cls.Before
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+",
+ },
+ new TestCase
+ {
+ Name = "property_rename",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ static public int Before { get { return 2; } }
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ static public int After { get { return 4; } }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ sys.my_int = Cls.Before
+
+def after_reload():
+ print(sys.my_int)
+ try:
+ assert 2 == Cls.Before
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+",
+ },
+
+ new TestCase
+ {
+ Name = "event_rename",
+ DotNetBefore = @"
+ using System;
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static event Action Before;
+ public static void Call()
+ {
+ Before();
+ }
+ }
+ }",
+ DotNetAfter = @"
+ using System;
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static event Action After;
+ public static void Call()
+ {
+ After();
+ }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+called = False
+
+def callback_function():
+ global called
+ called = True
+
+def before_reload():
+ global called
+ called = False
+ Cls.Before += callback_function
+ Cls.Call()
+ assert called is True
+
+def after_reload():
+ global called
+ assert called is True
+ called = False
+ Cls.Call()
+ assert called is False
+ #try:
+ # assert 2 == Cls.Before
+ #except TypeError:
+ # print('Caught expected exception')
+ #else:
+ # raise AssertionError('Failed to throw exception')
+",
+ },
+
+ new TestCase
+ {
+ Name = "namespace_rename",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public int Foo;
+ public Cls(int i)
+ {
+ Foo = i;
+ }
+ }
+ }",
+ DotNetAfter = @"
+ namespace NewTestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public int Foo;
+ public Cls(int i)
+ {
+ Foo = i;
+ }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+import TestNamespace
+
+def before_reload():
+ sys.my_cls = TestNamespace.Cls
+ sys.my_inst = TestNamespace.Cls(1)
+
+def after_reload():
+ try:
+ TestNamespace.Cls(2)
+ sys.my_cls.Member()
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "field_visibility_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int Foo = 1;
+ public static int Field = 2;
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int Foo = 1;
+ private static int Field = 2;
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ assert 2 == Cls.Field
+ assert 1 == Cls.Foo
+
+def after_reload():
+ assert 1 == Cls.Foo
+ try:
+ assert 1 == Cls.Field
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "method_visibility_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int Foo() { return 1; }
+ public static int Function() { return 2; }
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int Foo() { return 1; }
+ private static int Function() { return 2; }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ sys.my_func = Cls.Function
+ assert 1 == Cls.Foo()
+ assert 2 == Cls.Function()
+
+def after_reload():
+ assert 1 == Cls.Foo()
+ try:
+ assert 2 == Cls.Function()
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+
+ try:
+ assert 2 == sys.my_func()
+ except TypeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "property_visibility_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int Foo { get { return 1; } }
+ public static int Property { get { return 2; } }
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int Foo { get { return 1; } }
+ private static int Property { get { return 2; } }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ assert 1 == Cls.Foo
+ assert 2 == Cls.Property
+
+def after_reload():
+ assert 1 == Cls.Foo
+ try:
+ assert 2 == Cls.Property
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "class_visibility_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class PublicClass { }
+
+ [System.Serializable]
+ public class Cls { }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ internal class Cls { }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+import TestNamespace
+
+def before_reload():
+ sys.my_cls = TestNamespace.Cls
+
+def after_reload():
+ sys.my_cls()
+
+ try:
+ TestNamespace.Cls()
+ except AttributeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "method_parameters_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static void MyFunction(int a)
+ {
+ System.Console.WriteLine(string.Format(""MyFunction says: {0}"", a));
+ }
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static void MyFunction(string a)
+ {
+ System.Console.WriteLine(string.Format(""MyFunction says: {0}"", a));
+ }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ sys.my_cls = Cls
+ sys.my_func = Cls.MyFunction
+ sys.my_cls.MyFunction(1)
+ sys.my_func(2)
+
+def after_reload():
+ try:
+ sys.my_cls.MyFunction(1)
+ except TypeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+
+ try:
+ sys.my_func(2)
+ except TypeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+
+ # Calling the function from the class passes
+ sys.my_cls.MyFunction('test')
+
+ try:
+ # calling the callable directly fails
+ sys.my_func('test')
+ except TypeError:
+ print('Caught expected exception')
+ else:
+ raise AssertionError('Failed to throw exception')
+
+ Cls.MyFunction('another test')
+
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "method_return_type_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static int MyFunction()
+ {
+ return 2;
+ }
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ public static string MyFunction()
+ {
+ return ""22"";
+ }
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ sys.my_cls = Cls
+ sys.my_func = Cls.MyFunction
+ assert 2 == sys.my_cls.MyFunction()
+ assert 2 == sys.my_func()
+
+def after_reload():
+ assert '22' == sys.my_cls.MyFunction()
+ assert '22' == sys.my_func()
+ assert '22' == Cls.MyFunction()
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "field_type_change",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ static public int Field = 2;
+ }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Cls
+ {
+ static public string Field = ""22"";
+ }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+from TestNamespace import Cls
+
+def before_reload():
+ sys.my_cls = Cls
+ assert 2 == sys.my_cls.Field
+
+def after_reload():
+ assert '22' == Cls.Field
+ assert '22' == sys.my_cls.Field
+ ",
+ },
+
+ new TestCase
+ {
+ Name = "construct_removed_class",
+ DotNetBefore = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class Before { }
+ }",
+ DotNetAfter = @"
+ namespace TestNamespace
+ {
+ [System.Serializable]
+ public class After { }
+ }",
+ PythonCode = @"
+import clr
+import sys
+clr.AddReference('DomainTests')
+import TestNamespace
+
+def before_reload():
+ sys.my_cls = TestNamespace.Before
+
+def after_reload():
+ bar = sys.my_cls()
+
+ # Don't crash!
+ print(bar)
+ print(bar.__str__())
+ print(bar.__repr__())
+ ",
+ },
+ };
+
+ ///
+ /// The runner's code. Runs the python code
+ /// This is a template for string.Format
+ /// Arg 0 is the reload mode: ShutdownMode.Reload or other.
+ /// Arg 1 is the no-arg python function to run, before or after.
+ ///
+ const string CaseRunnerTemplate = @"
+using System;
+using System.IO;
+using Python.Runtime;
+namespace CaseRunner
+{{
+ class CaseRunner
+ {{
+ public static int Main()
+ {{
+ try
+ {{
+ PythonEngine.Initialize(mode:{0});
+ using (Py.GIL())
+ {{
+ // Because the generated assemblies are in the $TEMP folder, add it to the path
+ var temp = Path.GetTempPath();
+ dynamic sys = Py.Import(""sys"");
+ sys.path.append(new PyString(temp));
+ dynamic test_mod = Py.Import(""domain_test_module.mod"");
+ test_mod.{1}_reload();
+ }}
+ PythonEngine.Shutdown();
+ }}
+ catch (PythonException pe)
+ {{
+ throw new ArgumentException(message:pe.Message+"" ""+pe.StackTrace);
+ }}
+ catch (Exception e)
+ {{
+ Console.WriteLine(e.StackTrace);
+ throw;
+ }}
+ return 0;
+ }}
+ }}
+}}
+";
+ readonly static string PythonDllLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../runtime/bin/Python.Runtime.dll");
+
+ public static int Main(string[] args)
+ {
+// We require this slightly convoluted way of ifdef'ing because the Python
+// comments '#' are mistaken as C# preprocessior directive
+#if !NETCOREAPP
+ TestCase testCase;
+ if (args.Length < 1)
+ {
+ testCase = Cases[0];
+ }
+ else
+ {
+ string testName = args[0];
+ Console.WriteLine($"-- Looking for domain reload test case {testName}");
+ testCase = Cases.First(c => c.Name == testName);
+ }
+ Console.WriteLine($"-- Running domain reload test case: {testCase.Name}");
+
+ var tempFolderPython = Path.Combine(Path.GetTempPath(), "Python.Runtime.dll");
+ if (File.Exists(tempFolderPython))
+ {
+ File.Delete(tempFolderPython);
+ }
+
+ File.Copy(PythonDllLocation, tempFolderPython);
+
+ CreatePythonModule(testCase);
+ {
+ var runnerAssembly = CreateCaseRunnerAssembly(verb:"before");
+ CreateTestClassAssembly(testCase.DotNetBefore);
+ {
+ var runnerDomain = CreateDomain("case runner before");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+ {
+ var runnerDomain = CreateDomain("case runner before (again)");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+ }
+
+ {
+ var runnerAssembly = CreateCaseRunnerAssembly(verb:"after");
+ CreateTestClassAssembly(testCase.DotNetAfter);
+
+ // Do it twice for good measure
+ {
+ var runnerDomain = CreateDomain("case runner after");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+ {
+ var runnerDomain = CreateDomain("case runner after (again)");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+ }
+#endif
+ return 0;
+ }
+#if !NETCOREAPP
+
+ static void RunAndUnload(AppDomain domain, string assemblyPath)
+ {
+ // Somehow the stack traces during execution sometimes have the wrong line numbers.
+ // Add some info for when debugging is required.
+ Console.WriteLine($"-- Running domain {domain.FriendlyName}");
+ domain.ExecuteAssembly(assemblyPath);
+ AppDomain.Unload(domain);
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ }
+
+ static string CreateTestClassAssembly(string code)
+ {
+ return CreateAssembly(TestAssemblyName + ".dll", code, exe: false);
+ }
+
+ static string CreateCaseRunnerAssembly(string verb, string shutdownMode = "ShutdownMode.Reload")
+ {
+ var code = string.Format(CaseRunnerTemplate, shutdownMode, verb);
+ var name = "TestCaseRunner.exe";
+
+ return CreateAssembly(name, code, exe: true);
+ }
+ static string CreateAssembly(string name, string code, bool exe = false)
+ {
+ // Console.WriteLine(code);
+ // Never return or hold the Assembly instance. This will cause
+ // the assembly to be loaded into the current domain and this
+ // interferes with the tests. The Domain can execute fine from a
+ // path, so let's return that.
+ CSharpCodeProvider provider = new CSharpCodeProvider();
+ CompilerParameters parameters = new CompilerParameters();
+ parameters.GenerateExecutable = exe;
+ var assemblyName = name;
+ var assemblyFullPath = Path.Combine(Path.GetTempPath(), assemblyName);
+ parameters.OutputAssembly = assemblyFullPath;
+ parameters.ReferencedAssemblies.Add("System.dll");
+ parameters.ReferencedAssemblies.Add("System.Core.dll");
+ parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
+ parameters.ReferencedAssemblies.Add(PythonDllLocation);
+ CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
+ if (results.NativeCompilerReturnValue != 0)
+ {
+ var stderr = System.Console.Error;
+ stderr.WriteLine($"Error in {name} compiling:\n{code}");
+ foreach (var error in results.Errors)
+ {
+ stderr.WriteLine(error);
+ }
+ throw new ArgumentException("Error compiling code");
+ }
+
+ return assemblyFullPath;
+ }
+
+ static AppDomain CreateDomain(string name)
+ {
+ // Create the domain. Make sure to set PrivateBinPath to a relative
+ // path from the CWD (namely, 'bin').
+ // See https://stackoverflow.com/questions/24760543/createinstanceandunwrap-in-another-domain
+ var currentDomain = AppDomain.CurrentDomain;
+ var domainsetup = new AppDomainSetup()
+ {
+ ApplicationBase = Path.GetTempPath(),
+ ConfigurationFile = currentDomain.SetupInformation.ConfigurationFile,
+ LoaderOptimization = LoaderOptimization.SingleDomain,
+ PrivateBinPath = "."
+ };
+ var domain = AppDomain.CreateDomain(
+ $"My Domain {name}",
+ currentDomain.Evidence,
+ domainsetup);
+
+ return domain;
+ }
+
+ static string CreatePythonModule(TestCase testCase)
+ {
+ var modulePath = Path.Combine(Path.GetTempPath(), "domain_test_module");
+ if (Directory.Exists(modulePath))
+ {
+ Directory.Delete(modulePath, recursive: true);
+ }
+ Directory.CreateDirectory(modulePath);
+
+ File.Create(Path.Combine(modulePath, "__init__.py")).Close(); //Create and don't forget to close!
+ using (var writer = File.CreateText(Path.Combine(modulePath, "mod.py")))
+ {
+ writer.Write(testCase.PythonCode);
+ }
+
+ return null;
+ }
+#endif
+
+ }
+}
diff --git a/src/domain_tests/test_domain_reload.py b/src/domain_tests/test_domain_reload.py
new file mode 100644
index 000000000..e4d6bf6d7
--- /dev/null
+++ b/src/domain_tests/test_domain_reload.py
@@ -0,0 +1,73 @@
+import subprocess
+import os
+
+import pytest
+
+def _run_test(testname):
+ dirname = os.path.split(__file__)[0]
+ exename = os.path.join(dirname, 'bin', 'Python.DomainReloadTests.exe')
+ proc = subprocess.Popen([
+ exename,
+ testname,
+ ])
+ proc.wait()
+
+ assert proc.returncode == 0
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_rename_class():
+ _run_test('class_rename')
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_rename_class_member_static_function():
+ _run_test('static_member_rename')
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_rename_class_member_function():
+ _run_test('member_rename')
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_rename_class_member_field():
+ _run_test('field_rename')
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_rename_class_member_property():
+ _run_test('property_rename')
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_rename_namespace():
+ _run_test('namespace_rename')
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_field_visibility_change():
+ _run_test("field_visibility_change")
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_method_visibility_change():
+ _run_test("method_visibility_change")
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_property_visibility_change():
+ _run_test("property_visibility_change")
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_class_visibility_change():
+ _run_test("class_visibility_change")
+
+@pytest.mark.xfail(reason="Issue not yet fixed.")
+def test_method_parameters_change():
+ _run_test("method_parameters_change")
+
+def test_method_return_type_change():
+ _run_test("method_return_type_change")
+
+def test_field_type_change():
+ _run_test("field_type_change")
+
+@pytest.mark.xfail(reason="Events not yet serializable")
+def test_rename_event():
+ _run_test('event_rename')
+
+@pytest.mark.xfail(reason="newly instanced object uses PyType_GenericAlloc")
+def test_construct_removed_class():
+ _run_test("construct_removed_class")
\ No newline at end of file
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy