Skip to content

Commit 03c865e

Browse files
authored
Merge pull request #1483 from nunit/issue-1466
Reorganize assembly resolution; fix error loading WindowsBase.dll
2 parents d22ffe9 + fac8914 commit 03c865e

File tree

3 files changed

+180
-94
lines changed

3 files changed

+180
-94
lines changed

src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyLoadContext.cs

-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@ internal sealed class TestAssemblyLoadContext : AssemblyLoadContext
1515
{
1616
private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyLoadContext));
1717

18-
private readonly string _testAssemblyPath;
1918
private readonly string _basePath;
2019
private readonly TestAssemblyResolver _resolver;
2120
private readonly System.Runtime.Loader.AssemblyDependencyResolver _runtimeResolver;
2221

2322
public TestAssemblyLoadContext(string testAssemblyPath)
2423
{
25-
_testAssemblyPath = testAssemblyPath;
2624
_resolver = new TestAssemblyResolver(this, testAssemblyPath);
2725
_basePath = Path.GetDirectoryName(testAssemblyPath);
2826
_runtimeResolver = new AssemblyDependencyResolver(testAssemblyPath);

src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs

+169-87
Original file line numberDiff line numberDiff line change
@@ -2,171 +2,251 @@
22

33
#if NETCOREAPP3_1_OR_GREATER
44

5+
using Microsoft.Extensions.DependencyModel;
6+
using Microsoft.Extensions.DependencyModel.Resolution;
7+
using Microsoft.Win32;
58
using System;
69
using System.Collections.Generic;
710
using System.IO;
811
using System.Linq;
912
using System.Reflection;
1013
using System.Runtime.InteropServices;
1114
using System.Runtime.Loader;
12-
using Microsoft.Extensions.DependencyModel;
13-
using Microsoft.Extensions.DependencyModel.Resolution;
14-
using Microsoft.Win32;
15+
using TestCentric.Metadata;
1516

1617
namespace NUnit.Engine.Internal
1718
{
1819
internal sealed class TestAssemblyResolver : IDisposable
1920
{
2021
private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyResolver));
2122

22-
private readonly ICompilationAssemblyResolver _assemblyResolver;
23-
private readonly DependencyContext _dependencyContext;
2423
private readonly AssemblyLoadContext _loadContext;
2524

2625
private static readonly string INSTALL_DIR;
2726
private static readonly string WINDOWS_DESKTOP_DIR;
2827
private static readonly string ASP_NET_CORE_DIR;
29-
private static readonly List<string> AdditionalFrameworkDirectories;
28+
29+
// Our Strategies for resolving references
30+
List<ResolutionStrategy> ResolutionStrategies;
3031

3132
static TestAssemblyResolver()
3233
{
3334
INSTALL_DIR = GetDotNetInstallDirectory();
3435
WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App");
3536
ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App");
36-
37-
AdditionalFrameworkDirectories = new List<string>();
38-
if (Directory.Exists(WINDOWS_DESKTOP_DIR))
39-
AdditionalFrameworkDirectories.Add(WINDOWS_DESKTOP_DIR);
40-
if (Directory.Exists(ASP_NET_CORE_DIR))
41-
AdditionalFrameworkDirectories.Add(ASP_NET_CORE_DIR);
4237
}
4338

44-
public TestAssemblyResolver(AssemblyLoadContext loadContext, string assemblyPath)
39+
public TestAssemblyResolver(AssemblyLoadContext loadContext, string testAssemblyPath)
4540
{
4641
_loadContext = loadContext;
47-
_dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(assemblyPath));
4842

49-
_assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
50-
{
51-
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(assemblyPath)),
52-
new ReferenceAssemblyPathResolver(),
53-
new PackageCompilationAssemblyResolver()
54-
});
43+
InitializeResolutionStrategies(loadContext, testAssemblyPath);
5544

5645
_loadContext.Resolving += OnResolving;
5746
}
5847

