180 lines
6.7 KiB
C#
180 lines
6.7 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Cysharp.Threading.Tasks;
|
|
using NUnit.Framework;
|
|
using ShrinkApp;
|
|
using ShrinkContext;
|
|
using UnityEngine;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace ShrinkContext.AppAdapter.Tests
|
|
{
|
|
/// <summary>
|
|
/// ShrinkApp 安装器适配器:
|
|
/// 依赖缺失等待而非抛错(对照 ShrinkAppHost.SortInstallers 的行为差异)、
|
|
/// 依赖就绪顺序保证、重复 ModuleId 退化为供给冲突而非宿主级失败、服务注册可见性。
|
|
/// </summary>
|
|
public class ShrinkAppInstallerComponentTests
|
|
{
|
|
private ShrinkContextRuntime _runtime = null!;
|
|
private ShrinkAppServices _services = null!;
|
|
private ShrinkAppSettings _settings = null!;
|
|
private static long _sequence;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_runtime = new ShrinkContextRuntime();
|
|
_services = new ShrinkAppServices();
|
|
_settings = ScriptableObject.CreateInstance<ShrinkAppSettings>();
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
if (_settings != null)
|
|
Object.DestroyImmediate(_settings);
|
|
}
|
|
|
|
private ShrinkFiber UseInstaller(FakeInstaller installer)
|
|
{
|
|
return _runtime.Use(new ShrinkAppInstallerComponent(installer, _settings), _services);
|
|
}
|
|
|
|
[Test]
|
|
public void MissingDependency_WaitsInsteadOfThrowing()
|
|
{
|
|
var dependent = new FakeInstaller("app.b", "b-service") { DependsOn = new[] { "app.a" } };
|
|
var fiber = UseInstaller(dependent);
|
|
|
|
Assert.AreEqual(ShrinkFiberState.Inactive, fiber.State,
|
|
"依赖模块未就绪时安装器保持非活动(对照:ShrinkAppHost 在排序期直接抛依赖缺失)");
|
|
Assert.AreEqual(0, dependent.RegisterCount);
|
|
Assert.AreEqual(0, dependent.InitCount);
|
|
}
|
|
|
|
[Test]
|
|
public void DependencyArrives_DependentActivates_AfterProvider()
|
|
{
|
|
var provider = new FakeInstaller("app.a", "a-service");
|
|
var dependent = new FakeInstaller("app.b", "b-service") { DependsOn = new[] { "app.a" } };
|
|
|
|
var dependentFiber = UseInstaller(dependent);
|
|
var providerFiber = UseInstaller(provider);
|
|
|
|
Assert.AreEqual(ShrinkFiberState.Active, providerFiber.State);
|
|
Assert.AreEqual(ShrinkFiberState.Active, dependentFiber.State,
|
|
"依赖键由提供者发布后,依赖安装器被响应式激活");
|
|
|
|
// 激活次序由依赖关系保证:提供者先完成 Register+Init
|
|
Assert.AreEqual(1, provider.InitCount);
|
|
Assert.AreEqual(1, dependent.InitCount);
|
|
Assert.Less(provider.CompletedAt, dependent.CompletedAt,
|
|
"依赖者的初始化必须晚于其依赖的提供者(空间可组合性排序)");
|
|
|
|
Assert.IsTrue(_services.TryGet<FakeService>(out var service));
|
|
Assert.AreEqual("b-service", service!.Value);
|
|
}
|
|
|
|
[Test]
|
|
public void DuplicateModuleId_SecondFailsAsSupplyConflict_FirstStaysActive()
|
|
{
|
|
var first = new FakeInstaller("app.dup", "first");
|
|
var second = new FakeInstaller("app.dup", "second");
|
|
|
|
var firstFiber = UseInstaller(first);
|
|
var secondFiber = UseInstaller(second);
|
|
|
|
Assert.AreEqual(ShrinkFiberState.Active, firstFiber.State);
|
|
Assert.AreEqual(ShrinkFiberState.Inactive, secondFiber.State);
|
|
Assert.IsInstanceOf<ShrinkSupplyConflictException>(secondFiber.LastError,
|
|
"重复 ModuleId 退化为该纤程的供给冲突失败,而不是宿主级异常(对照 ShrinkHost 的 Duplicate installer 抛错)");
|
|
Assert.AreEqual(0, second.InitCount);
|
|
}
|
|
|
|
[Test]
|
|
public void RetireProvider_DependentInstallerDeactivates()
|
|
{
|
|
var provider = new FakeInstaller("app.a", "a-service");
|
|
var dependent = new FakeInstaller("app.b", "b-service") { DependsOn = new[] { "app.a" } };
|
|
|
|
var providerFiber = UseInstaller(provider);
|
|
var dependentFiber = UseInstaller(dependent);
|
|
Assert.AreEqual(ShrinkFiberState.Active, dependentFiber.State);
|
|
|
|
TestAwait.Run(_runtime.RetireAsync(providerFiber));
|
|
|
|
Assert.AreEqual(ShrinkFiberState.Inactive, dependentFiber.State,
|
|
"提供者退役后依赖安装器自动停用(服务注销为已知边界,见组件注释)");
|
|
}
|
|
[Test]
|
|
public void MissingServicesConfig_FailsWithClearError()
|
|
{
|
|
var installer = new FakeInstaller("app.x", "x");
|
|
var component = new ShrinkAppInstallerComponent(installer, _settings);
|
|
var fiber = _runtime.Use(component, config: null!);
|
|
|
|
Assert.AreEqual(ShrinkFiberState.Inactive, fiber.State);
|
|
Assert.IsInstanceOf<InvalidOperationException>(fiber.LastError);
|
|
StringAssert.Contains("ShrinkAppServices", fiber.LastError!.Message);
|
|
}
|
|
|
|
// ---- 测试替身 ----
|
|
|
|
private sealed class FakeInstaller : IShrinkAppModuleInstaller
|
|
{
|
|
public FakeInstaller(string moduleId, string serviceValue)
|
|
{
|
|
ModuleId = moduleId;
|
|
ServiceValue = serviceValue;
|
|
}
|
|
|
|
public string ModuleId { get; }
|
|
public int Order => 0;
|
|
public IReadOnlyList<string> DependsOn { get; set; } = Array.Empty<string>();
|
|
public int RegisterCount { get; private set; }
|
|
public int InitCount { get; private set; }
|
|
public long CompletedAt { get; private set; }
|
|
private string ServiceValue { get; }
|
|
|
|
public void RegisterServices(ShrinkAppContext context)
|
|
{
|
|
RegisterCount++;
|
|
context.Services.Register(new FakeService(ServiceValue));
|
|
}
|
|
|
|
public UniTask InitializeAsync(ShrinkAppContext context)
|
|
{
|
|
InitCount++;
|
|
CompletedAt = ++_sequence;
|
|
return UniTask.CompletedTask;
|
|
}
|
|
}
|
|
|
|
private sealed class FakeService
|
|
{
|
|
public FakeService(string value)
|
|
{
|
|
Value = value;
|
|
}
|
|
|
|
public string Value { get; }
|
|
}
|
|
}
|
|
|
|
/// <summary>跨程序集测试辅助(与 ShrinkContext.Core.Tests.TestAwait 同语义)。</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();
|
|
}
|
|
}
|
|
}
|