Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 059612d

Browse files
committed
Add unit test cases for System.Runtime.Loader.AssemblyLoadContext
Usage of TPA and AssemblyLoadContext is mutually exclusive, you cannot use both. Since the premise is that you either want to use the default binding mechanism (via coreclr TPA binder) or supply your own (via AssemblyLoadContext) for your own assemblies. Corerun blindly adds all the assemblies (including test assemblies) in the current directory to the TPA list (and hence cannot be loaded via custom load contexts). To workaround this issue the test assembly which is loaded by a custom load context is embedded as a resource to the unit test assembly. This resource is extracted at runtime and prevents corerun from adding it to the TPA list.
1 parent 8ca44f6 commit 059612d

12 files changed

+1955
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 14
4+
VisualStudioVersion = 14.0.23107.0
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.Loader.Tests", "tests\System.Runtime.Loader.Tests.csproj", "{701CB3BC-00DC-435D-BDE4-C5FC29A708A7}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.Loader.TestAssembly", "tests\System.Runtime.Loader.TestAssembly\System.Runtime.Loader.TestAssembly.csproj", "{396D6EBF-60BD-4DAF-8783-FB403E070A56}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
EndGlobalSection
14+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
15+
{701CB3BC-00DC-435D-BDE4-C5FC29A708A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
16+
{701CB3BC-00DC-435D-BDE4-C5FC29A708A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
17+
{396D6EBF-60BD-4DAF-8783-FB403E070A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+
{396D6EBF-60BD-4DAF-8783-FB403E070A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+
EndGlobalSection
20+
GlobalSection(SolutionProperties) = preSolution
21+
HideSolutionNode = FALSE
22+
EndGlobalSection
23+
EndGlobal
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Xunit;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Threading.Tasks;
11+
12+
namespace System.Runtime.Loader.Tests
13+
{
14+
public class AssemblyLoadContextTest
15+
{
16+
const string TESTASSEMBLY = "System.Runtime.Loader.TestAssembly";
17+
18+
[Fact]
19+
public static void GetAssemblyNameTest_ValidAssembly()
20+
{
21+
var expectedName = typeof(ISet<>).GetTypeInfo().Assembly.GetName();
22+
var actualAsmName = AssemblyLoadContext.GetAssemblyName("System.Runtime.dll");
23+
Assert.Equal(expectedName.FullName, actualAsmName.FullName);
24+
}
25+
26+
[Fact]
27+
public static void GetAssemblyNameTest_AssemblyNotFound()
28+
{
29+
Assert.Throws(typeof(FileNotFoundException),
30+
() => AssemblyLoadContext.GetAssemblyName("Non.Existing.Assembly.dll"));
31+
}
32+
33+
[Fact]
34+
public static void GetAssemblyNameTest_NullParameter()
35+
{
36+
Assert.Throws(typeof(ArgumentNullException),
37+
() => AssemblyLoadContext.GetAssemblyName(null));
38+
}
39+
40+
[Fact]
41+
public static void LoadAssemblyByPath_ValidUserAssembly()
42+
{
43+
var asmName = new AssemblyName(TESTASSEMBLY);
44+
var loadContext = new ResourceAssemblyLoadContext();
45+
loadContext.LoadBy = LoadBy.Path;
46+
47+
var asm = loadContext.LoadFromAssemblyName(asmName);
48+
49+
Assert.NotNull(asm);
50+
Assert.True(asm.DefinedTypes.Any(t => t.Name == "TestClass"));
51+
}
52+
53+
[Fact]
54+
public static void LoadAssemblyByStream_ValidUserAssembly()
55+
{
56+
var asmName = new AssemblyName(TESTASSEMBLY);
57+
var loadContext = new ResourceAssemblyLoadContext();
58+
loadContext.LoadBy = LoadBy.Stream;
59+
60+
var asm = loadContext.LoadFromAssemblyName(asmName);
61+
62+
Assert.NotNull(asm);
63+
Assert.True(asm.DefinedTypes.Any(t => t.Name == "TestClass"));
64+
}
65+
66+
[Fact]
67+
public static void LoadFromAssemblyName_AssemblyNotFound()
68+
{
69+
var asmName = new AssemblyName("Non.Existing.Assembly.dll");
70+
var loadContext = new ResourceAssemblyLoadContext();
71+
loadContext.LoadBy = LoadBy.Path;
72+
73+
Assert.Throws(typeof(FileNotFoundException),
74+
() => loadContext.LoadFromAssemblyName(asmName));
75+
}
76+
77+
[Fact]
78+
public static void LoadFromAssemblyName_ValidTrustedPlatformAssembly()
79+
{
80+
var asmName = AssemblyLoadContext.GetAssemblyName("System.Runtime.dll");
81+
var loadContext = new CustomTPALoadContext();
82+
83+
// Usage of TPA and AssemblyLoadContext is mutually exclusive, you cannot use both.
84+
// Since the premise is that you either want to use the default binding mechanism (via coreclr TPA binder)
85+
// or supply your own (via AssemblyLoadContext) for your own assemblies.
86+
Assert.Throws(typeof(FileLoadException),
87+
() => loadContext.LoadFromAssemblyName(asmName));
88+
}
89+
90+
[Fact]
91+
public static void GetLoadContextTest_ValidUserAssembly()
92+
{
93+
var asmName = new AssemblyName(TESTASSEMBLY);
94+
var loadContext = new ResourceAssemblyLoadContext();
95+
loadContext.LoadBy = LoadBy.Stream;
96+
97+
var asm = loadContext.LoadFromAssemblyName(asmName);
98+
var context = AssemblyLoadContext.GetLoadContext(asm);
99+
100+
Assert.True(context == loadContext);
101+
}
102+
103+
[Fact]
104+
public static void GetLoadContextTest_ValidTrustedPlatformAssembly()
105+
{
106+
var asm = typeof(ISet<>).GetTypeInfo().Assembly;
107+
var context = AssemblyLoadContext.GetLoadContext(asm);
108+
109+
Assert.NotNull(context);
110+
}
111+
112+
[Fact]
113+
public static void InitializeDefaultContextTest()
114+
{
115+
var loadContext = new ResourceAssemblyLoadContext();
116+
117+
// because the coreclr binding model is already locked for the appdomain
118+
// and cannot be reset
119+
Assert.Throws(typeof(InvalidOperationException),
120+
() => AssemblyLoadContext.InitializeDefaultContext(loadContext));
121+
}
122+
}
123+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Reflection;
7+
8+
namespace System.Runtime.Loader.Tests
9+
{
10+
public class CustomTPALoadContext : AssemblyLoadContext
11+
{
12+
protected override Assembly Load(AssemblyName assemblyName)
13+
{
14+
string assemblyPath = Path.Combine(Directory.GetCurrentDirectory(), assemblyName.Name + ".dll");
15+
return LoadFromAssemblyPath(assemblyPath);
16+
}
17+
}
18+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Reflection;
7+
8+
namespace System.Runtime.Loader.Tests
9+
{
10+
public enum LoadBy
11+
{
12+
Path,
13+
Stream
14+
}
15+
16+
public class ResourceAssemblyLoadContext : AssemblyLoadContext
17+
{
18+
public LoadBy LoadBy { get; set; }
19+
20+
public ResourceAssemblyLoadContext()
21+
{
22+
LoadBy = LoadBy.Path;
23+
}
24+
25+
// A custom load context which only loads a given assembly if it is an embedded resource.
26+
protected override Assembly Load(AssemblyName assemblyName)
27+
{
28+
string assembly = assemblyName.Name + ".dll";
29+
var currentAsm = typeof(ResourceAssemblyLoadContext).GetTypeInfo().Assembly;
30+
var asmStream = currentAsm.GetManifestResourceStream("System.Runtime.Loader.Tests." + assembly);
31+
32+
if (asmStream == null)
33+
{
34+
return null;
35+
}
36+
37+
if (LoadBy == LoadBy.Path)
38+
{
39+
// corerun blindly adds all the assemblies (including test assemblies) in the current directory to the TPA list.
40+
// Custom Load Contexts cannot be used to load an assembly in the TPA list.
41+
// Hence using this hack - where the user test assembly "System.Runtime.Loader.TestAssembly" is added as an embedded resource.
42+
// This custom load context will extract that resource and store it at the %temp% path at runtime.
43+
// This prevents the corerun from adding the the test assembly to the TPA list.
44+
// Once loaded it is not possible to unload the assembly, therefore it cannot be deleted.
45+
string path = Path.Combine(Path.GetTempPath(), assembly);
46+
using (FileStream output = File.OpenWrite(path))
47+
{
48+
asmStream.CopyTo(output);
49+
}
50+
51+
return LoadFromAssemblyPath(path);
52+
}
53+
else if (LoadBy == LoadBy.Stream)
54+
{
55+
return LoadFromStream(asmStream);
56+
}
57+
58+
return null;
59+
}
60+
}
61+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<OutputType>Library</OutputType>
8+
<AppDesignerFolder>Properties</AppDesignerFolder>
9+
<RootNamespace>System.Runtime.Loader.TestAssembly</RootNamespace>
10+
<AssemblyName>System.Runtime.Loader.TestAssembly</AssemblyName>
11+
<ProjectGuid>{396D6EBF-60BD-4DAF-8783-FB403E070A56}</ProjectGuid>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<Compile Include="TestClass.cs" />
15+
</ItemGroup>
16+
<ItemGroup>
17+
<None Include="project.json" />
18+
</ItemGroup>
19+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
20+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+

2+
3+
namespace System.Runtime.Loader.Tests
4+
{
5+
public class TestClass
6+
{
7+
}
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"dependencies": {
3+
"System.Runtime": "4.0.20-beta-*",
4+
"System.Reflection": "4.0.10-beta-*",
5+
"System.Runtime.Extensions": "4.0.10-beta-*"
6+
},
7+
"frameworks": {
8+
"dnxcore50": {}
9+
}
10+
}

0 commit comments

Comments
 (0)