fix(plugins): close LA5 host lifecycle review
This commit is contained in:
parent
95f4be94db
commit
fbe9c8a288
25 changed files with 1043 additions and 120 deletions
|
|
@ -1,17 +1,20 @@
|
|||
using System.Text.Json;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AcDream.App.Configuration;
|
||||
using AcDream.App.Plugins;
|
||||
using AcDream.Core.Plugins;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Platform;
|
||||
using AcDream.Plugin.Abstractions;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Tests.Fixtures.CampaignLa;
|
||||
|
||||
namespace AcDream.App.Tests.Plugins;
|
||||
|
||||
public sealed class GraphicalPluginSessionTests
|
||||
{
|
||||
private const string FixtureId = "acdream.test.host-fixture";
|
||||
private const string ThrowingId = "acdream.test.throwing-fixture";
|
||||
|
||||
[Fact]
|
||||
public void ConfiguredSetLoadsOnlyAllowedPluginAndReportsBothOutcomes()
|
||||
|
|
@ -27,12 +30,13 @@ public sealed class GraphicalPluginSessionTests
|
|||
var ui = new BufferedUiRegistry();
|
||||
var host = new AppPluginHost(logger, state, events, selection, ui);
|
||||
|
||||
using GraphicalPluginSession plugins = GraphicalPluginSession.Start(
|
||||
using GraphicalPluginSession plugins = GraphicalPluginSession.Create(
|
||||
paths,
|
||||
[FixtureId.ToUpperInvariant(), "acdream.test.missing"],
|
||||
"gui-session",
|
||||
host,
|
||||
new SessionStatusWriter(statusPath));
|
||||
plugins.Start();
|
||||
|
||||
Assert.Equal(1, plugins.LoadedCount);
|
||||
Assert.True(host.HasUi);
|
||||
|
|
@ -42,19 +46,20 @@ public sealed class GraphicalPluginSessionTests
|
|||
message => message.Contains("fixture-enabled:hasUi=True", StringComparison.Ordinal));
|
||||
|
||||
JsonElement[] statuses = ReadStatuses(statusPath);
|
||||
Assert.Equal(["pluginLoaded", "pluginFailed"], EventNames(statuses));
|
||||
Assert.Equal(FixtureId, statuses[0].GetProperty("plugin").GetString());
|
||||
Assert.Equal(["started", "pluginLoaded", "pluginFailed"], EventNames(statuses));
|
||||
Assert.Equal(FixtureId, statuses[1].GetProperty("plugin").GetString());
|
||||
Assert.Equal(
|
||||
"acdream.test.missing",
|
||||
statuses[1].GetProperty("plugin").GetString());
|
||||
statuses[2].GetProperty("plugin").GetString());
|
||||
Assert.Contains(
|
||||
"not found",
|
||||
statuses[1].GetProperty("error").GetString(),
|
||||
statuses[2].GetProperty("error").GetString(),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
WeakReference context = Assert.Single(
|
||||
plugins.CaptureLoadContextWeakReferences());
|
||||
plugins.Dispose();
|
||||
Assert.Equal(0, ui.RegistrationCount);
|
||||
Collect(context);
|
||||
Assert.False(context.IsAlive);
|
||||
}
|
||||
|
|
@ -66,6 +71,12 @@ public sealed class GraphicalPluginSessionTests
|
|||
ApplicationPathSet paths = Paths(temporary.Path);
|
||||
InstallFixture(paths.PluginsDirectory, FixtureId);
|
||||
string statusPath = Path.Combine(temporary.Path, "status.jsonl");
|
||||
string configPath = Path.Combine(temporary.Path, "session.json");
|
||||
File.WriteAllText(
|
||||
configPath,
|
||||
LauncherCoreSessionConfigFixture.ComposeEmptyPlugins());
|
||||
(_, SessionDescriptor descriptor) =
|
||||
SessionConfigurationLoader.Load(configPath);
|
||||
var ui = new BufferedUiRegistry();
|
||||
var host = new AppPluginHost(
|
||||
new CapturingLogger(),
|
||||
|
|
@ -74,16 +85,70 @@ public sealed class GraphicalPluginSessionTests
|
|||
new SelectionState(),
|
||||
ui);
|
||||
|
||||
using GraphicalPluginSession plugins = GraphicalPluginSession.Start(
|
||||
using GraphicalPluginSession plugins = GraphicalPluginSession.Create(
|
||||
paths,
|
||||
[],
|
||||
descriptor.Plugins,
|
||||
"gui-session",
|
||||
host,
|
||||
new SessionStatusWriter(statusPath));
|
||||
plugins.Start();
|
||||
|
||||
Assert.Equal(0, plugins.LoadedCount);
|
||||
Assert.NotNull(descriptor.Plugins);
|
||||
Assert.Empty(descriptor.Plugins);
|
||||
Assert.Empty(ui.Drain());
|
||||
Assert.Equal(["started"], EventNames(ReadStatuses(statusPath)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowAfterRegistrationRollsBackUiAndEventsAndCollectsContext()
|
||||
{
|
||||
using var temporary = new TemporaryDirectory();
|
||||
ApplicationPathSet paths = Paths(temporary.Path);
|
||||
string pluginDirectory = InstallFixture(
|
||||
paths.PluginsDirectory,
|
||||
ThrowingId,
|
||||
"throwing-fixture");
|
||||
File.WriteAllText(
|
||||
Path.Combine(pluginDirectory, "throw-after-register"),
|
||||
string.Empty);
|
||||
string statusPath = Path.Combine(temporary.Path, "status.jsonl");
|
||||
var events = new WorldEvents();
|
||||
var ui = new BufferedUiRegistry();
|
||||
var host = new AppPluginHost(
|
||||
new CapturingLogger(),
|
||||
new WorldGameState(),
|
||||
events,
|
||||
new SelectionState(),
|
||||
ui);
|
||||
|
||||
using GraphicalPluginSession plugins = GraphicalPluginSession.Create(
|
||||
paths,
|
||||
[ThrowingId],
|
||||
"gui-session",
|
||||
host,
|
||||
new SessionStatusWriter(statusPath));
|
||||
plugins.Start();
|
||||
|
||||
Assert.Equal(0, plugins.LoadedCount);
|
||||
Assert.Empty(ui.Drain());
|
||||
Assert.False(File.Exists(statusPath));
|
||||
Assert.Equal(0, ui.RegistrationCount);
|
||||
events.FireEntitySpawned(new WorldEntitySnapshot(
|
||||
1u,
|
||||
2u,
|
||||
default,
|
||||
System.Numerics.Quaternion.Identity));
|
||||
Assert.False(File.Exists(
|
||||
Path.Combine(pluginDirectory, "unexpected-callback")));
|
||||
Assert.Equal(
|
||||
["started", "pluginFailed"],
|
||||
EventNames(ReadStatuses(statusPath)));
|
||||
|
||||
WeakReference context = Assert.Single(
|
||||
plugins.CaptureLoadContextWeakReferences());
|
||||
plugins.Dispose();
|
||||
Collect(context);
|
||||
Assert.False(context.IsAlive);
|
||||
}
|
||||
|
||||
private static ApplicationPathSet Paths(string root) => new(
|
||||
|
|
@ -114,11 +179,14 @@ public sealed class GraphicalPluginSessionTests
|
|||
private static string[] EventNames(IEnumerable<JsonElement> events) =>
|
||||
events.Select(static item => item.GetProperty("e").GetString()!).ToArray();
|
||||
|
||||
private static void InstallFixture(string root, string id)
|
||||
private static string InstallFixture(
|
||||
string root,
|
||||
string id,
|
||||
string directoryName = "host-fixture")
|
||||
{
|
||||
string source = FixtureAssemblyPath();
|
||||
Assert.True(File.Exists(source), $"fixture DLL not found: {source}");
|
||||
string pluginDirectory = Path.Combine(root, "host-fixture");
|
||||
string pluginDirectory = Path.Combine(root, directoryName);
|
||||
Directory.CreateDirectory(pluginDirectory);
|
||||
string fileName = Path.GetFileName(source);
|
||||
File.Copy(source, Path.Combine(pluginDirectory, fileName));
|
||||
|
|
@ -132,6 +200,7 @@ public sealed class GraphicalPluginSessionTests
|
|||
entryDll = fileName,
|
||||
apiVersion = 1,
|
||||
}));
|
||||
return pluginDirectory;
|
||||
}
|
||||
|
||||
private static string FixtureAssemblyPath()
|
||||
|
|
@ -195,8 +264,22 @@ public sealed class GraphicalPluginSessionTests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
Directory.Delete(Path, recursive: true);
|
||||
for (int attempt = 0; Directory.Exists(Path); attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
return;
|
||||
}
|
||||
catch (Exception error)
|
||||
when (error is IOException or UnauthorizedAccessException
|
||||
&& attempt < 9)
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue