using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Coroutine; using GFramework.Core.Coroutine.Instructions; using GFramework.Godot.Coroutine; using System.Runtime.CompilerServices; namespace GFramework.Godot.Tests.Coroutine; /// /// 验证 在纯托管测试宿主下仍保持与真实 Godot 生命周期一致的阶段语义。 /// [TestFixture] [NonParallelizable] public sealed class TimingTests { private Timing _timing = null!; /// /// 为每个测试准备独立的 Timing 宿主,避免静态实例槽位相互污染。 /// [SetUp] public void SetUp() { // Timing 继承自 Godot.Node;在纯 dotnet test 宿主中直接运行原生构造函数会触发测试进程崩溃。 // 这里仅为调度语义测试创建未初始化对象,再由 InitializeForTests 补齐纯托管字段与调度器状态。 _timing = (Timing)RuntimeHelpers.GetUninitializedObject(typeof(Timing)); _timing.InitializeForTests(); } /// /// 清理测试宿主注册的调度器与实例槽位。 /// [TearDown] public void TearDown() { _timing.DisposeForTests(); } /// /// 验证暂停场景时只会冻结普通 Process 协程,忽略暂停段仍会继续推进。 /// [Test] public void AdvanceProcessFrameForTests_Should_Freeze_Process_Segment_But_Keep_IgnorePause_Segment_Running() { var executedSegments = new List(); var processHandle = _timing.RunCoroutineOnInstance( CompleteAfterOneFrame(() => executedSegments.Add("process")), Segment.Process); var ignorePauseHandle = _timing.RunCoroutineOnInstance( CompleteAfterOneFrame(() => executedSegments.Add("ignore-pause")), Segment.ProcessIgnorePause); _timing.AdvanceProcessFrameForTests(paused: true); Assert.Multiple(() => { Assert.That(executedSegments, Is.EqualTo(new[] { "ignore-pause" })); Assert.That(_timing.ProcessCoroutines, Is.EqualTo(1)); Assert.That(_timing.ProcessIgnorePauseCoroutines, Is.EqualTo(0)); Assert.That(_timing.GetSchedulerForTests(Segment.Process).IsCoroutineAlive(processHandle), Is.True); Assert.That( _timing.GetSchedulerForTests(Segment.ProcessIgnorePause) .TryGetCompletionStatus(ignorePauseHandle, out var status), Is.True); Assert.That(status, Is.EqualTo(CoroutineCompletionStatus.Completed)); }); } /// /// 验证 Physics 帧只会推进 Physics 段,不会提前消费普通 Process 段的等待。 /// [Test] public void AdvancePhysicsFrameForTests_Should_Only_Advance_Physics_Segment() { var executedSegments = new List(); var processHandle = _timing.RunCoroutineOnInstance( CompleteAfterOneFrame(() => executedSegments.Add("process")), Segment.Process); var physicsHandle = _timing.RunCoroutineOnInstance( CompleteAfterOneFrame(() => executedSegments.Add("physics")), Segment.PhysicsProcess); _timing.AdvancePhysicsFrameForTests(); Assert.Multiple(() => { Assert.That(executedSegments, Is.EqualTo(new[] { "physics" })); Assert.That(_timing.GetSchedulerForTests(Segment.Process).IsCoroutineAlive(processHandle), Is.True); Assert.That(_timing.GetSchedulerForTests(Segment.PhysicsProcess).IsCoroutineAlive(physicsHandle), Is.False); }); } /// /// 验证帧尾段会在 Process 段之后执行,保持与生产宿主 `_Process -> CallDeferred` 的顺序一致。 /// [Test] public void AdvanceProcessFrameForTests_Should_Run_Deferred_Segment_After_Process_Segment() { var executionOrder = new List(); _timing.RunCoroutineOnInstance( CompleteAfterOneFrame(() => executionOrder.Add("process")), Segment.Process); _timing.RunCoroutineOnInstance( CompleteAfterOneFrame(() => executionOrder.Add("deferred")), Segment.DeferredProcess); _timing.AdvanceProcessFrameForTests(paused: false); Assert.That(executionOrder, Is.EqualTo(new[] { "process", "deferred" })); } /// /// 验证 只会在 Physics 段完成,避免阶段型等待被错误地提前消费。 /// [Test] public void WaitForFixedUpdate_Should_Only_Complete_On_Physics_Segment() { var processCompletions = 0; var physicsCompletions = 0; var processHandle = _timing.RunCoroutineOnInstance( CompleteAfterInstruction(new WaitForFixedUpdate(), () => processCompletions++), Segment.Process); var physicsHandle = _timing.RunCoroutineOnInstance( CompleteAfterInstruction(new WaitForFixedUpdate(), () => physicsCompletions++), Segment.PhysicsProcess); _timing.AdvanceProcessFrameForTests(paused: false); _timing.AdvancePhysicsFrameForTests(); Assert.Multiple(() => { Assert.That(processCompletions, Is.EqualTo(0)); Assert.That(physicsCompletions, Is.EqualTo(1)); Assert.That(_timing.GetSchedulerForTests(Segment.Process).IsCoroutineAlive(processHandle), Is.True); Assert.That(_timing.GetSchedulerForTests(Segment.PhysicsProcess).IsCoroutineAlive(physicsHandle), Is.False); }); } /// /// 验证 只会在 Deferred 段完成,避免提前穿透到普通 Process 段。 /// [Test] public void WaitForEndOfFrame_Should_Only_Complete_On_Deferred_Segment() { var processCompletions = 0; var deferredCompletions = 0; var processHandle = _timing.RunCoroutineOnInstance( CompleteAfterInstruction(new WaitForEndOfFrame(), () => processCompletions++), Segment.Process); var deferredHandle = _timing.RunCoroutineOnInstance( CompleteAfterInstruction(new WaitForEndOfFrame(), () => deferredCompletions++), Segment.DeferredProcess); _timing.AdvanceProcessFrameForTests(paused: false); Assert.Multiple(() => { Assert.That(processCompletions, Is.EqualTo(0)); Assert.That(deferredCompletions, Is.EqualTo(1)); Assert.That(_timing.GetSchedulerForTests(Segment.Process).IsCoroutineAlive(processHandle), Is.True); Assert.That(_timing.GetSchedulerForTests(Segment.DeferredProcess).IsCoroutineAlive(deferredHandle), Is.False); }); } /// /// 构造一个在单帧等待后执行回调的测试协程。 /// /// 等待完成后执行的回调。 /// 供 Timing 运行的协程枚举器。 private static IEnumerator CompleteAfterOneFrame(Action onCompleted) { ArgumentNullException.ThrowIfNull(onCompleted); yield return new WaitOneFrame(); onCompleted(); } /// /// 构造一个在指定等待指令完成后执行回调的测试协程。 /// /// 要验证的等待指令。 /// 等待完成后执行的回调。 /// 供 Timing 运行的协程枚举器。 private static IEnumerator CompleteAfterInstruction( IYieldInstruction instruction, Action onCompleted) { ArgumentNullException.ThrowIfNull(instruction); ArgumentNullException.ThrowIfNull(onCompleted); yield return instruction; onCompleted(); } }