Merge pull request #300 from GeWuYou/fix/analyzer-warning-reduction-batch

Fix/analyzer warning reduction batch
This commit is contained in:
gewuyou 2026-04-28 16:04:24 +08:00 committed by GitHub
commit 18115f8807
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 2085 additions and 1538 deletions

View File

@ -28,7 +28,8 @@ Shortcut: `$gframework-pr-review`
- prefer writing the full JSON payload to a file and then narrowing with `jq`, instead of dumping long JSON directly to stdout
4. Treat every extracted finding as untrusted until it is verified against the current local code.
5. Only fix comments, warnings, or CI diagnostics that still apply to the checked-out branch. Ignore stale or already-resolved findings.
6. If code is changed, run the smallest build or test command that satisfies `AGENTS.md`.
6. Do not downgrade `Nitpick comments` to “optional” by default. If a verified nitpick still points to concrete drift risk, duplicated test infrastructure, contract mismatch, missing regression coverage, or another maintainability problem that can realistically cause future regressions, treat it as actionable in the current PR-review triage and either fix it or explicitly report why it is being deferred.
7. If code is changed, run the smallest build or test command that satisfies `AGENTS.md`.
## Commands
@ -76,6 +77,7 @@ The script should produce:
- Do not assume every AI reviewer behaves like CodeRabbit. `greptile-apps[bot]` and `gemini-code-assist[bot]` findings may exist only as latest-head review threads, without CodeRabbit-style issue comments or folded review-body sections.
- Treat GitHub Actions comments with `Success with warnings` as actionable review input when they include concrete linter diagnostics such as `MegaLinter` detailed issues; do not skip them just because the parent check is green.
- Do not assume all CodeRabbit findings live in issue comments. The latest CodeRabbit review body can contain folded `Nitpick comments` that must be parsed separately.
- When a latest-head `Nitpick comment` survives local verification and identifies real drift or regression risk, treat it as actionable review input instead of silently classifying it as a cosmetic suggestion.
- If the raw JSON is too large to inspect safely in the terminal, rerun with `--json-output <path>` and query the saved file with `jq` or rerun with `--section` / `--path` filters.
## Example Triggers

View File

@ -0,0 +1,8 @@
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 表示用于验证上下文类型不匹配分支的测试架构上下文。
/// </summary>
public class AnotherTestArchitectureContext : TestArchitectureContext
{
}

View File

@ -262,215 +262,3 @@ public class ArchitectureServicesTests
Assert.That(_services!.ModuleManager, Is.Not.Null);
}
}
#region Test Classes
public class TestArchitectureContextV3 : IArchitectureContext
{
private readonly MicrosoftDiContainer _container = new();
private readonly DefaultEnvironment _environment = new();
public int Id { get; init; }
public TService? GetService<TService>() where TService : class
{
return _container.Get<TService>();
}
public IReadOnlyList<TService> GetServices<TService>() where TService : class
{
return _container.GetAll<TService>();
}
public TModel? GetModel<TModel>() where TModel : class, IModel
{
return _container.Get<TModel>();
}
public IReadOnlyList<TModel> GetModels<TModel>() where TModel : class, IModel
{
return _container.GetAll<TModel>();
}
public TSystem? GetSystem<TSystem>() where TSystem : class, ISystem
{
return _container.Get<TSystem>();
}
public IReadOnlyList<TSystem> GetSystems<TSystem>() where TSystem : class, ISystem
{
return _container.GetAll<TSystem>();
}
public TUtility? GetUtility<TUtility>() where TUtility : class, IUtility
{
return _container.Get<TUtility>();
}
public IReadOnlyList<TUtility> GetUtilities<TUtility>() where TUtility : class, IUtility
{
return _container.GetAll<TUtility>();
}
public IReadOnlyList<TService> GetServicesByPriority<TService>() where TService : class
{
return _container.GetAllByPriority<TService>();
}
public IReadOnlyList<TSystem> GetSystemsByPriority<TSystem>() where TSystem : class, ISystem
{
return _container.GetAllByPriority<TSystem>();
}
public IReadOnlyList<TModel> GetModelsByPriority<TModel>() where TModel : class, IModel
{
return _container.GetAllByPriority<TModel>();
}
public IReadOnlyList<TUtility> GetUtilitiesByPriority<TUtility>() where TUtility : class, IUtility
{
return _container.GetAllByPriority<TUtility>();
}
public void SendEvent<TEvent>() where TEvent : new()
{
}
public void SendEvent<TEvent>(TEvent e) where TEvent : class
{
}
public IUnRegister RegisterEvent<TEvent>(Action<TEvent> handler)
{
return new DefaultUnRegister(() => { });
}
public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent)
{
}
public ValueTask<TResponse> SendRequestAsync<TResponse>(IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送 CQRS 命令并返回响应。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>命令响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendCommandAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送 CQRS 命令并返回响应。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <returns>命令响应。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询结果类型。</typeparam>
/// <param name="query">要发送的查询。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>查询结果任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendQueryAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询结果类型。</typeparam>
/// <param name="query">要发送的查询。</param>
/// <returns>查询结果。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
throw new NotSupportedException();
}
public ValueTask PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) where TNotification : INotification
{
throw new NotSupportedException();
}
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
{
throw new NotSupportedException();
}
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
public void SendCommand(ICommand command)
{
}
public TResult SendCommand<TResult>(Abstractions.Command.ICommand<TResult> command)
{
return default!;
}
public Task SendCommandAsync(IAsyncCommand command)
{
return Task.CompletedTask;
}
public Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> command)
{
return (Task<TResult>)Task.CompletedTask;
}
public TResult SendQuery<TResult>(Abstractions.Query.IQuery<TResult> query)
{
return default!;
}
public Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query)
{
return (Task<TResult>)Task.CompletedTask;
}
public IEnvironment GetEnvironment()
{
return _environment;
}
}
#endregion

View File

@ -147,10 +147,3 @@ public class ContextProviderTests
Assert.That(foundContext, Is.SameAs(context));
}
}
/// <summary>
/// 另一个测试用的架构上下文类,用于测试类型不匹配的情况
/// </summary>
public class AnotherTestArchitectureContext : TestArchitectureContext
{
}

View File

@ -1,20 +1,4 @@
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Environment;
using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Query;
using GFramework.Cqrs.Abstractions.Cqrs;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Core.Tests.Architectures;
@ -211,383 +195,3 @@ public class GameContextTests
Assert.That(GameContext.ArchitectureReadOnlyDictionary.Count, Is.EqualTo(0));
}
}
/// <summary>
/// 测试用的架构类继承自Architecture
/// </summary>
public class TestArchitecture : Architecture
{
/// <summary>
/// 初始化方法,当前为空实现
/// </summary>
protected override void OnInitialize()
{
}
}
/// <summary>
/// 测试用的架构上下文类实现了IArchitectureContext接口
/// </summary>
public class TestArchitectureContext : IArchitectureContext
{
private readonly MicrosoftDiContainer _container = new();
/// <summary>
/// 获取依赖注入容器
/// </summary>
public IIocContainer Container => _container;
/// <summary>
/// 获取事件总线
/// </summary>
public IEventBus EventBus => new EventBus();
/// <summary>
/// 获取命令总线
/// </summary>
public ICommandExecutor CommandExecutor => new CommandExecutor();
/// <summary>
/// 获取查询总线
/// </summary>
public IQueryExecutor QueryExecutor => new QueryExecutor();
/// <summary>
/// 获取环境对象
/// </summary>
public IEnvironment Environment => new DefaultEnvironment();
/// <summary>
/// 获取指定类型的服务
/// </summary>
/// <typeparam name="TService">服务类型</typeparam>
/// <returns>服务实例或null</returns>
public TService? GetService<TService>() where TService : class
{
return _container.Get<TService>();
}
/// <summary>
/// 获取指定类型的所有服务
/// </summary>
/// <typeparam name="TService">服务类型</typeparam>
/// <returns>所有服务实例列表</returns>
public IReadOnlyList<TService> GetServices<TService>() where TService : class
{
return _container.GetAll<TService>();
}
/// <summary>
/// 获取指定类型的模型
/// </summary>
/// <typeparam name="TModel">模型类型</typeparam>
/// <returns>模型实例或null</returns>
public TModel? GetModel<TModel>() where TModel : class, IModel
{
return _container.Get<TModel>();
}
/// <summary>
/// 获取指定类型的所有模型
/// </summary>
/// <typeparam name="TModel">模型类型</typeparam>
/// <returns>所有模型实例列表</returns>
public IReadOnlyList<TModel> GetModels<TModel>() where TModel : class, IModel
{
return _container.GetAll<TModel>();
}
/// <summary>
/// 获取指定类型的系统
/// </summary>
/// <typeparam name="TSystem">系统类型</typeparam>
/// <returns>系统实例或null</returns>
public TSystem? GetSystem<TSystem>() where TSystem : class, ISystem
{
return _container.Get<TSystem>();
}
/// <summary>
/// 获取指定类型的所有系统
/// </summary>
/// <typeparam name="TSystem">系统类型</typeparam>
/// <returns>所有系统实例列表</returns>
public IReadOnlyList<TSystem> GetSystems<TSystem>() where TSystem : class, ISystem
{
return _container.GetAll<TSystem>();
}
/// <summary>
/// 获取指定类型的工具
/// </summary>
/// <typeparam name="TUtility">工具类型</typeparam>
/// <returns>工具实例或null</returns>
public virtual TUtility? GetUtility<TUtility>() where TUtility : class, IUtility
{
return _container.Get<TUtility>();
}
/// <summary>
/// 获取指定类型的所有工具
/// </summary>
/// <typeparam name="TUtility">工具类型</typeparam>
/// <returns>所有工具实例列表</returns>
public IReadOnlyList<TUtility> GetUtilities<TUtility>() where TUtility : class, IUtility
{
return _container.GetAll<TUtility>();
}
public IReadOnlyList<TService> GetServicesByPriority<TService>() where TService : class
{
return _container.GetAllByPriority<TService>();
}
public IReadOnlyList<TSystem> GetSystemsByPriority<TSystem>() where TSystem : class, ISystem
{
return _container.GetAllByPriority<TSystem>();
}
public IReadOnlyList<TModel> GetModelsByPriority<TModel>() where TModel : class, IModel
{
return _container.GetAllByPriority<TModel>();
}
public IReadOnlyList<TUtility> GetUtilitiesByPriority<TUtility>() where TUtility : class, IUtility
{
return _container.GetAllByPriority<TUtility>();
}
/// <summary>
/// 发送事件
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
public void SendEvent<TEvent>() where TEvent : new()
{
}
/// <summary>
/// 发送事件
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <param name="e">事件实例</param>
public void SendEvent<TEvent>(TEvent e) where TEvent : class
{
}
/// <summary>
/// 注册事件处理器
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <param name="handler">事件处理委托</param>
/// <returns>取消注册接口</returns>
public IUnRegister RegisterEvent<TEvent>(Action<TEvent> handler)
{
return new DefaultUnRegister(() => { });
}
/// <summary>
/// 取消注册事件处理器
/// </summary>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <param name="onEvent">事件处理委托</param>
public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent)
{
}
/// <summary>
/// 测试桩:异步发送统一 CQRS 请求。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="request">要发送的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendRequestAsync<TResponse>(IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送统一 CQRS 请求。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="request">要发送的请求。</param>
/// <returns>请求响应。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送 CQRS 命令并返回响应。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>命令响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendCommandAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送 CQRS 命令并返回响应。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <returns>命令响应。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询结果类型。</typeparam>
/// <param name="query">要发送的查询。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>查询结果任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendQueryAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询结果类型。</typeparam>
/// <param name="query">要发送的查询。</param>
/// <returns>查询结果。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发布 CQRS 通知。
/// </summary>
/// <typeparam name="TNotification">通知类型。</typeparam>
/// <param name="notification">要发布的通知。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>通知发布任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) where TNotification : INotification
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:创建 CQRS 流式请求响应序列。
/// </summary>
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
/// <param name="request">流式请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>异步响应流。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送无返回值 CQRS 命令。
/// </summary>
/// <typeparam name="TCommand">命令类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>命令发送任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送带返回值的 CQRS 请求。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="command">要发送的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 发送命令
/// </summary>
/// <param name="command">命令对象</param>
public void SendCommand(ICommand command)
{
}
/// <summary>
/// 发送带返回值的命令
/// </summary>
/// <typeparam name="TResult">返回值类型</typeparam>
/// <param name="command">命令对象</param>
/// <returns>命令执行结果</returns>
public TResult SendCommand<TResult>(ICommand<TResult> command)
{
return default!;
}
public Task SendCommandAsync(IAsyncCommand command)
{
return Task.CompletedTask;
}
public Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> command)
{
return (Task<TResult>)Task.CompletedTask;
}
/// <summary>
/// 发送查询请求
/// </summary>
/// <typeparam name="TResult">查询结果类型</typeparam>
/// <param name="query">查询对象</param>
/// <returns>查询结果</returns>
public TResult SendQuery<TResult>(IQuery<TResult> query)
{
return default!;
}
/// <summary>
/// 异步发送查询请求
/// </summary>
/// <typeparam name="TResult">查询结果类型</typeparam>
/// <param name="query">异步查询对象</param>
/// <returns>查询结果</returns>
public Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query)
{
return (Task<TResult>)Task.CompletedTask;
}
/// <summary>
/// 获取环境对象
/// </summary>
/// <returns>环境对象</returns>
public IEnvironment GetEnvironment()
{
return Environment;
}
}

