Files
Workspace/Assets/Modules/ShrinkContext.Core/Tests/TestComponents.cs
T

342 lines
11 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using ShrinkContext;
namespace ShrinkContext.Tests
{
/// <summary>提供者测试组件:把 Value 设置到供给键上;OnApply 作为装载闸门。</summary>
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<string> Inject { get; set; } = Array.Empty<string>();
public IReadOnlyList<string> Provide => _provide;
public Func<UniTask>? 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);
}
}
/// <summary>消费者测试组件:装载时读取依赖键的当前值;OnApply 作为装载闸门。</summary>
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<string> Inject => _inject;
public IReadOnlyList<string> Provide => Array.Empty<string>();
public Func<UniTask>? 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<object>(Key);
}
}
/// <summary>记录效应顺序的提供者/消费者组合组件(验证 drain-before-inverse 次序)。</summary>
public sealed class OrderProviderComponent : IShrinkComponent
{
private readonly string[] _provide;
public OrderProviderComponent(string name, string key, object? value, List<string> 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<string> Log { get; }
public IReadOnlyList<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> 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;
}
}
/// <summary>记录效应顺序的消费者组件。</summary>
public sealed class OrderConsumerComponent : IShrinkComponent
{
private readonly string[] _inject;
public OrderConsumerComponent(string name, string key, List<string> log)
{
Name = name;
Key = key;
Log = log;
_inject = new[] { key };
}
public string Name { get; }
public string Key { get; }
public List<string> Log { get; }
public IReadOnlyList<string> Inject => _inject;
public IReadOnlyList<string> Provide => Array.Empty<string>();
public object? LastSeen { get; private set; }
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
{
LastSeen = ctx.Get<object>(Key);
ctx.Effect(
() => Log.Add("consumer-load"),
() => Log.Add("consumer-undo"));
return UniTask.CompletedTask;
}
}
/// <summary>apply 中抛异常的组件:验证错误路径的部分回滚。</summary>
public sealed class FailingComponent : IShrinkComponent
{
private readonly string[] _provide;
public FailingComponent(string name, string key, Func<UniTask>? beforeThrow = null)
{
Name = name;
Key = key;
BeforeThrow = beforeThrow;
_provide = new[] { key };
}
public string Name { get; }
public string Key { get; }
public Func<UniTask>? BeforeThrow { get; set; }
public IReadOnlyList<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> 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);
}
}
/// <summary>
/// 分步效应组件(论文 𝔈iterΓ):每步等待对应闸门放行后执行并产出逆操作;
/// 用于验证目标变化在步进边界中断迭代并部分回滚。
/// </summary>
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<string>();
Gates = new List<UniTaskCompletionSource>();
for (var i = 0; i < stepCount; i++)
Gates.Add(new UniTaskCompletionSource());
}
public string Name { get; }
public string Key { get; }
public List<string> Steps { get; }
public List<UniTaskCompletionSource> Gates { get; }
public int ExecutedSteps { get; private set; }
public IReadOnlyList<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> 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<UniTask> Current { get; private set; } = null!;
public async UniTask<bool> 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;
}
}
}
/// <summary>配置驱动值的提供者组件:apply 时把纤程 config 写入供给键(加载器测试用)。</summary>
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<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> Provide => _provide;
public UniTask ApplyAsync(ShrinkCtx ctx, object? config)
{
ApplyCount++;
ctx.Set(Key, config);
return UniTask.CompletedTask;
}
}
/// <summary>故意写入未声明键,验证 Provide 运行时契约。</summary>
public sealed class UndeclaredProviderComponent : IShrinkComponent
{
public string Name => "undeclared-provider";
public IReadOnlyList<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> Provide => Array.Empty<string>();
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<string>("prefix", out var prefix))
text = prefix + text;
return context.TryGetMetadata<string>("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<string> Inject => Array.Empty<string>();
public IReadOnlyList<string> 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<string> Inject => _inject;
public IReadOnlyList<string> Provide => Array.Empty<string>();
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<string>(Key);
}
/// <summary>测试辅助:同步等待 UniTask(测试保证全部延续同步完成,无死锁风险)。</summary>
public static class TestAwait
{
public static void Run(UniTask task)
{
task.GetAwaiter().GetResult();
}
public static T Run<T>(UniTask<T> task)
{
return task.GetAwaiter().GetResult();
}
}
}