diff --git a/pythonnet.15.sln b/pythonnet.15.sln index ce863817f..cfe3807ae 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python.DomainReloadTests", "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,66 @@ 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|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|Any CPU.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|Any CPU.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x64.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x64.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x86.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x86.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|Any CPU.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|Any CPU.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x64.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x64.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x86.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x86.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|Any CPU.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|Any CPU.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x64.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x64.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x86.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x86.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|Any CPU.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|Any CPU.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x64.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x64.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x86.ActiveCfg = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x86.Build.0 = Debug|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|Any CPU.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|Any CPU.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x64.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x64.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x86.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x86.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|Any CPU.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|Any CPU.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x64.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x64.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x86.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x86.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|Any CPU.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|Any CPU.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x64.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x64.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x86.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x86.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|Any CPU.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|Any CPU.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x64.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x64.Build.0 = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x86.ActiveCfg = Release|Any CPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/pythonnet.sln b/pythonnet.sln index c5afd66c3..7b198b336 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 = DebugMono|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMono|x86.ActiveCfg = DebugMono|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x64.ActiveCfg = DebugWin|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x64.Build.0 = DebugWin|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x86.ActiveCfg = DebugWin|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x86.Build.0 = DebugWin|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x64.Build.0 = ReleaseWin|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x86.Build.0 = ReleaseWin|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86 + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|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..0b69ccb3d --- /dev/null +++ b/src/domain_tests/Python.DomainReloadTests.15.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {F2FB6DA3-318E-4F30-9A1F-932C667E38C5} + Exe + Python.DomainReloadTests + Python.DomainReloadTests + bin\ + v4.0 + 512 + true + true + + + AnyCPU + true + full + false + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/domain_tests/Python.DomainReloadTests.csproj b/src/domain_tests/Python.DomainReloadTests.csproj new file mode 100644 index 000000000..0914070ce --- /dev/null +++ b/src/domain_tests/Python.DomainReloadTests.csproj @@ -0,0 +1,86 @@ + + + + + Debug + AnyCPU + {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2} + Exe + Python.DomainReloadTests + Python.DomainReloadTests + v4.0 + bin\ + 512 + true + true + + + x86 + + + x64 + + + true + DEBUG;TRACE + full + + + + + true + pdbonly + + + true + DEBUG;TRACE + full + + + + + true + pdbonly + + + true + DEBUG;TRACE + full + + + + + true + pdbonly + + + true + DEBUG;TRACE + full + + + + + true + pdbonly + + + + + + + + + + + + + + + + + + + + + \ 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..33c203ba0 --- /dev/null +++ b/src/domain_tests/TestRunner.cs @@ -0,0 +1,232 @@ +// 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; + +namespace Python.DomainReloadTests +{ + /// + /// 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. + /// It's Main() will: + /// * Run the runner and unlod it's domain + /// * Modify and re-compile the test class + /// * Re-run the runner and unload it twice + /// + class TestRunner + { + /// + /// The code of the test class that changes + /// + const string ChangingClassTemplate = @" +using System; + +namespace TestNamespace +{ + [Serializable] + public class {class} + { + } +}"; + + /// + /// The Python code that accesses the test class + /// + const string PythonCode = @"import clr +clr.AddReference('TestClass') +import sys +from TestNamespace import {class} +import TestNamespace +foo = None +def do_work(): + sys.my_obj = {class} + +def test_work(): + print({class}) + print(sys.my_obj) +"; + + /// + /// The runner's code. Runs the python code + /// + 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}_work(); + }} + PythonEngine.Shutdown(); + }} + catch (PythonException pe) + {{ + throw new ArgumentException(message:pe.Message+"" ""+pe.StackTrace); + }} + return 0; + }} + }} +}} +"; + readonly static string PythonDllLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../runtime/bin/Python.Runtime.dll"); + + public static int Main(string[] args) + { + if (args.Length < 1) + { + args = new string[] {"TestClass", "NewTestClass"}; + // return 123; + } + Console.WriteLine($"Testing with arguments: {string.Join(", ", args)}"); + + var tempFolderPython = Path.Combine(Path.GetTempPath(), "Python.Runtime.dll"); + if (File.Exists(tempFolderPython)) + { + File.Delete(tempFolderPython); + } + + File.Copy(PythonDllLocation, tempFolderPython); + + CreatePythonModule(args[0]); + { + var runnerAssembly = CreateCaseRunnerAssembly(verb:"do"); + CreateTestClassAssembly(className: args[0]); + + var runnerDomain = CreateDomain("case runner"); + RunAndUnload(runnerDomain, runnerAssembly); + } + + { + var runnerAssembly = CreateCaseRunnerAssembly(verb:"test"); + // remove the method + CreateTestClassAssembly(className: args[1]); + + // Do it twice for good measure + { + var runnerDomain = CreateDomain("case runner 2"); + RunAndUnload(runnerDomain, runnerAssembly); + } + { + var runnerDomain = CreateDomain("case runner 3"); + RunAndUnload(runnerDomain, runnerAssembly); + } + } + + return 0; + } + + 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($"Runining domain {domain.FriendlyName}"); + domain.ExecuteAssembly(assemblyPath); + AppDomain.Unload(domain); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + + static string CreateTestClassAssembly(string className) + { + var name = "TestClass.dll"; + string code = ChangingClassTemplate.Replace("{class}", className); + + return CreateAssembly(name, code, exe: false); + } + + static string CreateCaseRunnerAssembly(string shutdownMode = "ShutdownMode.Reload", string verb = "do") + { + 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) + { + foreach (var error in results.Errors) + { + System.Console.WriteLine(error); + } + throw new ArgumentException(); + } + + 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(string className) + { + 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(PythonCode.Replace("{class}", className)); + } + + return null; + } + + } +} diff --git a/src/domain_tests/test_domain_reload.py b/src/domain_tests/test_domain_reload.py new file mode 100644 index 000000000..991ae7a14 --- /dev/null +++ b/src/domain_tests/test_domain_reload.py @@ -0,0 +1,22 @@ +import subprocess +import os + +def runit(m1, m2, member): + proc = subprocess.Popen([os.path.join(os.path.split(__file__)[0], 'bin', 'Python.DomainReloadTests.exe'), m1, m2, member]) + proc.wait() + + assert proc.returncode == 0 + +def test_remove_method(): + + m1 = 'public static void TestMethod() {Console.WriteLine("from test method");}' + m2 = 'public static void TestMethod2() {Console.WriteLine("from test method");}' + member = 'TestMethod' + runit(m1, m2, member) + +def test_remove_member(): + + m1 = 'public static int TestMember = -1;' + m2 = 'public static int TestMember2 = -1;' + member = 'TestMember' + runit(m1, m2, member) \ No newline at end of file diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 08dc1d860..484cc48c4 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -116,6 +116,7 @@ + diff --git a/src/runtime/fieldobject.cs b/src/runtime/fieldobject.cs index 86b93dd1b..6820927c2 100644 --- a/src/runtime/fieldobject.cs +++ b/src/runtime/fieldobject.cs @@ -1,5 +1,8 @@ using System; using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.IO; namespace Python.Runtime { @@ -9,11 +12,11 @@ namespace Python.Runtime [Serializable] internal class FieldObject : ExtensionType { - private FieldInfo info; + private MaybeSerialize m_info; public FieldObject(FieldInfo info) { - this.info = info; + m_info = new MaybeSerialize(info); } /// @@ -24,14 +27,25 @@ public FieldObject(FieldInfo info) public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) { var self = (FieldObject)GetManagedObject(ds); - object result; if (self == null) { return IntPtr.Zero; } + try + { + return self.tp_descr_get(ob, tp); + } + catch (Exception e) + { + Exceptions.SetError(Exceptions.TypeError, e.Message); + return IntPtr.Zero; + } + } - FieldInfo info = self.info; + IntPtr tp_descr_get(IntPtr ob, IntPtr tp) + { + FieldInfo info = m_info.Value; if (ob == IntPtr.Zero || ob == Runtime.PyNone) { @@ -43,7 +57,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) } try { - result = info.GetValue(null); + var result = info.GetValue(null); return Converter.ToPython(result, info.FieldType); } catch (Exception e) @@ -61,7 +75,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) Exceptions.SetError(Exceptions.TypeError, "instance is not a clr object"); return IntPtr.Zero; } - result = info.GetValue(co.inst); + var result = info.GetValue(co.inst); return Converter.ToPython(result, info.FieldType); } catch (Exception e) @@ -79,7 +93,6 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) public new static int tp_descr_set(IntPtr ds, IntPtr ob, IntPtr val) { var self = (FieldObject)GetManagedObject(ds); - object newval; if (self == null) { @@ -91,8 +104,21 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) Exceptions.SetError(Exceptions.TypeError, "cannot delete field"); return -1; } + try + { + return self.tp_descr_set(ob, val); + } + catch (Exception e) + { + Exceptions.SetError(Exceptions.TypeError, e.Message); + return -1; + } + } - FieldInfo info = self.info; + + int tp_descr_set(IntPtr ob, IntPtr val) + { + FieldInfo info = m_info.Value; if (info.IsLiteral || info.IsInitOnly) { @@ -111,6 +137,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) } } + object newval; if (!Converter.ToManaged(val, info.FieldType, out newval, true)) { return -1; @@ -147,7 +174,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp) public static IntPtr tp_repr(IntPtr ob) { var self = (FieldObject)GetManagedObject(ob); - return Runtime.PyString_FromString($""); + return Runtime.PyString_FromString($""); } } } diff --git a/src/runtime/maybeserialize.cs b/src/runtime/maybeserialize.cs new file mode 100644 index 000000000..70733476a --- /dev/null +++ b/src/runtime/maybeserialize.cs @@ -0,0 +1,132 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.IO; + +namespace Python.Runtime +{ + /// + /// A MaybeSerialize<T> delays errors from serialization and + /// deserialization until the item is used. + /// + /// Python for .NET uses this in the C# reloading architecture. + /// If e.g. a class member was renamed when reloading, references to the + /// old field will be invalid, but the rest of the system will still work. + /// Code that tries to use the old field will receive an exception. + /// + /// Assumption: the item being wrapped by MaybeSerialize will never be null. + /// + [Serializable] + internal struct MaybeSerialize : ISerializable where T : class + { + /// + /// The item being wrapped. + /// + /// If this is null, that means we failed to serialize or deserialize it. + /// + private T m_item; + + /// + /// A string useful for debugging the error. + /// + /// This is null if m_item deserialized properly. + /// Otherwise, it will be derived off of m_item.ToString() when we + /// serialized. + /// + private string m_name; + + /// + /// Store an item in such a way that it can be deserialized. + /// + /// It must not be null. + /// + public MaybeSerialize(T item) + { + if (item == null) + { + throw new System.ArgumentNullException("Trying to store a null"); + } + m_item = item; + m_name = null; + } + + /// + /// Get the underlying deserialized value, or throw an exception + /// if deserialiation failed. + /// + public T Value + { + get + { + if (m_item == null) + { + throw new SerializationException($"The .NET object underlying {m_name} no longer exists"); + } + return m_item; + } + } + + /// + /// Get a printable name. + /// + public string ToString() + { + if (m_item == null) + { + return $"(missing {m_name})"; + } + else + { + return m_item.ToString(); + } + } + + /// + /// Implements ISerializable + /// + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (m_item == null) + { + // Save the name; this failed to reload in a previous + // generation but we still need to remember what it was. + info.AddValue("n", m_name); + } + else + { + // Try to save the item. If it fails, too bad. + try + { + info.AddValue("i", m_item); + } + catch(SerializationException _) + { + } + + // Also save the name in case the item doesn't deserialize + info.AddValue("n", m_item.ToString()); + } + } + + /// + /// Implements ISerializable + /// + private MaybeSerialize(SerializationInfo info, StreamingContext context) + { + try + { + // Try to deserialize the item. It might fail, or it might + // have already failed so there just isn't an "i" to find. + m_item = (T)info.GetValue("i", typeof(T)); + m_name = null; + } + catch (SerializationException _) + { + // Getting the item failed, so get the name. + m_item = null; + m_name = info.GetString("n"); + } + } + } +} 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