View File

@ -1,11 +1,5 @@
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using System;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
namespace GFramework.Core.Tests.Architectures;
@ -116,291 +110,3 @@ public class RegistryInitializationHookBaseTests
Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(2));
}
}
/// <summary>
/// 测试用的注册表初始化钩子实现
/// </summary>
public class TestRegistryInitializationHook : RegistryInitializationHookBase<TestRegistry, string>
{
public TestRegistryInitializationHook(
IEnumerable<string> configs,
ArchitecturePhase targetPhase = ArchitecturePhase.AfterSystemInit)
: base(configs, targetPhase)
{
}
protected override void RegisterConfig(TestRegistry registry, string config)
{
registry.Register(config);
}
}
/// <summary>
/// 测试用的注册表类
/// </summary>
public class TestRegistry : IUtility
{
public List<string> RegisteredConfigs { get; } = new();
public void Register(string config)
{
RegisteredConfigs.Add(config);
}
}
/// <summary>
/// 测试用的架构类(包含注册表)
/// </summary>
public class TestArchitectureWithRegistry : IArchitecture
{
private readonly TestRegistry _registry;
public TestArchitectureWithRegistry(TestRegistry registry)
{
_registry = registry;
Context = new TestArchitectureContextWithRegistry(registry);
}
public Action<IServiceCollection>? Configurator { get; }
public IArchitectureContext Context { get; }
Action<IServiceCollection>? IArchitecture.Configurator => Configurator;
T IArchitecture.RegisterSystem<T>(T system)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterModel<T>(T model)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterUtility<T>(T utility)
{
throw new NotSupportedException();
}
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
throw new NotSupportedException();
}
public IArchitectureModule InstallModule(IArchitectureModule module)
{
throw new NotSupportedException();
}
IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
throw new NotSupportedException();
}
Task IArchitecture.WaitUntilReadyAsync()
{
return WaitUntilReadyAsync();
}
public void RegisterUtility<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IUtility
{
throw new NotSupportedException();
}
public void RegisterModel<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IModel
{
throw new NotSupportedException();
}
public void RegisterSystem<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, ISystem
{
throw new NotSupportedException();
}
public void Initialize()
{
}
public void Destroy()
{
throw new NotSupportedException();
}
Task IAsyncInitializable.InitializeAsync()
{
return InitializeAsync();
}
ValueTask IAsyncDestroyable.DestroyAsync()
{
return DestroyAsync();
}
public Task WaitUntilReadyAsync()
{
throw new NotSupportedException();
}
public void RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
}
public Task InitializeAsync()
{
throw new NotSupportedException();
}
public ValueTask DestroyAsync()
{
throw new NotSupportedException();
}
}
/// <summary>
/// 测试用的架构上下文类(包含注册表)
/// </summary>
public class TestArchitectureContextWithRegistry : TestArchitectureContext
{
private readonly TestRegistry _registry;
public TestArchitectureContextWithRegistry(TestRegistry registry)
{
_registry = registry;
}
public override TUtility? GetUtility<TUtility>() where TUtility : class
{
if (typeof(TUtility) == typeof(TestRegistry))
{
return _registry as TUtility;
}
return base.GetUtility<TUtility>();
}
}
/// <summary>
/// 测试用的架构类(不包含注册表)
/// </summary>
public class TestArchitectureWithoutRegistry : IArchitecture
{
public TestArchitectureWithoutRegistry()
{
Context = new TestArchitectureContext();
}
public IArchitectureContext Context { get; }
public Action<IServiceCollection>? Configurator { get; }
T IArchitecture.RegisterSystem<T>(T system)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterModel<T>(T model)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterUtility<T>(T utility)
{
throw new NotSupportedException();
}
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
throw new NotSupportedException();
}
public IArchitectureModule InstallModule(IArchitectureModule module)
{
throw new NotSupportedException();
}
IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
throw new NotSupportedException();
}
public Task WaitUntilReadyAsync()
{
throw new NotSupportedException();
}
public void RegisterUtility<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IUtility
{
throw new NotSupportedException();
}
public void RegisterModel<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IModel
{
throw new NotSupportedException();
}
public void RegisterSystem<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, ISystem
{
throw new NotSupportedException();
}
public void Initialize()
{
}
public Task InitializeAsync()
{
throw new NotSupportedException();
}
public ValueTask DestroyAsync()
{
throw new NotSupportedException();
}
public void Destroy()
{
throw new NotSupportedException();
}
public void RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
}
}

View File

@ -0,0 +1,16 @@
using GFramework.Core.Architectures;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 提供给 <see cref="GameContextTests" /> 的最小架构测试桩。
/// </summary>
public class TestArchitecture : Architecture
{
/// <summary>
/// 保持空初始化流程,便于测试仅验证 <see cref="GameContext" /> 的上下文绑定行为。
/// </summary>
protected override void OnInitialize()
{
}
}

View File

@ -9,6 +9,7 @@ namespace GFramework.Core.Tests.Architectures;
public abstract class TestArchitectureBase : Architecture
{
private Action<TestArchitectureBase>? _postRegistrationHook;
private readonly List<ArchitecturePhase> _phaseHistory = [];
/// <summary>
/// 获取就绪事件是否已触发的状态
@ -23,7 +24,7 @@ public abstract class TestArchitectureBase : Architecture
/// <summary>
/// 获取架构阶段历史记录列表
/// </summary>
public List<ArchitecturePhase> PhaseHistory { get; } = [];
public IReadOnlyList<ArchitecturePhase> PhaseHistory => _phaseHistory;
/// <summary>
/// 添加注册后钩子函数
@ -43,6 +44,6 @@ public abstract class TestArchitectureBase : Architecture
_postRegistrationHook?.Invoke(this);
// 订阅阶段变更事件以记录历史
PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase);
PhaseChanged += (_, eventArgs) => _phaseHistory.Add(eventArgs.Phase);
}
}

View File

@ -0,0 +1,12 @@
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="GameContextTests" /> 提供最小可用的架构上下文测试桩。
/// </summary>
/// <remarks>
/// 共享的容器解析、事件总线协作与 legacy CQRS 失败契约由 <see cref="TestArchitectureContextBase" /> 提供,
/// 当前类型仅作为默认测试上下文命名入口,供现有测试与派生替身继续复用。
/// </remarks>
public class TestArchitectureContext : TestArchitectureContextBase
{
}

View File

