Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 48 additions & 7 deletions src/NUnitCommon/nunit.agent.core/Drivers/NUnitFrameworkApi2018.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;

using NUnit.Common;
#if NETCOREAPP
using System.Runtime.Loader;
using NUnit.Engine.Internal;
#endif

Expand Down Expand Up @@ -40,10 +41,12 @@ public class NUnitFrameworkApi2018 : NUnitFrameworkApi
private Type? _frameworkControllerType;

#if NETCOREAPP
private TestAssemblyLoadContext? _assemblyLoadContext;
private AssemblyLoadContext? _assemblyLoadContext;
private TestAssemblyResolver? _testAssemblyResolver;
private Assembly? _testAssembly;
private Assembly? _frameworkAssembly;

internal List<ResolutionStrategy>? ResolutionStrategies => _assemblyLoadContext?.ResolutionStrategies;
internal List<ResolutionStrategy>? ResolutionStrategies => _testAssemblyResolver?.ResolutionStrategies;
#endif

private string? _testAssemblyPath;
Expand All @@ -67,6 +70,10 @@ public string Load(string testAssemblyPath, IDictionary<string, object> settings
_testAssemblyPath = Path.GetFullPath(testAssemblyPath);
var idPrefix = _driverId + "-";

bool useDefaultAssemblyLoadContext = false;
if (settings.TryGetValue(SettingDefinitions.UseDefaultAssemblyLoadContext, out var val))
useDefaultAssemblyLoadContext = (bool)val;

#if NETFRAMEWORK
try
{
Expand Down Expand Up @@ -95,12 +102,46 @@ public string Load(string testAssemblyPath, IDictionary<string, object> settings
var controllerAssembly = _frameworkControllerType?.Assembly?.GetName();
log.Debug($"Controller assembly is {controllerAssembly}");
#else
_assemblyLoadContext = new TestAssemblyLoadContext(testAssemblyPath);
try
{
_testAssembly = AssemblyHelper.FindLoadedAssemblyByPath(_testAssemblyPath);

if (_testAssembly is not null)
{
_assemblyLoadContext = AssemblyLoadContext.GetLoadContext(_testAssembly);
log.Debug($" Already loaded in context {_assemblyLoadContext}");
}
else
{
_assemblyLoadContext = useDefaultAssemblyLoadContext
? AssemblyLoadContext.Default
: new AssemblyLoadContext(Path.GetFileNameWithoutExtension(testAssemblyPath));
_testAssembly = _assemblyLoadContext.LoadFromAssemblyPath(testAssemblyPath);
log.Debug($" Loaded into new context {_assemblyLoadContext}");
}

_testAssemblyResolver = new TestAssemblyResolver(_assemblyLoadContext.ShouldNotBeNull(), testAssemblyPath);
}
catch (Exception e)
{
var msg = $"Failed to load test assembly {testAssemblyPath}";
log.Error(msg);
throw new NUnitEngineException(msg, e);
}
log.Debug($"Loaded {testAssemblyPath}");

var testAssembly = LoadAssembly(testAssemblyPath);
_frameworkAssembly = LoadAssembly(_nunitRef);
try
{
_frameworkAssembly = LoadAssembly(_nunitRef);
}
catch (Exception e)
{
log.Error($"{FAILED_TO_LOAD_NUNIT}\r\n{e}");
throw new NUnitEngineException(FAILED_TO_LOAD_NUNIT, e);
}
log.Debug("Loaded nunit.framework");

_frameworkController = CreateInstance(CONTROLLER_TYPE, testAssembly, idPrefix, settings);
_frameworkController = CreateInstance(CONTROLLER_TYPE, _testAssembly, idPrefix, settings);
if (_frameworkController is null)
{
log.Error(INVALID_FRAMEWORK_MESSAGE);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

#if NETCOREAPP3_1_OR_GREATER
#if NETCOREAPP3_1_OR_GREATER && false

using System.Reflection;
using System.Runtime.InteropServices;
Expand Down
23 changes: 18 additions & 5 deletions src/NUnitCommon/nunit.agent.core/TestAssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,20 @@ internal sealed class TestAssemblyResolver : IDisposable
private static readonly Logger log = InternalTrace.GetLogger(typeof(TestAssemblyResolver));

private readonly AssemblyLoadContext _loadContext;
private readonly string _basePath;
private readonly AssemblyDependencyResolver _assemblyDependencyResolver;

// Our Strategies for resolving references
internal List<ResolutionStrategy> ResolutionStrategies = new List<ResolutionStrategy>();

public TestAssemblyResolver(AssemblyLoadContext loadContext, string testAssemblyPath)
{
_loadContext = loadContext;
_basePath = Path.GetDirectoryName(testAssemblyPath).ShouldNotBeNull();
_assemblyDependencyResolver = new AssemblyDependencyResolver(testAssemblyPath);
#if NET8_0_OR_GREATER
AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", _basePath);
#endif

InitializeResolutionStrategies(loadContext, testAssemblyPath);

Expand Down Expand Up @@ -76,15 +83,21 @@ public void Dispose()
_loadContext.Resolving -= OnResolving;
}

public Assembly? Resolve(AssemblyLoadContext context, AssemblyName assemblyName)
{
return OnResolving(context, assemblyName);
}

private Assembly? OnResolving(AssemblyLoadContext loadContext, AssemblyName assemblyName)
{
Guard.ArgumentNotNull(loadContext);

var runtimeResolverPath = _assemblyDependencyResolver.ResolveAssemblyToPath(assemblyName);
if (!string.IsNullOrEmpty(runtimeResolverPath) && File.Exists(runtimeResolverPath))
{
var loadedAssembly = _loadContext.LoadFromAssemblyPath(runtimeResolverPath);
if (loadedAssembly is not null)
{
log.Info($"Assembly {assemblyName} ({loadedAssembly}) is loaded using the deps.json info");
return loadedAssembly;
}
}

foreach (var strategy in ResolutionStrategies)
{
strategy.Calls++;
Expand Down
2 changes: 0 additions & 2 deletions src/NUnitCommon/nunit.agent.core/nunit.agent.core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1' OR '$(TargetFramework)'=='net6.0'">
<PackageReference Include="Microsoft.Win32.Registry" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="3.1.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
<PackageReference Include="Microsoft.Win32.Registry" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
</ItemGroup>

Expand Down
13 changes: 13 additions & 0 deletions src/NUnitCommon/nunit.common/AssemblyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace NUnit
{
Expand Down Expand Up @@ -76,5 +77,17 @@ internal static string GetAssemblyPathFromCodeBase(string codeBase)
return codeBase.Substring(start);
}
#endif

// For assemblies already loaded by MTP or by some other means
public static Assembly? FindLoadedAssemblyByPath(string assemblyPath)
{
var full = Path.GetFullPath(assemblyPath);

return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a =>
!a.IsDynamic &&
!string.IsNullOrEmpty(a.Location) &&
StringComparer.OrdinalIgnoreCase.Equals(Path.GetFullPath(a.Location), full));
}
}
}
3 changes: 2 additions & 1 deletion src/NUnitCommon/nunit.common/DotNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ internal static bool FindBestRuntime(Version targetVersion, IEnumerable<RuntimeI

foreach (var candidate in availableRuntimes)
{
if (candidate.Version >= targetVersion)
if (candidate.Version.Major > targetVersion.Major ||
candidate.Version.Major == targetVersion.Major && candidate.Version.Minor >= candidate.Version.Minor)
if (bestRuntime is null || candidate.Version.Major == bestRuntime.Version.Major)
bestRuntime = candidate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
<ItemGroup>
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnitLite" Version="4.3.2" />
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="NSubstitute" Version="4.3.0" />
Expand Down
12 changes: 12 additions & 0 deletions src/NUnitEngine/nunit.engine.api/SettingDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,18 @@ static SettingDefinitions()
/// </summary>
public static SettingDefinition<string> ImageTargetFrameworkName { get; } = new(nameof(ImageTargetFrameworkName), string.Empty);

/// <summary>
/// Set this to true to force use of the default assembly load context for the
/// test assembly and in resolving all dependencies rather than creating and
/// using a separate instance of AssemblyLoadContext.
/// </summary>
/// <remarks>
/// This is provided for use by the NUnit3 VS Adapter and may not work if used
/// outside of that context. It must be set in the top-level package via the
/// AddSetting method so that the same value is passed to all subpackages.
/// </remarks>
public const string UseDefaultAssemblyLoadContext = "UseDefaultAssemblyLoadContext";

#endregion

#region Settings Used by the NUnit Framework
Expand Down
Loading