Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[System.Runtime.Loader] Add hot reload test infrastructure #51144

Merged
merged 22 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a29f09c
[System.Runtime.Loader] Add hot reload test infrastructure
lambdageek Apr 12, 2021
11c9512
Make a proper task for computing hotreload-delta-gen output files
lambdageek Apr 15, 2021
690b98b
Don't need DeltaCount property, compute from json
lambdageek Apr 15, 2021
717afef
Add dependency on hotreload-delta-gen tool
lambdageek Apr 22, 2021
e15aa8d
use 'dotnet tool run hotreload-delta-gen' to generate EnC deltas
lambdageek Apr 22, 2021
1159924
Use remote executor if DOTNET_MODIFIABLE_ASSEMBLIES is not set
lambdageek Apr 27, 2021
cf3e448
Use DotNetTool property to run hotreload-delta-gen
lambdageek Apr 27, 2021
7812159
Don't run on Mono for now
lambdageek Apr 27, 2021
0f29dd0
bump hotreload-delta-gen package version
lambdageek Apr 27, 2021
ab5e519
Install the CLI tools in CI
lambdageek Apr 28, 2021
15f757b
Run tests on Mono if feature enabled and interp is used
lambdageek Apr 28, 2021
2d9549a
fix remote executor detection
lambdageek Apr 29, 2021
5942faf
remove unneeded DefineConstants
lambdageek May 3, 2021
c82877a
Use `$(TargetPath)` as input to ComputeDeltaOutputNames
lambdageek May 4, 2021
5a98a17
drop "BuildingProject" property check
lambdageek May 4, 2021
ecb9362
Use Microsoft.DotNet.HotReload.Utils.Generator.BuildTool instead of h…
lambdageek May 4, 2021
321000c
Use well known version for Generator BuiltTool version
lambdageek May 4, 2021
e816211
Revert "Install the CLI tools in CI"
lambdageek May 4, 2021
2992e23
Use published Generator.BuildTool package
lambdageek May 5, 2021
003b3dc
fix typos, indentantion; add copyright headers
lambdageek May 6, 2021
b5b2b81
Remove ComputeDeltaFileOutputNames use nuget version
lambdageek May 7, 2021
b3347f2
Fix style nits; add more copyright headers
lambdageek May 10, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,9 @@
<Uri>https://github.com/dotnet/emsdk</Uri>
<Sha>e51b2a920817aec200d7306b95f616f9451d47a3</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.HotReload.Utils.Generator.BuildTool" Version="1.0.1-alpha.0.21257.1">
<Uri>https://github.com/dotnet/hotreload-utils</Uri>
<Sha>078f7f63b604e97c64cf907e9932a85f8aeb5dc3</Sha>
</Dependency>
</ToolsetDependencies>
</Dependencies>
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
<MicrosoftNETTestSdkVersion>16.9.0-preview-20201201-01</MicrosoftNETTestSdkVersion>
<MicrosoftDotNetXHarnessTestRunnersXunitVersion>1.0.0-prerelease.21255.1</MicrosoftDotNetXHarnessTestRunnersXunitVersion>
<MicrosoftDotNetXHarnessCLIVersion>1.0.0-prerelease.21255.1</MicrosoftDotNetXHarnessCLIVersion>
<MicrosoftDotNetHotReloadUtilsGeneratorBuildToolVersion>1.0.1-alpha.0.21257.1</MicrosoftDotNetHotReloadUtilsGeneratorBuildToolVersion>
<XUnitVersion>2.4.1</XUnitVersion>
<XUnitRunnerVisualStudioVersion>2.4.2</XUnitRunnerVisualStudioVersion>
<CoverletCollectorVersion>1.3.0</CoverletCollectorVersion>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project>
<Import Project="..\..\Directory.Build.props" />

<ItemGroup>
<!-- This package from https://github.com/dotnet/hotreload-utils provides
targets that read the json delta script and generates deltas based on the baseline assembly and the modified sources.

Projects must define the DeltaScript property that specifies the (relative) path to the json script.
Deltas will be emitted next to the output assembly. Deltas will be copied when the current
project is referenced from other other projects.
-->
<PackageReference Include="Microsoft.DotNet.HotReload.Utils.Generator.BuildTool" Version="$(MicrosoftDotNetHotReloadUtilsGeneratorBuildToolVersion)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<Import Project="..\..\..\Directory.Build.targets" />
</Project>
Original file line number Diff line number Diff line change
@@ -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.

lambdageek marked this conversation as resolved.
Show resolved Hide resolved
namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class MethodBody1 {
public static string StaticMethod1 () {
return "OLD STRING";
}
}
}
Original file line number Diff line number Diff line change
@@ -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.

lambdageek marked this conversation as resolved.
Show resolved Hide resolved
namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class MethodBody1 {
public static string StaticMethod1 () {
return "NEW STRING";
}
}
}
Original file line number Diff line number Diff line change
@@ -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.

lambdageek marked this conversation as resolved.
Show resolved Hide resolved
namespace System.Reflection.Metadata.ApplyUpdate.Test
{
public class MethodBody1 {
public static string StaticMethod1 () {
return "NEWEST STRING";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>System.Runtime.Loader.Tests</RootNamespace>
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
</PropertyGroup>
<ItemGroup>
<Compile Include="MethodBody1.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": [
{"document": "MethodBody1.cs", "update": "MethodBody1_v1.cs"},
{"document": "MethodBody1.cs", "update": "MethodBody1_v2.cs"},
]
}

42 changes: 42 additions & 0 deletions src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
}
148 changes: 148 additions & 0 deletions src/libraries/System.Runtime.Loader/tests/ApplyUpdateUtil.cs
Original file line number Diff line number Diff line change
@@ -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";

lambdageek marked this conversation as resolved.
Show resolved Hide resolved
[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<bool> s_isMonoRuntime = new Lazy<bool>(() => Type.GetType("Mono.RuntimeStructs") != null);
public static bool IsMonoRuntime => s_isMonoRuntime.Value;

private static readonly Lazy<bool> s_isSupportedMonoConfiguration = new Lazy<bool>(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<Type>());

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, int> 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<string> testBody,
string arg1,
RemoteInvokeOptions options = null)
{
if (UseRemoteExecutor)
{
AddRemoteInvokeOptions(ref options);
RemoteExecutor.Invoke(testBody, arg1, options).Dispose();
}
else
{
testBody(arg1);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<GenerateDependencyFile>false</GenerateDependencyFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="ApplyUpdateTest.cs" />
<Compile Include="ApplyUpdateUtil.cs" />
<Compile Include="AssemblyExtensionsTest.cs" />
<Compile Include="AssemblyLoadContextTest.cs" />
<Compile Include="CollectibleAssemblyLoadContextTest.cs" />
Expand All @@ -34,6 +36,7 @@
<ProjectReference Include="ReferencedClassLibNeutralIsSatellite\ReferencedClassLibNeutralIsSatellite.csproj" />
<ProjectReference Include="LoaderLinkTest.Shared\LoaderLinkTest.Shared.csproj" />
<ProjectReference Include="LoaderLinkTest.Dynamic\LoaderLinkTest.Dynamic.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1\System.Reflection.Metadata.ApplyUpdate.Test.MethodBody1.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetOS)' == 'Browser'">
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
Expand Down