#nullable enable using System; using System.Collections.Generic; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using ShrinkContext; namespace ShrinkContext.Tests { /// 提供者测试组件:把 Value 设置到供给键上;OnApply 作为装载闸门。 public sealed class ProviderComponent : IShrinkComponent { private readonly string[] _provide; public ProviderComponent(string name, string key, object? value = null) { Name = name; Value = value; _provide = new[] { key }; } public string Name { get; } public object? Value { get; set; } public IReadOnlyList Inject { get; set; } = Array.Empty(); public IReadOnlyList Provide => _provide; public Func? OnApply { get; set; } public int ApplyCount { get; private set; } public async UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ApplyCount++; if (OnApply != null) await OnApply(); foreach (var key in _provide) ctx.Set(key, Value); } } /// 消费者测试组件:装载时读取依赖键的当前值;OnApply 作为装载闸门。 public sealed class ConsumerComponent : IShrinkComponent { private readonly string[] _inject; public ConsumerComponent(string name, string key) { Name = name; Key = key; _inject = new[] { key }; } public string Name { get; } public string Key { get; } public IReadOnlyList Inject => _inject; public IReadOnlyList Provide => Array.Empty(); public Func? OnApply { get; set; } public int LoadCount { get; private set; } public object? LastSeen { get; private set; } public async UniTask ApplyAsync(ShrinkCtx ctx, object? config) { LoadCount++; if (OnApply != null) await OnApply(); LastSeen = ctx.Get(Key); } } /// 记录效应顺序的提供者/消费者组合组件(验证 drain-before-inverse 次序)。 public sealed class OrderProviderComponent : IShrinkComponent { private readonly string[] _provide; public OrderProviderComponent(string name, string key, object? value, List log) { Name = name; Key = key; Value = value; Log = log; _provide = new[] { key }; } public string Name { get; } public string Key { get; } public object? Value { get; } public List Log { get; } public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Set(Key, Value); ctx.Effect( () => Log.Add("provider-effect"), () => Log.Add("provider-undo")); return UniTask.CompletedTask; } } /// 记录效应顺序的消费者组件。 public sealed class OrderConsumerComponent : IShrinkComponent { private readonly string[] _inject; public OrderConsumerComponent(string name, string key, List log) { Name = name; Key = key; Log = log; _inject = new[] { key }; } public string Name { get; } public string Key { get; } public List Log { get; } public IReadOnlyList Inject => _inject; public IReadOnlyList Provide => Array.Empty(); public object? LastSeen { get; private set; } public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { LastSeen = ctx.Get(Key); ctx.Effect( () => Log.Add("consumer-load"), () => Log.Add("consumer-undo")); return UniTask.CompletedTask; } } /// apply 中抛异常的组件:验证错误路径的部分回滚。 public sealed class FailingComponent : IShrinkComponent { private readonly string[] _provide; public FailingComponent(string name, string key, Func? beforeThrow = null) { Name = name; Key = key; BeforeThrow = beforeThrow; _provide = new[] { key }; } public string Name { get; } public string Key { get; } public Func? BeforeThrow { get; set; } public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public async UniTask ApplyAsync(ShrinkCtx ctx, object? config) { if (BeforeThrow != null) await BeforeThrow(); ctx.Set(Key, "poison"); throw new InvalidOperationException("apply failed: " + Name); } } /// /// 分步效应组件(论文 𝔈iterΓ):每步等待对应闸门放行后执行并产出逆操作; /// 用于验证目标变化在步进边界中断迭代并部分回滚。 /// public sealed class IterativeComponent : IShrinkComponent, IShrinkIterativeComponent { private readonly string[] _provide; public IterativeComponent(string name, string key, int stepCount) { Name = name; Key = key; _provide = new[] { key }; Steps = new List(); Gates = new List(); for (var i = 0; i < stepCount; i++) Gates.Add(new UniTaskCompletionSource()); } public string Name { get; } public string Key { get; } public List Steps { get; } public List Gates { get; } public int ExecutedSteps { get; private set; } public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { throw new NotSupportedException("Iterative component must be driven via ApplySteps."); } public IShrinkStepEffectEnumerator ApplySteps(ShrinkCtx ctx, object? config) => new StepEnumerator(this, ctx); private sealed class StepEnumerator : IShrinkStepEffectEnumerator { private readonly IterativeComponent _owner; private readonly ShrinkCtx _ctx; private int _index = -1; public StepEnumerator(IterativeComponent owner, ShrinkCtx ctx) { _owner = owner; _ctx = ctx; } public Func Current { get; private set; } = null!; public async UniTask MoveNextAsync() { _index++; if (_index >= _owner.Gates.Count) return false; await _owner.Gates[_index].Task; var step = _index + 1; _ctx.Set(_owner.Key, "step-" + step); _owner.Steps.Add("step" + step); _owner.ExecutedSteps++; Current = () => { _owner.Steps.Add("undo" + step); return UniTask.CompletedTask; }; return true; } } } /// 配置驱动值的提供者组件:apply 时把纤程 config 写入供给键(加载器测试用)。 public sealed class ConfigProviderComponent : IShrinkComponent { private readonly string[] _provide; public ConfigProviderComponent(string name, string key) { Name = name; Key = key; _provide = new[] { key }; } public string Name { get; } public string Key { get; } public int ApplyCount { get; private set; } public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ApplyCount++; ctx.Set(Key, config); return UniTask.CompletedTask; } } /// 故意写入未声明键,验证 Provide 运行时契约。 public sealed class UndeclaredProviderComponent : IShrinkComponent { public string Name => "undeclared-provider"; public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => Array.Empty(); public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Set("not-declared", 1); return UniTask.CompletedTask; } } public sealed class MetadataSuffixPolicy : IShrinkCoeffectAccessPolicy { public object? Resolve(ShrinkCoeffectAccessContext context, object? value) { var text = value?.ToString() ?? string.Empty; if (context.TryGetMetadata("prefix", out var prefix)) text = prefix + text; return context.TryGetMetadata("suffix", out var suffix) ? text + suffix : text; } } public sealed class PolicyProviderComponent : IShrinkComponent { private readonly string[] _provide; public PolicyProviderComponent(string key) { Key = key; _provide = new[] { key }; } public string Key { get; } public string Name => "policy-provider"; public IReadOnlyList Inject => Array.Empty(); public IReadOnlyList Provide => _provide; public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { ctx.Set(Key, config?.ToString() ?? "value", new MetadataSuffixPolicy()); return UniTask.CompletedTask; } } public sealed class PolicyConsumerComponent : IShrinkComponent { private readonly string[] _inject; private ShrinkCtx? _ctx; public PolicyConsumerComponent(string key) { Key = key; _inject = new[] { key }; } public string Key { get; } public string Name => "policy-consumer"; public int ApplyCount { get; private set; } public IReadOnlyList Inject => _inject; public IReadOnlyList Provide => Array.Empty(); public UniTask ApplyAsync(ShrinkCtx ctx, object? config) { _ctx = ctx; ApplyCount++; return UniTask.CompletedTask; } public string Read() => (_ctx ?? throw new InvalidOperationException("Consumer is not active.")) .Get(Key); } /// 测试辅助:同步等待 UniTask(测试保证全部延续同步完成,无死锁风险)。 public static class TestAwait { public static void Run(UniTask task) { task.GetAwaiter().GetResult(); } public static T Run(UniTask task) { return task.GetAwaiter().GetResult(); } } }