Files
cneicy 6eebb8e8e4
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:49:58 +08:00

157 lines
6.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#nullable enable
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using NUnit.Framework;
using ShrinkContext;
namespace ShrinkContext.Tests
{
/// <summary>
/// 余效应操作与介导访问(论文 3.2 / 算法 2、6):
/// set 是可逆效应;代理介导的 get 拒绝未声明/未激活访问;隔离域派生独立绑定。
/// </summary>
public class CoeffectAccessTests
{
private ShrinkContextRuntime _runtime = null!;
private ShrinkCtx _ctx = null!;
[SetUp]
public void SetUp()
{
_runtime = new ShrinkContextRuntime();
_ctx = _runtime.RootContext;
}
[Test]
public void Set_IsReversibleEffect_EarlyWithdrawAndContextDispose()
{
var handle = _ctx.Set("cfg", 42);
Assert.IsTrue(_runtime.TryGetRaw<int>(_ctx, "cfg", out var v1));
Assert.AreEqual(42, v1);
// 早期手动撤回
TestAwait.Run(handle.DisposeAsync());
Assert.IsFalse(_runtime.TryGetRaw<int>(_ctx, "cfg", out _));
// 再次安装后随上下文卸载自动撤回
_ctx.Set("cfg", 43);
Assert.IsTrue(_runtime.TryGetRaw<int>(_ctx, "cfg", out var v2));
Assert.AreEqual(43, v2);
TestAwait.Run(_ctx.DisposeAsync());
Assert.IsFalse(_runtime.TryGetRaw<int>(_ctx, "cfg", out _), "set 的逆操作必须移除绑定");
}
[Test]
public void MediatedGet_RejectsUndeclaredKey_InApply()
{
var provider = new ProviderComponent("provider", "real", "v");
var fiber = _runtime.Use(provider);
Assert.AreEqual(ShrinkFiberState.Active, fiber.State);
var greedy = new GreedyConsumerComponent("greedy", "real", "undeclared");
var greedyFiber = _runtime.Use(greedy);
Assert.AreEqual(ShrinkFiberState.Inactive, greedyFiber.State);
Assert.IsInstanceOf<ShrinkUndeclaredAccessException>(greedyFiber.LastError,
"未声明的依赖访问在访问点被拒绝,并使装载失败(算法 6 UNDECLARED_ACCESS");
}
[Test]
public void MediatedGet_ThrowsWhenOwnKeyNotActivated()
{
var consumer = new ConsumerComponent("consumer", "missing");
var fiber = _runtime.Use(consumer);
Assert.AreEqual(ShrinkFiberState.Inactive, fiber.State,
"依赖缺失时组件保持非活动,而不是抛错(响应式余效应)");
Assert.Throws<ShrinkInactiveAccessException>(() => fiber.Ctx.Get<object>("missing"));
}
[Test]
public void Isolate_DerivesIndependentRealm_RestoreByDiscard()
{
_ctx.Set("k", 1);
Assert.IsTrue(_runtime.TryGetRaw<int>(_ctx, "k", out var rootValue));
Assert.AreEqual(1, rootValue);
var isolated = _ctx.Isolate("k", "test-realm");
// 隔离域内初始为空:逻辑键相同但 realm 不同
Assert.IsFalse(_runtime.TryGetRaw<int>(isolated, "k", out _));
isolated.Set("k", 2);
Assert.IsTrue(_runtime.TryGetRaw<int>(isolated, "k", out var isolatedValue));
Assert.AreEqual(2, isolatedValue);
Assert.IsTrue(_runtime.TryGetRaw<int>(_ctx, "k", out var rootValueAfter));
Assert.AreEqual(1, rootValueAfter, "隔离域写入不得影响父上下文绑定");
// 派生上下文的恢复是隐式的:丢弃即恢复(论文定义 27 派生实现)
TestAwait.Run(isolated.DisposeAsync());
Assert.IsFalse(_runtime.TryGetRaw<int>(isolated, "k", out _));
Assert.IsTrue(_runtime.TryGetRaw<int>(_ctx, "k", out var rootValueFinal));
Assert.AreEqual(1, rootValueFinal);
}
[Test]
public void TypedKey_PreservesVersionedIdentityAcrossMediatedAccess()
{
var key = new ShrinkKey<string>("test.package", "typed-service", 2);
Assert.AreEqual("test.package/typed-service@v2", key.Id);
_runtime.Use(new PolicyProviderComponent(key.Id), "typed-value");
var consumer = _runtime.Use(new PolicyConsumerComponent(key.Id));
Assert.AreEqual(ShrinkFiberState.Active, consumer.State);
Assert.AreEqual("typed-value", consumer.Ctx.Get(key));
}
[Test]
public void Intercept_MergesRootToLeafAndChildMetadataWins()
{
const string key = "policy.service";
_runtime.Use(new PolicyProviderComponent(key), "value");
var consumer = _runtime.Use(new PolicyConsumerComponent(key));
Assert.AreEqual(ShrinkFiberState.Active, consumer.State);
var parent = consumer.Ctx.Intercept(key, new Dictionary<string, object?>
{
["prefix"] = "parent-",
["suffix"] = "-parent"
});
var child = parent.Intercept(key, new Dictionary<string, object?>
{
["suffix"] = "-child"
});
Assert.AreEqual("parent-value-child", child.Get<string>(key));
}
/// <summary>声明一个键却访问另一个未声明键的组件(验证能力介导纪律)。</summary>
private sealed class GreedyConsumerComponent : IShrinkComponent
{
private readonly string[] _inject;
public GreedyConsumerComponent(string name, string declaredKey, string undeclaredKey)
{
Name = name;
UndeclaredKey = undeclaredKey;
_inject = new[] { declaredKey };
}
public string Name { get; }
public string UndeclaredKey { get; }
public IReadOnlyList<string> Inject => _inject;
public IReadOnlyList<string> Provide => Array.Empty<string>();
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
{
ctx.Get<object>(UndeclaredKey);
return UniTask.CompletedTask;
}
}
}
}