#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 { /// /// ShrinkApp 安装器适配器: /// 依赖缺失等待而非抛错(对照 ShrinkAppHost.SortInstallers 的行为差异)、 /// 依赖就绪顺序保证、重复 ModuleId 退化为供给冲突而非宿主级失败、服务注册可见性。 /// 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(); } [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(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(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(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 DependsOn { get; set; } = Array.Empty(); 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; } } } /// 跨程序集测试辅助(与 ShrinkContext.Core.Tests.TestAwait 同语义)。 public static class TestAwait { public static void Run(UniTask task) { task.GetAwaiter().GetResult(); } public static T Run(UniTask task) { return task.GetAwaiter().GetResult(); } } }