#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);
}
}
}