#nullable enable using System.Collections.Generic; using Cysharp.Threading.Tasks; using NUnit.Framework; using ShrinkContext; namespace ShrinkContext.Tests { /// /// 可逆效应语义(论文 3.1 / 算法 1): /// 定理 7(恢复到初始上下文)、定理 16(LIFO 逐步还原)、armed 单次执行、异步前向等待。 /// public class EffectTrackingTests { private ShrinkContextRuntime _runtime = null!; private ShrinkCtx _ctx = null!; [SetUp] public void SetUp() { _runtime = new ShrinkContextRuntime(); _ctx = _runtime.RootContext; } [Test] public void DisposeAsync_RestoresInitialState_Theorem7() { var shared = new Dictionary(); _ctx.Effect(() => shared["a"] = 1, () => shared.Remove("a")); _ctx.Effect(() => shared["b"] = 2, () => shared.Remove("b")); _ctx.Effect(() => shared["c"] = 3, () => shared.Remove("c")); Assert.AreEqual(3, shared.Count); TestAwait.Run(_ctx.DisposeAsync()); Assert.IsEmpty(shared, "累加器回滚后上下文必须恢复到初始状态(定理 7)"); } [Test] public void ManualDispose_UndoesOnlyItsOwnEffect() { var shared = new Dictionary(); _ctx.Effect(() => shared["a"] = 1, () => shared.Remove("a")); var effectB = _ctx.Effect(() => shared["b"] = 2, () => shared.Remove("b")); _ctx.Effect(() => shared["c"] = 3, () => shared.Remove("c")); TestAwait.Run(effectB.DisposeAsync()); Assert.IsFalse(shared.ContainsKey("b")); Assert.IsTrue(shared.ContainsKey("a")); Assert.IsTrue(shared.ContainsKey("c"), "选择性撤销不得影响其它效应(𝔈Γ 提升)"); TestAwait.Run(_ctx.DisposeAsync()); Assert.IsEmpty(shared); } [Test] public void ManualDispose_IsIdempotent_ArmedSemantics() { var undoCount = 0; var handle = _ctx.Effect(() => { }, () => undoCount++); TestAwait.Run(handle.DisposeAsync()); TestAwait.Run(handle.DisposeAsync()); TestAwait.Run(_ctx.DisposeAsync()); Assert.AreEqual(1, undoCount, "逆操作至多执行一次(armed 语义)"); } [Test] public void AsyncInverses_RunInLifoOrder() { var log = new List(); var h1 = TestAwait.Run(_ctx.EffectAsync(async () => { log.Add("f1"); return () => { log.Add("i1"); return UniTask.CompletedTask; }; })); var h2 = TestAwait.Run(_ctx.EffectAsync(async () => { log.Add("f2"); return async () => { log.Add("i2"); await UniTask.CompletedTask; }; })); TestAwait.Run(h2.DisposeAsync()); CollectionAssert.AreEqual(new[] { "f1", "f2", "i2" }, log); TestAwait.Run(_ctx.DisposeAsync()); CollectionAssert.AreEqual(new[] { "f1", "f2", "i2", "i1" }, log, "后注册的逆操作先执行(LIFO)"); } [Test] public void DisposeAsync_WaitsForInflightForwardBeforeRecovering() { var log = new List(); var gate = new UniTaskCompletionSource(); var effectTask = _ctx.EffectAsync(async () => { await gate.Task; log.Add("fwd"); return () => { log.Add("inv"); return UniTask.CompletedTask; }; }); // 前向仍在进行中时启动卸载:dispose 应先等待前向完成,再执行其产出的逆操作 var disposeTask = _ctx.DisposeAsync(); Assert.IsEmpty(log, "前向未完成时不得提前执行恢复"); gate.TrySetResult(); TestAwait.Run(disposeTask); var handle = TestAwait.Run(effectTask); CollectionAssert.AreEqual(new[] { "fwd", "inv" }, log, "dispose = await task; recover()"); Assert.IsFalse(handle.IsArmed); } } }