using System.Runtime.Loader;
using Engine.Kernel.Diagnostics;
using Engine.Kernel.Events;
using Engine.Kernel.Plugins;
using Engine.Kernel.Scheduling;
using Engine.Kernel.Services;
using Engine.Kernel.World;
namespace Engine.ConformanceHarness;
///
/// The test docs/kernel-contract.md §4 calls the one thing that keeps this
/// architecture from slowly degrading: load and unload a plugin 200 times,
/// and after every cycle verify the ALC actually collected. Runs against
/// Sandbox.Echo — see the ReferenceOutputAssembly="false" note in this
/// project's .csproj for why that reference doesn't link its types in, and
/// the CopyToOutputDirectory items for how its built DLLs end up sitting
/// next to plugin.json under this project's own output.
///
public class AlcUnloadTests
{
private static string PluginDirectory =>
Path.Combine(AppContext.BaseDirectory, "plugins", "sandbox.echo");
private static PluginHost NewHost() =>
new(new GameWorld(), new ServiceRegistry(), new Schedule(), new EventBus(), new Time());
[Fact]
public void Plugin_Survives_200_Load_Unload_Cycles()
{
var host = NewHost();
for (var i = 0; i < 200; i++)
{
var id = host.Load(PluginDirectory);
var weakAlc = host.Unload(id);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.False(weakAlc.IsAlive, $"ALC survived unload cycle {i}.");
}
// The WeakReference check above only proves the collectible ALC
// let go. It says nothing about the Default ALC, which is never
// supposed to grow at all across reloads — Contracts loads once
// and every later cycle should find it already there. A count
// above 1 here would be a real, separate leak this test would
// otherwise miss entirely.
var contractsCopies = AssemblyLoadContext.Default.Assemblies
.Count(a => a.GetName().Name == "Sandbox.Echo.Contracts");
Assert.Equal(1, contractsCopies);
}
[Fact]
public void Load_Configures_The_Plugin_Without_Throwing()
{
var host = NewHost();
var id = host.Load(PluginDirectory);
Assert.Equal("sandbox.echo", id);
}
[Fact]
public void Load_Throws_When_The_Same_Plugin_Is_Already_Loaded()
{
var host = NewHost();
host.Load(PluginDirectory);
Assert.Throws(() => host.Load(PluginDirectory));
}
[Fact]
public void Unload_Throws_When_The_Plugin_Was_Never_Loaded()
{
var host = NewHost();
Assert.Throws(() => host.Unload("sandbox.echo"));
}
}