201 lines
6.6 KiB
C#
201 lines
6.6 KiB
C#
using System.Text.Json;
|
|
using AcDream.Core.Plugins;
|
|
using AcDream.Core.Selection;
|
|
using AcDream.Plugin.Abstractions;
|
|
|
|
namespace AcDream.Core.Tests.Plugins;
|
|
|
|
public sealed class PluginSessionTests
|
|
{
|
|
[Fact]
|
|
public void AbsentAllowListLoadsEveryDiscoveredPlugin()
|
|
{
|
|
using var temporary = new TemporaryDirectory();
|
|
InstallFixture(temporary.Path, "alpha", "acdream.test.alpha");
|
|
InstallFixture(temporary.Path, "beta", "acdream.test.beta");
|
|
var statuses = new List<PluginSessionStatus>();
|
|
var plugins = new PluginSession(new StubHost(), statuses.Add);
|
|
|
|
plugins.Start([temporary.Path], allowList: null);
|
|
|
|
Assert.Equal(2, plugins.LoadedCount);
|
|
Assert.Equal(
|
|
["acdream.test.alpha", "acdream.test.beta"],
|
|
plugins.LoadedPluginIds);
|
|
Assert.All(
|
|
statuses,
|
|
status => Assert.Equal(PluginSessionStatusKind.Loaded, status.Kind));
|
|
ReleaseAndCollect(plugins);
|
|
}
|
|
|
|
[Fact]
|
|
public void AllowListIsCaseInsensitiveAndOneFailureDoesNotBlockAnotherPlugin()
|
|
{
|
|
using var temporary = new TemporaryDirectory();
|
|
InstallFixture(temporary.Path, "good", "acdream.test.good");
|
|
InstallBroken(temporary.Path, "broken", "acdream.test.broken");
|
|
var statuses = new List<PluginSessionStatus>();
|
|
var plugins = new PluginSession(new StubHost(), statuses.Add);
|
|
|
|
plugins.Start(
|
|
[temporary.Path],
|
|
[
|
|
"ACDREAM.TEST.BROKEN",
|
|
"ACDREAM.TEST.GOOD",
|
|
"acdream.test.missing",
|
|
]);
|
|
|
|
Assert.Equal(["acdream.test.good"], plugins.LoadedPluginIds);
|
|
Assert.Equal(
|
|
[
|
|
("ACDREAM.TEST.BROKEN", PluginSessionStatusKind.Failed),
|
|
("acdream.test.good", PluginSessionStatusKind.Loaded),
|
|
("acdream.test.missing", PluginSessionStatusKind.Failed),
|
|
],
|
|
statuses.Select(static status => (status.Plugin, status.Kind)));
|
|
Assert.All(
|
|
statuses.Where(static status => status.Kind == PluginSessionStatusKind.Failed),
|
|
status => Assert.False(string.IsNullOrWhiteSpace(status.Error)));
|
|
ReleaseAndCollect(plugins);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExplicitEmptyAllowListLoadsNothing()
|
|
{
|
|
using var temporary = new TemporaryDirectory();
|
|
InstallFixture(temporary.Path, "fixture", "acdream.test.fixture");
|
|
var statuses = new List<PluginSessionStatus>();
|
|
using var plugins = new PluginSession(new StubHost(), statuses.Add);
|
|
|
|
plugins.Start([temporary.Path], []);
|
|
|
|
Assert.Equal(0, plugins.LoadedCount);
|
|
Assert.Empty(statuses);
|
|
}
|
|
|
|
private static void ReleaseAndCollect(PluginSession plugins)
|
|
{
|
|
IReadOnlyList<WeakReference> contexts =
|
|
plugins.CaptureLoadContextWeakReferences();
|
|
plugins.Dispose();
|
|
for (int attempt = 0;
|
|
attempt < 10 && contexts.Any(static context => context.IsAlive);
|
|
attempt++)
|
|
{
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
}
|
|
Assert.All(contexts, static context => Assert.False(context.IsAlive));
|
|
}
|
|
|
|
private static void InstallFixture(string root, string folder, string id)
|
|
{
|
|
string source = FixturePluginPath();
|
|
Assert.True(File.Exists(source), $"fixture DLL not found: {source}");
|
|
string pluginDirectory = Path.Combine(root, folder);
|
|
Directory.CreateDirectory(pluginDirectory);
|
|
string fileName = Path.GetFileName(source);
|
|
File.Copy(source, Path.Combine(pluginDirectory, fileName));
|
|
WriteManifest(pluginDirectory, id, fileName);
|
|
}
|
|
|
|
private static void InstallBroken(string root, string folder, string id)
|
|
{
|
|
string pluginDirectory = Path.Combine(root, folder);
|
|
Directory.CreateDirectory(pluginDirectory);
|
|
WriteManifest(pluginDirectory, id, "missing.dll");
|
|
}
|
|
|
|
private static void WriteManifest(
|
|
string directory,
|
|
string id,
|
|
string entryDll) =>
|
|
File.WriteAllText(
|
|
Path.Combine(directory, "plugin.json"),
|
|
JsonSerializer.Serialize(new
|
|
{
|
|
id,
|
|
displayName = id,
|
|
version = "1.0.0",
|
|
entryDll,
|
|
apiVersion = 1,
|
|
}));
|
|
|
|
private static string FixturePluginPath()
|
|
{
|
|
string configuration = new DirectoryInfo(AppContext.BaseDirectory)
|
|
.Parent!.Name;
|
|
string root = FindRepoRoot(AppContext.BaseDirectory);
|
|
return Path.Combine(
|
|
root,
|
|
"tests",
|
|
"AcDream.Core.Tests.Fixtures.HelloPlugin",
|
|
"bin",
|
|
configuration,
|
|
"net10.0",
|
|
"AcDream.Core.Tests.Fixtures.HelloPlugin.dll");
|
|
}
|
|
|
|
private static string FindRepoRoot(string start)
|
|
{
|
|
DirectoryInfo? directory = new(start);
|
|
while (directory is not null
|
|
&& !File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
{
|
|
directory = directory.Parent;
|
|
}
|
|
return directory?.FullName
|
|
?? throw new InvalidOperationException("Repository root not found.");
|
|
}
|
|
|
|
private sealed class StubHost : IPluginHost
|
|
{
|
|
public bool HasUi => false;
|
|
public IPluginLogger Log { get; } = new StubLogger();
|
|
public IGameState State { get; } = new StubState();
|
|
public IEvents Events { get; } = new StubEvents();
|
|
public ISelectionService Selection { get; } = new SelectionState();
|
|
public IUiRegistry Ui => NoOpUiRegistry.Instance;
|
|
}
|
|
|
|
private sealed class StubLogger : IPluginLogger
|
|
{
|
|
public void Info(string message) { }
|
|
public void Warn(string message) { }
|
|
public void Error(string message, Exception? exception = null) { }
|
|
}
|
|
|
|
private sealed class StubState : IGameState
|
|
{
|
|
public IReadOnlyList<WorldEntitySnapshot> Entities => [];
|
|
}
|
|
|
|
private sealed class StubEvents : IEvents
|
|
{
|
|
public event Action<WorldEntitySnapshot> EntitySpawned
|
|
{
|
|
add { }
|
|
remove { }
|
|
}
|
|
}
|
|
|
|
private sealed class TemporaryDirectory : IDisposable
|
|
{
|
|
internal TemporaryDirectory()
|
|
{
|
|
Path = System.IO.Path.Combine(
|
|
System.IO.Path.GetTempPath(),
|
|
$"acdream-plugin-session-{Guid.NewGuid():N}");
|
|
Directory.CreateDirectory(Path);
|
|
}
|
|
|
|
internal string Path { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(Path))
|
|
Directory.Delete(Path, recursive: true);
|
|
}
|
|
}
|
|
}
|