diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4bc501bae3f533..3217c8e5f7a969 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -210,5 +210,9 @@ https://github.com/dotnet/emsdk e51b2a920817aec200d7306b95f616f9451d47a3 + + https://github.com/dotnet/hotreload-utils + 078f7f63b604e97c64cf907e9932a85f8aeb5dc3 + diff --git a/eng/Versions.props b/eng/Versions.props index 142ec08d6ffb9f..1682110853cc2d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -149,6 +149,7 @@ 16.9.0-preview-20201201-01 1.0.0-prerelease.21255.1 1.0.0-prerelease.21255.1 + 1.0.1-alpha.0.21257.1 2.4.1 2.4.2 1.3.0 diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/Directory.Build.props b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/Directory.Build.props new file mode 100644 index 00000000000000..e2d777b74f8bc2 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/Directory.Build.props @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/Directory.Build.targets b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/Directory.Build.targets new file mode 100644 index 00000000000000..11832c6fa179de --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1.cs new file mode 100644 index 00000000000000..22b259c2f81059 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class MethodBody1 { + public static string StaticMethod1 () { + return "OLD STRING"; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1_v1.cs new file mode 100644 index 00000000000000..1fee80ba1bdb4d --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1_v1.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class MethodBody1 { + public static string StaticMethod1 () { + return "NEW STRING"; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1_v2.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1_v2.cs new file mode 100644 index 00000000000000..3fefeeaa3f2f89 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/MethodBody1_v2.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class MethodBody1 { + public static string StaticMethod1 () { + return "NEWEST STRING"; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj new file mode 100644 index 00000000000000..f05397e884c83a --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj @@ -0,0 +1,11 @@ + + + System.Runtime.Loader.Tests + $(NetCoreAppCurrent) + true + deltascript.json + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/deltascript.json new file mode 100644 index 00000000000000..8e738364bc7475 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1/deltascript.json @@ -0,0 +1,7 @@ +{ + "changes": [ + {"document": "MethodBody1.cs", "update": "MethodBody1_v1.cs"}, + {"document": "MethodBody1.cs", "update": "MethodBody1_v2.cs"}, + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs new file mode 100644 index 00000000000000..57c71039774d89 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Reflection.Metadata +{ + /// + /// The general setup for ApplyUpdate tests is: + /// + /// Each test Foo has a corresponding assembly under + /// System.Reflection.Metadata.ApplyUpate.Test.Foo The Foo.csproj has a delta + /// script that applies one or more updates to Foo.dll The ApplyUpdateTest + /// testsuite runs each test in sequence, loading the corresponding + /// assembly, applying an update to it and observing the results. + [Collection(nameof(ApplyUpdateUtil.NoParallelTests))] + [ConditionalClass(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] + public class ApplyUpdateTest + { + [Fact] + void StaticMethodBodyUpdate() + { + ApplyUpdateUtil.TestCase(static () => + { + var assm = typeof (ApplyUpdate.Test.MethodBody1).Assembly; + + var r = ApplyUpdate.Test.MethodBody1.StaticMethod1(); + Assert.Equal("OLD STRING", r); + + ApplyUpdateUtil.ApplyUpdate(assm); + + r = ApplyUpdate.Test.MethodBody1.StaticMethod1(); + Assert.Equal("NEW STRING", r); + + ApplyUpdateUtil.ApplyUpdate(assm); + + r = ApplyUpdate.Test.MethodBody1.StaticMethod1 (); + Assert.Equal ("NEWEST STRING", r); + }); + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs new file mode 100644 index 00000000000000..baa5ac27b76b1b --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; +using Microsoft.DotNet.RemoteExecutor; + +namespace System.Reflection.Metadata +{ + public class ApplyUpdateUtil { + internal const string DotNetModifiableAssembliesSwitch = "DOTNET_MODIFIABLE_ASSEMBLIES"; + internal const string DotNetModifiableAssembliesValue = "debug"; + + [CollectionDefinition("NoParallelTests", DisableParallelization = true)] + public class NoParallelTests { } + + /// Whether ApplyUpdate is supported by the environment, test configuration, and runtime. + /// + /// We need: + /// 1. Either DOTNET_MODIFIABLE_ASSEMBLIES=debug is set, or we can use the RemoteExecutor to run a child process with that environment; and, + /// 2. Either Mono in a supported configuration (interpreter as the execution engine, and the hot reload feature enabled), or CoreCLR; and, + /// 3. The test assemblies are compiled in the Debug configuration. + public static bool IsSupported => (IsModifiableAssembliesSet || IsRemoteExecutorSupported) && + (!IsMonoRuntime || IsSupportedMonoConfiguration) && + IsSupportedTestConfiguration(); + + public static bool IsModifiableAssembliesSet => + String.Equals(DotNetModifiableAssembliesValue, Environment.GetEnvironmentVariable(DotNetModifiableAssembliesSwitch), StringComparison.InvariantCultureIgnoreCase); + + // static cctor for RemoteExecutor throws on wasm. + public static bool IsRemoteExecutorSupported => !RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")) && RemoteExecutor.IsSupported; + + // copied from https://github.com/dotnet/arcade/blob/6cc4c1e9e23d5e65e88a8a57216b3d91e9b3d8db/src/Microsoft.DotNet.XUnitExtensions/src/DiscovererHelpers.cs#L16-L17 + private static readonly Lazy s_isMonoRuntime = new Lazy(() => Type.GetType("Mono.RuntimeStructs") != null); + public static bool IsMonoRuntime => s_isMonoRuntime.Value; + + private static readonly Lazy s_isSupportedMonoConfiguration = new Lazy(CheckSupportedMonoConfiguration); + + public static bool IsSupportedMonoConfiguration => s_isSupportedMonoConfiguration.Value; + + // Not every build of Mono supports ApplyUpdate + internal static bool CheckSupportedMonoConfiguration() + { + // check that interpreter is enabled, and the build has hot reload capabilities enabled. + var isInterp = RuntimeFeature.IsDynamicCodeSupported && !RuntimeFeature.IsDynamicCodeCompiled; + return isInterp && HasApplyUpdateCapabilities(); + } + + internal static bool HasApplyUpdateCapabilities() + { + var ty = typeof(AssemblyExtensions); + var mi = ty.GetMethod("GetApplyUpdateCapabilities", BindingFlags.NonPublic | BindingFlags.Static, Array.Empty()); + + if (mi == null) + return false; + + var caps = mi.Invoke(null, null); + + // any non-empty string, assumed to be at least "baseline" + return caps is string {Length: > 0}; + } + + // Only Debug assemblies are editable + internal static bool IsSupportedTestConfiguration() + { +#if DEBUG + return true; +#else + return false; +#endif + } + + private static System.Collections.Generic.Dictionary assembly_count = new(); + + internal static void ApplyUpdate (System.Reflection.Assembly assm) + { + int count; + if (!assembly_count.TryGetValue(assm, out count)) + count = 1; + else + count++; + assembly_count [assm] = count; + + /* FIXME WASM: Location is empty on wasm. Make up a name based on Name */ + string basename = assm.Location; + if (basename == "") + basename = assm.GetName().Name + ".dll"; + Console.Error.WriteLine($"Apply Delta Update for {basename}, revision {count}"); + + string dmeta_name = $"{basename}.{count}.dmeta"; + string dil_name = $"{basename}.{count}.dil"; + byte[] dmeta_data = System.IO.File.ReadAllBytes(dmeta_name); + byte[] dil_data = System.IO.File.ReadAllBytes(dil_name); + byte[] dpdb_data = null; // TODO also use the dpdb data + + AssemblyExtensions.ApplyUpdate(assm, dmeta_data, dil_data, dpdb_data); + } + + internal static bool UseRemoteExecutor => !IsModifiableAssembliesSet; + + internal static void AddRemoteInvokeOptions (ref RemoteInvokeOptions options) + { + options = options ?? new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables.Add(DotNetModifiableAssembliesSwitch, DotNetModifiableAssembliesValue); + } + + /// Run the given test case, which applies updates to the given assembly. + /// + /// Note that the testBody should be a static delegate or a static + /// lambda - it must not use state from the enclosing method. + public static void TestCase(Action testBody, + RemoteInvokeOptions options = null) + { + if (UseRemoteExecutor) + { + Console.Error.WriteLine ($"Running test using RemoteExecutor"); + AddRemoteInvokeOptions(ref options); + RemoteExecutor.Invoke(testBody, options).Dispose(); + } + else + { + Console.Error.WriteLine($"Running test using direct invoke"); + testBody(); + } + } + + /// Run the given test case, which applies updates to the given + /// assembly, and has 1 additional argument. + /// + /// Note that the testBody should be a static delegate or a static + /// lambda - it must not use state from the enclosing method. + public static void TestCase(Action testBody, + string arg1, + RemoteInvokeOptions options = null) + { + if (UseRemoteExecutor) + { + AddRemoteInvokeOptions(ref options); + RemoteExecutor.Invoke(testBody, arg1, options).Dispose(); + } + else + { + testBody(arg1); + } + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index 7182a634c5f837..3e8e0bcea43745 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -8,6 +8,8 @@ false + + @@ -34,6 +36,7 @@ +