DotNetCorePlugins/test/Plugins.Tests/BasicAssemblyLoaderTests.cs

157 lines
6.3 KiB
C#

// Copyright (c) Nate McMaster.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using McMaster.Extensions.Xunit;
using Test.Referenced.Library;
using Xunit;
namespace McMaster.NETCore.Plugins.Tests
{
public class BasicAssemblyLoaderTests
{
[Fact]
public void PluginLoaderCanUnload()
{
var path = TestResources.GetTestProjectAssembly("NetCoreApp2App");
// See https://github.com/dotnet/coreclr/pull/22221
ExecuteAndUnload(path, out var weakRef);
// Force a GC collect to ensure unloaded has completed
for (var i = 0; weakRef.IsAlive && (i < 10); i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Assert.False(weakRef.IsAlive);
}
[MethodImpl(MethodImplOptions.NoInlining)] // ensure no local vars are create
private void ExecuteAndUnload(string path, out WeakReference weakRef)
{
var loader = PluginLoader.CreateFromAssemblyFile(path, c => { c.IsUnloadable = true; });
var assembly = loader.LoadDefaultAssembly();
var method = assembly
.GetType("NetCoreApp2App.Program", throwOnError: true)!
.GetMethod("GetGreeting", BindingFlags.Static | BindingFlags.Public);
Assert.True(loader.IsUnloadable);
Assert.NotNull(method);
Assert.Equal("Hello world!", method!.Invoke(null, Array.Empty<object>()));
loader.Dispose();
Assert.Throws<ObjectDisposedException>(() => loader.LoadDefaultAssembly());
weakRef = new WeakReference(loader.LoadContext, trackResurrection: true);
}
[Fact]
public void LoadsNetCoreProjectWithNativeDeps()
{
var path = TestResources.GetTestProjectAssembly("PowerShellPlugin");
var loader = PluginLoader.CreateFromAssemblyFile(path);
var assembly = loader.LoadDefaultAssembly();
var method = assembly
.GetType("PowerShellPlugin.Program", throwOnError: true)!
.GetMethod("GetGreeting", BindingFlags.Static | BindingFlags.Public);
Assert.NotNull(method);
Assert.Equal("hello", method!.Invoke(null, Array.Empty<object>()));
}
[SkippableFact]
[SkipOnOS(OS.Linux | OS.MacOS)]
public void LoadsNativeDependenciesWhenDllImportUsesFilename()
{
// SqlClient has P/invoke that calls "sni.dll" on Windows. This test checks
// that native libraries can still be resolved in this case.
var path = TestResources.GetTestProjectAssembly("SqlClientApp");
var loader = PluginLoader.CreateFromAssemblyFile(path);
var assembly = loader.LoadDefaultAssembly();
var method = assembly
.GetType("SqlClientApp.Program", throwOnError: true)!
.GetMethod("Run", BindingFlags.Static | BindingFlags.Public);
Assert.NotNull(method);
Assert.Equal(true, method!.Invoke(null, Array.Empty<object>()));
}
[Fact]
public void LoadsNetCoreApp2Project()
{
var path = TestResources.GetTestProjectAssembly("NetCoreApp2App");
var loader = PluginLoader.CreateFromAssemblyFile(path);
var assembly = loader.LoadDefaultAssembly();
var method = assembly
.GetType("NetCoreApp2App.Program", throwOnError: true)!
.GetMethod("GetGreeting", BindingFlags.Static | BindingFlags.Public);
Assert.NotNull(method);
Assert.Equal("Hello world!", method!.Invoke(null, Array.Empty<object>()));
}
[Fact]
public void LoadsNetStandard20Project()
{
var path = TestResources.GetTestProjectAssembly("NetStandardClassLib");
var loader = PluginLoader.CreateFromAssemblyFile(path);
var assembly = loader.LoadDefaultAssembly();
var type = assembly.GetType("NetStandardClassLib.Class1", throwOnError: true);
var method = type!.GetMethod("GetColor", BindingFlags.Instance | BindingFlags.Public);
Assert.NotNull(method);
Assert.Equal("Red", method!.Invoke(Activator.CreateInstance(type), Array.Empty<object>()));
}
[Fact]
public void ItPrefersRuntimeSpecificManagedAssetsOverRidlessOnes()
{
// System.Drawing.Common is an example of a package which has both rid-specific and ridless versions
// The package has lib/netstandard2.0/System.Drawing.Common.dll, but also has runtimes/{win,unix}/lib/netcoreapp2.0/System.Drawing.Common.dll
// In this case, the host will pick the rid-specific version
var path = TestResources.GetTestProjectAssembly("DrawingApp");
var loader = PluginLoader.CreateFromAssemblyFile(path);
var assembly = loader.LoadDefaultAssembly();
var type = assembly.GetType("Finder", throwOnError: true)!;
var method = type.GetMethod("FindDrawingAssembly", BindingFlags.Static | BindingFlags.Public);
Assert.NotNull(method);
Assert.Contains("runtimes", (string?)method!.Invoke(null, Array.Empty<object>()));
}
[Fact]
[UseCulture("es")]
public void ItLoadsSatelliteAssemblies()
{
var fruit = GetPlátano();
Assert.Equal("Plátano", fruit.GetFlavor());
}
[Fact]
[UseCulture("en")]
public void ItLoadsDefaultCultureAssemblies()
{
var fruit = GetPlátano();
Assert.Equal("Banana", fruit.GetFlavor());
}
private IFruit GetPlátano()
{
var path = TestResources.GetTestProjectAssembly("Plátano");
var loader = PluginLoader.CreateFromAssemblyFile(path,
isUnloadable: true,
sharedTypes: new[] { typeof(IFruit) });
var assembly = loader.LoadDefaultAssembly();
var type = Assert.Single(assembly.GetTypes(), t => typeof(IFruit).IsAssignableFrom(t));
return (IFruit)Activator.CreateInstance(type)!;
}
}
}