48+
private void InitializeResolutionStrategies(AssemblyLoadContext loadContext, string testAssemblyPath)
49+
{
50+
// First, looking only at direct references by the test assembly, try to determine if
51+
// this assembly is using WindowsDesktop (either SWF or WPF) and/or AspNetCore.
52+
AssemblyDefinition assemblyDef = AssemblyDefinition.ReadAssembly(testAssemblyPath);
53+
bool isWindowsDesktop = false;
54+
bool isAspNetCore = false;
55+
foreach (var reference in assemblyDef.MainModule.GetTypeReferences())
56+
{
57+
string fn = reference.FullName;
58+
if (fn.StartsWith("System.Windows.") || fn.StartsWith("PresentationFramework"))
59+
isWindowsDesktop = true;
60+
if (fn.StartsWith("Microsoft.AspNetCore."))
61+
isAspNetCore = true;
62+
}
63+
64+
// Initialize the list of ResolutionStrategies in the best order depending on
65+
// what we learned.
66+
ResolutionStrategies = new List<ResolutionStrategy>();
67+
68+
if (isWindowsDesktop && Directory.Exists(WINDOWS_DESKTOP_DIR))
69+
ResolutionStrategies.Add(new AdditionalDirectoryStrategy(WINDOWS_DESKTOP_DIR));
70+
if (isAspNetCore && Directory.Exists(ASP_NET_CORE_DIR))
71+
ResolutionStrategies.Add(new AdditionalDirectoryStrategy(ASP_NET_CORE_DIR));
72+
ResolutionStrategies.Add(new TrustedPlatformAssembliesStrategy());
73+
ResolutionStrategies.Add(new RuntimeLibrariesStrategy(loadContext, testAssemblyPath));
74+
if (!isWindowsDesktop && Directory.Exists(WINDOWS_DESKTOP_DIR))
75+
ResolutionStrategies.Add(new AdditionalDirectoryStrategy(WINDOWS_DESKTOP_DIR));
76+
if (!isAspNetCore && Directory.Exists(ASP_NET_CORE_DIR))
77+
ResolutionStrategies.Add(new AdditionalDirectoryStrategy(ASP_NET_CORE_DIR));
78+
}
79+
5980
public void Dispose()
6081
{
6182
_loadContext.Resolving -= OnResolving;
6283
}
6384

64-
public Assembly Resolve(AssemblyLoadContext context, AssemblyName name)
85+
public Assembly Resolve(AssemblyLoadContext context, AssemblyName assemblyName)
86+
{
87+
return OnResolving(context, assemblyName);
88+
}
89+
90+
private Assembly OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName)
6591
{
66-
return OnResolving(context, name);
92+
if (loadContext == null) throw new ArgumentNullException("context");
93+
94+
Assembly loadedAssembly;
95+
foreach (var strategy in ResolutionStrategies)
96+
if (strategy.TryToResolve(loadContext, assemblyName, out loadedAssembly))
97+
return loadedAssembly;
98+
99+
log.Info("Cannot resolve assembly '{0}'", assemblyName);
100+
return null;
67101
}
68102

69-
private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name)
103+
#region Nested ResolutionStrategy Classes
104+
105+
public abstract class ResolutionStrategy
70106
{
71-
context = context ?? _loadContext;
107+
public abstract bool TryToResolve(
108+
AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly);
109+
}
72110

73-
if (TryLoadFromTrustedPlatformAssemblies(context, name, out var loadedAssembly))
111+
public class TrustedPlatformAssembliesStrategy : ResolutionStrategy
112+
{
113+
public override bool TryToResolve(
114+
AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly)
74115
{
75-
log.Info("'{0}' assembly is loaded from trusted path '{1}'", name, loadedAssembly.Location);
76-
return loadedAssembly;
116+
return TryLoadFromTrustedPlatformAssemblies(loadContext, assemblyName, out loadedAssembly);
77117
}
78118

79-
foreach (var library in _dependencyContext.RuntimeLibraries)
119+
private static bool TryLoadFromTrustedPlatformAssemblies(
120+
AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly)
80121
{
81-
var wrapper = new CompilationLibrary(
82-
library.Type,
83-
library.Name,
84-
library.Version,
85-
library.Hash,
86-
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
87-
library.Dependencies,
88-
library.Serviceable);
89-
90-
var assemblies = new List<string>();
91-
_assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies);
92-
93-
foreach (var assemblyPath in assemblies)
122+
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing
123+
loadedAssembly = null;
124+
var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string;
125+
if (string.IsNullOrEmpty(trustedAssemblies))
94126
{
95-
if (name.Name == Path.GetFileNameWithoutExtension(assemblyPath))
127+
return false;
128+
}
129+
130+
var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":";
131+
foreach (var assemblyPath in trustedAssemblies.Split(separator))
132+
{
133+
var fileName = Path.GetFileNameWithoutExtension(assemblyPath);
134+
if (FileMatchesAssembly(fileName) && File.Exists(assemblyPath))
96135
{
97-
loadedAssembly = context.LoadFromAssemblyPath(assemblyPath);
98-
log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies",
99-
name,
100-
loadedAssembly.Location,
101-
library.Name);
136+
loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath);
137+
log.Info("'{0}' assembly is loaded from trusted path '{1}'", assemblyPath, loadedAssembly.Location);
138+
139+
return true;
140+
}
141+
}
142+
143+
return false;
144+
145+
bool FileMatchesAssembly(string fileName) =>
146+
string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase);
147+
}
148+
}
149+
150+
public class RuntimeLibrariesStrategy : ResolutionStrategy
151+
{
152+
private DependencyContext _dependencyContext;
153+
private readonly ICompilationAssemblyResolver _assemblyResolver;
154+
155+
public RuntimeLibrariesStrategy(AssemblyLoadContext loadContext, string testAssemblyPath)
156+
{
157+
_dependencyContext = DependencyContext.Load(loadContext.LoadFromAssemblyPath(testAssemblyPath));
158+
159+
_assemblyResolver = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
160+
{
161+
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(testAssemblyPath)),
162+
new ReferenceAssemblyPathResolver(),
163+
new PackageCompilationAssemblyResolver()
164+
});
165+
}
102166

