feat(cordis): 接入上下文组合与模组事务热替换
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
#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>测试辅助:同步等待 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user