#nullable enable using System; using System.Linq; using System.Runtime.CompilerServices; using Cysharp.Threading.Tasks; using NUnit.Framework; namespace ShrinkContext.Tests { public class RetirementRetentionTests { [Test] public void DeepDependencyChainRetiresWithoutGrowingTheCallStack() { var runtime = new ShrinkContextRuntime(); ShrinkFiber first = null; for (var i = 0; i < 1000; i++) { var fiber = runtime.Use(new ChainComponent(i)); if (i == 0) first = fiber; } TestAwait.Run(runtime.RetireAsync(first)); Assert.That(runtime.Fibers.All(f => f.State == ShrinkFiberState.Inactive)); Assert.That(runtime.CaptureDiagnostic().BindingCount, Is.Zero); TestAwait.Run(runtime.ShutdownAsync()); Assert.That(runtime.Fibers, Is.Empty); } private sealed class ChainComponent : IShrinkComponent { public ChainComponent(int index) { Name = "chain-" + index; Inject = index == 0 ? Array.Empty() : new[] { "chain-" + (index - 1) }; Provide = new[] { Name }; } public string Name { get; } public System.Collections.Generic.IReadOnlyList Inject { get; } public System.Collections.Generic.IReadOnlyList Provide { get; } public UniTask ApplyAsync(ShrinkCtx ctx, object config) { ctx.Set(Name, this); return UniTask.CompletedTask; } } [Test] public void RepeatedRetirementKeepsOnlyBoundedValueHistory() { var runtime = new ShrinkContextRuntime(8); for (var i = 0; i < 10000; i++) TestAwait.Run(runtime.RetireAsync(runtime.Use(new ProviderComponent("provider", "value", i)))); Assert.That(runtime.Fibers, Is.Empty); var diagnostic = runtime.CaptureDiagnostic(); Assert.That(diagnostic.Fibers.Count, Is.EqualTo(8)); Assert.That(diagnostic.Fibers.All(f => f.Retired)); Assert.That(diagnostic.BindingCount, Is.Zero); TestAwait.Run(runtime.ShutdownAsync()); } [Test] public void RuntimeAndParentEffectsDoNotRetainRetiredComponent() { var runtime = new ShrinkContextRuntime(); var weak = CreateAndRetire(runtime); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Assert.That(weak.IsAlive, Is.False); GC.KeepAlive(runtime); } [MethodImpl(MethodImplOptions.NoInlining)] private static WeakReference CreateAndRetire(ShrinkContextRuntime runtime) { var component = new ProviderComponent("collectible", "key", new byte[1024]); var weak = new WeakReference(component); TestAwait.Run(runtime.RetireAsync(runtime.Use(component))); return weak; } [Test] public void DiagnosticProvidersRespectRealmsAndExcludeRetired() { var runtime = new ShrinkContextRuntime(); var p = runtime.Use(new ProviderComponent("p", "key", "value")); var c = runtime.Use(new ConsumerComponent("c", "key")); Assert.That(runtime.CaptureDiagnostic().Fibers.Single(f => f.Uid == c.Uid).Dependencies.Single().PotentialProviderUids, Is.EqualTo(new[] { p.Uid })); TestAwait.Run(runtime.RetireAsync(p)); Assert.That(runtime.CaptureDiagnostic().Fibers.Single(f => f.Uid == c.Uid).Dependencies.Single().PotentialProviderUids, Is.Empty); TestAwait.Run(runtime.ShutdownAsync()); } } }