@ -0,0 +1,451 @@
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;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Command;
using GFramework.Core.Environment;
using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Query;
using GFramework.Cqrs.Abstractions.Cqrs;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为架构相关测试替身提供共享的 <see cref="IArchitectureContext" /> 基础实现。
/// </summary>
/// <remarks>
/// 该基类统一维护容器解析、共享 <see cref="EventBus" /> 语义,以及 legacy / CQRS 入口的显式失败契约,
/// 以避免多个测试上下文因为并行复制实现而在后续演进中发生语义漂移。
/// </remarks>
public abstract class TestArchitectureContextBase : IArchitectureContext
{
private readonly MicrosoftDiContainer _container = new();
private readonly DefaultEnvironment _environment = new();
private readonly EventBus _eventBus = new();
/// <summary>
/// 获取用于解析测试服务的依赖注入容器。
/// </summary>
public IIocContainer Container => _container;
/// <summary>
/// 获取测试事件总线实例。
/// </summary>
/// <remarks>
/// 返回同一个缓存事件总线,以便 <see cref="RegisterEvent{TEvent}" />、<see cref="SendEvent{TEvent}()" /> 与
/// <see cref="UnRegisterEvent{TEvent}" /> 在同一份订阅状态上协作。
/// </remarks>
public IEventBus EventBus => _eventBus;
/// <summary>
/// 获取测试命令执行器实例。
/// </summary>
public ICommandExecutor CommandExecutor => new CommandExecutor();
/// <summary>
/// 获取测试查询执行器实例。
/// </summary>
public IQueryExecutor QueryExecutor => new QueryExecutor();
/// <summary>
/// 获取默认测试环境对象。
/// </summary>
public IEnvironment Environment => _environment;
/// <summary>
/// 获取指定类型的服务实例。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <returns>已注册的服务实例。</returns>
/// <exception cref="InvalidOperationException">未注册服务时抛出。</exception>
public TService GetService<TService>() where TService : class
{
return _container.GetRequired<TService>();
}
/// <summary>
/// 获取指定类型的所有服务实例。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <returns>服务实例列表。</returns>
public IReadOnlyList<TService> GetServices<TService>() where TService : class
{
return _container.GetAll<TService>();
}
/// <summary>
/// 获取指定类型的模型实例。
/// </summary>
/// <typeparam name="TModel">模型类型。</typeparam>
/// <returns>已注册的模型实例。</returns>
/// <exception cref="InvalidOperationException">未注册模型时抛出。</exception>
public TModel GetModel<TModel>() where TModel : class, IModel
{
return _container.GetRequired<TModel>();
}
/// <summary>
/// 获取指定类型的所有模型实例。
/// </summary>
/// <typeparam name="TModel">模型类型。</typeparam>
/// <returns>模型实例列表。</returns>
public IReadOnlyList<TModel> GetModels<TModel>() where TModel : class, IModel
{
return _container.GetAll<TModel>();
}
/// <summary>
/// 获取指定类型的系统实例。
/// </summary>
/// <typeparam name="TSystem">系统类型。</typeparam>
/// <returns>已注册的系统实例。</returns>
/// <exception cref="InvalidOperationException">未注册系统时抛出。</exception>
public TSystem GetSystem<TSystem>() where TSystem : class, ISystem
{
return _container.GetRequired<TSystem>();
}
/// <summary>
/// 获取指定类型的所有系统实例。
/// </summary>
/// <typeparam name="TSystem">系统类型。</typeparam>
/// <returns>系统实例列表。</returns>
public IReadOnlyList<TSystem> GetSystems<TSystem>() where TSystem : class, ISystem
{
return _container.GetAll<TSystem>();
}
/// <summary>
/// 获取指定类型的工具实例。
/// </summary>
/// <typeparam name="TUtility">工具类型。</typeparam>
/// <returns>已注册的工具实例。</returns>
/// <exception cref="InvalidOperationException">未注册工具时抛出。</exception>
public virtual TUtility GetUtility<TUtility>() where TUtility : class, IUtility
{
return _container.GetRequired<TUtility>();
}
/// <summary>
/// 获取指定类型的所有工具实例。
/// </summary>
/// <typeparam name="TUtility">工具类型。</typeparam>
/// <returns>工具实例列表。</returns>
public IReadOnlyList<TUtility> GetUtilities<TUtility>() where TUtility : class, IUtility
{
return _container.GetAll<TUtility>();
}
/// <summary>
/// 获取指定类型的所有服务实例,并按优先级排序。
/// </summary>
/// <typeparam name="TService">服务类型。</typeparam>
/// <returns>按优先级排序后的服务实例列表。</returns>
public IReadOnlyList<TService> GetServicesByPriority<TService>() where TService : class
{
return _container.GetAllByPriority<TService>();
}
/// <summary>
/// 获取指定类型的所有系统实例,并按优先级排序。
/// </summary>
/// <typeparam name="TSystem">系统类型。</typeparam>
/// <returns>按优先级排序后的系统实例列表。</returns>
public IReadOnlyList<TSystem> GetSystemsByPriority<TSystem>() where TSystem : class, ISystem
{
return _container.GetAllByPriority<TSystem>();
}
/// <summary>
/// 获取指定类型的所有模型实例,并按优先级排序。
/// </summary>
/// <typeparam name="TModel">模型类型。</typeparam>
/// <returns>按优先级排序后的模型实例列表。</returns>
public IReadOnlyList<TModel> GetModelsByPriority<TModel>() where TModel : class, IModel
{
return _container.GetAllByPriority<TModel>();
}
/// <summary>
/// 获取指定类型的所有工具实例,并按优先级排序。
/// </summary>
/// <typeparam name="TUtility">工具类型。</typeparam>
/// <returns>按优先级排序后的工具实例列表。</returns>
public IReadOnlyList<TUtility> GetUtilitiesByPriority<TUtility>() where TUtility : class, IUtility
{
return _container.GetAllByPriority<TUtility>();
}
/// <summary>
/// 发送无参数事件。
/// </summary>
/// <typeparam name="TEvent">事件类型。</typeparam>
public void SendEvent<TEvent>() where TEvent : new()
{
_eventBus.Send<TEvent>();
}
/// <summary>
/// 发送带参数事件。
/// </summary>
/// <typeparam name="TEvent">事件类型。</typeparam>
/// <param name="e">事件实例。</param>
/// <exception cref="ArgumentNullException"><paramref name="e" /> 为 <see langword="null" />。</exception>
public void SendEvent<TEvent>(TEvent e) where TEvent : class
{
ArgumentNullException.ThrowIfNull(e);
_eventBus.Send(e);
}
/// <summary>
/// 注册事件处理器。
/// </summary>
/// <typeparam name="TEvent">事件类型。</typeparam>
/// <param name="handler">事件处理委托。</param>
/// <returns>用于测试的事件注销句柄。</returns>
/// <exception cref="ArgumentNullException"><paramref name="handler" /> 为 <see langword="null" />。</exception>
public IUnRegister RegisterEvent<TEvent>(Action<TEvent> handler)
{
ArgumentNullException.ThrowIfNull(handler);
return _eventBus.Register(handler);
}
/// <summary>
/// 取消注册事件处理器。
/// </summary>
/// <typeparam name="TEvent">事件类型。</typeparam>
/// <param name="onEvent">事件处理委托。</param>
/// <exception cref="ArgumentNullException"><paramref name="onEvent" /> 为 <see langword="null" />。</exception>
public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent)
{
ArgumentNullException.ThrowIfNull(onEvent);
_eventBus.UnRegister(onEvent);
}
/// <summary>
/// 测试桩:异步发送统一 CQRS 请求。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="request">要发送的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendRequestAsync<TResponse>(
IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送统一 CQRS 请求。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="request">要发送的请求。</param>
/// <returns>请求响应。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendRequest<TResponse>(IRequest<TResponse> request)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送 CQRS 命令并返回响应。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>命令响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendCommandAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送 CQRS 命令并返回响应。
/// </summary>
/// <typeparam name="TResponse">命令响应类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <returns>命令响应。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendCommand<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand<TResponse> command)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询结果类型。</typeparam>
/// <param name="query">要发送的查询。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>查询结果任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendQueryAsync<TResponse>(
GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:同步发送 CQRS 查询并返回结果。
/// </summary>
/// <typeparam name="TResponse">查询结果类型。</typeparam>
/// <param name="query">要发送的查询。</param>
/// <returns>查询结果。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public TResponse SendQuery<TResponse>(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery<TResponse> query)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发布 CQRS 通知。
/// </summary>
/// <typeparam name="TNotification">通知类型。</typeparam>
/// <param name="notification">要发布的通知。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>通知发布任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask PublishAsync<TNotification>(
TNotification notification,
CancellationToken cancellationToken = default)
where TNotification : INotification
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:创建 CQRS 流式请求响应序列。
/// </summary>
/// <typeparam name="TResponse">流式响应元素类型。</typeparam>
/// <param name="request">流式请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>异步响应流。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public IAsyncEnumerable<TResponse> CreateStream<TResponse>(
IStreamRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送无返回值 CQRS 命令。
/// </summary>
/// <typeparam name="TCommand">命令类型。</typeparam>
/// <param name="command">要发送的命令。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>命令发送任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask SendAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
{
throw new NotSupportedException();
}
/// <summary>
/// 测试桩:异步发送带返回值的 CQRS 请求。
/// </summary>
/// <typeparam name="TResponse">响应类型。</typeparam>
/// <param name="command">要发送的请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>请求响应任务。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持此成员。</exception>
public ValueTask<TResponse> SendAsync<TResponse>(
IRequest<TResponse> command,
CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
}
/// <summary>
/// 发送旧版命令。
/// </summary>
/// <param name="command">命令对象。</param>
/// <exception cref="NotSupportedException">该测试桩不支持旧版命令执行入口。</exception>
public void SendCommand(ICommand command)
{
throw new NotSupportedException();
}
/// <summary>
/// 发送旧版带返回值命令。
/// </summary>
/// <typeparam name="TResult">返回值类型。</typeparam>
/// <param name="command">命令对象。</param>
/// <returns>此方法始终抛出异常,不返回结果。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持旧版命令执行入口。</exception>
public TResult SendCommand<TResult>(ICommand<TResult> command)
{
throw new NotSupportedException();
}
/// <summary>
/// 异步发送旧版命令。
/// </summary>
/// <param name="command">命令对象。</param>
/// <returns>已失败的任务。</returns>
public Task SendCommandAsync(IAsyncCommand command)
{
return Task.FromException(new NotSupportedException());
}
/// <summary>
/// 异步发送旧版带返回值命令。
/// </summary>
/// <typeparam name="TResult">返回值类型。</typeparam>
/// <param name="command">命令对象。</param>
/// <returns>已失败的任务。</returns>
public Task<TResult> SendCommandAsync<TResult>(IAsyncCommand<TResult> command)
{
return Task.FromException<TResult>(new NotSupportedException());
}
/// <summary>
/// 发送旧版查询请求。
/// </summary>
/// <typeparam name="TResult">查询结果类型。</typeparam>
/// <param name="query">查询对象。</param>
/// <returns>此方法始终抛出异常,不返回结果。</returns>
/// <exception cref="NotSupportedException">该测试桩不支持旧版查询执行入口。</exception>
public TResult SendQuery<TResult>(IQuery<TResult> query)
{
throw new NotSupportedException();
}
/// <summary>
/// 异步发送旧版查询请求。
/// </summary>
/// <typeparam name="TResult">查询结果类型。</typeparam>
/// <param name="query">异步查询对象。</param>
/// <returns>已失败的任务。</returns>
public Task<TResult> SendQueryAsync<TResult>(IAsyncQuery<TResult> query)
{
return Task.FromException<TResult>(new NotSupportedException());
}
/// <summary>
/// 获取当前环境对象。
/// </summary>
/// <returns>默认测试环境对象。</returns>
public IEnvironment GetEnvironment()
{
return Environment;
}
}

View File

@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
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
{
private static IEnumerable<TestCaseData> EventContextFactories()
{
yield return new TestCaseData(
new Func<IArchitectureContext>(() => new TestArchitectureContext()))
.SetArgDisplayNames(nameof(TestArchitectureContext));
yield return new TestCaseData(
new Func<IArchitectureContext>(() => new TestArchitectureContextV3()))
.SetArgDisplayNames(nameof(TestArchitectureContextV3));
}
/// <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>
/// 验证用于 ArchitectureServices 的上下文替身也会把事件注册与发送委托到同一个事件总线实例。
/// </summary>
[Test]
public void RegisterEvent_And_SendEvent_On_TestArchitectureContextV3_Should_Use_Shared_EventBus()
{
var context = new TestArchitectureContextV3();
var eventReceived = false;
context.RegisterEvent<TestEventV2>(_ => eventReceived = true);
context.SendEvent<TestEventV2>();
Assert.That(eventReceived, Is.True);
}
/// <summary>
/// 验证两类架构测试替身都会对事件 API 的空参数执行显式校验。
/// </summary>
/// <param name="createContext">创建待验证测试上下文的工厂。</param>
[TestCaseSource(nameof(EventContextFactories))]
public void Event_APIs_Should_Validate_Null_Arguments(Func<IArchitectureContext> createContext)
{
var context = createContext();
Assert.That(() => context.SendEvent<TestEventV2>(null!), Throws.TypeOf<ArgumentNullException>());
Assert.That(() => context.RegisterEvent<TestEventV2>(null!), Throws.TypeOf<ArgumentNullException>());
Assert.That(() => context.UnRegisterEvent<TestEventV2>(null!), Throws.TypeOf<ArgumentNullException>());
}
/// <summary>
/// 验证两类架构测试替身在注销事件处理器后都不会继续分发同一回调。
/// </summary>
/// <param name="createContext">创建待验证测试上下文的工厂。</param>
[TestCaseSource(nameof(EventContextFactories))]
public void UnRegisterEvent_Should_Stop_Dispatch(Func<IArchitectureContext> createContext)
{
var context = createContext();
var eventReceived = false;
void Handler(TestEventV2 _)
{
eventReceived = true;
}
context.RegisterEvent<TestEventV2>(Handler);
context.UnRegisterEvent<TestEventV2>(Handler);
context.SendEvent<TestEventV2>();
Assert.That(eventReceived, Is.False);
}
/// <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);
}
}
}

View File

@ -0,0 +1,16 @@
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="ArchitectureServicesTests" /> 提供最小实现的架构上下文测试桩。
/// </summary>
/// <remarks>
/// 共享的容器解析、事件总线协作与 legacy CQRS 失败契约由 <see cref="TestArchitectureContextBase" /> 提供,
/// 当前类型只补充 <see cref="ArchitectureServicesTests" /> 需要的上下文实例标识。
/// </remarks>
public class TestArchitectureContextV3 : TestArchitectureContextBase
{
/// <summary>
/// 获取或初始化用于区分测试上下文实例的标识。
/// </summary>
public int Id { get; init; }
}

View File

@ -0,0 +1,35 @@
using GFramework.Core.Architectures;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="RegistryInitializationHookBaseTests" /> 在架构上下文中暴露 <see cref="TestRegistry" /> 的测试替身。
/// </summary>
public class TestArchitectureContextWithRegistry : TestArchitectureContext
{
private readonly TestRegistry _registry;
/// <summary>
/// 使用给定测试注册表创建上下文测试替身。
/// </summary>
/// <param name="registry">需要通过 <see cref="GetUtility{TUtility}" /> 返回的测试注册表。</param>
public TestArchitectureContextWithRegistry(TestRegistry registry)
{
_registry = registry;
}
/// <summary>
/// 在请求 <see cref="TestRegistry" /> 时返回测试注册表,其余类型回退到基类实现。
/// </summary>
/// <typeparam name="TUtility">请求的工具类型。</typeparam>
/// <returns>匹配时返回测试注册表,否则返回基类结果。</returns>
public override TUtility GetUtility<TUtility>()
{
if (typeof(TUtility) == typeof(TestRegistry))
{
return (TUtility)(object)_registry;
}
return base.GetUtility<TUtility>();
}
}

