chore: initialize standalone UPM package
Publish UPM package / publish (push) Failing after 1s

This commit is contained in:
2026-08-26 02:49:58 +08:00
commit 6eebb8e8e4
50 changed files with 3458 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
#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;
}
}
}
}