103-
return loadedAssembly;
167+
public override bool TryToResolve(
168+
AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly)
169+
{
170+
foreach (var library in _dependencyContext.RuntimeLibraries)
171+
{
172+
var wrapper = new CompilationLibrary(
173+
library.Type,
174+
library.Name,
175+
library.Version,
176+
library.Hash,
177+
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths),
178+
library.Dependencies,
179+
library.Serviceable);
180+
181+
var assemblies = new List<string>();
182+
_assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies);
183+
184+
foreach (var assemblyPath in assemblies)
185+
{
186+
if (assemblyName.Name == Path.GetFileNameWithoutExtension(assemblyPath))
187+
{
188+
loadedAssembly = loadContext.LoadFromAssemblyPath(assemblyPath);
189+
log.Info("'{0}' ({1}) assembly is loaded from runtime libraries {2} dependencies",
190+
assemblyName,
191+
loadedAssembly.Location,
192+
library.Name);
193+
194+
return true;
195+
}
104196
}
105197
}
198+
199+
loadedAssembly = null;
200+
return false;
106201
}
202+
}
203+
204+
public class AdditionalDirectoryStrategy : ResolutionStrategy
205+
{
206+
private string _frameworkDirectory;
107207

108-
if (name.Version == null)
208+
public AdditionalDirectoryStrategy(string frameworkDirectory)
109209
{
110-
return null;
210+
_frameworkDirectory = frameworkDirectory;
111211
}
112212

113-
foreach (string frameworkDirectory in AdditionalFrameworkDirectories)
213+
public override bool TryToResolve(
214+
AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly)
114215
{
115-
var versionDir = FindBestVersionDir(frameworkDirectory, name.Version);
216+
loadedAssembly = null;
217+
if (assemblyName.Version == null)
218+
return false;
219+
220+
var versionDir = FindBestVersionDir(_frameworkDirectory, assemblyName.Version);
116221

117222
if (versionDir != null)
118223
{
119-
string candidate = Path.Combine(frameworkDirectory, versionDir, name.Name + ".dll");
224+
string candidate = Path.Combine(_frameworkDirectory, versionDir, assemblyName.Name + ".dll");
120225
if (File.Exists(candidate))
121226
{
122-
loadedAssembly = context.LoadFromAssemblyPath(candidate);
227+
loadedAssembly = loadContext.LoadFromAssemblyPath(candidate);
123228
log.Info("'{0}' ({1}) assembly is loaded from AdditionalFrameworkDirectory {2} dependencies with best candidate version {3}",
124-
name,
229+
assemblyName,
125230
loadedAssembly.Location,
126-
frameworkDirectory,
231+
_frameworkDirectory,
127232
versionDir);
128233

129-
return loadedAssembly;
234+
return true;
130235
}
131236
else
132237
{
133-
log.Debug("Best version dir for {0} is {1}, but there is no {2} file", frameworkDirectory, versionDir, candidate);
238+
log.Debug("Best version dir for {0} is {1}, but there is no {2} file", _frameworkDirectory, versionDir, candidate);
239+
return false;
134240
}
135241
}
136-
}
137-
138-
log.Info("Cannot resolve assembly '{0}'", name);
139-
return null;
140-
}
141242

142-
private static bool TryLoadFromTrustedPlatformAssemblies(AssemblyLoadContext context, AssemblyName assemblyName, out Assembly loadedAssembly)
143-
{
144-
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing
145-
loadedAssembly = null;
146-
var trustedAssemblies = System.AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string;
147-
if (string.IsNullOrEmpty(trustedAssemblies))
148-
{
149243
return false;
150244
}
245+
}
151246

152-
var separator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":";
153-
foreach (var assemblyPath in trustedAssemblies.Split(separator))
154-
{
155-
var fileName = Path.GetFileNameWithoutExtension(assemblyPath);
156-
if (string.Equals(fileName, assemblyName.Name, StringComparison.InvariantCultureIgnoreCase) == false)
157-
{
158-
continue;
159-
}
160-
161-
if (File.Exists(assemblyPath))
162-
{
163-
loadedAssembly = context.LoadFromAssemblyPath(assemblyPath);
164-
return true;
165-
}
166-
}
247+
#endregion
167248

168-
return false;
169-
}
249+
#region HelperMethods
170250

171251
private static string GetDotNetInstallDirectory()
172252
{
@@ -232,6 +312,8 @@ private static bool TryGetVersionFromString(string text, out Version newVersion)
232312
return false;
233313
}
234314
}
315+
316+
#endregion
235317
}
236318
}
237319
#endif

0 commit comments

Comments
 (0)