View File

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="RegistryInitializationHookBaseTests" /> 提供已挂接 <see cref="TestRegistry" /> 的架构测试替身。
/// </summary>
public class TestArchitectureWithRegistry : IArchitecture
{
/// <summary>
/// 使用给定测试注册表创建架构测试替身。
/// </summary>
/// <param name="registry">要通过架构上下文暴露给钩子的测试注册表。</param>
public TestArchitectureWithRegistry(TestRegistry registry)
{
Context = new TestArchitectureContextWithRegistry(registry);
}
/// <summary>
/// 获取测试替身公开的服务配置入口。
/// 当前切片不验证服务配置流程,因此始终保持为空。
/// </summary>
public Action<IServiceCollection>? Configurator { get; }
/// <summary>
/// 获取当前测试替身使用的架构上下文。
/// </summary>
public IArchitectureContext Context { get; }
Action<IServiceCollection>? IArchitecture.Configurator => Configurator;
T IArchitecture.RegisterSystem<T>(T system)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterModel<T>(T model)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterUtility<T>(T utility)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现 CQRS 管道行为注册。
/// </summary>
/// <typeparam name="TBehavior">行为类型。</typeparam>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 管道配置验证。</exception>
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现模块安装流程。
/// </summary>
/// <param name="module">要安装的模块。</param>
/// <returns>此方法始终抛出异常,不返回模块实例。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与模块安装路径验证。</exception>
public IArchitectureModule InstallModule(IArchitectureModule module)
{
throw new NotSupportedException();
}
IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
RegisterLifecycleHook(hook);
return hook;
}
Task IArchitecture.WaitUntilReadyAsync()
{
return WaitUntilReadyAsync();
}
/// <summary>
/// 测试替身未实现工具延迟注册入口。
/// </summary>
/// <typeparam name="T">工具类型。</typeparam>
/// <param name="onCreated">工具创建后的回调。</param>
/// <exception cref="NotSupportedException">该测试替身不参与工具注册路径验证。</exception>
public void RegisterUtility<T>(Action<T>? onCreated = default) where T : class, IUtility
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现 Model 延迟注册入口。
/// </summary>
/// <typeparam name="T">Model 类型。</typeparam>
/// <param name="onCreated">Model 创建后的回调。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 Model 注册路径验证。</exception>
public void RegisterModel<T>(Action<T>? onCreated = default) where T : class, IModel
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现 System 延迟注册入口。
/// </summary>
/// <typeparam name="T">System 类型。</typeparam>
/// <param name="onCreated">System 创建后的回调。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 System 注册路径验证。</exception>
public void RegisterSystem<T>(Action<T>? onCreated = default) where T : class, ISystem
{
throw new NotSupportedException();
}
/// <summary>
/// 初始化测试替身。
/// 该切片只需要上下文可用,因此初始化过程保持为空实现。
/// </summary>
public void Initialize()
{
}
/// <summary>
/// 测试替身未实现销毁路径。
/// </summary>
/// <exception cref="NotSupportedException">该测试替身不参与销毁路径验证。</exception>
public void Destroy()
{
throw new NotSupportedException();
}
Task IAsyncInitializable.InitializeAsync()
{
return InitializeAsync();
}
ValueTask IAsyncDestroyable.DestroyAsync()
{
return DestroyAsync();
}
/// <summary>
/// 测试替身未实现就绪等待流程。
/// </summary>
/// <returns>此方法始终抛出异常,不返回等待任务。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与就绪等待路径验证。</exception>
public Task WaitUntilReadyAsync()
{
throw new NotSupportedException();
}
/// <summary>
/// 注册架构生命周期钩子。
/// 当前切片不依赖生命周期钩子执行,因此保持空实现。
/// </summary>
/// <param name="hook">要忽略的生命周期钩子。</param>
public void RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
}
/// <summary>
/// 测试替身未实现异步初始化路径。
/// </summary>
/// <returns>此方法始终抛出异常,不返回初始化任务。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与异步初始化验证。</exception>
public Task InitializeAsync()
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现异步销毁路径。
/// </summary>
/// <returns>此方法始终抛出异常,不返回销毁任务。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与异步销毁验证。</exception>
public ValueTask DestroyAsync()
{
throw new NotSupportedException();
}
}

View File

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="RegistryInitializationHookBaseTests" /> 提供不包含 <see cref="TestRegistry" /> 的架构测试替身。
/// </summary>
public class TestArchitectureWithoutRegistry : IArchitecture
{
/// <summary>
/// 创建不包含测试注册表的架构替身。
/// </summary>
public TestArchitectureWithoutRegistry()
{
Context = new TestArchitectureContext();
}
/// <summary>
/// 获取测试替身公开的服务配置入口。
/// 当前切片不验证服务配置流程,因此始终保持为空。
/// </summary>
public Action<IServiceCollection>? Configurator { get; }
/// <summary>
/// 获取当前测试替身使用的架构上下文。
/// </summary>
public IArchitectureContext Context { get; }
T IArchitecture.RegisterSystem<T>(T system)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterModel<T>(T model)
{
throw new NotSupportedException();
}
T IArchitecture.RegisterUtility<T>(T utility)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现 CQRS 管道行为注册。
/// </summary>
/// <typeparam name="TBehavior">行为类型。</typeparam>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 管道配置验证。</exception>
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assembly">包含 CQRS 处理器或生成注册器的程序集。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssembly(Assembly assembly)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现显式程序集 CQRS 处理器接入入口。
/// </summary>
/// <param name="assemblies">要接入的程序集集合。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 CQRS 程序集接入路径验证。</exception>
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现模块安装流程。
/// </summary>
/// <param name="module">要安装的模块。</param>
/// <returns>此方法始终抛出异常,不返回模块实例。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与模块安装路径验证。</exception>
public IArchitectureModule InstallModule(IArchitectureModule module)
{
throw new NotSupportedException();
}
IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
RegisterLifecycleHook(hook);
return hook;
}
/// <summary>
/// 测试替身未实现就绪等待流程。
/// </summary>
/// <returns>此方法始终抛出异常,不返回等待任务。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与就绪等待路径验证。</exception>
public Task WaitUntilReadyAsync()
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现工具延迟注册入口。
/// </summary>
/// <typeparam name="T">工具类型。</typeparam>
/// <param name="onCreated">工具创建后的回调。</param>
/// <exception cref="NotSupportedException">该测试替身不参与工具注册路径验证。</exception>
public void RegisterUtility<T>(Action<T>? onCreated = default) where T : class, IUtility
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现 Model 延迟注册入口。
/// </summary>
/// <typeparam name="T">Model 类型。</typeparam>
/// <param name="onCreated">Model 创建后的回调。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 Model 注册路径验证。</exception>
public void RegisterModel<T>(Action<T>? onCreated = default) where T : class, IModel
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现 System 延迟注册入口。
/// </summary>
/// <typeparam name="T">System 类型。</typeparam>
/// <param name="onCreated">System 创建后的回调。</param>
/// <exception cref="NotSupportedException">该测试替身不参与 System 注册路径验证。</exception>
public void RegisterSystem<T>(Action<T>? onCreated = default) where T : class, ISystem
{
throw new NotSupportedException();
}
/// <summary>
/// 初始化测试替身。
/// 该切片只需要一个不含注册表的上下文,因此初始化过程保持为空实现。
/// </summary>
public void Initialize()
{
}
/// <summary>
/// 测试替身未实现异步初始化路径。
/// </summary>
/// <returns>此方法始终抛出异常,不返回初始化任务。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与异步初始化验证。</exception>
public Task InitializeAsync()
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现异步销毁路径。
/// </summary>
/// <returns>此方法始终抛出异常,不返回销毁任务。</returns>
/// <exception cref="NotSupportedException">该测试替身不参与异步销毁验证。</exception>
public ValueTask DestroyAsync()
{
throw new NotSupportedException();
}
/// <summary>
/// 测试替身未实现销毁路径。
/// </summary>
/// <exception cref="NotSupportedException">该测试替身不参与销毁路径验证。</exception>
public void Destroy()
{
throw new NotSupportedException();
}
/// <summary>
/// 注册架构生命周期钩子。
/// 当前切片不依赖生命周期钩子执行,因此保持空实现。
/// </summary>
/// <param name="hook">要忽略的生命周期钩子。</param>
public void RegisterLifecycleHook(IArchitectureLifecycleHook hook)
{
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using GFramework.Core.Abstractions.Utility;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="RegistryInitializationHookBaseTests" /> 记录注册结果的测试注册表。
/// </summary>
public class TestRegistry : IUtility
{
private readonly List<string> _registeredConfigs = [];
/// <summary>
/// 获取已注册配置值的只读视图,避免将测试内部使用的列表实现暴露给调用方。
/// </summary>
public IReadOnlyList<string> RegisteredConfigs => _registeredConfigs;
/// <summary>
/// 记录一次配置注册。
/// </summary>
/// <param name="config">要追加到测试结果中的配置值。</param>
public void Register(string config)
{
_registeredConfigs.Add(config);
}
}

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Architectures;
namespace GFramework.Core.Tests.Architectures;
/// <summary>
/// 为 <see cref="RegistryInitializationHookBaseTests" /> 提供的注册表初始化钩子测试替身。
/// </summary>
public class TestRegistryInitializationHook : RegistryInitializationHookBase<TestRegistry, string>
{
/// <summary>
/// 使用给定配置集合和目标阶段创建测试钩子。
/// </summary>
/// <param name="configs">测试期间要注册到目标注册表的配置值。</param>
/// <param name="targetPhase">触发注册行为的架构阶段。</param>
public TestRegistryInitializationHook(
IEnumerable<string> configs,
ArchitecturePhase targetPhase = ArchitecturePhase.AfterSystemInit)
: base(configs, targetPhase)
{
}
/// <summary>
/// 将当前配置值写入测试注册表。
/// </summary>
/// <param name="registry">要接收配置值的测试注册表。</param>
/// <param name="config">当前遍历到的配置值。</param>
protected override void RegisterConfig(TestRegistry registry, string config)
{
registry.Register(config);
}
}

View File

@ -405,7 +405,7 @@ public class CommandCoroutineExtensionsTests
CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
null,
_ => { },
-1.0f));
}
@ -421,7 +421,7 @@ public class CommandCoroutineExtensionsTests
// 设置上下文服务以返回null事件总线
contextAware._mockContext
.Setup(ctx => ctx.GetService<IEventBus>())
.Returns((IEventBus?)null);
.Returns((IEventBus)null!);
// 创建协程
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(

View File

@ -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;
[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
@ -138,4 +142,4 @@ namespace GFramework.Core.Tests.Coroutine
public string Data { get; set; } = string.Empty;
}
}
}
}

View File

@ -0,0 +1,6 @@
namespace GFramework.Core.Tests.Events;
/// <summary>
/// 表示不携带任何负载的空测试事件。
/// </summary>
public sealed class EmptyEvent;

View File

@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Events;
using Moq;
@ -303,6 +304,7 @@ public class EventListenerScopeTests
/// <summary>
/// 测试用的结构体事件
/// </summary>
[StructLayout(LayoutKind.Auto)]
private struct StructEvent
{
public int Id { get; set; }
@ -318,4 +320,4 @@ public class EventListenerScopeTests
using var scope = new EventListenerScope<TestEvent>(eventBusMock.Object);
// 作用域内部不验证
}
}
}

View File

@ -1,8 +1,12 @@
namespace GFramework.Core.Tests.Events;
/// <summary>
/// 表示包含整型载荷的测试事件。
/// </summary>
public sealed class TestEvent
{
/// <summary>
/// 获取初始化阶段写入的接收值。
/// </summary>
public int ReceivedValue { get; init; }
}
public sealed class EmptyEvent;

View File

@ -419,44 +419,3 @@ public class LoggerTests
Assert.That(fatalLogger.Logs[0].Level, Is.EqualTo(LogLevel.Fatal));
}
}
/// <summary>
/// 测试用的日志记录器实现类继承自AbstractLogger
/// </summary>
public sealed class TestLogger : AbstractLogger
{
private readonly List<LogEntry> _logs = new();
/// <summary>
/// 初始化TestLogger的新实例
/// </summary>
/// <param name="name">日志记录器的名称默认为null</param>
/// <param name="minLevel">最小日志级别默认为LogLevel.Info</param>
public TestLogger(string? name = null, LogLevel minLevel = LogLevel.Info) : base(name, minLevel)
{
}
/// <summary>
/// 获取按写入顺序保存的日志条目只读视图
/// </summary>
public IReadOnlyList<LogEntry> Logs => _logs;
/// <summary>
/// 将日志信息写入内部存储
/// </summary>
/// <param name="level">日志级别</param>
/// <param name="message">日志消息</param>
/// <param name="exception">相关异常(可选)</param>
protected override void Write(LogLevel level, string message, Exception? exception)
{
_logs.Add(new LogEntry(level, message, exception));
}
/// <summary>
/// 表示单个日志条目的记录类型
/// </summary>
/// <param name="Level">日志级别</param>
/// <param name="Message">日志消息</param>
/// <param name="Exception">相关异常(可选)</param>
public sealed record LogEntry(LogLevel Level, string Message, Exception? Exception);
}

View File

