mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 05:54:26 +01:00
Tests: All plugin integration tests to resolve plugin's types
Reported by @napoly In an integration test for a plugin, attempt to resolve a type provided by that plugin using `BTCPayServerTester`. For example: ``` tester.GetService<MoneroRPCProvider>(); ``` The type should be resolved successfully. The type fails to resolve. During the test run, the dotnet runtime attempts to load `MoneroRPCProvider` in the default load context (`AssemblyLoadContext.Default`). It locates the plugin assembly in the test directory and loads it there. In contrast, when BTCPay Server loads a plugin, it creates a dedicated plugin load context, and the plugin’s `MoneroRPCProvider` is loaded inside that context. This results in two distinct `MoneroRPCProvider` types: one in the default context and one in the plugin context. This PR forces the plugin context, during integration tests, to load the types it resolves into the default assembly context rather than its own. This prevents duplicate type definitions. As a side effect, behavior may differ slightly between running BTCPay Server normally and running tests, but this should be acceptable in most cases. Relevant discussion: #6851
This commit is contained in:
@@ -88,6 +88,12 @@ namespace BTCPayServer.Tests
|
||||
public bool MockRates { get; set; } = true;
|
||||
public string SocksEndpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This helps testing plugins.
|
||||
/// See https://github.com/btcpayserver/btcpayserver/pull/7008
|
||||
/// </summary>
|
||||
public bool LoadPluginsInDefaultAssemblyContext { get; set; } = true;
|
||||
|
||||
public HashSet<string> Chains { get; set; } = new HashSet<string>() { "BTC" };
|
||||
public bool UseLightning { get; set; }
|
||||
public bool CheatMode { get; set; } = true;
|
||||
@@ -167,6 +173,8 @@ namespace BTCPayServer.Tests
|
||||
#if DEBUG
|
||||
confBuilder.AddJsonFile("appsettings.dev.json", true, false);
|
||||
#endif
|
||||
if (LoadPluginsInDefaultAssemblyContext)
|
||||
confBuilder.AddInMemoryCollection([new("TEST_RUNNER_ENABLED", "true")]);
|
||||
var conf = confBuilder.Build();
|
||||
_Host = new WebHostBuilder()
|
||||
.UseDefaultServiceProvider(options =>
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
|
||||
private bool _isCollectible;
|
||||
private bool _loadInMemory;
|
||||
private bool _loadAssembliesInDefaultLoadContext;
|
||||
private bool _shadowCopyNativeLibraries;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,6 +66,7 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
_lazyLoadReferences,
|
||||
_isCollectible,
|
||||
_loadInMemory,
|
||||
_loadAssembliesInDefaultLoadContext,
|
||||
_shadowCopyNativeLibraries);
|
||||
}
|
||||
|
||||
@@ -319,6 +321,18 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will load assemblies into the default load context.
|
||||
/// This is used for integration tests. Tests run in the default load context, so we do
|
||||
/// not want type mismatch errors due to loading types in different load contexts.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AssemblyLoadContextBuilder LoadAssembliesInDefaultLoadContext()
|
||||
{
|
||||
_loadAssembliesInDefaultLoadContext = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shadow copy native libraries (unmanaged DLLs) to avoid locking of these files.
|
||||
/// This is not as efficient, so is not enabled by default, but is required for scenarios
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
private readonly bool _preferDefaultLoadContext;
|
||||
private readonly string[] _resourceRoots;
|
||||
private readonly bool _loadInMemory;
|
||||
private readonly bool _loadAssembliesInDefaultLoadContext;
|
||||
private readonly bool _lazyLoadReferences;
|
||||
private readonly List<AssemblyLoadContext> _assemblyLoadContexts = new();
|
||||
private readonly AssemblyDependencyResolver _dependencyResolver;
|
||||
@@ -49,6 +50,7 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
bool lazyLoadReferences,
|
||||
bool isCollectible,
|
||||
bool loadInMemory,
|
||||
bool loadAssembliesInDefaultLoadContext,
|
||||
bool shadowCopyNativeLibraries)
|
||||
: base(Path.GetFileNameWithoutExtension(mainAssemblyPath), isCollectible)
|
||||
{
|
||||
@@ -190,11 +192,12 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
return null;
|
||||
}
|
||||
|
||||
private AssemblyLoadContext LoadContext => _loadAssembliesInDefaultLoadContext ? Default : this;
|
||||
public Assembly LoadAssemblyFromFilePath(string path)
|
||||
{
|
||||
if (!_loadInMemory)
|
||||
{
|
||||
return LoadFromAssemblyPath(path);
|
||||
return LoadContext.LoadFromAssemblyPath(path);
|
||||
}
|
||||
|
||||
using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
@@ -202,9 +205,9 @@ namespace BTCPayServer.Plugins.Dotnet.Loader
|
||||
if (File.Exists(pdbPath))
|
||||
{
|
||||
using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
return LoadFromStream(file, pdbFile);
|
||||
return LoadContext.LoadFromStream(file, pdbFile);
|
||||
}
|
||||
return LoadFromStream(file);
|
||||
return LoadContext.LoadFromStream(file);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -120,5 +120,12 @@ namespace BTCPayServer.Plugins.Dotnet
|
||||
/// Default value is 200 milliseconds.
|
||||
/// </summary>
|
||||
public TimeSpan ReloadDelay { get; set; } = TimeSpan.FromMilliseconds(200);
|
||||
|
||||
/// <summary>
|
||||
/// This will load assemblies into the default load context.
|
||||
/// This is used for integration tests. Tests run in the default load context, so we do
|
||||
/// not want type mismatch errors due to loading types in different load contexts.
|
||||
/// </summary>
|
||||
public bool LoadAssembliesInDefaultLoadContext { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +344,10 @@ namespace BTCPayServer.Plugins.Dotnet
|
||||
|
||||
builder.SetMainAssemblyPath(config.MainAssemblyPath);
|
||||
builder.SetDefaultContext(config.DefaultContext);
|
||||
if (config.LoadAssembliesInDefaultLoadContext)
|
||||
{
|
||||
builder.LoadAssembliesInDefaultLoadContext();
|
||||
}
|
||||
|
||||
foreach (var ext in config.PrivateAssemblies)
|
||||
{
|
||||
|
||||
@@ -187,6 +187,7 @@ namespace BTCPayServer.Plugins
|
||||
// this ensures that the version of MVC is shared between this app and the plugin
|
||||
c.PreferSharedTypes = true;
|
||||
c.IsUnloadable = false;
|
||||
c.LoadAssembliesInDefaultLoadContext = config.GetOrDefault<bool>("TEST_RUNNER_ENABLED", false);
|
||||
});
|
||||
var pluginAssembly = loader.LoadDefaultAssembly();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user