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 @@
+