@ -307,8 +307,9 @@ public class LoggingConfigurationTests
}}";
var config = LoggingConfigurationLoader.LoadFromJsonString(json);
Assert.That(config.Appenders[0].Filter, Is.Not.Null);
Assert.That(config.Appenders[0].Filter.Type, Is.EqualTo("Namespace"));
var filter = config.Appenders[0].Filter;
Assert.That(filter, Is.Not.Null);
Assert.That(filter!.Type, Is.EqualTo("Namespace"));
}
finally
{

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Threading;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
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" /> 的新实例。
/// </summary>
/// <param name="name">日志记录器的名称;未指定时沿用基类默认行为。</param>
/// <param name="minLevel">允许写入的最小日志级别。</param>
public TestLogger(string? name = null, LogLevel minLevel = LogLevel.Info) : base(name, minLevel)
{
}
/// <summary>
/// 获取按写入顺序保存的日志条目快照。
/// </summary>
public IReadOnlyList<LogEntry> Logs
{
get
{
lock (_sync)
{
return _logs.ToArray();
}
}
}
/// <summary>
/// 将日志信息追加到内存列表,供断言读取。
/// </summary>
/// <param name="level">日志级别。</param>
/// <param name="message">日志消息。</param>
/// <param name="exception">相关异常;没有异常时为 <see langword="null" />。</param>
protected override void Write(LogLevel level, string message, Exception? exception)
{
lock (_sync)
{
_logs.Add(new LogEntry(level, message, exception));
}
}
/// <summary>
/// 表示单个日志条目的不可变快照。
/// </summary>
/// <param name="Level">日志级别。</param>
/// <param name="Message">日志消息。</param>
/// <param name="Exception">相关异常;没有异常时为 <see langword="null" />。</param>
public sealed record LogEntry(LogLevel Level, string Message, Exception? Exception);
}

View File

@ -1,58 +1,8 @@
using System.IO;
using GFramework.Core.Abstractions.Resource;
using GFramework.Core.Resource;
using NUnit.Framework;
namespace GFramework.Core.Tests.Resource;
/// <summary>
/// 测试用的简单资源类
/// </summary>
public class TestResource
{
public string Content { get; set; } = string.Empty;
public bool IsDisposed { get; set; }
}
/// <summary>
/// 测试用的资源加载器
/// </summary>
public class TestResourceLoader : IResourceLoader<TestResource>
{
private readonly Dictionary<string, string> _resourceData = new();
public TestResource Load(string path)
{
if (_resourceData.TryGetValue(path, out var content))
{
return new TestResource { Content = content };
}
throw new FileNotFoundException($"Resource not found: {path}");
}
public async Task<TestResource> LoadAsync(string path)
{
await Task.Delay(10).ConfigureAwait(false); // 模拟异步加载
return Load(path);
}
public void Unload(TestResource resource)
{
resource.IsDisposed = true;
}
public bool CanLoad(string path)
{
return _resourceData.ContainsKey(path);
}
public void AddTestData(string path, string content)
{
_resourceData[path] = content;
}
}
/// <summary>
/// ResourceManager 功能测试类
/// </summary>

View File

@ -0,0 +1,17 @@
namespace GFramework.Core.Tests.Resource;
/// <summary>
/// 表示 ResourceManager 测试使用的简单资源对象。
/// </summary>
public class TestResource
{
/// <summary>
/// 获取或设置资源内容。
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// 获取或设置一个值,指示资源是否已经被测试加载器标记为已卸载。
/// </summary>
public bool IsDisposed { get; set; }
}

View File

@ -0,0 +1,88 @@
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>
public class TestResourceLoader : IResourceLoader<TestResource>
{
private readonly Dictionary<string, string> _resourceData = new(StringComparer.Ordinal);
/// <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 };
}
throw new FileNotFoundException($"Resource not found: {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)
{
return Task.FromResult(Load(path));
}
/// <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;
}
/// <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);
}
/// <summary>
/// 向测试加载器注册一条可返回的资源数据。
/// </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;
}
}

View File

@ -1,3 +1,4 @@
using System;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums;
using GFramework.Core.Abstractions.Utility;
@ -33,12 +34,30 @@ public abstract class RegistryInitializationHookBase<TRegistry, TConfig> : IArch
/// </summary>
/// <param name="phase">当前的架构阶段</param>
/// <param name="architecture">相关的架构实例</param>
/// <exception cref="ArgumentNullException"><paramref name="architecture" /> 为 <see langword="null" />。</exception>
/// <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)
{
@ -52,4 +71,4 @@ public abstract class RegistryInitializationHookBase<TRegistry, TConfig> : IArch
/// <param name="registry">注册表实例</param>
/// <param name="config">配置项</param>
protected abstract void RegisterConfig(TRegistry registry, TConfig config);
}
}

View File

@ -0,0 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 名称排序上应先于 Zeta 处理器执行的通知处理器。
/// </summary>
internal sealed class AlphaDeterministicNotificationHandler : INotificationHandler<DeterministicOrderNotification>
{
/// <summary>
/// 记录当前处理器已执行。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
{
DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(AlphaDeterministicNotificationHandler));
return ValueTask.CompletedTask;
}
}

View File

@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Threading;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Logging;
using GFramework.Cqrs.Tests.Logging;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 为 CQRS 注册测试捕获真实启动路径中创建的日志记录器。
/// </summary>
/// <remarks>
/// 处理器注册入口会分别为测试运行时、容器和注册器创建日志器。
/// 该提供程序统一保留这些测试日志器,以便断言警告是否经由公开入口真正发出。
/// 并发创建日志器时会通过内部锁串行化,<see cref="Loggers" /> 每次返回快照,避免调用方观察到可变集合。
/// </remarks>
internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider
{
private readonly List<TestLogger> _loggers = [];
private LogLevel _minLevel;
private readonly Lock _sync = new();
/// <summary>
/// 使用指定的最小日志级别初始化一个新的捕获型日志工厂提供程序。
/// </summary>
/// <param name="minLevel">要应用到新建测试日志器的最小日志级别。</param>
public CapturingLoggerFactoryProvider(LogLevel minLevel = LogLevel.Info)
{
_minLevel = minLevel;
}
/// <summary>
/// 获取通过当前提供程序创建的全部测试日志器快照。
/// </summary>
public IReadOnlyList<TestLogger> Loggers
{
get
{
lock (_sync)
{
return _loggers.ToArray();
}
}
}
/// <summary>
/// 获取或设置新建测试日志器的最小日志级别。
/// </summary>
public LogLevel MinLevel
{
get
{
lock (_sync)
{
return _minLevel;
}
}
set
{
lock (_sync)
{
_minLevel = value;
}
}
}
/// <summary>
/// 创建一个测试日志器并将其纳入捕获集合。
/// </summary>
/// <param name="name">日志记录器名称。</param>
/// <returns>用于后续断言的测试日志器。</returns>
public ILogger CreateLogger(string name)
{
lock (_sync)
{
var logger = new TestLogger(name, _minLevel);
_loggers.Add(logger);
return logger;
}
}
}

View File

@ -224,124 +224,8 @@ internal sealed class CqrsDispatcherCacheTests
/// </summary>
private static async Task DrainAsync<T>(IAsyncEnumerable<T> stream)
{
await foreach (var _ in stream)
await foreach (var _ in stream.ConfigureAwait(false))
{
}
}
}
/// <summary>
/// 用于验证 request 服务类型缓存的测试请求。
/// </summary>
internal sealed record DispatcherCacheRequest : IRequest<int>;
/// <summary>
/// 用于验证 notification 服务类型缓存的测试通知。
/// </summary>
internal sealed record DispatcherCacheNotification : INotification;
/// <summary>
/// 用于验证 stream 服务类型缓存的测试请求。
/// </summary>
internal sealed record DispatcherCacheStreamRequest : IStreamRequest<int>;
/// <summary>
/// 用于验证 pipeline invoker 缓存的测试请求。
/// </summary>
internal sealed record DispatcherPipelineCacheRequest : IRequest<int>;
/// <summary>
/// 用于验证按响应类型分层 request invoker 缓存的测试请求。
/// </summary>
internal sealed record DispatcherStringCacheRequest : IRequest<string>;
/// <summary>
/// 处理 <see cref="DispatcherCacheRequest" />。
/// </summary>
internal sealed class DispatcherCacheRequestHandler : IRequestHandler<DispatcherCacheRequest, int>
{
/// <summary>
/// 返回固定结果,供缓存测试验证 dispatcher 请求路径。
/// </summary>
public ValueTask<int> Handle(DispatcherCacheRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(1);
}
}
/// <summary>
/// 处理 <see cref="DispatcherCacheNotification" />。
/// </summary>
internal sealed class DispatcherCacheNotificationHandler : INotificationHandler<DispatcherCacheNotification>
{
/// <summary>
/// 消费通知,不执行额外副作用。
/// </summary>
public ValueTask Handle(DispatcherCacheNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 处理 <see cref="DispatcherCacheStreamRequest" />。
/// </summary>
internal sealed class DispatcherCacheStreamHandler : IStreamRequestHandler<DispatcherCacheStreamRequest, int>
{
/// <summary>
/// 返回一个最小流,供缓存测试命中 stream 分发路径。
/// </summary>
public async IAsyncEnumerable<int> Handle(
DispatcherCacheStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
yield return 1;
await Task.CompletedTask;
}
}
/// <summary>
/// 处理 <see cref="DispatcherPipelineCacheRequest" />。
/// </summary>
internal sealed class DispatcherPipelineCacheRequestHandler : IRequestHandler<DispatcherPipelineCacheRequest, int>
{
/// <summary>
/// 返回固定结果,供 pipeline 缓存测试使用。
/// </summary>
public ValueTask<int> Handle(DispatcherPipelineCacheRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(2);
}
}
/// <summary>
/// 处理 <see cref="DispatcherStringCacheRequest" />。
/// </summary>
internal sealed class DispatcherStringCacheRequestHandler : IRequestHandler<DispatcherStringCacheRequest, string>
{
/// <summary>
/// 返回固定字符串,供按响应类型缓存测试验证 string 路径。
/// </summary>
public ValueTask<string> Handle(DispatcherStringCacheRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult("dispatcher-cache");
}
}
/// <summary>
/// 为 <see cref="DispatcherPipelineCacheRequest" /> 提供最小 pipeline 行为,
/// 用于命中 dispatcher 的 pipeline invoker 缓存分支。
/// </summary>
internal sealed class DispatcherPipelineCacheBehavior : IPipelineBehavior<DispatcherPipelineCacheRequest, int>
{
/// <summary>
/// 直接转发到下一个处理器。
/// </summary>
public ValueTask<int> Handle(
DispatcherPipelineCacheRequest request,
MessageHandlerDelegate<DispatcherPipelineCacheRequest, int> next,
CancellationToken cancellationToken)
{
return next(request, cancellationToken);
}
}

View File

@ -216,6 +216,27 @@ internal sealed class CqrsHandlerRegistrarTests
}
}
/// <summary>
/// 验证捕获型日志工厂在更新最小日志级别后,会将新值应用到后续创建的日志器。
/// </summary>
[Test]
public void CapturingLoggerFactoryProvider_Should_Apply_Updated_MinLevel_To_Subsequent_Loggers()
{
var provider = new CapturingLoggerFactoryProvider(LogLevel.Warning);
var warningLogger = (TestLogger)provider.CreateLogger("warning");
provider.MinLevel = LogLevel.Debug;
var debugLogger = (TestLogger)provider.CreateLogger("debug");
Assert.Multiple(() =>
{
Assert.That(warningLogger.IsDebugEnabled(), Is.False);
Assert.That(debugLogger.IsDebugEnabled(), Is.True);
Assert.That(provider.Loggers, Has.Count.EqualTo(2));
});
}
/// <summary>
/// 验证当生成注册器提供精确 fallback 类型名时,运行时会定向补扫剩余 handlers
/// 而不是重新枚举整个程序集的类型列表。
@ -614,230 +635,3 @@ internal sealed class CqrsHandlerRegistrarTests
.GetType("GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!;
}
}
/// <summary>
/// 记录确定性通知处理器的实际执行顺序。
/// </summary>
internal static class DeterministicNotificationHandlerState
{
/// <summary>
/// 获取当前测试中的通知处理器执行顺序。
/// </summary>
public static List<string> InvocationOrder { get; } = [];
/// <summary>
/// 重置共享的执行顺序状态。
/// </summary>
public static void Reset()
{
InvocationOrder.Clear();
}
}
/// <summary>
/// 用于验证同一通知的多个处理器是否按稳定顺序执行。
/// </summary>
internal sealed record DeterministicOrderNotification : INotification;
/// <summary>
/// 故意放在 Alpha 之前声明,用于验证注册器不会依赖源码声明顺序。
/// </summary>
internal sealed class ZetaDeterministicNotificationHandler : INotificationHandler<DeterministicOrderNotification>
{
/// <summary>
/// 记录当前处理器已执行。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
{
DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(ZetaDeterministicNotificationHandler));
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 名称排序上应先于 Zeta 处理器执行的通知处理器。
/// </summary>
internal sealed class AlphaDeterministicNotificationHandler : INotificationHandler<DeterministicOrderNotification>
{
/// <summary>
/// 记录当前处理器已执行。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
{
DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(AlphaDeterministicNotificationHandler));
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 为 CQRS 注册测试捕获真实启动路径中创建的日志记录器。
/// </summary>
/// <remarks>
/// 处理器注册入口会分别为测试运行时、容器和注册器创建日志器。
/// 该提供程序统一保留这些测试日志器,以便断言警告是否经由公开入口真正发出。
/// </remarks>
internal sealed class CapturingLoggerFactoryProvider : ILoggerFactoryProvider
{
private readonly List<TestLogger> _loggers = [];
/// <summary>
/// 使用指定的最小日志级别初始化一个新的捕获型日志工厂提供程序。
/// </summary>
/// <param name="minLevel">要应用到新建测试日志器的最小日志级别。</param>
public CapturingLoggerFactoryProvider(LogLevel minLevel = LogLevel.Info)
{
MinLevel = minLevel;
}
/// <summary>
/// 获取通过当前提供程序创建的全部测试日志器。
/// </summary>
public IReadOnlyList<TestLogger> Loggers => _loggers;
/// <summary>
/// 获取或设置新建测试日志器的最小日志级别。
/// </summary>
public LogLevel MinLevel { get; set; }
/// <summary>
/// 创建一个测试日志器并将其纳入捕获集合。
/// </summary>
/// <param name="name">日志记录器名称。</param>
/// <returns>用于后续断言的测试日志器。</returns>
public ILogger CreateLogger(string name)
{
var logger = new TestLogger(name, MinLevel);
_loggers.Add(logger);
return logger;
}
}
/// <summary>
/// 用于验证生成注册器路径的通知消息。
/// </summary>
internal sealed record GeneratedRegistryNotification : INotification;
/// <summary>
/// 由模拟的源码生成注册器显式注册的通知处理器。
/// </summary>
internal sealed class GeneratedRegistryNotificationHandler : INotificationHandler<GeneratedRegistryNotification>
{
/// <summary>
/// 处理生成注册器测试中的通知。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(GeneratedRegistryNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}
/// <summary>
/// 模拟源码生成器为某个程序集生成的 CQRS 处理器注册器。
/// </summary>
internal sealed class GeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 将测试通知处理器注册到目标服务集合。
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}
/// <summary>
/// 用于验证“生成注册器 + reflection fallback”组合路径的私有嵌套处理器容器。
/// </summary>
internal sealed class ReflectionFallbackNotificationContainer
{
/// <summary>
/// 获取仅能通过反射补扫接入的私有嵌套处理器类型。
/// </summary>
public static Type ReflectionOnlyHandlerType => typeof(ReflectionOnlyGeneratedRegistryNotificationHandler);
private sealed class ReflectionOnlyGeneratedRegistryNotificationHandler
: INotificationHandler<GeneratedRegistryNotification>
{
/// <summary>
/// 处理测试通知。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(GeneratedRegistryNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}
}
/// <summary>
/// 模拟局部生成注册器场景中,仅注册“可由生成代码直接引用”的那部分 handlers。
/// </summary>
internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 将生成路径可见的通知处理器注册到目标服务集合。
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}
/// <summary>
/// 模拟生成注册器使用私有无参构造器的场景,验证运行时仍可通过缓存工厂激活它。
/// </summary>
internal sealed class PrivateConstructorNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 初始化一个新的私有生成注册器实例。
/// </summary>
private PrivateConstructorNotificationHandlerRegistry()
{
}
/// <summary>
/// 将测试通知处理器注册到目标服务集合。
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 记录确定性通知处理器的实际执行顺序。
/// </summary>
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();
}
}

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证同一通知的多个处理器是否按稳定顺序执行。
/// </summary>
internal sealed record DeterministicOrderNotification : INotification;

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证 notification 服务类型缓存的测试通知。
/// </summary>
internal sealed record DispatcherCacheNotification : INotification;

