From 686647c06bb8f604e18c3af5502583918a27ae01 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:26:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(game):=20=E4=BF=AE=E5=A4=8D=20YAML=20?= =?UTF-8?q?=E7=83=AD=E9=87=8D=E8=BD=BD=E5=8F=96=E6=B6=88=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 ReadYamlAsync 在取消时错误包装异常的问题,并对齐 IntegerTryParseDelegate 的可空性签名 - 更新 Ioc 与 Query 测试辅助类型的 XML 文档,并让 IPrioritizedService 复用 IMixedService 的 Name 契约 - 补充 YamlConfigLoader 取消语义回归测试并同步 analyzer warning reduction 跟踪 --- GFramework.Core.Tests/Ioc/IMixedService.cs | 3 + .../Ioc/IPrioritizedService.cs | 3 +- .../Ioc/PrioritizedService.cs | 2 +- .../Query/TestAsyncQueryWithExceptionV4.cs | 1 + .../Config/YamlConfigLoaderTests.cs | 62 ++++++++++++++++++- GFramework.Game/Config/YamlConfigLoader.cs | 7 ++- .../analyzer-warning-reduction-tracking.md | 39 +++++------- .../analyzer-warning-reduction-trace.md | 30 +++++++++ 8 files changed, 120 insertions(+), 27 deletions(-) diff --git a/GFramework.Core.Tests/Ioc/IMixedService.cs b/GFramework.Core.Tests/Ioc/IMixedService.cs index 0a788f9b..33c791f5 100644 --- a/GFramework.Core.Tests/Ioc/IMixedService.cs +++ b/GFramework.Core.Tests/Ioc/IMixedService.cs @@ -5,5 +5,8 @@ namespace GFramework.Core.Tests.Ioc; /// public interface IMixedService { + /// + /// 获取或设置服务名称。 + /// string? Name { get; set; } } diff --git a/GFramework.Core.Tests/Ioc/IPrioritizedService.cs b/GFramework.Core.Tests/Ioc/IPrioritizedService.cs index b0998c0d..4e52fe7a 100644 --- a/GFramework.Core.Tests/Ioc/IPrioritizedService.cs +++ b/GFramework.Core.Tests/Ioc/IPrioritizedService.cs @@ -5,7 +5,6 @@ namespace GFramework.Core.Tests.Ioc; /// /// 优先级服务接口 /// -public interface IPrioritizedService : IPrioritized +public interface IPrioritizedService : IPrioritized, IMixedService { - string? Name { get; set; } } diff --git a/GFramework.Core.Tests/Ioc/PrioritizedService.cs b/GFramework.Core.Tests/Ioc/PrioritizedService.cs index da06740b..1c20cb94 100644 --- a/GFramework.Core.Tests/Ioc/PrioritizedService.cs +++ b/GFramework.Core.Tests/Ioc/PrioritizedService.cs @@ -3,7 +3,7 @@ namespace GFramework.Core.Tests.Ioc; /// /// 实现优先级的服务 /// -public sealed class PrioritizedService : IPrioritizedService, IMixedService +public sealed class PrioritizedService : IPrioritizedService { /// /// 获取或设置优先级 diff --git a/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs b/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs index f321d756..3c9ac640 100644 --- a/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs +++ b/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs @@ -19,6 +19,7 @@ public sealed class TestAsyncQueryWithExceptionV4 : AbstractAsyncQuery /// 查询输入参数 + /// 返回一个不会正常完成的 ,因为该方法始终抛出异常。 /// 总是抛出异常 protected override Task OnDoAsync(TestAsyncQueryInputV2 input) { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs index 4b04ae0b..94b662cd 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Reflection; +using System.Threading; using GFramework.Core.Abstractions.Events; using GFramework.Game.Abstractions.Config; using GFramework.Game.Config; @@ -2784,6 +2786,48 @@ public class YamlConfigLoaderTests }); } + /// + /// 验证底层文件读取在取消时会保留 , + /// 避免热重载把会话级取消误报为配置读取失败。 + /// + [Test] + public async Task ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested() + { + CreateConfigFile( + "monster/slime.yaml", + """ + id: 1 + name: Slime + hp: 10 + """); + + var loader = new YamlConfigLoader(_rootPath) + .RegisterTable("monster", "monster", static config => config.Id); + var registration = GetSingleYamlTableRegistration(loader); + var readYamlAsyncMethod = registration.GetType() + .GetMethod("ReadYamlAsync", BindingFlags.Instance | BindingFlags.NonPublic); + + Assert.That(readYamlAsyncMethod, Is.Not.Null); + + using var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + + // 通过反射直接命中注册项的文件读取路径,稳定回归本次取消语义修复。 + var readTask = (Task)readYamlAsyncMethod!.Invoke( + registration, + new object?[] + { + Path.Combine(_rootPath, "monster"), + Path.Combine(_rootPath, "monster", "slime.yaml"), + null, + cancellationTokenSource.Token + })!; + + Assert.That( + async () => await readTask.ConfigureAwait(false), + Throws.InstanceOf()); + } + /// /// 验证依赖关系仅来自 contains 子 schema 时,热重载仍会追踪该依赖并在目标表破坏引用后回滚。 /// @@ -2928,7 +2972,7 @@ public class YamlConfigLoaderTests Assert.That(exception!.ParamName, Is.EqualTo("options")); } - + /// /// 验证热重载失败时会保留旧表状态,并通过失败回调暴露诊断信息。 /// @@ -3372,6 +3416,22 @@ public class YamlConfigLoaderTests CreateConfigFile(relativePath, content); } + private static object GetSingleYamlTableRegistration(YamlConfigLoader loader) + { + var registrationsField = typeof(YamlConfigLoader).GetField( + "_registrations", + BindingFlags.Instance | BindingFlags.NonPublic); + + Assert.That(registrationsField, Is.Not.Null); + + var registrations = registrationsField!.GetValue(loader) as System.Collections.IList; + + Assert.That(registrations, Is.Not.Null); + Assert.That(registrations!.Count, Is.EqualTo(1)); + + return registrations[0]!; + } + /// /// 在限定时间内等待异步任务完成,避免文件监听测试无限挂起。 /// diff --git a/GFramework.Game/Config/YamlConfigLoader.cs b/GFramework.Game/Config/YamlConfigLoader.cs index 975befe1..33ff4931 100644 --- a/GFramework.Game/Config/YamlConfigLoader.cs +++ b/GFramework.Game/Config/YamlConfigLoader.cs @@ -553,6 +553,11 @@ public sealed class YamlConfigLoader : IConfigLoader { return await File.ReadAllTextAsync(file, cancellationToken).ConfigureAwait(false); } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + // 保留原始取消语义,避免热重载把会话级取消误报为配置读取失败。 + throw; + } catch (Exception exception) { throw ConfigLoadExceptionFactory.Create( @@ -699,7 +704,7 @@ public sealed class YamlConfigLoader : IConfigLoader private static class CrossTableReferenceValidator { private delegate bool IntegerTryParseDelegate( - string value, + string? value, NumberStyles style, IFormatProvider? provider, out T result); diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index 2227a3d0..55ad6417 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -6,37 +6,32 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-083` -- 当前阶段:`Phase 83` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-084` +- 当前阶段:`Phase 84` - 当前焦点: - - `2026-04-27` 主线程已修复 `GFramework.Game/Config/YamlConfigLoader.cs` 的 `MA0051`、`MA0002` 与 `MA0158`,当前非增量仓库根构建已不再报告该文件 warning - - 并行 worker 已将 `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 末尾的 `10` 个测试辅助接口/类拆分到 `Ioc/` 同目录独立文件 - - 已接受第二波 worker 的已落地结果:`GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs` 末尾辅助类型已拆分到 `Query/` 同目录独立文件 - - 最新 non-incremental 仓库根基线已从 `397` 条 warning / `316` 个唯一位点降到 `353` 条 warning / `279` 个唯一位点 - - 当前剩余 warning 热点仍集中在 `GFramework.Cqrs.Tests/Mediator/*` 的大体量 `MA0048`、以及 `YamlConfigSchemaValidator*` 等高耦合 slice + - `2026-04-27` 已完成 PR `#297` 的 CodeRabbit follow-up,修复 `YamlConfigLoader` 的取消语义与 `IntegerTryParseDelegate` 可空性问题 + - 已补齐 `GFramework.Core.Tests/Ioc` 与 `GFramework.Core.Tests/Query` 中 review 指向的 XML 文档缺口,并让 `IPrioritizedService` 复用 `IMixedService.Name` 契约 + - 已新增 `YamlConfigLoaderTests` 回归测试,锁定“取消时保留 `OperationCanceledException`”这一行为 + - 当前分支的下一波 warning reduction 仍建议回到 `ArchitectureContextTests.cs`、`AsyncQueryExecutorTests.cs` 或 `YamlConfigSchemaValidator*` 的后续 slice ## 当前活跃事实 - 当前 `origin/main` 基线提交为 `b6a9fef`(`2026-04-27T10:53:34+08:00`)。 - 当前直接验证结果: - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release` - - 最新结果:成功;`111 Warning(s)`、`0 Error(s)`,其中不再包含 `GFramework.Game/Config/YamlConfigLoader.cs` 的 warning + - 最新结果:成功;`0 Warning(s)`、`0 Error(s)` - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` - 最新结果:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet clean` - - 最新结果:成功;为本轮最终 warning 基线刷新提供非增量起点 - - `dotnet build` - - 最新结果:成功;`353 Warning(s)`、`0 Error(s)`,唯一 warning 位点 `279` - - 当前构建输出已不再包含 `GFramework.Game/Config/YamlConfigLoader.cs`、`GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 与 `GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs` -- 当前分支 stop-condition 指标: - - 当前待提交工作树 footprint: - - 最新结果:`22` changed files,距离 `$gframework-batch-boot 50` 的停止线仍有余量 + - `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 改动文件无需额外格式化 - 当前批次摘要: - - 本轮完成 `YamlConfigLoader.cs` 的单文件 warning 清理,并通过受影响模块 Release 构建验证 - - 本轮完成 `MicrosoftDiContainerTests.cs` 的 ownership-bounded `MA0048` 拆分 slice,新增 `10` 个同目录辅助类型文件并保持测试语义不变 - - 本轮还完成 `AbstractAsyncQueryTests.cs` 的 `MA0048` 拆分 slice,新增 `7` 个同目录辅助类型文件并保持测试语义不变 - - 本轮 non-incremental 仓库根 warning 真值从 `397` 降到 `353`,减少 `44` 条;唯一位点从 `316` 降到 `279`,减少 `37` 个 - - 已尝试为 `ArchitectureContextTests.cs` 启动下一波 subagent,但在共享工作树落地前已停止,不计入本轮已完成事实 + - 本轮完成 PR `#297` 最新 head review 中仍然有效的 `3` 个 open threads 修复:`YamlConfigLoader` 取消语义、`IMixedService.Name` XML 文档、`IPrioritizedService` 相关契约整理 + - 本轮同时吸收 CodeRabbit folded nitpick 中仍然成立的 `2` 个点:`IntegerTryParseDelegate` 可空性对齐、`TestAsyncQueryWithExceptionV4.OnDoAsync` 的 `` 文档 + - 本轮新增一条精确回归测试,确保底层 YAML 文件读取在取消时继续抛出 `OperationCanceledException` 系列异常,而不是包装成 `ConfigLoadException` - 当前建议保留到下一波次的候选: - `GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs` 的 `7` 个 `MA0048` - `GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs` 的 `7` 个 `MA0048` @@ -72,6 +67,6 @@ ## 下一步建议 -1. 提交本轮 `YamlConfigLoader.cs`、`MicrosoftDiContainerTests.cs`、`AbstractAsyncQueryTests.cs` 的 warning reduction 结果及 `ai-plan` 同步。 +1. 提交本轮 PR `#297` review follow-up 与 `ai-plan` 同步。 2. 下一波优先挑选 `ArchitectureContextTests.cs` 或 `AsyncQueryExecutorTests.cs` 这类 `7`-warning 的纯 `MA0048` 单文件切片。 3. 继续将 `YamlConfigSchemaValidator*` 与 `GFramework.Cqrs.Tests/Mediator/*` 作为独立高风险波次处理。 diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index efb1d296..20ac885c 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -1,5 +1,35 @@ # Analyzer Warning Reduction 追踪 +## 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` 第一个参数改为 `string?` + - 校验 `GFramework.Core.Tests/Ioc/*` 与 `Query/TestAsyncQueryWithExceptionV4.cs` 后,补齐缺失 XML 文档,让 `IPrioritizedService` 继承 `IMixedService` 复用 `Name` 契约,并补上 `` 文档 + - 新增 `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` 的辅助类型