-
Notifications
You must be signed in to change notification settings - Fork 162
Description
Hello!
I am using Fody Costura for packing/embedding assemblies into one DLL. When the method from e.g. a test is called, Costura unpacks the needed assemblies into the AppData\Local\Temp\Costura\{guid} folder. For example the assembly with test is called TestAssembly.dll and inside there is Costura packed assembly EmbeddedAssembly.dll that is not present in the directory of the TestAssembly.dll, but unpacked into that Costura folder on demand.
Now, the issue is, that when I'm trying to run the tests with NUnit.Engine, when the test contains something from the EmbeddedAssembly.dll, it's properly unpacked into the Costura folder, but the assembly cannot be resolved. The internal log file shows:
Debug [17] TestAssemblyLoadContext: Loading EmbeddedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null assembly
Debug [17] TestAssemblyResolver: Best version dir for C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App is 6.0.31, but there is no C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.31\EmbeddedAssembly.dll file
Debug [17] TestAssemblyResolver: Best version dir for C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App is 6.0.31, but there is no C:\Program Files (x86)\dotnet\shared\Microsoft.AspNetCore.App\6.0.31\EmbeddedAssembly.dll file
Info [17] TestAssemblyResolver: Cannot resolve assembly 'EmbeddedAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
The TestAssembly.dll is .NET 8.0. My project that runs those tests is in .NET Standard 2.0 with NUnit.Engine version 3.18.3, but the behavior is the same when I build the project on .NET 8.0 and use the newest engine (3.20.1).
I have my own solution for that issue, but that requires changes in the NUnit.Engine itself. I cloned the repo and added new ResolutionStrategy for the TestAssemblyResolver.cs that gets all the DLLs from the Costura folder and also very important for my case, checks the version of the DLL, not only the name:
public class CosturaDirectoryStrategy : ResolutionStrategy
{
private string _costuraDirectory;
public CosturaDirectoryStrategy(string costuraDirectory)
{
_costuraDirectory = costuraDirectory;
}
public override bool TryToResolve(
AssemblyLoadContext loadContext, AssemblyName assemblyName, out Assembly loadedAssembly)
{
loadedAssembly = null;
if (assemblyName.Version == null)
return false;
try
{
var allDirs = Directory.GetDirectories(_costuraDirectory, "*", SearchOption.AllDirectories);
var sortedDirs = allDirs
.Select(d => new DirectoryInfo(d))
.OrderByDescending(di => di.LastWriteTimeUtc)
.ToList();
foreach (var dir in sortedDirs)
{
string candidate = Path.Combine(dir.FullName, assemblyName.Name + ".dll");
if (File.Exists(candidate))
{
try
{
var candidateName = AssemblyName.GetAssemblyName(candidate);
if (candidateName.Version == assemblyName.Version)
{
loadedAssembly = loadContext.LoadFromAssemblyPath(candidate);
log.Info("'{0}' ({1}) assembly v{2} is loaded from CosturaDirectory {3}",
assemblyName.Name,
loadedAssembly.Location,
candidateName.Version,
dir.FullName);
return true;
}
}
catch (BadImageFormatException)
{
continue;
}
}
}
log.Debug("No matching DLL for {0}, version {1} found in {2}",
assemblyName.Name,
assemblyName.Version,
_costuraDirectory);
return false;
}
catch (Exception ex)
{
log.Error("Error while resolving from Costura directories: '{0}'", ex.Message);
return false;
}
}
}
With that and pointing to the Path.Combine(Path.GetTempPath(), "Costura") directory it works well.
Is there any possibility to get it done with current implementation of NUnit.Engine, without having to resort to cloning the repo and adding that ResolutionStrategy? I was looking at the TrustedPlatformAssembliesStrategy, but the problem is that the assembly might not be yet available before Costura unpacks it and this is already happening during the test execution.
Also worth to mention that test run from Rider or Visual Studio test explorers works well, without this issue.