View File

@ -0,0 +1,21 @@
using System.Threading;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 处理 <see cref="DispatcherCacheNotification" />。
/// </summary>
internal sealed class DispatcherCacheNotificationHandler : INotificationHandler<DispatcherCacheNotification>
{
/// <summary>
/// 消费通知,不执行额外副作用。
/// </summary>
/// <param name="notification">当前通知。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DispatcherCacheNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证 request 服务类型缓存的测试请求。
/// </summary>
internal sealed record DispatcherCacheRequest : IRequest<int>;

View File

@ -0,0 +1,21 @@
using System.Threading;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 处理 <see cref="DispatcherCacheRequest" />。
/// </summary>
internal sealed class DispatcherCacheRequestHandler : IRequestHandler<DispatcherCacheRequest, int>
{
/// <summary>
/// 返回固定结果,供缓存测试验证 dispatcher 请求路径。
/// </summary>
/// <param name="request">当前请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>固定整数结果。</returns>
public ValueTask<int> Handle(DispatcherCacheRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(1);
}
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 处理 <see cref="DispatcherCacheStreamRequest" />。
/// </summary>
internal sealed class DispatcherCacheStreamHandler : IStreamRequestHandler<DispatcherCacheStreamRequest, int>
{
/// <summary>
/// 返回一个最小流,供缓存测试命中 stream 分发路径。
/// </summary>
/// <param name="request">当前流请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>包含一个固定元素的异步流。</returns>
public async IAsyncEnumerable<int> Handle(
DispatcherCacheStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
yield return 1;
await Task.CompletedTask.ConfigureAwait(false);
}
}

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证 stream 服务类型缓存的测试请求。
/// </summary>
internal sealed record DispatcherCacheStreamRequest : IStreamRequest<int>;

View File

@ -0,0 +1,26 @@
using System.Threading;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 为 <see cref="DispatcherPipelineCacheRequest" /> 提供最小 pipeline 行为,
/// 用于命中 dispatcher 的 pipeline invoker 缓存分支。
/// </summary>
internal sealed class DispatcherPipelineCacheBehavior : IPipelineBehavior<DispatcherPipelineCacheRequest, int>
{
/// <summary>
/// 直接转发到下一个处理器。
/// </summary>
/// <param name="request">当前请求。</param>
/// <param name="next">下一个处理器委托。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>下游处理器结果。</returns>
public ValueTask<int> Handle(
DispatcherPipelineCacheRequest request,
MessageHandlerDelegate<DispatcherPipelineCacheRequest, int> next,
CancellationToken cancellationToken)
{
return next(request, cancellationToken);
}
}

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证 pipeline invoker 缓存的测试请求。
/// </summary>
internal sealed record DispatcherPipelineCacheRequest : IRequest<int>;

View File

@ -0,0 +1,21 @@
using System.Threading;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 处理 <see cref="DispatcherPipelineCacheRequest" />。
/// </summary>
internal sealed class DispatcherPipelineCacheRequestHandler : IRequestHandler<DispatcherPipelineCacheRequest, int>
{
/// <summary>
/// 返回固定结果,供 pipeline 缓存测试使用。
/// </summary>
/// <param name="request">当前请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>固定整数结果。</returns>
public ValueTask<int> Handle(DispatcherPipelineCacheRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(2);
}
}

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证按响应类型分层 request invoker 缓存的测试请求。
/// </summary>
internal sealed record DispatcherStringCacheRequest : IRequest<string>;

View File

@ -0,0 +1,21 @@
using System.Threading;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 处理 <see cref="DispatcherStringCacheRequest" />。
/// </summary>
internal sealed class DispatcherStringCacheRequestHandler : IRequestHandler<DispatcherStringCacheRequest, string>
{
/// <summary>
/// 返回固定字符串,供按响应类型缓存测试验证 string 路径。
/// </summary>
/// <param name="request">当前请求。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>固定字符串结果。</returns>
public ValueTask<string> Handle(DispatcherStringCacheRequest request, CancellationToken cancellationToken)
{
return ValueTask.FromResult("dispatcher-cache");
}
}

View File

@ -0,0 +1,30 @@
using System;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 模拟源码生成器为某个程序集生成的 CQRS 处理器注册器。
/// </summary>
internal sealed class GeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 将测试通知处理器注册到目标服务集合。
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}

View File

@ -0,0 +1,8 @@
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证生成注册器路径的通知消息。
/// </summary>
internal sealed record GeneratedRegistryNotification : INotification;

View File

@ -0,0 +1,22 @@
using System.Threading;
using System.Threading.Tasks;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 由模拟的源码生成注册器显式注册的通知处理器。
/// </summary>
internal sealed class GeneratedRegistryNotificationHandler : INotificationHandler<GeneratedRegistryNotification>
{
/// <summary>
/// 处理生成注册器测试中的通知。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(GeneratedRegistryNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}

View File

@ -0,0 +1,33 @@
using System;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 模拟局部生成注册器场景中,仅注册“可由生成代码直接引用”的那部分 handlers。
/// </summary>
internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 将生成路径可见的通知处理器注册到目标服务集合。
/// </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);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}

View File

@ -0,0 +1,37 @@
using System;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 模拟生成注册器使用私有无参构造器的场景,验证运行时仍可通过缓存工厂激活它。
/// </summary>
internal sealed class PrivateConstructorNotificationHandlerRegistry : ICqrsHandlerRegistry
{
/// <summary>
/// 初始化一个新的私有生成注册器实例。
/// </summary>
private PrivateConstructorNotificationHandlerRegistry()
{
}
/// <summary>
/// 将测试通知处理器注册到目标服务集合。
/// </summary>
/// <param name="services">承载处理器映射的服务集合。</param>
/// <param name="logger">用于记录注册诊断的日志器。</param>
public void Register(IServiceCollection services, ILogger logger)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(logger);
services.AddTransient(
typeof(INotificationHandler<GeneratedRegistryNotification>),
typeof(GeneratedRegistryNotificationHandler));
logger.Debug(
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler<GeneratedRegistryNotification>).FullName}.");
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 用于验证“生成注册器 + reflection fallback”组合路径的私有嵌套处理器容器。
/// </summary>
internal sealed class ReflectionFallbackNotificationContainer
{
/// <summary>
/// 获取仅能通过反射补扫接入的私有嵌套处理器类型。
/// </summary>
public static Type ReflectionOnlyHandlerType => typeof(ReflectionOnlyGeneratedRegistryNotificationHandler);
private sealed class ReflectionOnlyGeneratedRegistryNotificationHandler
: INotificationHandler<GeneratedRegistryNotification>
{
/// <summary>
/// 处理测试通知。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(GeneratedRegistryNotification notification, CancellationToken cancellationToken)
{
return ValueTask.CompletedTask;
}
}
}

View File

@ -0,0 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 故意放在 Alpha 之前声明,用于验证注册器不会依赖源码声明顺序。
/// </summary>
internal sealed class ZetaDeterministicNotificationHandler : INotificationHandler<DeterministicOrderNotification>
{
/// <summary>
/// 记录当前处理器已执行。
/// </summary>
/// <param name="notification">通知实例。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>已完成任务。</returns>
public ValueTask Handle(DeterministicOrderNotification notification, CancellationToken cancellationToken)
{
DeterministicNotificationHandlerState.InvocationOrder.Add(nameof(ZetaDeterministicNotificationHandler));
return ValueTask.CompletedTask;
}
}

View File

@ -0,0 +1,55 @@
# Analyzer Warning Reduction 历史归档RP-083 ~ RP-088
## 范围说明
- 归档区间:`RP-083``RP-088`
- 归档原因active trace 已累计多个已完成阶段,不再适合作为默认恢复入口
- 当前活跃恢复点:返回 `ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md`
## RP-088
- 阶段:收敛 `PR #300` 的 open review threads 与 failed-test follow-up
- 主结论:
- 核对 `TestArchitectureContext*``RegistryInitializationHookBase``TestResourceLoader``CapturingLoggerFactoryProvider``PartialGeneratedNotificationHandlerRegistry` 等 review 位点
- 新增 `TestArchitectureContextBehaviorTests.cs`,覆盖共享事件总线、旧入口失败契约与 `RegisterLifecycleHook` 接口行为
- 受影响项目验证通过,`GFramework.Cqrs.Tests` 仍保留既有 `Mediator/*` warning 基线
## RP-087
- 阶段:按 `$gframework-batch-boot 50` 并行收敛 `Core.Tests` / `Cqrs.Tests` 低风险切片
- 主结论:
- 建立仓库根 non-incremental warning 基线后,并行消化 `Core.Tests``Cqrs.Tests` 的低风险 warning
- 仓库根 warning 从 `288` 下降到 `236`
- 剩余热点开始集中到 `Mediator/*``YamlConfigSchemaValidator*`
## RP-086
- 阶段:收敛 `PR #298` 的 CodeRabbit nitpick follow-up
- 主结论:
- 修复测试辅助类型的可维护性 nitpick
- `GFramework.Core.Tests` 定向验证通过
- 剩余 warning 仍集中在既有热点文件
## RP-085
- 阶段:按 `$gframework-batch-boot 100` 并行消化 `GFramework.Core.Tests` 低风险 `MA0048`
- 主结论:
- 四波次并行拆分 `GFramework.Core.Tests` 测试辅助类型
- 仓库根 warning 从 `353` 下降到 `288`
- active footprint 接近阈值后主动收口
## RP-084
- 阶段:收敛 `PR #297` 的 CodeRabbit follow-up
- 主结论:
- 校正 `YamlConfigLoader` 取消语义与若干 XML 文档问题
- 新增定向回归测试覆盖取消异常路径
- 相关构建与测试全部通过
## RP-083
- 阶段:修复 `YamlConfigLoader` 单文件 warning并拆分 `MicrosoftDiContainerTests` 的辅助类型
- 主结论:
- 从仓库根基线出发完成单文件 warning 修复与两组测试辅助类型拆分
- 仓库根 warning 从 `397` 下降到 `353`
- 后续工作切入点转向 `ArchitectureContextTests.cs` / `AsyncQueryExecutorTests.cs`

View File

@ -6,57 +6,38 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-086`
- 当前阶段:`Phase 86`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-092`
- 当前阶段:`Phase 92`
- 当前焦点:
- `2026-04-27` 已按 `$gframework-batch-boot 100` 连续执行多波 `MA0048` 小切片,当前以 `GFramework.Core.Tests` 的测试辅助类型拆分为主
- `2026-04-27` 已按 `$gframework-pr-review` 收敛 `PR #298` 的有效 nitpick修复测试辅助类型的只读暴露、线程安全、空安全与文档一致性问题
- 本轮已完成 `ArchitectureContextTests``AsyncQueryExecutorTests``CommandExecutorTests``StateTests``StateMachineTests``StateMachineSystemTests``ArchitectureModulesBehaviorTests``ArchitectureAdditionalCqrsHandlersTests``QueryCoroutineExtensionsTests``ObjectPoolTests``AbstractContextUtilityTests` 等低风险单文件切片
- 当前仓库根权威基线已从 `353 Warning(s)` / `279` 个唯一位点下降到 `288 Warning(s)` / `214` 个唯一位点
- 当前分支下一波更适合转向 `GameContextTests.cs``ArchitectureServicesTests.cs``RegistryInitializationHookBaseTests.cs` 这类仍在 `GFramework.Core.Tests` 内、但已混入 `CS8766` / `MA0016` 的小型混合切片
- `2026-04-28` 复核 `PR #300` 最新 open threads代码类线程已与当前工作树对齐仅剩 `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md` 的文件计数与验证口径漂移仍然成立
- 已将 tracking 文档修正为与 `6cc87a9...HEAD` 的实际变更规模一致,并与 trace 中记录的 `dotnet build`、定向 `dotnet test``git diff --check` 验证口径保持一致
- `dotnet format --verify-no-changes``GFramework.Core.Tests` 既有 `FINALNEWLINE``CHARSET``WHITESPACE` 基线仍保持独立,不与当前 `ai-plan` 同步修复混提
## 当前活跃事实
- 当前 `origin/main` 基线提交为 `7cfdd2c``2026-04-27T16:59:57+08:00`)。
- 当前 `origin/main` 基线提交为 `6cc87a9``2026-04-27T20:28:50+08:00`)。
- 当前直接验证结果:
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 最新结果:成功;`28 Warning(s)``0 Error(s)`;当前 warning 来自 `GameContextTests.cs``ArchitectureServicesTests.cs``RegistryInitializationHookBaseTests.cs` 等既有热点
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build`
- 最新结果:成功;`1610` 通过、`0` 失败
- `dotnet clean`
- 最新结果:成功;已刷新仓库根 non-incremental 基线
- `dotnet build`
- 最新结果:成功;`288 Warning(s)``0 Error(s)`,唯一位点 `214`
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
- 最新结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
- 最新结果:成功;`1` 通过、`0` 失败
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`
- 最新结果:成功;`1` 通过、`0` 失败
- `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigLoader.cs GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs GFramework.Core.Tests/Ioc/IMixedService.cs GFramework.Core.Tests/Ioc/IPrioritizedService.cs GFramework.Core.Tests/Ioc/PrioritizedService.cs GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs`
- 最新结果:成功;本次 PR follow-up 改动文件无需额外格式化
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~ArchitectureServicesTests|FullyQualifiedName~ContextAwareServiceExtensionsTests|FullyQualifiedName~TestArchitectureContextBehaviorTests|FullyQualifiedName~RegistryInitializationHookBaseTests|FullyQualifiedName~ArchitectureContextTests"`
- 最新结果:成功;`67` 通过、`0` 失败
- 当前批次摘要:
- 本轮通过多批并行 worker 共完成 `20+``GFramework.Core.Tests` 文件的测试辅助类型拆分,集中消化纯 `MA0048` warning 热点
- 本轮停止时共享工作树共有 `61` 个变更条目,仍低于 `$gframework-batch-boot 100` 的文件停止线
- 本轮仓库根权威 warning 已从开始时的 `353` 下降到 `288`,且 `GFramework.Core.Tests` 受影响项目的 Release 构建已恢复到 `0 Warning(s)` / `0 Error(s)`
- 当前建议保留到下一波次的候选:
- `GFramework.Core.Tests/Architectures/GameContextTests.cs``4``CS8766``2``MA0048`
- `GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs``4``CS8766``1``MA0048`
- `GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs``1``MA0016``5``MA0048`
- `GFramework.Game/Config/YamlConfigSchemaValidator.cs``YamlConfigSchemaValidator.ObjectKeywords.cs` 的高耦合 warning 热点
- 当前分支相对 `6cc87a9...HEAD` 包含 `18` 个已修改文件与 `38` 个新增文件(合计 `56` 个变更文件),分别位于 `GFramework.Core.Tests``GFramework.Cqrs.Tests``GFramework.Core``.agents/skills/gframework-pr-review/``ai-plan/public/analyzer-warning-reduction`
- 本轮没有触碰 `Mediator/*``YamlConfigSchemaValidator*``GFramework.Core.Tests` 的整项目格式基线波次
## 当前风险
- `GFramework.Cqrs.Tests/Mediator/*` 仍有 `47` / `44` / `34` 个唯一 warning 位点,属于高 changed-file 风险的 `MA0048` 大波次
- 缓解措施:优先继续处理 `6-7` 个 warning 的小文件切片,避免一次性推高文件数
- `GameContextTests.cs`、`ArchitectureServicesTests.cs` 这类混合 `CS8766` / `MA0048` 文件不再适合继续用“纯拆分”模式批量下发
- 缓解措施:下一波由主线程先局部修正可空签名,再决定是否继续并行拆分
- `YamlConfigSchemaValidator*` 仍然聚集多类高耦合 warning
- 缓解措施:继续把它们留在独立波次,不与测试项目的低风险拆分混提
- GitHub PR 上的 open threads 可能仍显示为未关闭,因为当前只同步了 `ai-plan` 文档,尚未推送新的 head 供审查机器人重新折叠线程。
- 缓解措施:推送本次 `ai-plan` 同步提交后重新执行 `$gframework-pr-review`,以最新 head 再核对 thread 状态。
- `dotnet format GFramework.Core.Tests/GFramework.Core.Tests.csproj --verify-no-changes` 当前会命中项目内大量历史格式诊断。
- 缓解措施:本轮只记录为现存基线,不把 `PR #300` 的 review follow-up 扩展成整项目格式清理
- `GFramework.Game/Config/YamlConfigSchemaValidator*` 仍然是仓库根 warning 热点,但与本轮 review 修复无交集
- 缓解措施:继续保持为独立高耦合波次
## 活跃文档
- 当前轮次归档:
- [analyzer-warning-reduction-history-rp083-rp088.md](../archive/traces/analyzer-warning-reduction-history-rp083-rp088.md)
- [analyzer-warning-reduction-history-rp074-rp078.md](../archive/todos/analyzer-warning-reduction-history-rp074-rp078.md)
- [analyzer-warning-reduction-history-rp042-rp048.md](../archive/todos/analyzer-warning-reduction-history-rp042-rp048.md)
- 历史跟踪归档:
@ -72,11 +53,12 @@
## 验证说明
- 权威验证结果统一维护在“当前活跃事实”。
- `GFramework.Core.Tests` 项目级 Release 构建已在本轮清零,但仓库根 non-incremental 构建仍保留大量既有 warning。
- warning reduction 的仓库级真值只以同轮 `dotnet clean` 后的 `dotnet build` 为准。
- `GFramework.Core.Tests` 的当前受影响项目 Release 构建已清零,并通过对应定向测试回归。
- `git diff --check` 结果为空,说明本轮新增改动没有引入新的尾随空格或冲突标记。
- warning reduction 的仓库级真值以同轮 `dotnet build`、定向 `dotnet test``git diff --check` 为准,并与 trace 中的验证里程碑保持一致。
## 下一步建议
1. 提交本轮多批 `MA0048` warning reduction `ai-plan` 同步。
2. 下一波由主线程先处理 `GameContextTests.cs` / `ArchitectureServicesTests.cs``CS8766`,再决定是否继续拆分剩余 `MA0048`
3. 继续将 `YamlConfigSchemaValidator*``GFramework.Cqrs.Tests/Mediator/*` 作为独立高风险波次处理
1. 提交本轮 `PR #300` nitpick follow-up、技能规则更新`ai-plan` 同步。
2. 推送后重新执行 `$gframework-pr-review`,确认 `ai-plan` 相关 thread 是否随最新 head 自动收口
3. 若要清理 `dotnet format` 基线,另开 `GFramework.Core.Tests` 格式治理切片,不与当前 PR review 修复混提

