mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(test-helpers): 收敛PR300评审问题
- 修复测试架构上下文、生命周期钩子与注册表初始化钩子的评审问题,避免静默成功或错误共享状态 - 补充 TestResourceLoader、TestLogger、CapturingLoggerFactoryProvider 与 CQRS 测试辅助类型的契约文档和并发语义 - 新增测试覆盖并更新 analyzer-warning-reduction 活跃跟踪,记录 PR #300 跟进验证与现存 Cqrs warning blocker
This commit is contained in:
parent
ba4ace8d40
commit
5693ab7e6f
@ -1,3 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Environment;
|
||||
@ -27,6 +31,7 @@ namespace GFramework.Core.Tests.Architectures;
|
||||
public class TestArchitectureContext : IArchitectureContext
|
||||
{
|
||||
private readonly MicrosoftDiContainer _container = new();
|
||||
private readonly EventBus _eventBus = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取用于解析测试服务的依赖注入容器。
|
||||
@ -36,7 +41,11 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <summary>
|
||||
/// 获取测试事件总线实例。
|
||||
/// </summary>
|
||||
public IEventBus EventBus => new EventBus();
|
||||
/// <remarks>
|
||||
/// 返回同一个缓存事件总线,以便 <see cref="RegisterEvent{TEvent}" />、<see cref="SendEvent{TEvent}()" /> 与
|
||||
/// <see cref="UnRegisterEvent{TEvent}" /> 在同一份订阅状态上协作。
|
||||
/// </remarks>
|
||||
public IEventBus EventBus => _eventBus;
|
||||
|
||||
/// <summary>
|
||||
/// 获取测试命令执行器实例。
|
||||
@ -183,6 +192,7 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <typeparam name="TEvent">事件类型。</typeparam>
|
||||
public void SendEvent<TEvent>() where TEvent : new()
|
||||
{
|
||||
_eventBus.Send<TEvent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -192,6 +202,8 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <param name="e">事件实例。</param>
|
||||
public void SendEvent<TEvent>(TEvent e) where TEvent : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(e);
|
||||
_eventBus.Send(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -199,10 +211,11 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent">事件类型。</typeparam>
|
||||
/// <param name="handler">事件处理委托。</param>
|
||||
/// <returns>用于测试的空注销句柄。</returns>
|
||||
/// <returns>用于测试的事件注销句柄。</returns>
|
||||
public IUnRegister RegisterEvent<TEvent>(Action<TEvent> handler)
|
||||
{
|
||||
return new DefaultUnRegister(() => { });
|
||||
ArgumentNullException.ThrowIfNull(handler);
|
||||
return _eventBus.Register(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -212,6 +225,8 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// <param name="onEvent">事件处理委托。</param>
|
||||
public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(onEvent);
|
||||
_eventBus.UnRegister(onEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -358,8 +373,10 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// 发送旧版命令。
|
||||
/// </summary>
|
||||
/// <param name="command">命令对象。</param>
|
||||
/// <exception cref="NotSupportedException">该测试桩不支持旧版命令执行入口。</exception>
|
||||
public void SendCommand(ICommand command)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -367,20 +384,21 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">返回值类型。</typeparam>
|
||||
/// <param name="command">命令对象。</param>
|
||||
/// <returns>测试桩默认返回值。</returns>
|
||||
/// <returns>此方法始终抛出异常,不返回结果。</returns>
|
||||
/// <exception cref="NotSupportedException">该测试桩不支持旧版命令执行入口。</exception>
|
||||
public TResult SendCommand<TResult>(ICommand<TResult> command)
|
||||
{
|
||||
return default!;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送旧版命令。
|
||||
/// </summary>
|
||||
/// <param name="command">命令对象。</param>
|
||||
/// <returns>已完成任务。</returns>
|
||||
/// <returns>已失败的任务。</returns>
|
||||
public Task SendCommandAsync(IAsyncCommand command)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return Task.FromException(new NotSupportedException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -388,10 +406,10 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">返回值类型。</typeparam>
|
||||
/// <param name="command">命令对象。</param>
|
||||
/// <returns>包含测试桩默认返回值的任务。</returns>
|
||||
/// <returns>已失败的任务。</returns>
|
||||
public Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> command)
|
||||
{
|
||||
return Task.FromResult(default(TResult)!);
|
||||
return Task.FromException<TResult>(new NotSupportedException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -399,10 +417,11 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果类型。</typeparam>
|
||||
/// <param name="query">查询对象。</param>
|
||||
/// <returns>测试桩默认返回值。</returns>
|
||||
/// <returns>此方法始终抛出异常,不返回结果。</returns>
|
||||
/// <exception cref="NotSupportedException">该测试桩不支持旧版查询执行入口。</exception>
|
||||
public TResult SendQuery<TResult>(IQuery<TResult> query)
|
||||
{
|
||||
return default!;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -410,10 +429,10 @@ public class TestArchitectureContext : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果类型。</typeparam>
|
||||
/// <param name="query">异步查询对象。</param>
|
||||
/// <returns>包含测试桩默认返回值的任务。</returns>
|
||||
/// <returns>已失败的任务。</returns>
|
||||
public Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query)
|
||||
{
|
||||
return Task.FromResult(default(TResult)!);
|
||||
return Task.FromException<TResult>(new NotSupportedException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Command;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖测试架构上下文替身的共享事件与显式失败契约。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class TestArchitectureContextBehaviorTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证测试上下文会把事件注册与发送委托到同一个事件总线实例。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterEvent_And_SendEvent_Should_Use_Shared_EventBus()
|
||||
{
|
||||
var context = new TestArchitectureContext();
|
||||
var eventReceived = false;
|
||||
|
||||
context.RegisterEvent<TestEventV2>(_ => eventReceived = true);
|
||||
context.SendEvent<TestEventV2>();
|
||||
|
||||
Assert.That(eventReceived, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证测试上下文的旧版命令与查询入口会显式抛出未支持异常。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Legacy_Entries_Should_Throw_Or_Return_Faulted_Tasks()
|
||||
{
|
||||
var context = new TestArchitectureContext();
|
||||
|
||||
Assert.That(() => context.SendCommand(new TestCommandV2()), Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
() => context.SendCommand(new TestCommandWithResultV2 { Result = 1 }),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(() => context.SendQuery(new TestQueryV2 { Result = 1 }), Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
async () => await context.SendCommandAsync(new TestAsyncCommand()).ConfigureAwait(false),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
async () => await context.SendCommandAsync(new TestAsyncCommandWithResult()).ConfigureAwait(false),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
async () => await context.SendQueryAsync(new TestAsyncQuery()).ConfigureAwait(false),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证用于 ArchitectureServices 的上下文替身也会把旧版入口显式标记为不支持。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Legacy_Entries_On_TestArchitectureContextV3_Should_Throw_Or_Return_Faulted_Tasks()
|
||||
{
|
||||
var context = new TestArchitectureContextV3();
|
||||
|
||||
Assert.That(() => context.SendCommand(new TestCommandV2()), Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
() => context.SendCommand(new TestCommandWithResultV2 { Result = 1 }),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(() => context.SendQuery(new TestQueryV2 { Result = 1 }), Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
async () => await context.SendCommandAsync(new TestAsyncCommand()).ConfigureAwait(false),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
async () => await context.SendCommandAsync(new TestAsyncCommandWithResult()).ConfigureAwait(false),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
Assert.That(
|
||||
async () => await context.SendQueryAsync(new TestAsyncQuery()).ConfigureAwait(false),
|
||||
Throws.TypeOf<NotSupportedException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证两类架构测试替身在接口视角下都会以 no-op 方式接受生命周期钩子。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterLifecycleHook_Via_Interface_Should_Return_Original_Hook()
|
||||
{
|
||||
IArchitecture withRegistry = new TestArchitectureWithRegistry(new TestRegistry());
|
||||
IArchitecture withoutRegistry = new TestArchitectureWithoutRegistry();
|
||||
var hook = new NoOpLifecycleHook();
|
||||
|
||||
Assert.That(withRegistry.RegisterLifecycleHook(hook), Is.SameAs(hook));
|
||||
Assert.That(withoutRegistry.RegisterLifecycleHook(hook), Is.SameAs(hook));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为旧版异步命令入口提供最小实现的测试命令。
|
||||
/// </summary>
|
||||
private sealed class TestAsyncCommand : IAsyncCommand
|
||||
{
|
||||
public Task ExecuteAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public IArchitectureContext GetContext()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetContext(IArchitectureContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为旧版异步命令入口提供最小实现的带结果测试命令。
|
||||
/// </summary>
|
||||
private sealed class TestAsyncCommandWithResult : IAsyncCommand<int>
|
||||
{
|
||||
public Task<int> ExecuteAsync()
|
||||
{
|
||||
return Task.FromResult(1);
|
||||
}
|
||||
|
||||
public IArchitectureContext GetContext()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetContext(IArchitectureContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为旧版异步查询入口提供最小实现的测试查询。
|
||||
/// </summary>
|
||||
private sealed class TestAsyncQuery : IAsyncQuery<int>
|
||||
{
|
||||
public Task<int> DoAsync()
|
||||
{
|
||||
return Task.FromResult(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为生命周期钩子接口提供空实现的测试替身。
|
||||
/// </summary>
|
||||
private sealed class NoOpLifecycleHook : IArchitectureLifecycleHook
|
||||
{
|
||||
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(architecture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -343,8 +343,10 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
||||
/// 发送旧版无返回值命令。
|
||||
/// </summary>
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <exception cref="NotSupportedException">该测试桩不支持旧版命令执行入口。</exception>
|
||||
public void SendCommand(ICommand command)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -352,20 +354,21 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令响应类型。</typeparam>
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <returns>默认响应值。</returns>
|
||||
/// <returns>此方法始终抛出异常,不返回结果。</returns>
|
||||
/// <exception cref="NotSupportedException">该测试桩不支持旧版命令执行入口。</exception>
|
||||
public TResult SendCommand<TResult>(GFramework.Core.Abstractions.Command.ICommand<TResult> command)
|
||||
{
|
||||
return default!;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发送旧版无返回值命令。
|
||||
/// </summary>
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <returns>已完成任务。</returns>
|
||||
/// <returns>已失败的任务。</returns>
|
||||
public Task SendCommandAsync(IAsyncCommand command)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return Task.FromException(new NotSupportedException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -373,10 +376,10 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">命令响应类型。</typeparam>
|
||||
/// <param name="command">要发送的命令。</param>
|
||||
/// <returns>占位任务。</returns>
|
||||
/// <returns>已失败的任务。</returns>
|
||||
public Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> command)
|
||||
{
|
||||
return (Task<TResult>)Task.CompletedTask;
|
||||
return Task.FromException<TResult>(new NotSupportedException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -384,10 +387,11 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果类型。</typeparam>
|
||||
/// <param name="query">要发送的查询。</param>
|
||||
/// <returns>默认查询结果。</returns>
|
||||
/// <returns>此方法始终抛出异常,不返回结果。</returns>
|
||||
/// <exception cref="NotSupportedException">该测试桩不支持旧版查询执行入口。</exception>
|
||||
public TResult SendQuery<TResult>(GFramework.Core.Abstractions.Query.IQuery<TResult> query)
|
||||
{
|
||||
return default!;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -395,10 +399,10 @@ public class TestArchitectureContextV3 : IArchitectureContext
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">查询结果类型。</typeparam>
|
||||
/// <param name="query">要发送的查询。</param>
|
||||
/// <returns>占位任务。</returns>
|
||||
/// <returns>已失败的任务。</returns>
|
||||
public Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query)
|
||||
{
|
||||
return (Task<TResult>)Task.CompletedTask;
|
||||
return Task.FromException<TResult>(new NotSupportedException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -95,7 +95,8 @@ public class TestArchitectureWithRegistry : IArchitecture
|
||||
|
||||
IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
RegisterLifecycleHook(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
Task IArchitecture.WaitUntilReadyAsync()
|
||||
|
||||
@ -93,7 +93,8 @@ public class TestArchitectureWithoutRegistry : IArchitecture
|
||||
|
||||
IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
RegisterLifecycleHook(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Coroutine.Instructions;
|
||||
using GFramework.Core.Events;
|
||||
@ -8,25 +9,28 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
[TestFixture]
|
||||
public class WaitForMultipleEventsTests
|
||||
{
|
||||
private IEventBus? _eventBus;
|
||||
|
||||
private IEventBus EventBus => _eventBus ?? throw new InvalidOperationException("EventBus has not been initialized.");
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
eventBus = new EventBus();
|
||||
_eventBus = new EventBus();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
(eventBus as IDisposable)?.Dispose();
|
||||
(EventBus as IDisposable)?.Dispose();
|
||||
_eventBus = null;
|
||||
}
|
||||
|
||||
private IEventBus eventBus = null!;
|
||||
|
||||
[Test]
|
||||
public void Constructor_RegistersBothEventTypes()
|
||||
{
|
||||
// Arrange & Act
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(eventBus);
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(EventBus);
|
||||
|
||||
// Assert
|
||||
Assert.That(waitForMultipleEvents.IsDone, Is.False);
|
||||
@ -37,11 +41,11 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
public async Task FirstEventWins_WhenBothEventsFired()
|
||||
{
|
||||
// Arrange
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(eventBus);
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(EventBus);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent1 { Data = "first_event" });
|
||||
eventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
EventBus.Send(new TestEvent1 { Data = "first_event" });
|
||||
EventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
|
||||
// Assert
|
||||
Assert.That(waitForMultipleEvents.IsDone, Is.True);
|
||||
@ -54,10 +58,10 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
public async Task SecondEventWins_WhenOnlySecondEventFired()
|
||||
{
|
||||
// Arrange
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(eventBus);
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(EventBus);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
EventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
|
||||
// Assert
|
||||
Assert.That(waitForMultipleEvents.IsDone, Is.True);
|
||||
@ -70,11 +74,11 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
public async Task FirstEventWins_WhenBothEventsFiredInReverseOrder()
|
||||
{
|
||||
// Arrange
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(eventBus);
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(EventBus);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
eventBus.Send(new TestEvent1 { Data = "first_event" });
|
||||
EventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
EventBus.Send(new TestEvent1 { Data = "first_event" });
|
||||
|
||||
// Assert
|
||||
Assert.That(waitForMultipleEvents.IsDone, Is.True);
|
||||
@ -88,10 +92,10 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
public async Task MultipleEvents_AfterCompletion_DoNotOverrideState()
|
||||
{
|
||||
// Arrange
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(eventBus);
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(EventBus);
|
||||
|
||||
// Act - Fire first event
|
||||
eventBus.Send(new TestEvent1 { Data = "first_event" });
|
||||
EventBus.Send(new TestEvent1 { Data = "first_event" });
|
||||
|
||||
// Verify first event was processed
|
||||
Assert.That(waitForMultipleEvents.IsDone, Is.True);
|
||||
@ -99,7 +103,7 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
Assert.That(waitForMultipleEvents.FirstEventData?.Data, Is.EqualTo("first_event"));
|
||||
|
||||
// Fire second event after completion
|
||||
eventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
EventBus.Send(new TestEvent2 { Data = "second_event" });
|
||||
|
||||
// Assert - The state should not change
|
||||
Assert.That(waitForMultipleEvents.IsDone, Is.True);
|
||||
@ -113,13 +117,13 @@ namespace GFramework.Core.Tests.Coroutine
|
||||
public async Task Disposal_PreventsFurtherEventHandling()
|
||||
{
|
||||
// Arrange
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(eventBus);
|
||||
var waitForMultipleEvents = new WaitForMultipleEvents<TestEvent1, TestEvent2>(EventBus);
|
||||
|
||||
// Act - Dispose the instance
|
||||
waitForMultipleEvents.Dispose();
|
||||
|
||||
// Fire an event after disposal
|
||||
eventBus.Send(new TestEvent1 { Data = "after_disposal" });
|
||||
EventBus.Send(new TestEvent1 { Data = "after_disposal" });
|
||||
|
||||
// Assert - Event should not be processed due to disposal
|
||||
// Since we disposed, no event data should be captured
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
|
||||
@ -6,9 +9,13 @@ namespace GFramework.Core.Tests.Logging;
|
||||
/// <summary>
|
||||
/// 表示供日志相关测试复用的内存日志记录器。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 并发写入会通过内部锁串行化;<see cref="Logs" /> 每次返回快照,避免断言观察到正在被修改的可变集合。
|
||||
/// </remarks>
|
||||
public sealed class TestLogger : AbstractLogger
|
||||
{
|
||||
private readonly List<LogEntry> _logs = new();
|
||||
private readonly Lock _sync = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 <see cref="TestLogger" /> 的新实例。
|
||||
@ -20,9 +27,18 @@ public sealed class TestLogger : AbstractLogger
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取按写入顺序保存的日志条目只读视图。
|
||||
/// 获取按写入顺序保存的日志条目快照。
|
||||
/// </summary>
|
||||
public IReadOnlyList<LogEntry> Logs => _logs;
|
||||
public IReadOnlyList<LogEntry> Logs
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _logs.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将日志信息追加到内存列表,供断言读取。
|
||||
@ -32,7 +48,10 @@ public sealed class TestLogger : AbstractLogger
|
||||
/// <param name="exception">相关异常;没有异常时为 <see langword="null" />。</param>
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
_logs.Add(new LogEntry(level, message, exception));
|
||||
lock (_sync)
|
||||
{
|
||||
_logs.Add(new LogEntry(level, message, exception));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,18 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using GFramework.Core.Abstractions.Resource;
|
||||
|
||||
namespace GFramework.Core.Tests.Resource;
|
||||
|
||||
/// <summary>
|
||||
/// 为 ResourceManager 测试提供可控数据源的资源加载器。
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 为 ResourceManager 测试提供可控数据源的资源加载器。
|
||||
/// </summary>
|
||||
public class TestResourceLoader : IResourceLoader<TestResource>
|
||||
{
|
||||
private readonly Dictionary<string, string> _resourceData = new(StringComparer.Ordinal);
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 同步加载指定路径的测试资源。
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径。</param>
|
||||
/// <returns>加载得到的测试资源。</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path" /> 为空字符串。</exception>
|
||||
/// <exception cref="FileNotFoundException">指定路径的测试资源不存在。</exception>
|
||||
public TestResource Load(string path)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
|
||||
if (_resourceData.TryGetValue(path, out var content))
|
||||
{
|
||||
return new TestResource { Content = content };
|
||||
@ -21,22 +33,40 @@ public class TestResourceLoader : IResourceLoader<TestResource>
|
||||
throw new FileNotFoundException($"Resource not found: {path}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TestResource> LoadAsync(string path)
|
||||
/// <summary>
|
||||
/// 异步加载指定路径的测试资源。
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径。</param>
|
||||
/// <returns>加载得到的测试资源任务。</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path" /> 为空字符串。</exception>
|
||||
/// <exception cref="FileNotFoundException">指定路径的测试资源不存在。</exception>
|
||||
public Task<TestResource> LoadAsync(string path)
|
||||
{
|
||||
await Task.Delay(10).ConfigureAwait(false); // 模拟异步加载
|
||||
return Load(path);
|
||||
return Task.FromResult(Load(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 卸载已加载的测试资源。
|
||||
/// </summary>
|
||||
/// <param name="resource">要标记为已释放的资源。</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="resource" /> 为 <see langword="null" />。</exception>
|
||||
public void Unload(TestResource resource)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(resource);
|
||||
resource.IsDisposed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// 判断当前加载器是否包含指定路径的测试资源。
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径。</param>
|
||||
/// <returns>存在对应测试资源时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path" /> 为 <see langword="null" />。</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path" /> 为空字符串。</exception>
|
||||
public bool CanLoad(string path)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
return _resourceData.ContainsKey(path);
|
||||
}
|
||||
|
||||
@ -45,8 +75,14 @@ public class TestResourceLoader : IResourceLoader<TestResource>
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径。</param>
|
||||
/// <param name="content">资源内容。</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="path" /> 或 <paramref name="content" /> 为 <see langword="null" />。
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path" /> 为空字符串。</exception>
|
||||
public void AddTestData(string path, string content)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
_resourceData[path] = content;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
@ -33,12 +34,29 @@ public abstract class RegistryInitializationHookBase<TRegistry, TConfig> : IArch
|
||||
/// </summary>
|
||||
/// <param name="phase">当前的架构阶段</param>
|
||||
/// <param name="architecture">相关的架构实例</param>
|
||||
/// <remarks>
|
||||
/// 当目标注册表未被装入当前架构上下文时,该钩子会保持 no-op,
|
||||
/// 以便同一组配置可以安全复用于不包含该注册表的测试或裁剪场景。
|
||||
/// </remarks>
|
||||
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
|
||||
{
|
||||
if (phase != _targetPhase) return;
|
||||
ArgumentNullException.ThrowIfNull(architecture);
|
||||
|
||||
var registry = architecture.Context.GetUtility<TRegistry>();
|
||||
if (registry == null) return;
|
||||
if (phase != _targetPhase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TRegistry registry;
|
||||
|
||||
try
|
||||
{
|
||||
registry = architecture.Context.GetUtility<TRegistry>();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var config in _configs)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Tests.Logging;
|
||||
@ -11,10 +12,12 @@ namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
/// <remarks>
|
||||
/// 处理器注册入口会分别为测试运行时、容器和注册器创建日志器。
|
||||
/// 该提供程序统一保留这些测试日志器,以便断言警告是否经由公开入口真正发出。
|
||||
/// 并发创建日志器时会通过内部锁串行化,<see cref="Loggers" /> 每次返回快照,避免调用方观察到可变集合。
|
||||
/// </remarks>
|
||||
internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider
|
||||
{
|
||||
private readonly List<TestLogger> _loggers = [];
|
||||
private readonly Lock _sync = new();
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定的最小日志级别初始化一个新的捕获型日志工厂提供程序。
|
||||
@ -26,9 +29,18 @@ internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取通过当前提供程序创建的全部测试日志器。
|
||||
/// 获取通过当前提供程序创建的全部测试日志器快照。
|
||||
/// </summary>
|
||||
public IReadOnlyList<TestLogger> Loggers => _loggers;
|
||||
public IReadOnlyList<TestLogger> Loggers
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _loggers.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置新建测试日志器的最小日志级别。
|
||||
@ -43,7 +55,12 @@ internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider
|
||||
public ILogger CreateLogger(string name)
|
||||
{
|
||||
var logger = new TestLogger(name, MinLevel);
|
||||
_loggers.Add(logger);
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_loggers.Add(logger);
|
||||
}
|
||||
|
||||
return logger;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,11 +10,18 @@ internal static class DeterministicNotificationHandlerState
|
||||
/// <summary>
|
||||
/// 获取当前测试中的通知处理器执行顺序。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该集合仅供顺序测试断言使用,不提供并发安全保证。
|
||||
/// 若多个处理器在并行测试中同时写入,调用方可能观察到竞争条件或未定义顺序。
|
||||
/// </remarks>
|
||||
public static List<string> InvocationOrder { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 重置共享的执行顺序状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法只支持在单线程测试准备阶段调用;并发调用会与 <see cref="InvocationOrder" /> 的直接写入互相竞争。
|
||||
/// </remarks>
|
||||
public static void Reset()
|
||||
{
|
||||
InvocationOrder.Clear();
|
||||
|
||||
@ -16,6 +16,9 @@ internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandler
|
||||
/// </summary>
|
||||
/// <param name="services">承载处理器映射的服务集合。</param>
|
||||
/// <param name="logger">用于记录注册诊断的日志器。</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="services" /> 或 <paramref name="logger" /> 为 <see langword="null" />。
|
||||
/// </exception>
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
@ -6,41 +6,38 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-087`
|
||||
- 当前阶段:`Phase 87`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-088`
|
||||
- 当前阶段:`Phase 88`
|
||||
- 当前焦点:
|
||||
- `2026-04-28` 已按 `$gframework-batch-boot 50` 先执行仓库根 `dotnet clean` + `dotnet build`,建立本轮权威基线 `288 Warning(s)` / `214` 个唯一位点
|
||||
- 本轮已并行收敛 `GameContextTests.cs`、`ArchitectureServicesTests.cs`、`RegistryInitializationHookBaseTests.cs`、`CqrsDispatcherCacheTests.cs` 与 `CqrsHandlerRegistrarTests.cs`
|
||||
- 主线程已补齐 `ResourceManagerTests.cs`、`TestEvent.cs`、`LoggerTests.cs`、`ContextProviderTests.cs`、`TestArchitectureBase.cs`、`CommandCoroutineExtensionsTests.cs` 等 `Core.Tests` 零散 warning
|
||||
- 当前 `GFramework.Core.Tests` 与 `GFramework.Cqrs.Tests` 的受影响项目 Release 构建都已恢复到 `0 Warning(s)` / `0 Error(s)`
|
||||
- 当前仓库根权威基线已从本轮开始时的 `288 Warning(s)` / `214` 个唯一位点下降到 `236 Warning(s)` / `162` 个唯一位点;剩余 warning 只集中在 `Mediator/*` 与 `YamlConfigSchemaValidator*`
|
||||
- `2026-04-28` 已执行 `$gframework-pr-review`,确认 `PR #300` 最新 head 上仍有 `8` 条 CodeRabbit open threads、`1` 个 failed test,以及 `dotnet-format restore failed` 的 CI 噪音
|
||||
- 本轮已核对并收敛仍然成立的 review comments:`TestArchitectureContext*` 旧入口显式失败、共享事件总线、`RegisterLifecycleHook` 语义统一、`TestResourceLoader` 契约、`TestLogger` / `CapturingLoggerFactoryProvider` 快照访问、`DeterministicNotificationHandlerState` 并发说明与 `PartialGeneratedNotificationHandlerRegistry` XML 异常文档
|
||||
- 已新增 `TestArchitectureContextBehaviorTests.cs`,直接覆盖共享事件总线、旧入口失败契约与接口视角生命周期钩子行为
|
||||
- `RegistryInitializationHookBase` 现已在注册表缺失时保持 no-op,修复了 PR 上报的失败测试 `OnPhase_Should_Not_Throw_When_Registry_Not_Found`
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前 `origin/main` 基线提交为 `6cc87a9`(`2026-04-27T20:28:50+08:00`)。
|
||||
- 当前直接验证结果:
|
||||
- `dotnet clean`
|
||||
- 最新结果:成功;已刷新本轮 final non-incremental 仓库根基线
|
||||
- `dotnet build`
|
||||
- 最新结果:成功;`236 Warning(s)`、`0 Error(s)`,唯一位点 `162`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 最新结果:成功;`125 Warning(s)`、`0 Error(s)`;warning 仍集中在既有 `Mediator/*` 文件,不在本轮 PR review 修复写集内
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~RegistryInitializationHookBaseTests|FullyQualifiedName~WaitForMultipleEventsTests|FullyQualifiedName~ResourceManagerTests|FullyQualifiedName~LoggerTests|FullyQualifiedName~TestArchitectureContextBehaviorTests"`
|
||||
- 最新结果:成功;`97` 通过、`0` 失败
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsHandlerRegistrarTests"`
|
||||
- 最新结果:成功;`11` 通过、`0` 失败
|
||||
- 当前批次摘要:
|
||||
- 本轮接受并集成 `GameContextTests.cs`、`ArchitectureServicesTests.cs`、`RegistryInitializationHookBaseTests.cs`、`CqrsDispatcherCacheTests.cs`、`CqrsHandlerRegistrarTests.cs` 五个并行 worker 切片
|
||||
- 主线程补齐 `Core.Tests` 内剩余零散 warning,使 `GFramework.Core.Tests` 项目级 Release 构建回到 `0 Warning(s)` / `0 Error(s)`
|
||||
- 当前 `origin/main...HEAD` 已提交 branch diff 仍为 `21` 个文件;计入当前待提交工作树后的并集 footprint 为 `45 / 50` 个文件,已接近本轮停止线
|
||||
- 当前建议保留到下一波次的候选:
|
||||
- `GFramework.Cqrs.Tests/Mediator/MediatorArchitectureIntegrationTests.cs`、`MediatorComprehensiveTests.cs`、`MediatorAdvancedFeaturesTests.cs` 的高密度 `MA0048` / `MA0004`
|
||||
- `GFramework.Game/Config/YamlConfigSchemaValidator.cs` 与 `YamlConfigSchemaValidator.ObjectKeywords.cs` 的高耦合 warning 热点
|
||||
- 当前工作树包含 `11` 个已修改文件和 `1` 个新增测试文件,全部来自 `Core` / `Core.Tests` / `Cqrs.Tests` 的 PR review follow-up
|
||||
- 本轮没有触碰 `Mediator/*` 或 `YamlConfigSchemaValidator*` 的高耦合 warning 波次
|
||||
|
||||
## 当前风险
|
||||
|
||||
- `GFramework.Cqrs.Tests/Mediator/*` 仍有 `94` / `88` / `68` 条输出 warning,属于高 changed-file 风险的 `MA0048` 大波次。
|
||||
- 缓解措施:当前 footprint 已到 `45 / 50`,下一轮应在新提交基础上单独规划 `Mediator*` 波次,而不是继续叠在本轮工作树上。
|
||||
- `YamlConfigSchemaValidator*` 仍然聚集 `222` 条输出 warning,且同时混有 `MA0048`、`MA0009`、`MA0051`、`MA0006`。
|
||||
- 缓解措施:保持为独立高耦合波次,不与测试项目拆分混提。
|
||||
- `GFramework.Cqrs.Tests` 当前项目级 Release 构建仍有 `125` 条既有 warning,主要集中在 `MediatorArchitectureIntegrationTests.cs`、`MediatorAdvancedFeaturesTests.cs` 与 `MediatorComprehensiveTests.cs`。
|
||||
- 缓解措施:本轮仅记录为现存 blocker,不在 PR #300 的 review follow-up 里扩展到 `Mediator/*` warning reduction 波次。
|
||||
- `GFramework.Game/Config/YamlConfigSchemaValidator*` 仍然是仓库根 warning 热点,但与本轮 review 修复无交集。
|
||||
- 缓解措施:继续保持为独立高耦合波次。
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -60,11 +57,12 @@
|
||||
## 验证说明
|
||||
|
||||
- 权威验证结果统一维护在“当前活跃事实”。
|
||||
- `GFramework.Core.Tests` 与 `GFramework.Cqrs.Tests` 的当前受影响项目 Release 构建都已在本轮清零,但仓库根 non-incremental 构建仍保留 `Mediator/*` 与 `YamlConfigSchemaValidator*` 既有 warning。
|
||||
- `GFramework.Core` 与 `GFramework.Core.Tests` 的当前受影响项目 Release 构建都已清零,并通过对应定向测试回归。
|
||||
- `GFramework.Cqrs.Tests` 的本轮 helper 改动已由 `CqrsHandlerRegistrarTests` 回归覆盖,但项目级 Release 构建仍暴露 `Mediator/*` 的既有 warning。
|
||||
- warning reduction 的仓库级真值只以同轮 `dotnet clean` 后的 `dotnet build` 为准。
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 提交本轮 `Core.Tests` / `Cqrs.Tests` warning reduction 与 `ai-plan` 同步。
|
||||
2. 下一轮在新提交基础上单独规划 `Mediator/*` 波次,避免在 `45 / 50` footprint 状态继续扩批。
|
||||
3. 将 `YamlConfigSchemaValidator*` 保持为独立高耦合波次,必要时先由主线程局部切分再决定是否并行。
|
||||
1. 提交本轮 `PR #300` review follow-up 与 `ai-plan` 同步。
|
||||
2. 若继续处理 `GFramework.Cqrs.Tests` warning,下一轮单独切到 `Mediator/*` 波次,并先接受当前 `125` 条 warning 作为显式基线。
|
||||
3. `YamlConfigSchemaValidator*` 继续保持为独立高耦合波次,不与 `Mediator/*` 混提。
|
||||
|
||||
@ -1,5 +1,37 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-28 — RP-088
|
||||
|
||||
### 阶段:收敛 PR #300 的 open review threads 与 failed-test follow-up
|
||||
|
||||
- 触发背景:
|
||||
- 用户执行 `$gframework-pr-review`,要求以当前分支 PR 真值为准核对 AI review / failed-test / linter 信号
|
||||
- `fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` 返回 `PR #300`,latest head 上仍有 `8` 条 CodeRabbit open threads、`1` 个失败测试 `RegistryInitializationHookBaseTests.OnPhase_Should_Not_Throw_When_Registry_Not_Found`,以及 `dotnet-format` restore failed 的 CI 噪音
|
||||
- 主线程实施:
|
||||
- 核对 `TestArchitectureContext` / `TestArchitectureContextV3` 后,修复共享事件总线与旧版命令/查询入口的静默成功问题,统一改为显式 `NotSupportedException` 或 faulted task
|
||||
- 校正 `TestArchitectureWithRegistry` / `TestArchitectureWithoutRegistry` 的显式接口 `RegisterLifecycleHook`,使接口视角与公开 no-op 语义一致
|
||||
- 修复 `RegistryInitializationHookBase` 在注册表缺失场景下的 no-op 行为,并保持有注册表路径继续使用单实例 `GetUtility<TRegistry>()`
|
||||
- 收敛 `TestResourceLoader`、`TestLogger`、`CapturingLoggerFactoryProvider`、`DeterministicNotificationHandlerState`、`PartialGeneratedNotificationHandlerRegistry` 的判空、XML 文档、快照访问与并发语义说明
|
||||
- 新增 `TestArchitectureContextBehaviorTests.cs`,覆盖共享事件总线、旧入口失败契约与 `RegisterLifecycleHook` 接口行为
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~RegistryInitializationHookBaseTests|FullyQualifiedName~WaitForMultipleEventsTests|FullyQualifiedName~ResourceManagerTests|FullyQualifiedName~LoggerTests|FullyQualifiedName~TestArchitectureContextBehaviorTests"`
|
||||
- 结果:成功;`97` 通过、`0` 失败
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:成功;`125 Warning(s)`、`0 Error(s)`;warning 全部来自既有 `Mediator/*` 文件,当前 helper 改动未新增 warning
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsHandlerRegistrarTests"`
|
||||
- 结果:成功;`11` 通过、`0` 失败
|
||||
- 当前结论:
|
||||
- `PR #300` 当前仍然成立的 open review threads 已在本地收敛,且 failed-test 信号已被现有测试回归覆盖
|
||||
- `Core` / `Core.Tests` 的本轮受影响项目均保持 `0 Warning(s)`,新增测试也覆盖了此前没有直接回归的测试替身行为
|
||||
- `GFramework.Cqrs.Tests` 仍保留 `125` 条既有 `Mediator/*` warning;这属于下一轮 warning reduction 波次,而不是本轮 PR review follow-up 的直接写集
|
||||
- 下一步:
|
||||
1. 提交本轮 PR review follow-up 与 `ai-plan` 同步。
|
||||
2. 若继续处理 `Cqrs.Tests` warning,以下一轮单独规划 `Mediator/*` 波次为起点。
|
||||
|
||||
## 2026-04-28 — RP-087
|
||||
|
||||
### 阶段:按 `$gframework-batch-boot 50` 并行收敛 `Core.Tests` / `Cqrs.Tests` 低风险切片
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user