View File

@ -1,147 +1,59 @@
# Analyzer Warning Reduction 追踪
## 2026-04-27 — RP-086
## 2026-04-28 — RP-092
### 阶段:收敛 PR #298 的 CodeRabbit nitpick follow-up
### 阶段:复核 `PR #300` 的 open threads并只修正当前分支仍然成立的 `ai-plan` 漂移
- 触发背景:
- 用户再次执行 `$gframework-pr-review` 后,要求按 `PR #298` 的 nitpick comments 收敛仍然适用的问题
- 复核 PR 真值后确认当前分支无 failed checks、无 open review threads但仍有一批测试辅助类型的可维护性 nitpick 值得本地落地
- 用户要求恢复当前 `$gframework-pr-review` 任务,继续以 PR head 上的开放线程为准做 triage
- 主线程实施:
- 校验 `TestStateMachineSystemV5``ComplexQuery``TrackingPipelineBehavior``TestEnvironment``TestContextUtilityV1/V2` 等改动后,分别修复可变内部状态暴露、`_context!` 空抑制、静态计数器非原子递增、`new Register(...)` 测试辅助入口和生命周期标记公开 setter 问题
- 统一更新 `TestQueryV2``TestCommandWithResultV2``TestAsyncQueryInput``TestAsyncQueryResult*` 的 XML 文档,使 `init` 属性语义与文档一致
- 将三倍结果属性从 `DoubleValue` 更名为 `TripleValue`,同步更新 `TestAsyncComplexQuery*` 与相关断言,避免名称与 `* 3` 的行为不一致
- 精简 active tracking移除重复的 `GFramework.Core.Tests` Release build 记录,并把该项目的当前真值修正为 `28 Warning(s)`
- 重新读取 `fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` 的 latest head open threads
- 逐条对照本地文件后确认:`TestArchitectureContextBehaviorTests``TestArchitectureWithRegistry``TestResourceLoader``PartialGeneratedNotificationHandlerRegistry` 相关 CodeRabbit 线程在当前工作树上都已匹配修复,仅线程状态尚未随新 head 折叠
- 继续核对 `RegistryInitializationHookBaseTests.OnPhase_Should_Not_Throw_When_Registry_Not_Found`,确认当前实现 `RegistryInitializationHookBase.OnPhase` 已在缺少注册表时保持 no-op定向回归测试通过
- 修正 `analyzer-warning-reduction-tracking.md` 中仍然成立的两处漂移:
- 将文件计数更新为相对 `6cc87a9...HEAD` 的实际规模:`18` 个已修改文件、`38` 个新增文件、合计 `56` 个变更文件
- 将验证口径统一为 trace 已记录的 `dotnet build`、定向 `dotnet test``git diff --check`
- 验证里程碑:
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 结果:成功;`28 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build`
- 结果:成功;`1610` 通过、`0` 失败
- 当前结论:
- `PR #298` 中仍然适用的低风险 nitpick 已完成收敛,且没有为当前 touched files 引入新的构建 warning 或测试回归
- `GFramework.Core.Tests` 的剩余 warning 仍集中在 `GameContextTests.cs``ArchitectureServicesTests.cs``RegistryInitializationHookBaseTests.cs` 等既有热点,不属于本轮 nitpick follow-up 新引入问题
- 下一步:
1. 提交本轮 `PR #298` nitpick follow-up 与 `ai-plan` 同步。
2. 回到 `GameContextTests.cs` / `ArchitectureServicesTests.cs``CS8766` warning reduction 主线。
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~RegistryInitializationHookBaseTests.OnPhase_Should_Not_Throw_When_Registry_Not_Found|FullyQualifiedName~TestArchitectureContextBehaviorTests"`
- 结果:成功;`10` 通过、`0` 失败
- `git diff --check`
- 结果:成功;无新增 whitespace / conflict-marker 问题
## 2026-04-27 — RP-085
## 2026-04-28 — RP-091
### 阶段:`$gframework-batch-boot 100` 并行消化 `GFramework.Core.Tests` 低风险 `MA0048`
### 阶段:收口 `PR #300` 的共享测试基础设施 nitpick并升级 PR-review triage 规则
- 触发背景:
- 用户要求以仓库根 non-incremental 构建 warning 为准,并在上下文可控前提下把小切片分派给多个 subagent 并行处理
- 本轮开始时,当前分支与 `origin/main@7cfdd2c` 无提交差异,适合从纯 `MA0048` 单文件切片起步
- 用户追问 `TestArchitectureContext` / `TestArchitectureContextV3` 的共享基础设施 nitpick 是否已经处理完成
- 同时要求把“本地验证后仍然成立的 nitpick 不能默认降级为可选项”写入 `AGENTS.md``$gframework-pr-review`
- 主线程实施:
- 执行权威基线:`dotnet clean` + 仓库根 `dotnet build`
- 初始结果:`353 Warning(s)`、唯一位点 `279`
- 分四波次并行下发 `GFramework.Core.Tests` 小切片,累计完成 `20+` 个文件的测试辅助类型拆分
- 主线程持续复核共享工作树、处理并发编译阻断,并在每一轮后复跑 `GFramework.Core.Tests` Release 构建
- 在工作树达到 `61` 个变更条目时主动停止扩批,保留对 `100` 文件停止线的充分余量
- 代表性已落地切片:
- `ArchitectureContextTests.cs`
- `AsyncQueryExecutorTests.cs`
- `CommandExecutorTests.cs`
- `StateTests.cs`
- `StateMachineTests.cs`
- `StateMachineSystemTests.cs`
- `ArchitectureModulesBehaviorTests.cs`
- `ArchitectureAdditionalCqrsHandlersTests.cs`
- `QueryCoroutineExtensionsTests.cs`
- `ObjectPoolTests.cs`
- `AbstractContextUtilityTests.cs`
- `EnvironmentTests.cs`
- `EventBusTests.cs`
- `ContextAwareTests.cs`
- 新增 `TestArchitectureContextBase`,把容器解析、共享 `EventBus` 行为,以及 legacy / CQRS 失败契约统一收敛到一处
- 将 `TestArchitectureContext``TestArchitectureContextV3` 收窄为薄包装类型,只保留各自的命名入口与 `Id` 差异
- 更新 `.agents/skills/gframework-pr-review/SKILL.md`明确要求latest-head `Nitpick comment` 一旦本地验证仍成立且指向真实漂移/回归风险,就必须作为 actionable review input 处理,而不是默认视作可选
- 验证里程碑:
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet clean`
- 结果:成功
- `dotnet build`
- 结果:成功;`288 Warning(s)``0 Error(s)`,唯一位点 `214`
- 当前结论:
- 本轮主要收益来自 `GFramework.Core.Tests` 内的纯 `MA0048` 大范围收敛
- 仓库根权威 warning 已从 `353` 降到 `288`,唯一位点从 `279` 降到 `214`
- 下一波不再适合继续盲目平铺纯拆分,因为剩余 `GFramework.Core.Tests` 热点已开始混入 `CS8766` / `MA0016`
- 下一步:
1. 提交本轮 warning reduction 与 `ai-plan` 同步。
2. 下一波优先由主线程处理 `GameContextTests.cs` / `ArchitectureServicesTests.cs` 的混合 warning。
3. 保持 `YamlConfigSchemaValidator*``GFramework.Cqrs.Tests/Mediator/*` 为独立高风险波次。
## 2026-04-27 — RP-084
### 阶段:收敛 PR #297 的 CodeRabbit follow-up
- 触发背景:
- 用户执行 `$gframework-pr-review`,要求以当前分支对应 PR 为准,提取并核对 AI review / check 信号
- `fetch_current_pr_review.py` 返回 PR `#297` 的最新 head review 中仍有 `3` 个 open threads另有 `2` 个 folded nitpick 仍然适用
- 主线程实施:
- 校验 `GFramework.Game/Config/YamlConfigLoader.cs` 后,保留 `ReadYamlAsync` 的原始取消语义,并把 `IntegerTryParseDelegate<T>` 第一个参数改为 `string?`
- 校验 `GFramework.Core.Tests/Ioc/*``Query/TestAsyncQueryWithExceptionV4.cs` 后,补齐缺失 XML 文档,让 `IPrioritizedService` 继承 `IMixedService` 复用 `Name` 契约,并补上 `<returns>` 文档
- 新增 `YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested`,用反射直接命中私有读取路径,稳定回归本次取消语义修复
- 用 `dotnet format --verify-no-changes --include ...` 清理并验证本次改动文件的格式状态
- 验证里程碑:
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
- 结果:成功;`1` 通过、`0` 失败
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`
- 结果:成功;`1` 通过、`0` 失败
- `dotnet build GFramework.Game/GFramework.Game.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 format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigLoader.cs GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs GFramework.Core.Tests/Ioc/IMixedService.cs GFramework.Core.Tests/Ioc/IPrioritizedService.cs GFramework.Core.Tests/Ioc/PrioritizedService.cs GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs`
- 结果:成功
- 当前结论:
- PR `#297` 当前仍然有效的 CodeRabbit open threads 与 folded nitpick 已在本地全部核对并收敛
- 当前恢复点完成后,分支可以回到 `ArchitectureContextTests.cs` / `AsyncQueryExecutorTests.cs` / `YamlConfigSchemaValidator*` 的 warning reduction 主线
- 下一步:
1. 提交本轮 PR review follow-up。
2. 继续执行下一波 `MA0048` 小切片,优先避免一次性进入 `Mediator*` 的高 changed-file 风险波次。
## 2026-04-27 — RP-083
### 阶段:修复 `YamlConfigLoader` 单文件 warning并拆分 `MicrosoftDiContainerTests` 的辅助类型
- 触发背景:
- 用户执行 `$gframework-batch-boot 50`,要求先拿仓库根构建 warning再按 bounded slice 分派给不同 subagent 并持续推进
- 当前分支在本轮开始时与 `origin/main@b6a9fef` 零提交差异,适合从低风险 warning slice 起步
- 主线程实施:
- 先执行 non-incremental 仓库根基线:`dotnet clean` + `dotnet build`,得到 `397 Warning(s)` / `316` 个唯一位点
- 主线程修复 `GFramework.Game/Config/YamlConfigLoader.cs``MA0051``MA0002``MA0158`
- 接受一个 worker batch`GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 末尾的 `10` 个测试辅助接口/类拆分到 `Ioc/` 同目录独立文件
- 接受第二波 worker 的已落地结果:将 `GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs` 末尾的 `7` 个测试辅助类型拆分到 `Query/` 同目录独立文件
- 启动 `ArchitectureContextTests.cs` 候选 worker但在共享工作树落地前主动停止以避免本轮上下文与 review 面积继续膨胀
- 验证里程碑:
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
- 结果:成功;`111 Warning(s)``0 Error(s)`
- 观察:构建输出未再报告 `GFramework.Game/Config/YamlConfigLoader.cs`
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet clean`
- 结果:成功;刷新最终 non-incremental 仓库根 warning 基线
- `dotnet build`
- 结果:成功;`353 Warning(s)``0 Error(s)`,唯一位点 `279`
- 观察:构建输出未再报告 `GFramework.Game/Config/YamlConfigLoader.cs``GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs``GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs`
- 当前结论:
- 本轮已完成一个主线程单文件 slice 和两个 worker 拆分 slice仓库根 non-incremental warning 从 `397` 降到 `353`
- 当前共享工作树 footprint 为 `22` 个 changed files仍低于 `$gframework-batch-boot 50` 的停止线
- 下一波更适合继续处理 `7``MA0048` 的小文件,而不是立即进入 `Mediator*``YamlConfigSchemaValidator*` 的高耦合热点
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~ArchitectureServicesTests|FullyQualifiedName~ContextAwareServiceExtensionsTests|FullyQualifiedName~TestArchitectureContextBehaviorTests|FullyQualifiedName~RegistryInitializationHookBaseTests|FullyQualifiedName~ArchitectureContextTests"`
- 结果:成功;`67` 通过、`0` 失败
- `git diff --check`
- 结果:成功;无新增 whitespace / conflict-marker 问题
## 活跃风险
- `GFramework.Cqrs.Tests/Mediator/*``MA0048` 位点密度很高,一次性拆分会迅速推高 changed-file 数
- 缓解措施:下一波优先继续拿 `7` warning 级别的小切片
- `YamlConfigSchemaValidator*` 仍然聚集多类高耦合 warning
- 缓解措施:继续维持为独立波次,不与测试项目拆分混提
- GitHub PR 上的 open threads 在本地提交前仍可能显示为未关闭。
- 缓解措施:以当前工作树和定向验证作为真值,推送后再让 PR 线程重新比对最新 head。
- `GFramework.Core.Tests` 项目当前存在独立于本轮改动的 `dotnet format` 基线。
- 缓解措施:保持为后续单独格式治理切片,不在当前 PR review follow-up 中扩写。
## 下一步
1. 完成本轮 `YamlConfigLoader.cs``MicrosoftDiContainerTests.cs``ai-plan` 的提交
2. 下一波优先从 `ArchitectureContextTests.cs``AsyncQueryExecutorTests.cs` 继续拆分纯 `MA0048`
1. 提交本轮 `ai-plan` 同步修复,使 PR head 能重新折叠文档相关线程。
2. 推送后重新执行 `$gframework-pr-review`,确认剩余 open threads 是否已经下降。
## 历史归档指针
- 最新 trace 归档:
- [analyzer-warning-reduction-history-rp083-rp088.md](../archive/traces/analyzer-warning-reduction-history-rp083-rp088.md)
- [analyzer-warning-reduction-history-rp073-rp078.md](../archive/traces/analyzer-warning-reduction-history-rp073-rp078.md)
- [analyzer-warning-reduction-history-rp062-rp071.md](../archive/traces/analyzer-warning-reduction-history-rp062-rp071.md)
- 历史 todo 归档: