From 6ff07ad3d91a54bc07c95f30f485cb02160157b1 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:04:53 +0800 Subject: [PATCH 1/6] =?UTF-8?q?fix(godot):=20=E6=B8=85=E7=90=86=20Godot=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=B8=8E=E6=B5=8B=E8=AF=95=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化 GodotYamlConfigEnvironment 目录枚举逻辑,拆分 helper 以消除 MA0051 - 修复 Godot 生命周期 await 的上下文声明,显式保留主线程同步上下文 - 更新 Godot.Tests 异步断言与字符串 comparer,用例项目构建收敛到 0 warning(s) - 补充 analyzer-warning-reduction 跟踪与 trace,记录 RP-053 的批次结果与验证 --- ...ractArchitectureModuleInstallationTests.cs | 2 +- .../Config/GodotYamlConfigLoaderTests.cs | 6 +- .../Text/RichTextMarkupTests.cs | 5 +- .../Architectures/AbstractArchitecture.cs | 4 +- .../Config/GodotYamlConfigEnvironment.cs | 61 +++++++++--------- GFramework.Godot/Scene/SceneBehaviorBase.cs | 31 +++++----- .../analyzer-warning-reduction-tracking.md | 62 ++++++++----------- .../analyzer-warning-reduction-trace.md | 31 ++++++++++ 8 files changed, 115 insertions(+), 87 deletions(-) diff --git a/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs b/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs index 8952a12c..83dc2458 100644 --- a/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs +++ b/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs @@ -20,7 +20,7 @@ public sealed class AbstractArchitectureModuleInstallationTests var module = new RecordingGodotModule(); var exception = Assert.ThrowsAsync(async () => - await architecture.InstallGodotModuleForTestAsync(module)); + await architecture.InstallGodotModuleForTestAsync(module).ConfigureAwait(false)); Assert.Multiple(() => { diff --git a/GFramework.Godot.Tests/Config/GodotYamlConfigLoaderTests.cs b/GFramework.Godot.Tests/Config/GodotYamlConfigLoaderTests.cs index 33a0feb6..fbf5a71f 100644 --- a/GFramework.Godot.Tests/Config/GodotYamlConfigLoaderTests.cs +++ b/GFramework.Godot.Tests/Config/GodotYamlConfigLoaderTests.cs @@ -197,7 +197,7 @@ public sealed class GodotYamlConfigLoaderTests var loader = CreateLoader(isEditor: false); var exception = Assert.ThrowsAsync(async () => - await loader.LoadAsync(new ConfigRegistry())); + await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false)); Assert.Multiple(() => { @@ -225,7 +225,7 @@ public sealed class GodotYamlConfigLoaderTests configureLoader: static _ => { }); var exception = Assert.ThrowsAsync(async () => - await loader.LoadAsync(new ConfigRegistry())); + await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false)); Assert.That(exception!.ParamName, Is.EqualTo("relativePath")); } @@ -254,7 +254,7 @@ public sealed class GodotYamlConfigLoaderTests configureLoader: static _ => { }); var exception = Assert.ThrowsAsync(async () => - await loader.LoadAsync(new ConfigRegistry())); + await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false)); Assert.That(exception!.ParamName, Is.EqualTo("relativePath")); } diff --git a/GFramework.Godot.Tests/Text/RichTextMarkupTests.cs b/GFramework.Godot.Tests/Text/RichTextMarkupTests.cs index 127d783c..c3062f28 100644 --- a/GFramework.Godot.Tests/Text/RichTextMarkupTests.cs +++ b/GFramework.Godot.Tests/Text/RichTextMarkupTests.cs @@ -1,3 +1,4 @@ +using System; using GFramework.Godot.Text; namespace GFramework.Godot.Tests.Text; @@ -25,7 +26,7 @@ public sealed class RichTextMarkupTests [Test] public void Effect_Should_Sort_Environment_Parameters_By_Key() { - var env = new Dictionary + var env = new Dictionary(StringComparer.Ordinal) { ["tick"] = 0.1f, ["speed"] = 4 @@ -53,7 +54,7 @@ public sealed class RichTextMarkupTests [Test] public void Effect_Should_Reject_Invalid_Environment_Key_Tokens() { - var env = new Dictionary + var env = new Dictionary(StringComparer.Ordinal) { ["bad key"] = 1 }; diff --git a/GFramework.Godot/Architectures/AbstractArchitecture.cs b/GFramework.Godot/Architectures/AbstractArchitecture.cs index 1beb8609..f18483a1 100644 --- a/GFramework.Godot/Architectures/AbstractArchitecture.cs +++ b/GFramework.Godot/Architectures/AbstractArchitecture.cs @@ -112,8 +112,8 @@ public abstract class AbstractArchitecture( // 在附加流程完成前先登记模块,保证后续任一步失败时仍能参与架构销毁阶段的清理。 _extensions.Add(module); - // 等待锚点准备就绪,并保持 Godot 同步上下文,以便后续附加逻辑安全访问节点 API。 - await anchor.WaitUntilReadyAsync(); + // 显式保留 Godot 同步上下文,确保后续 AddChild 和 OnAttach 仍在节点可访问的主线程执行。 + await anchor.WaitUntilReadyAsync().ConfigureAwait(true); // 延迟调用将扩展节点添加为锚点的子节点 anchor.CallDeferred(Node.MethodName.AddChild, module.Node); diff --git a/GFramework.Godot/Config/GodotYamlConfigEnvironment.cs b/GFramework.Godot/Config/GodotYamlConfigEnvironment.cs index e77d325e..828f32c5 100644 --- a/GFramework.Godot/Config/GodotYamlConfigEnvironment.cs +++ b/GFramework.Godot/Config/GodotYamlConfigEnvironment.cs @@ -104,41 +104,36 @@ internal sealed class GodotYamlConfigEnvironment private static IReadOnlyList? EnumerateDirectoryCore(string path) { - if (!path.IsGodotPath()) + return path.IsGodotPath() + ? EnumerateGodotDirectory(path) + : EnumerateFileSystemDirectory(path); + } + + private static IReadOnlyList? EnumerateFileSystemDirectory(string path) + { + try { - try + if (!Directory.Exists(path)) { - if (!Directory.Exists(path)) - { - return null; - } + return null; + } - return Directory - .EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly) - .Select(static entryPath => new GodotYamlConfigDirectoryEntry( - Path.GetFileName(entryPath), - Directory.Exists(entryPath))) - .ToArray(); - } - catch (IOException) - { - // 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null,而不是泄漏底层异常。 - return null; - } - catch (UnauthorizedAccessException) - { - return null; - } - catch (ArgumentException) - { - return null; - } - catch (NotSupportedException) - { - return null; - } + return Directory + .EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly) + .Select(static entryPath => new GodotYamlConfigDirectoryEntry( + Path.GetFileName(entryPath), + Directory.Exists(entryPath))) + .ToArray(); } + catch (Exception ex) when (IsExpectedDirectoryEnumerationException(ex)) + { + // 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null,而不是泄漏底层异常。 + return null; + } + } + private static IReadOnlyList? EnumerateGodotDirectory(string path) + { using var directory = DirAccess.Open(path); if (directory == null) { @@ -170,9 +165,15 @@ internal sealed class GodotYamlConfigEnvironment // 目录枚举句柄必须成对结束,避免未来循环体扩展后在异常路径上遗留引擎状态。 directory.ListDirEnd(); } + return entries; } + private static bool IsExpectedDirectoryEnumerationException(Exception exception) + { + return exception is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException; + } + private static bool FileExistsCore(string path) { return path.IsGodotPath() diff --git a/GFramework.Godot/Scene/SceneBehaviorBase.cs b/GFramework.Godot/Scene/SceneBehaviorBase.cs index bfee418f..fee000f5 100644 --- a/GFramework.Godot/Scene/SceneBehaviorBase.cs +++ b/GFramework.Godot/Scene/SceneBehaviorBase.cs @@ -141,10 +141,11 @@ public abstract class SceneBehaviorBase : ISceneBehavior /// 当场景被其他场景覆盖或失去焦点时调用。 /// /// 表示暂停操作完成的ValueTask。 - public virtual async ValueTask OnPauseAsync() - { - if (_scene != null) - await _scene.OnPauseAsync(); + public virtual async ValueTask OnPauseAsync() + { + if (_scene != null) + // 暂停后紧接着会修改 Owner 的处理开关,必须回到 Godot 主线程继续执行。 + await _scene.OnPauseAsync().ConfigureAwait(true); // 暂停处理 Owner.SetProcess(false); @@ -159,13 +160,14 @@ public abstract class SceneBehaviorBase : ISceneBehavior /// 当场景重新获得焦点或从暂停状态恢复时调用。 /// /// 表示恢复操作完成的ValueTask。 - public virtual async ValueTask OnResumeAsync() - { - if (Owner.IsInvalidNode()) - return; + public virtual async ValueTask OnResumeAsync() + { + if (Owner.IsInvalidNode()) + return; - if (_scene != null) - await _scene.OnResumeAsync(); + if (_scene != null) + // 恢复完成后要立刻重新启用节点处理流程,因此显式保留当前同步上下文。 + await _scene.OnResumeAsync().ConfigureAwait(true); // 恢复处理 Owner.SetProcess(true); @@ -195,10 +197,11 @@ public abstract class SceneBehaviorBase : ISceneBehavior /// 在场景完全退出后调用,释放占用的内存和资源。 /// /// 表示卸载操作完成的ValueTask。 - public virtual async ValueTask OnUnloadAsync() - { - if (_scene != null) - await _scene.OnUnloadAsync(); + public virtual async ValueTask OnUnloadAsync() + { + if (_scene != null) + // 卸载后的 QueueFreeX 必须在 Godot 节点线程上调用,不能切走同步上下文。 + await _scene.OnUnloadAsync().ConfigureAwait(true); // 释放节点 Owner.QueueFreeX(); 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 3fa5eb3a..424cc2b6 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,36 +6,34 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-052` -- 当前阶段:`Phase 52` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-053` +- 当前阶段:`Phase 53` - 当前焦点: - - `2026-04-24` 本轮从当前 PR review 的未解决线程回切到 `GFramework.Game` / `GFramework.Godot.SourceGenerators.Tests` - - `UnifiedSettingsFile.Sections` 与 `CloneFile` fallback 已对齐为“可保留原 comparer 时保留,否则显式回退到 `StringComparer.Ordinal`”的文档与实现契约 - - `AutoRegisterExportedCollectionsGeneratorTests` 中剩余的 `await test.RunAsync();` 已统一补齐 `.ConfigureAwait(false)`,并同步让 `VerifyDiagnosticsAsync` 内部消费异步等待 - - 当前批次仍需避免混入与 analyzer-warning-reduction 无关的既有工作树改动 + - `2026-04-24` 本轮按 `$gframework-batch-boot 75` 重新建立当前分支相对 `origin/main` 的 batch 基线,并从整仓 `Release` build 里挑选低风险热点 + - `GFramework.Godot` 已完成一轮独立 warning 清理:`GodotYamlConfigEnvironment` 的目录枚举逻辑已拆分 helper,`AbstractArchitecture` / `SceneBehaviorBase` 中需要保留 Godot 同步上下文的 await 已显式改为 `.ConfigureAwait(true)` + - `GFramework.Godot.Tests` 已同步清理对应测试 warning:异步断言显式使用 `.ConfigureAwait(false)`,`RichTextMarkupTests` 中测试字典显式指定 `StringComparer.Ordinal` + - 当前代码批次相对 `origin/main` 的待提交 diff 为 `6` 个文件、`107` 行变更,远低于 `$gframework-batch-boot 75` 的主停止阈值;本轮在这里收口,是因为下一批候选将进入 `GFramework.Game` 等高基线模块,而不再是同等级低风险切片 ## 当前活跃事实 - 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值 -- 本轮已完成 `GFramework.Godot.SourceGenerators` warning 清理:clean `Release` build 从 9 个 warning 降至 0 个 warning -- 当前已确认解决的文件包括 `BindNodeSignalGenerator.cs`、`GetNodeGenerator.cs`、`GodotProjectMetadataGenerator.cs`、`Registration/AutoRegisterExportedCollectionsGenerator.cs` - 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本 -- 本轮直接执行仓库根目录 `dotnet build` 成功,并给出 `1184 warning(s)` 的真实输出 -- `GFramework.Godot.SourceGenerators.Tests` 已通过测试辅助模板抽取与 `ConfigureAwait(false)` 修正,当前 `Debug` / `Release` 构建均为 `0 Warning(s)` -- 本轮已验证 `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`,结果为 `Passed: 48` -- 本轮已把 PR #283 中仍打开的 `UnifiedSettingsDataRepository.cs` comparer 契约线程落到代码与 XML 注释,避免 fallback 语义继续依赖隐式默认 comparer -- 本轮已确认 `AutoRegisterExportedCollectionsGeneratorTests` 的 5 处裸 `await test.RunAsync();` 不是当前 Release build 告警来源,但仍作为 PR review 一致性项一并修正 +- 本轮直接执行仓库根目录 `dotnet build GFramework.sln -c Release` 成功,并给出 `1122 warning(s)` 的当前整仓观测值 +- `GFramework.Godot/GFramework.Godot.csproj -c Release` 在本轮收尾验证中已达到 `0 Warning(s)`、`0 Error(s)` +- `GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release` 在串行复验中已达到 `0 Warning(s)`、`0 Error(s)` +- 本轮已验证 `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"`,结果为 `Passed: 15` +- `GFramework.Godot` 原先暴露的 `MA0051` 与 `MA0004` 热点都已清理完成;当前同域低风险切片基本耗尽 ## 当前风险 - 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0 - 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build` - 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线 - - 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1184 warning(s)` direct build 观测值 -- 当前 worktree 已存在与本批次无关的未提交改动 - - 缓解措施:提交当前批次时只暂存 `GFramework.Godot.SourceGenerators.Tests` 与对应 `ai-plan` 文件,避免混入其他 topic 变更 -- `GFramework.Game` 当前 `Release` build 仍带有既有 analyzer warning 基线 - - 缓解措施:本轮仅验证改动未新增 `UnifiedSettingsDataRepository` / `UnifiedSettingsFile` 相关 warning;若继续在该模块做 warning reduction,需要另开切片处理现存基线 + - 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1122 warning(s)` direct build 观测值 +- 当前 worktree 仍存在未跟踪的 `.codex` 目录 + - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交 +- 下一轮最明显的剩余热点将转入 `GFramework.Game` 等高 warning 基线模块 + - 缓解措施:恢复时先重新跑整仓 build 热点筛选,再决定是否接受更高上下文成本的 `GFramework.Game` 切片,或先排查 solution-level clean 失败原因 ## 活跃文档 @@ -51,25 +49,19 @@ ## 验证说明 -- `dotnet clean` +- `dotnet clean GFramework.sln -c Release` - 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本 -- `dotnet build` - - 结果:成功;`1184 Warning(s)`、`0 Error(s)` -- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj` - - 初始结果:成功;`24 Warning(s)`、`0 Error(s)` - - 本轮收尾结果:成功;`0 Warning(s)`、`0 Error(s)` -- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release` +- `dotnet build GFramework.sln -c Release` + - 结果:成功;`1122 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` - 结果:成功;`0 Warning(s)`、`0 Error(s)` -- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build` - - 结果:成功;`Passed: 48`、`Failed: 0` -- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release` - - 结果:成功;`533 Warning(s)`、`0 Error(s)`;模块仍存在既有 warning 基线,本轮 follow-up 仅处理 PR review 指向的 comparer 契约与测试异步等待一致性 -- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release` - - 结果:成功;`0 Warning(s)`、`0 Error(s)` -- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build` - - 结果:成功;`Passed: 48`、`Failed: 0` +- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"` + - 结果:成功;`Passed: 15`、`Failed: 0` +- `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release` + - 首次并行验证:成功;`1 Warning(s)`、`0 Error(s)`;`MSB3026` 来自与并行 `dotnet test` 竞争同一输出 DLL + - 串行复验:成功;`0 Warning(s)`、`0 Error(s)` ## 下一步建议 -1. 提交当前 comparer 契约与 `ConfigureAwait(false)` PR follow-up,并确认只纳入本 topic 相关文件 -2. 视 PR review 反馈决定是否继续收敛 `GFramework.Game` 现有 warning 基线,或返回下一轮整仓 warning 热点筛选 +1. 提交当前 `GFramework.Godot` / `GFramework.Godot.Tests` warning 清理批次,并继续保持只纳入本 topic 相关文件 +2. 下一轮若继续使用 `$gframework-batch-boot 75`,先决定是优先排查 solution-level `dotnet clean` 失败,还是接受更高上下文成本进入 `GFramework.Game` warning 热点 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 95ac513a..e05a9c54 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 @@ -2,6 +2,37 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-24 — RP-053 + +### 阶段:`GFramework.Godot` / `GFramework.Godot.Tests` 小批次 warning 清理 + +- 触发背景: + - 用户以 `$gframework-batch-boot 75` 要求继续按批次推进 analyzer warning reduction,并以 `origin/main` 作为累计分支 diff 基线 + - 当前 worktree `fix/analyzer-warning-reduction-batch` 相对 `origin/main` 的已提交分支 diff 为 `0` 个文件,具备继续落一个低风险 warning batch 的空间 + - solution-level `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,因此本轮继续用直接 `dotnet build GFramework.sln -c Release` 建立热点观察值 +- 主线程实施: + - 运行 `dotnet build GFramework.sln -c Release`,确认当前整仓观测值为 `1122 warning(s)`,并从输出中挑选 `GFramework.Godot` 的小范围热点作为本轮批次 + - 在 `GodotYamlConfigEnvironment.cs` 中按“普通文件系统 / Godot 路径”拆分目录枚举 helper,消除 `MA0051` + - 在 `AbstractArchitecture.cs` 与 `SceneBehaviorBase.cs` 中将必须保留 Godot 主线程上下文的 await 显式改为 `.ConfigureAwait(true)`,清理 `MA0004` 并把线程意图写入注释 + - 在 `GFramework.Godot.Tests` 中补齐异步断言的 `.ConfigureAwait(false)`,并让 `RichTextMarkupTests` 的测试字典显式指定 `StringComparer.Ordinal` +- 验证里程碑: + - `dotnet clean GFramework.sln -c Release` + - 结果:失败;停在 `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.sln -c Release` + - 结果:成功;`1122 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` + - 第一轮修复后:成功;`12 Warning(s)`、`0 Error(s)`,仅剩 `MA0004` + - 第二轮修复后:成功;`0 Warning(s)`、`0 Error(s)` + - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"` + - 结果:成功;`Passed: 15`、`Failed: 0` + - `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release` + - 并行验证时:成功;`1 Warning(s)`、`0 Error(s)`;`MSB3026` 为与并行 `dotnet test` 竞争输出 DLL 的文件占用 + - 串行复验:成功;`0 Warning(s)`、`0 Error(s)` +- 当前结论: + - `GFramework.Godot` 与 `GFramework.Godot.Tests` 本轮直接涉及的 warning 已全部清零 + - 当前待提交代码批次相对 `origin/main` 的源码 diff 为 `6` 个文件、`107` 行,距离 `$gframework-batch-boot 75` 主停止阈值仍有充足余量 + - 继续推进的下一批候选将主要落在 `GFramework.Game` 等高 warning 基线模块,已不再属于当前同等级低风险切片,因此本轮在这里收口并进入提交 + ## 2026-04-24 — RP-052 ### 阶段:PR review follow-up(comparer 契约 + `ConfigureAwait(false)` 收尾) From 36507bbc52487c61583d6bd85c2ab7d80fb4c685 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 24 Apr 2026 18:17:21 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix(game-tests):=20=E6=94=B6=E6=95=9B=20Gam?= =?UTF-8?q?e.Tests=20=E6=B5=8B=E8=AF=95=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复多组 YAML 与 persistence 测试中的 ConfigureAwait 使用与状态校验,清理低风险 analyzer 告警 - 重构 PersistenceTestUtilities 为单类型文件,消除测试辅助模型的文件命名告警 - 更新 analyzer-warning-reduction 跟踪与 trace,记录 RP-054 批次结果与 75 文件阈值停止点 --- .../Config/YamlConfigLoaderAllOfTests.cs | 27 +++-- .../YamlConfigLoaderDependentRequiredTests.cs | 12 +- .../YamlConfigLoaderDependentSchemasTests.cs | 22 ++-- .../Config/YamlConfigLoaderEnumTests.cs | 6 +- .../Config/YamlConfigLoaderIfThenElseTests.cs | 31 +++-- .../Config/YamlConfigLoaderNegationTests.cs | 10 +- .../Config/YamlConfigSchemaValidatorTests.cs | 5 +- .../Config/YamlConfigTextValidatorTests.cs | 2 +- .../Data/PersistenceTestUtilities.cs | 114 ------------------ .../Data/PersistenceTests.cs | 16 +-- .../Data/TestDataLocation.cs | 49 ++++++++ GFramework.Game.Tests/Data/TestNamedData.cs | 14 +++ GFramework.Game.Tests/Data/TestSaveData.cs | 14 +++ GFramework.Game.Tests/Data/TestSimpleData.cs | 14 +++ .../Data/TestVersionedSaveData.cs | 35 ++++++ .../analyzer-warning-reduction-tracking.md | 48 ++++---- .../analyzer-warning-reduction-trace.md | 30 +++++ 17 files changed, 256 insertions(+), 193 deletions(-) delete mode 100644 GFramework.Game.Tests/Data/PersistenceTestUtilities.cs create mode 100644 GFramework.Game.Tests/Data/TestDataLocation.cs create mode 100644 GFramework.Game.Tests/Data/TestNamedData.cs create mode 100644 GFramework.Game.Tests/Data/TestSaveData.cs create mode 100644 GFramework.Game.Tests/Data/TestSimpleData.cs create mode 100644 GFramework.Game.Tests/Data/TestVersionedSaveData.cs diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs index 09f0eebb..538d651b 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs @@ -90,7 +90,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); var reward = table.Get(1).Reward; @@ -165,7 +165,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -210,7 +210,10 @@ public sealed class YamlConfigLoaderAllOfTests } """); - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } var loader = new YamlConfigLoader(_rootPath) .RegisterTable( @@ -220,7 +223,7 @@ public sealed class YamlConfigLoaderAllOfTests static config => config.Id); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -255,7 +258,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -295,7 +298,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -335,7 +338,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -375,7 +378,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -415,7 +418,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -455,7 +458,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -499,7 +502,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs index d9978c61..9c5501a1 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs @@ -75,7 +75,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); Assert.That(table.Count, Is.EqualTo(1)); @@ -169,7 +169,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); var reward = table.Get(1).Reward; @@ -217,7 +217,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -267,7 +267,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateCaseSensitiveRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -317,7 +317,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs index 66a1e1a9..009655db 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs @@ -74,7 +74,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -106,7 +106,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); Assert.That(table.Count, Is.EqualTo(1)); @@ -133,7 +133,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); var reward = table.Get(1).Reward; @@ -174,7 +174,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -220,7 +220,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -264,7 +264,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -283,7 +283,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests /// 要写入的 YAML 或 schema 内容。 private void CreateConfigFile(string relativePath, string content) { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar)); var directoryPath = Path.GetDirectoryName(filePath); @@ -353,7 +356,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests /// 已注册测试表与 schema 路径的加载器。 private YamlConfigLoader CreateMonsterRewardLoader() { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } return new YamlConfigLoader(_rootPath) .RegisterTable( diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs index d6253a8f..0edbb6e2 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs @@ -75,7 +75,7 @@ public class YamlConfigLoaderEnumTests var loader = CreateLoader(); var registry = new ConfigRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); Assert.Multiple(() => @@ -127,7 +127,7 @@ public class YamlConfigLoaderEnumTests var loader = CreateLoader(); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -176,7 +176,7 @@ public class YamlConfigLoaderEnumTests var loader = CreateLoader(); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs index c73c3a56..071bc3e6 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs @@ -105,7 +105,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -137,7 +137,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); var reward = table.Get(1).Reward; @@ -170,7 +170,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -202,7 +202,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); var reward = table.Get(1).Reward; @@ -250,7 +250,10 @@ public sealed class YamlConfigLoaderIfThenElseTests } """); - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } var loader = new YamlConfigLoader(_rootPath) .RegisterTable( @@ -260,7 +263,7 @@ public sealed class YamlConfigLoaderIfThenElseTests static config => config.Id); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -301,7 +304,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -342,7 +345,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -390,7 +393,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -409,7 +412,10 @@ public sealed class YamlConfigLoaderIfThenElseTests /// 配置文件内容。 private void CreateConfigFile(string relativePath, string content) { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar)); var directoryPath = Path.GetDirectoryName(filePath); @@ -496,7 +502,10 @@ public sealed class YamlConfigLoaderIfThenElseTests /// 已注册测试表与 schema 路径的加载器。 private YamlConfigLoader CreateMonsterRewardLoader() { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } return new YamlConfigLoader(_rootPath) .RegisterTable( diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs index 169a03bb..cc510b06 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs @@ -70,7 +70,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -118,7 +118,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); @@ -172,7 +172,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { @@ -227,7 +227,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var table = registry.GetTable("monster"); @@ -272,7 +272,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigSchemaValidatorTests.cs b/GFramework.Game.Tests/Config/YamlConfigSchemaValidatorTests.cs index 7a61a7e9..40b19abf 100644 --- a/GFramework.Game.Tests/Config/YamlConfigSchemaValidatorTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigSchemaValidatorTests.cs @@ -191,7 +191,10 @@ public sealed class YamlConfigSchemaValidatorTests string relativePath, string content) { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } var fullPath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar)); var directoryPath = Path.GetDirectoryName(fullPath); diff --git a/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs b/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs index 047b642c..483617c6 100644 --- a/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs @@ -132,7 +132,7 @@ public sealed class YamlConfigTextValidatorTests "monster/generated.yaml", """ id: 1 - """)); + """).ConfigureAwait(false)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Data/PersistenceTestUtilities.cs b/GFramework.Game.Tests/Data/PersistenceTestUtilities.cs deleted file mode 100644 index 08dfb4e1..00000000 --- a/GFramework.Game.Tests/Data/PersistenceTestUtilities.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using GFramework.Game.Abstractions.Data; -using GFramework.Game.Abstractions.Enums; - -namespace GFramework.Game.Tests.Data; - -/// -/// 为持久化测试提供稳定的测试数据位置实现。 -/// -internal sealed class TestDataLocation : IDataLocation -{ - /// - /// 初始化测试数据位置。 - /// - /// 测试使用的存储键。 - /// 测试使用的存储类型。 - /// 测试使用的命名空间。 - /// 附加测试元数据。 - public TestDataLocation( - string key, - StorageKinds kinds = StorageKinds.Local, - string? namespaceValue = null, - IReadOnlyDictionary? metadata = null) - { - Key = key; - Kinds = kinds; - Namespace = namespaceValue; - Metadata = metadata; - } - - /// - /// 获取测试数据对应的存储键。 - /// - public string Key { get; } - - /// - /// 获取测试数据使用的存储类型。 - /// - public StorageKinds Kinds { get; } - - /// - /// 获取测试数据使用的命名空间。 - /// - public string? Namespace { get; } - - /// - /// 获取附加到测试位置上的元数据。 - /// - public IReadOnlyDictionary? Metadata { get; } -} - -/// -/// 为基础存档仓库测试提供的简单存档模型。 -/// -internal sealed class TestSaveData : IData -{ - /// - /// 获取或设置测试存档中的名称字段。 - /// - public string Name { get; set; } = string.Empty; -} - -/// -/// 为存档迁移测试提供的版本化存档模型。 -/// -internal sealed class TestVersionedSaveData : IVersionedData -{ - /// - /// 获取或设置测试存档中的名称字段。 - /// - public string Name { get; set; } = string.Empty; - - /// - /// 获取或设置测试存档中的等级字段。 - /// - public int Level { get; set; } - - /// - /// 获取或设置测试存档中的经验字段。 - /// - public int Experience { get; set; } - - /// - /// 获取或设置当前测试存档的版本号。 - /// - public int Version { get; set; } = 3; - - /// - /// 获取或设置测试存档的最后修改时间。 - /// - public DateTime LastModified { get; set; } = DateTime.UtcNow; -} - -/// -/// 为通用持久化测试提供的简单数据模型。 -/// -internal sealed class TestSimpleData : IData -{ - /// - /// 获取或设置测试数据中的整数值。 - /// - public int Value { get; set; } -} - -/// -/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。 -/// -internal sealed class TestNamedData : IData -{ - /// - /// 获取或设置测试数据中的名称值。 - /// - public string Name { get; set; } = string.Empty; -} diff --git a/GFramework.Game.Tests/Data/PersistenceTests.cs b/GFramework.Game.Tests/Data/PersistenceTests.cs index 8813076b..f5549517 100644 --- a/GFramework.Game.Tests/Data/PersistenceTests.cs +++ b/GFramework.Game.Tests/Data/PersistenceTests.cs @@ -38,12 +38,12 @@ public class PersistenceTests using var storage = new FileStorage(root, new JsonSerializer(), ".json"); var saved = new TestSimpleData { Value = 5 }; - await storage.WriteAsync("folder/item", saved); + await storage.WriteAsync("folder/item", saved).ConfigureAwait(false); - var loaded = await storage.ReadAsync("folder/item"); + var loaded = await storage.ReadAsync("folder/item").ConfigureAwait(false); Assert.That(loaded.Value, Is.EqualTo(saved.Value)); - Assert.ThrowsAsync(async () => await storage.WriteAsync("../escape", new TestSimpleData())); + Assert.ThrowsAsync(async () => await storage.WriteAsync("../escape", new TestSimpleData()).ConfigureAwait(false)); } /// @@ -108,7 +108,7 @@ public class PersistenceTests .RegisterMigration(new TestSaveMigrationV2ToV3()); var loaded = await repository.LoadAsync(1); - var persisted = await storage.ReadAsync("saves/slot_1/save"); + var persisted = await storage.ReadAsync("saves/slot_1/save").ConfigureAwait(false); Assert.Multiple(() => { @@ -185,7 +185,7 @@ public class PersistenceTests var repository = new SaveRepository(storage, config) .RegisterMigration(new TestSaveMigrationV1ToV2()); - var exception = Assert.ThrowsAsync(async () => await repository.LoadAsync(1)); + var exception = Assert.ThrowsAsync(async () => await repository.LoadAsync(1).ConfigureAwait(false)); Assert.That(exception!.Message, Does.Contain("from version 2")); } @@ -218,8 +218,8 @@ public class PersistenceTests var repository = new SaveRepository(storage, config) .RegisterMigration(new TestSaveMigrationV1ToV2ReturningV3()); - var exception = Assert.ThrowsAsync(async () => await repository.LoadAsync(1)); - var persisted = await storage.ReadAsync("saves/slot_1/save"); + var exception = Assert.ThrowsAsync(async () => await repository.LoadAsync(1).ConfigureAwait(false)); + var persisted = await storage.ReadAsync("saves/slot_1/save").ConfigureAwait(false); Assert.Multiple(() => { @@ -270,7 +270,7 @@ public class PersistenceTests repository.RegisterMigration(new TestSaveMigrationV2ToV3()); continueMigration.Set(); - var exception = Assert.ThrowsAsync(async () => await loadTask); + var exception = Assert.ThrowsAsync(async () => await loadTask.ConfigureAwait(false)); var persisted = await storage.ReadAsync("saves/slot_1/save"); Assert.Multiple(() => diff --git a/GFramework.Game.Tests/Data/TestDataLocation.cs b/GFramework.Game.Tests/Data/TestDataLocation.cs new file mode 100644 index 00000000..b1cd6519 --- /dev/null +++ b/GFramework.Game.Tests/Data/TestDataLocation.cs @@ -0,0 +1,49 @@ +using GFramework.Game.Abstractions.Data; +using GFramework.Game.Abstractions.Enums; + +namespace GFramework.Game.Tests.Data; + +/// +/// 为持久化测试提供稳定的测试数据位置实现。 +/// +internal sealed class TestDataLocation : IDataLocation +{ + /// + /// 初始化测试数据位置。 + /// + /// 测试使用的存储键。 + /// 测试使用的存储类型。 + /// 测试使用的命名空间。 + /// 附加测试元数据。 + public TestDataLocation( + string key, + StorageKinds kinds = StorageKinds.Local, + string? namespaceValue = null, + IReadOnlyDictionary? metadata = null) + { + Key = key; + Kinds = kinds; + Namespace = namespaceValue; + Metadata = metadata; + } + + /// + /// 获取测试数据对应的存储键。 + /// + public string Key { get; } + + /// + /// 获取测试数据使用的存储类型。 + /// + public StorageKinds Kinds { get; } + + /// + /// 获取测试数据使用的命名空间。 + /// + public string? Namespace { get; } + + /// + /// 获取附加到测试位置上的元数据。 + /// + public IReadOnlyDictionary? Metadata { get; } +} diff --git a/GFramework.Game.Tests/Data/TestNamedData.cs b/GFramework.Game.Tests/Data/TestNamedData.cs new file mode 100644 index 00000000..4c355437 --- /dev/null +++ b/GFramework.Game.Tests/Data/TestNamedData.cs @@ -0,0 +1,14 @@ +using GFramework.Game.Abstractions.Data; + +namespace GFramework.Game.Tests.Data; + +/// +/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。 +/// +internal sealed class TestNamedData : IData +{ + /// + /// 获取或设置测试数据中的名称值。 + /// + public string Name { get; set; } = string.Empty; +} diff --git a/GFramework.Game.Tests/Data/TestSaveData.cs b/GFramework.Game.Tests/Data/TestSaveData.cs new file mode 100644 index 00000000..4236088e --- /dev/null +++ b/GFramework.Game.Tests/Data/TestSaveData.cs @@ -0,0 +1,14 @@ +using GFramework.Game.Abstractions.Data; + +namespace GFramework.Game.Tests.Data; + +/// +/// 为基础存档仓库测试提供的简单存档模型。 +/// +internal sealed class TestSaveData : IData +{ + /// + /// 获取或设置测试存档中的名称字段。 + /// + public string Name { get; set; } = string.Empty; +} diff --git a/GFramework.Game.Tests/Data/TestSimpleData.cs b/GFramework.Game.Tests/Data/TestSimpleData.cs new file mode 100644 index 00000000..6af95297 --- /dev/null +++ b/GFramework.Game.Tests/Data/TestSimpleData.cs @@ -0,0 +1,14 @@ +using GFramework.Game.Abstractions.Data; + +namespace GFramework.Game.Tests.Data; + +/// +/// 为通用持久化测试提供的简单数据模型。 +/// +internal sealed class TestSimpleData : IData +{ + /// + /// 获取或设置测试数据中的整数值。 + /// + public int Value { get; set; } +} diff --git a/GFramework.Game.Tests/Data/TestVersionedSaveData.cs b/GFramework.Game.Tests/Data/TestVersionedSaveData.cs new file mode 100644 index 00000000..bc8e2773 --- /dev/null +++ b/GFramework.Game.Tests/Data/TestVersionedSaveData.cs @@ -0,0 +1,35 @@ +using System; +using GFramework.Game.Abstractions.Data; + +namespace GFramework.Game.Tests.Data; + +/// +/// 为存档迁移测试提供的版本化存档模型。 +/// +internal sealed class TestVersionedSaveData : IVersionedData +{ + /// + /// 获取或设置测试存档中的名称字段。 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 获取或设置测试存档中的等级字段。 + /// + public int Level { get; set; } + + /// + /// 获取或设置测试存档中的经验字段。 + /// + public int Experience { get; set; } + + /// + /// 获取或设置当前测试存档的版本号。 + /// + public int Version { get; set; } = 3; + + /// + /// 获取或设置测试存档的最后修改时间。 + /// + public DateTime LastModified { get; set; } = DateTime.UtcNow; +} 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 424cc2b6..5085564e 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,34 +6,35 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-053` -- 当前阶段:`Phase 53` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-054` +- 当前阶段:`Phase 54` - 当前焦点: - - `2026-04-24` 本轮按 `$gframework-batch-boot 75` 重新建立当前分支相对 `origin/main` 的 batch 基线,并从整仓 `Release` build 里挑选低风险热点 - - `GFramework.Godot` 已完成一轮独立 warning 清理:`GodotYamlConfigEnvironment` 的目录枚举逻辑已拆分 helper,`AbstractArchitecture` / `SceneBehaviorBase` 中需要保留 Godot 同步上下文的 await 已显式改为 `.ConfigureAwait(true)` - - `GFramework.Godot.Tests` 已同步清理对应测试 warning:异步断言显式使用 `.ConfigureAwait(false)`,`RichTextMarkupTests` 中测试字典显式指定 `StringComparer.Ordinal` - - 当前代码批次相对 `origin/main` 的待提交 diff 为 `6` 个文件、`107` 行变更,远低于 `$gframework-batch-boot 75` 的主停止阈值;本轮在这里收口,是因为下一批候选将进入 `GFramework.Game` 等高基线模块,而不再是同等级低风险切片 + - `2026-04-24` 本轮继续按 `$gframework-batch-boot 75` 推进,切入 `GFramework.Game.Tests` 的低风险测试 warning,而不进入 `YamlConfigLoaderTests.cs` 等高上下文热点 + - 已完成 `PersistenceTestUtilities` 的单类型拆分,并在多组 YAML / persistence 测试中补齐 `.ConfigureAwait(false)` 与字段态显式状态检查 + - `GFramework.Game.Tests` 当前 `Release` build 已从本轮入口观测值 `116 warning(s)` 收敛到 `71 warning(s)`,且本轮 touched files 已不再出现在 warning 输出里 + - 当前工作树相对 `origin/main` 的累计 diff 已达到 `76` 个文件、`986` 行变更,超过 `$gframework-batch-boot 75` 的主停止阈值;本轮必须在提交后停止继续扩批 ## 当前活跃事实 - 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值 - 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本 -- 本轮直接执行仓库根目录 `dotnet build GFramework.sln -c Release` 成功,并给出 `1122 warning(s)` 的当前整仓观测值 -- `GFramework.Godot/GFramework.Godot.csproj -c Release` 在本轮收尾验证中已达到 `0 Warning(s)`、`0 Error(s)` -- `GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release` 在串行复验中已达到 `0 Warning(s)`、`0 Error(s)` -- 本轮已验证 `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"`,结果为 `Passed: 15` -- `GFramework.Godot` 原先暴露的 `MA0051` 与 `MA0004` 热点都已清理完成;当前同域低风险切片基本耗尽 +- 本轮直接执行仓库根目录 `dotnet build GFramework.sln -c Release` 成功,并给出 `116 warning(s)` 的当前整仓入口观测值;其中低风险热点主要落在 `GFramework.Game.Tests` +- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 在本轮收尾验证中为 `71 Warning(s)`、`0 Error(s)`;剩余 warning 已集中在未触碰的 `YamlConfigLoaderTests.cs`、`GeneratedConfigConsumerIntegrationTests.cs`、`GameConfigBootstrapTests.cs`、`ArchitectureConfigIntegrationTests.cs`、`JsonSerializerTests.cs` +- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"`,结果为 `Passed: 63` +- `PersistenceTestUtilities.cs` 已拆分为 `TestDataLocation.cs`、`TestSaveData.cs`、`TestVersionedSaveData.cs`、`TestSimpleData.cs`、`TestNamedData.cs`,与仓库“一文件一主类型”风格对齐 ## 当前风险 - 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0 - 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build` - 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线 - - 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1122 warning(s)` direct build 观测值 + - 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level / project-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 direct build 观测值 - 当前 worktree 仍存在未跟踪的 `.codex` 目录 - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交 -- 下一轮最明显的剩余热点将转入 `GFramework.Game` 等高 warning 基线模块 - - 缓解措施:恢复时先重新跑整仓 build 热点筛选,再决定是否接受更高上下文成本的 `GFramework.Game` 切片,或先排查 solution-level clean 失败原因 +- 当前批次已触发 `$gframework-batch-boot 75` 的主停止条件 + - 缓解措施:本轮提交后停止继续扩批;下一次继续前先评估是否需要基于更新后的 `origin/main` 重新选择基线,或切到新分支 / 新轮次处理剩余 `GFramework.Game.Tests` 热点 +- `GFramework.Game.Tests` 的剩余 warning 主要集中在大文件与集成测试文件 + - 缓解措施:后续若继续,优先把 `YamlConfigLoaderTests.cs` 单独作为一个高上下文切片处理,不要和其它 warning family 混批 ## 活跃文档 @@ -52,16 +53,15 @@ - `dotnet clean GFramework.sln -c Release` - 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本 - `dotnet build GFramework.sln -c Release` - - 结果:成功;`1122 Warning(s)`、`0 Error(s)` -- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` - - 结果:成功;`0 Warning(s)`、`0 Error(s)` -- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"` - - 结果:成功;`Passed: 15`、`Failed: 0` -- `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release` - - 首次并行验证:成功;`1 Warning(s)`、`0 Error(s)`;`MSB3026` 来自与并行 `dotnet test` 竞争同一输出 DLL - - 串行复验:成功;`0 Warning(s)`、`0 Error(s)` + - 结果:成功;`116 Warning(s)`、`0 Error(s)` +- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 结果:失败;clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)`、`0 Error(s)`,未输出额外错误文本 +- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 结果:成功;`71 Warning(s)`、`0 Error(s)` +- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"` + - 结果:成功;`Passed: 63`、`Failed: 0` ## 下一步建议 -1. 提交当前 `GFramework.Godot` / `GFramework.Godot.Tests` warning 清理批次,并继续保持只纳入本 topic 相关文件 -2. 下一轮若继续使用 `$gframework-batch-boot 75`,先决定是优先排查 solution-level `dotnet clean` 失败,还是接受更高上下文成本进入 `GFramework.Game` warning 热点 +1. 提交当前 `GFramework.Game.Tests` warning 清理批次与 `RP-054` tracking 更新,然后停止当前 batch loop,因为 branch diff 已达 `76/75` +2. 下一轮若继续 warning reduction,应先决定是重新整理 `origin/main` 基线,还是单独开一个高上下文批次处理 `YamlConfigLoaderTests.cs` 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 e05a9c54..fcb8b35e 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 @@ -2,6 +2,36 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-24 — RP-054 + +### 阶段:`GFramework.Game.Tests` 低风险测试 warning 批次(触发文件数停止阈值) + +- 触发背景: + - 用户要求“直接进入下一批”,继续沿 `$gframework-batch-boot 75` 自动推进 warning reduction + - 以 `origin/main` 为基线时,上一批提交后分支累计 diff 仍只有 `8` 个文件,足够再落一个独立批次 + - 重新执行 `dotnet clean GFramework.sln -c Release` 仍停在 `ValidateSolutionConfiguration`,因此继续以直接 `dotnet build GFramework.sln -c Release` 的输出挑选低风险热点 +- 主线程实施: + - 从整仓 `Release build` 的 `116 warning(s)` 入口观测值中,选择 `GFramework.Game.Tests` 的小型测试文件和 `PersistenceTestUtilities.cs` 作为当前批次,刻意避开 `YamlConfigLoaderTests.cs` 这类高上下文大文件 + - 在 `YamlConfigLoaderIfThenElseTests.cs`、`YamlConfigLoaderDependentSchemasTests.cs`、`YamlConfigLoaderDependentRequiredTests.cs`、`YamlConfigLoaderNegationTests.cs`、`YamlConfigLoaderAllOfTests.cs`、`YamlConfigLoaderEnumTests.cs`、`YamlConfigTextValidatorTests.cs`、`PersistenceTests.cs` 中补齐 `.ConfigureAwait(false)`,并把字段态 `_rootPath` 的 `ThrowIfNull` 改为显式 `InvalidOperationException` + - 将 `PersistenceTestUtilities.cs` 拆分为 `TestDataLocation.cs`、`TestSaveData.cs`、`TestVersionedSaveData.cs`、`TestSimpleData.cs`、`TestNamedData.cs`,消除 `MA0048` 并对齐仓库的一文件一主类型风格 + - 在 `YamlConfigSchemaValidatorTests.cs` 中把字段态 `_rootPath` 的校验改成显式状态异常,避免继续触发 `MA0015` +- 验证里程碑: + - `dotnet clean GFramework.sln -c Release` + - 结果:失败;停在 `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.sln -c Release` + - 结果:成功;`116 Warning(s)`、`0 Error(s)` + - `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 结果:失败;clean 阶段提前结束,`0 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 第一轮批次后:成功;`80 Warning(s)`、`0 Error(s)` + - 收尾修正后:成功;`71 Warning(s)`、`0 Error(s)` + - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"` + - 结果:成功;`Passed: 63`、`Failed: 0` +- 当前结论: + - `GFramework.Game.Tests` 本轮入口热点已从 `116 warning(s)` 收敛到 `71 warning(s)`,且本轮 touched files 不再出现在 warning 输出中 + - 当前工作树相对 `origin/main` 的累计 diff 已达到 `76` 个文件、`986` 行,超过 `$gframework-batch-boot 75` 的主停止阈值 + - 按批处理技能规则,本轮必须在提交当前批次后停止;剩余候选应在新一轮里单独评估,尤其是 `YamlConfigLoaderTests.cs` + ## 2026-04-24 — RP-053 ### 阶段:`GFramework.Godot` / `GFramework.Godot.Tests` 小批次 warning 清理 From 5aefd77ad0cb80398594e163e6c5e80d0c7ab60d Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 24 Apr 2026 18:29:17 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix(game-tests):=20=E6=94=B6=E6=95=9B?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=8E=E5=BA=8F=E5=88=97=E5=8C=96=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复架构配置、启动流程与序列化测试中的异步等待和 invariant 解析告警 - 补充 AllOf 与 persistence 测试的残余状态校验与 ConfigureAwait 修正,继续压低 Game.Tests warning - 更新 analyzer-warning-reduction 跟踪与 trace,纠正 RP-054 的 stop-condition 口径并记录 RP-055 指标 --- .../ArchitectureConfigIntegrationTests.cs | 26 ++++++------ .../Config/GameConfigBootstrapTests.cs | 16 +++---- .../Config/YamlConfigLoaderAllOfTests.cs | 10 ++++- .../Data/PersistenceTests.cs | 4 +- .../Serializer/JsonSerializerTests.cs | 5 ++- .../analyzer-warning-reduction-tracking.md | 42 +++++++++---------- .../analyzer-warning-reduction-trace.md | 25 +++++++++++ 7 files changed, 79 insertions(+), 49 deletions(-) diff --git a/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs b/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs index 3020f9fd..fb8ae72c 100644 --- a/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs +++ b/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs @@ -39,7 +39,7 @@ public class ArchitectureConfigIntegrationTests try { architecture = new ConsumerArchitecture(rootPath); - await architecture.InitializeAsync(); + await architecture.InitializeAsync().ConfigureAwait(false); initialized = true; var table = architecture.MonsterTable; @@ -63,7 +63,7 @@ public class ArchitectureConfigIntegrationTests { if (architecture is not null && initialized) { - await architecture.DestroyAsync(); + await architecture.DestroyAsync().ConfigureAwait(false); } DeleteDirectoryIfExists(rootPath); @@ -83,7 +83,7 @@ public class ArchitectureConfigIntegrationTests try { architecture = new ConsumerArchitecture(rootPath); - await architecture.InitializeAsync(); + await architecture.InitializeAsync().ConfigureAwait(false); initialized = true; Assert.Multiple(() => @@ -97,7 +97,7 @@ public class ArchitectureConfigIntegrationTests { if (architecture is not null && initialized) { - await architecture.DestroyAsync(); + await architecture.DestroyAsync().ConfigureAwait(false); } DeleteDirectoryIfExists(rootPath); @@ -119,16 +119,16 @@ public class ArchitectureConfigIntegrationTests var module = CreateModule(rootPath); firstArchitecture = new ModuleOnlyArchitecture(module); - await firstArchitecture.InitializeAsync(); + await firstArchitecture.InitializeAsync().ConfigureAwait(false); var wasInitializedBeforeDestroy = module.IsInitialized; - await firstArchitecture.DestroyAsync(); + await firstArchitecture.DestroyAsync().ConfigureAwait(false); firstDestroyed = true; firstArchitecture = null; GameContext.Clear(); var secondArchitecture = new ModuleOnlyArchitecture(module); var exception = - Assert.ThrowsAsync(async () => await secondArchitecture.InitializeAsync()); + Assert.ThrowsAsync(async () => await secondArchitecture.InitializeAsync().ConfigureAwait(false)); Assert.Multiple(() => { @@ -141,7 +141,7 @@ public class ArchitectureConfigIntegrationTests { if (firstArchitecture is not null && !firstDestroyed) { - await firstArchitecture.DestroyAsync(); + await firstArchitecture.DestroyAsync().ConfigureAwait(false); } DeleteDirectoryIfExists(rootPath); @@ -203,7 +203,7 @@ public class ArchitectureConfigIntegrationTests var module = CreateModule(rootPath); readyArchitecture = new ReadyOnlyArchitecture(); - await readyArchitecture.InitializeAsync(); + await readyArchitecture.InitializeAsync().ConfigureAwait(false); readyArchitectureInitialized = true; var exception = Assert.Throws(() => readyArchitecture.InstallModule(module)); @@ -216,13 +216,13 @@ public class ArchitectureConfigIntegrationTests Assert.That(module.IsInitialized, Is.False); }); - await readyArchitecture.DestroyAsync(); + await readyArchitecture.DestroyAsync().ConfigureAwait(false); readyArchitectureInitialized = false; readyArchitecture = null; GameContext.Clear(); retryArchitecture = new ModuleOnlyArchitecture(module); - await retryArchitecture.InitializeAsync(); + await retryArchitecture.InitializeAsync().ConfigureAwait(false); retryArchitectureInitialized = true; Assert.Multiple(() => @@ -235,12 +235,12 @@ public class ArchitectureConfigIntegrationTests { if (retryArchitecture is not null && retryArchitectureInitialized) { - await retryArchitecture.DestroyAsync(); + await retryArchitecture.DestroyAsync().ConfigureAwait(false); } if (readyArchitecture is not null && readyArchitectureInitialized) { - await readyArchitecture.DestroyAsync(); + await readyArchitecture.DestroyAsync().ConfigureAwait(false); } DeleteDirectoryIfExists(rootPath); diff --git a/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs b/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs index c6f28484..f9875933 100644 --- a/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs +++ b/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs @@ -50,7 +50,7 @@ public class GameConfigBootstrapTests var registry = new ConfigRegistry(); using var bootstrap = CreateBootstrap(registry); - await bootstrap.InitializeAsync(); + await bootstrap.InitializeAsync().ConfigureAwait(false); var monsterTable = registry.GetMonsterTable(); @@ -74,7 +74,7 @@ public class GameConfigBootstrapTests CreateMonsterFiles(); using var bootstrap = CreateBootstrap(); - await bootstrap.InitializeAsync(); + await bootstrap.InitializeAsync().ConfigureAwait(false); var reloadTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); bootstrap.StartHotReload( @@ -95,7 +95,7 @@ public class GameConfigBootstrapTests faction: dungeon """); - var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5)); + var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5)).ConfigureAwait(false); var monsterTable = bootstrap.Registry.GetMonsterTable(); Assert.Multiple(() => @@ -163,11 +163,11 @@ public class GameConfigBootstrapTests Is.True, "The first initialization attempt did not reach the guarded lifecycle section."); - var secondCallerException = Assert.ThrowsAsync(async () => await bootstrap.InitializeAsync()); + var secondCallerException = Assert.ThrowsAsync(async () => await bootstrap.InitializeAsync().ConfigureAwait(false)); continueInitialization.Set(); - Assert.DoesNotThrowAsync(async () => await firstInitializeTask); + Assert.DoesNotThrowAsync(async () => await firstInitializeTask.ConfigureAwait(false)); Assert.Multiple(() => { @@ -202,7 +202,7 @@ public class GameConfigBootstrapTests }) }); - var exception = Assert.ThrowsAsync(async () => await bootstrap.InitializeAsync()); + var exception = Assert.ThrowsAsync(async () => await bootstrap.InitializeAsync().ConfigureAwait(false)); Assert.Multiple(() => { @@ -311,12 +311,12 @@ public class GameConfigBootstrapTests /// 任务结果。 private static async Task WaitForTaskWithinAsync(Task task, TimeSpan timeout) { - var completedTask = await Task.WhenAny(task, Task.Delay(timeout)); + var completedTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false); if (!ReferenceEquals(completedTask, task)) { Assert.Fail($"Timed out after {timeout} while waiting for file watcher notification."); } - return await task; + return await task.ConfigureAwait(false); } } diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs index 538d651b..a3468a2b 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs @@ -522,7 +522,10 @@ public sealed class YamlConfigLoaderAllOfTests /// 要写入的 YAML 或 schema 内容。 private void CreateConfigFile(string relativePath, string content) { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } var filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar)); var directoryPath = Path.GetDirectoryName(filePath); @@ -609,7 +612,10 @@ public sealed class YamlConfigLoaderAllOfTests /// 已注册测试表与 schema 路径的加载器。 private YamlConfigLoader CreateMonsterRewardLoader() { - ArgumentNullException.ThrowIfNull(_rootPath); + if (_rootPath is null) + { + throw new InvalidOperationException("Root path is not initialized."); + } return new YamlConfigLoader(_rootPath) .RegisterTable( diff --git a/GFramework.Game.Tests/Data/PersistenceTests.cs b/GFramework.Game.Tests/Data/PersistenceTests.cs index f5549517..4cbcc5e0 100644 --- a/GFramework.Game.Tests/Data/PersistenceTests.cs +++ b/GFramework.Game.Tests/Data/PersistenceTests.cs @@ -271,7 +271,7 @@ public class PersistenceTests continueMigration.Set(); var exception = Assert.ThrowsAsync(async () => await loadTask.ConfigureAwait(false)); - var persisted = await storage.ReadAsync("saves/slot_1/save"); + var persisted = await storage.ReadAsync("saves/slot_1/save").ConfigureAwait(false); Assert.Multiple(() => { @@ -593,7 +593,7 @@ public class PersistenceTests throwingStorage.ThrowOnWrite = true; Assert.ThrowsAsync( - async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 })); + async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }).ConfigureAwait(false)); var cachedAfterFailure = await repository.LoadAsync(primaryLocation); Assert.That(cachedAfterFailure.Value, Is.EqualTo(1)); diff --git a/GFramework.Game.Tests/Serializer/JsonSerializerTests.cs b/GFramework.Game.Tests/Serializer/JsonSerializerTests.cs index a9f7e962..347092a9 100644 --- a/GFramework.Game.Tests/Serializer/JsonSerializerTests.cs +++ b/GFramework.Game.Tests/Serializer/JsonSerializerTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Newtonsoft.Json; using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer; @@ -182,8 +183,8 @@ public sealed class JsonSerializerTests var parts = raw.Split(':'); return new CoordinateStub { - X = int.Parse(parts[0]), - Y = int.Parse(parts[1]) + X = int.Parse(parts[0], CultureInfo.InvariantCulture), + Y = int.Parse(parts[1], CultureInfo.InvariantCulture) }; } } 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 5085564e..94fe2a80 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,35 +6,33 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-054` -- 当前阶段:`Phase 54` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-055` +- 当前阶段:`Phase 55` - 当前焦点: - - `2026-04-24` 本轮继续按 `$gframework-batch-boot 75` 推进,切入 `GFramework.Game.Tests` 的低风险测试 warning,而不进入 `YamlConfigLoaderTests.cs` 等高上下文热点 - - 已完成 `PersistenceTestUtilities` 的单类型拆分,并在多组 YAML / persistence 测试中补齐 `.ConfigureAwait(false)` 与字段态显式状态检查 - - `GFramework.Game.Tests` 当前 `Release` build 已从本轮入口观测值 `116 warning(s)` 收敛到 `71 warning(s)`,且本轮 touched files 已不再出现在 warning 输出里 - - 当前工作树相对 `origin/main` 的累计 diff 已达到 `76` 个文件、`986` 行变更,超过 `$gframework-batch-boot 75` 的主停止阈值;本轮必须在提交后停止继续扩批 + - `2026-04-24` 本轮先纠正了 batch stop-condition 的计算口径:应使用 `origin/main` 与 `HEAD` 的 merge-base 分支 diff,而不是工作树 diff + - 在该正确口径下,`RP-054` 提交后的真实 branch 体积是 `23` 个文件、`603` 行;当前这批提交后的投影体积是 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75` + - 本轮已完成 `ArchitectureConfigIntegrationTests`、`GameConfigBootstrapTests`、`JsonSerializerTests` 的小热点清理,并顺手补齐 `YamlConfigLoaderAllOfTests` / `PersistenceTests` 的残余 warning + - 当前仍在 `GFramework.Game.Tests` 内推进,但剩余热点已经越来越集中到 `YamlConfigLoaderTests.cs` 与 `GeneratedConfigConsumerIntegrationTests.cs` 这类高上下文文件 ## 当前活跃事实 - 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值 -- 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本 -- 本轮直接执行仓库根目录 `dotnet build GFramework.sln -c Release` 成功,并给出 `116 warning(s)` 的当前整仓入口观测值;其中低风险热点主要落在 `GFramework.Game.Tests` -- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 在本轮收尾验证中为 `71 Warning(s)`、`0 Error(s)`;剩余 warning 已集中在未触碰的 `YamlConfigLoaderTests.cs`、`GeneratedConfigConsumerIntegrationTests.cs`、`GameConfigBootstrapTests.cs`、`ArchitectureConfigIntegrationTests.cs`、`JsonSerializerTests.cs` -- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"`,结果为 `Passed: 63` -- `PersistenceTestUtilities.cs` 已拆分为 `TestDataLocation.cs`、`TestSaveData.cs`、`TestVersionedSaveData.cs`、`TestSimpleData.cs`、`TestNamedData.cs`,与仓库“一文件一主类型”风格对齐 +- 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线 +- 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release` 的 `116 warning(s)` +- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 已从上一批入口的 `116 warning(s)` 继续收敛到本轮收尾的 `63 warning(s)` +- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`,结果为 `Passed: 19` +- `GFramework.Game.Tests` 当前剩余 warning 主要集中在未触碰的 `YamlConfigLoaderTests.cs`、`GeneratedConfigConsumerIntegrationTests.cs`,以及少量未处理的 `GameConfigBootstrapTests` 之外热点 ## 当前风险 - 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0 - 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build` -- 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线 - - 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level / project-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 direct build 观测值 +- 仓库根目录与 `GFramework.Game.Tests` 的 `dotnet clean` 目前都无法给出新的 clean 基线 + - 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值 - 当前 worktree 仍存在未跟踪的 `.codex` 目录 - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交 -- 当前批次已触发 `$gframework-batch-boot 75` 的主停止条件 - - 缓解措施:本轮提交后停止继续扩批;下一次继续前先评估是否需要基于更新后的 `origin/main` 重新选择基线,或切到新分支 / 新轮次处理剩余 `GFramework.Game.Tests` 热点 -- `GFramework.Game.Tests` 的剩余 warning 主要集中在大文件与集成测试文件 - - 缓解措施:后续若继续,优先把 `YamlConfigLoaderTests.cs` 单独作为一个高上下文切片处理,不要和其它 warning family 混批 +- 下一轮若继续深入 `GFramework.Game.Tests`,很可能需要进入 `YamlConfigLoaderTests.cs` 这种高上下文大文件 + - 缓解措施:把它单独作为一个明确的新批次处理,不与其它 warning family 混批 ## 活跃文档 @@ -57,11 +55,11 @@ - `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - 结果:失败;clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)`、`0 Error(s)`,未输出额外错误文本 - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 结果:成功;`71 Warning(s)`、`0 Error(s)` -- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"` - - 结果:成功;`Passed: 63`、`Failed: 0` + - 结果:成功;`63 Warning(s)`、`0 Error(s)` +- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"` + - 结果:成功;`Passed: 19`、`Failed: 0` ## 下一步建议 -1. 提交当前 `GFramework.Game.Tests` warning 清理批次与 `RP-054` tracking 更新,然后停止当前 batch loop,因为 branch diff 已达 `76/75` -2. 下一轮若继续 warning reduction,应先决定是重新整理 `origin/main` 基线,还是单独开一个高上下文批次处理 `YamlConfigLoaderTests.cs` +1. 提交当前 `GFramework.Game.Tests` 小热点批次与 `RP-055` tracking 更新,继续保持只纳入本 topic 相关文件 +2. 下一轮若继续 warning reduction,应优先决定是否接受进入 `YamlConfigLoaderTests.cs` 的高上下文批次 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 fcb8b35e..b00458f6 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 @@ -2,6 +2,31 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-24 — RP-055 + +### 阶段:修正 stop-condition 口径并继续 `GFramework.Game.Tests` 小热点 + +- 触发背景: + - `RP-054` 之后复核 batch stop-condition 时,发现之前一度把工作树 diff 错当成了 skill 要求的 branch diff + - 按正确口径 `merge-base(origin/main, HEAD)` 计算,`RP-054` 提交后的真实分支体积是 `23` 个文件、`603` 行,因此仍可继续下一批 + - 当前剩余 warning 里,`ArchitectureConfigIntegrationTests`、`GameConfigBootstrapTests`、`JsonSerializerTests` 属于独立且低风险的小切片 +- 主线程实施: + - 在 `ArchitectureConfigIntegrationTests.cs` 中补齐异步架构初始化 / 销毁和异常断言的 `.ConfigureAwait(false)` + - 在 `GameConfigBootstrapTests.cs` 中补齐启动流程、并发初始化断言与 `WaitForTaskWithinAsync` 的 `.ConfigureAwait(false)` + - 在 `JsonSerializerTests.cs` 中将坐标解析改为 `CultureInfo.InvariantCulture` + - 顺手清理 `YamlConfigLoaderAllOfTests.cs` 与 `PersistenceTests.cs` 中上一批遗漏的字段态状态检查和异步等待 warning + - 纠正 active tracking:明确 stop-condition 必须使用 `origin/main...HEAD` 的 merge-base 分支 diff,而不是工作树 diff +- 验证里程碑: + - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 并行误用 build/test 时:出现 `MSB3026` / `CS2012` 文件占用噪声,不计入代码结论 + - 串行复验:成功;`63 Warning(s)`、`0 Error(s)` + - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"` + - 结果:成功;`Passed: 19`、`Failed: 0` +- 当前结论: + - `GFramework.Game.Tests` 已从上一批收尾时的 `71 warning(s)` 进一步降到 `63 warning(s)` + - 这次提交后的分支体积投影为 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75` + - 剩余热点越来越集中到 `YamlConfigLoaderTests.cs` 与 `GeneratedConfigConsumerIntegrationTests.cs`,后续继续时应把它们视为高上下文批次 + ## 2026-04-24 — RP-054 ### 阶段:`GFramework.Game.Tests` 低风险测试 warning 批次(触发文件数停止阈值) From 56ed66976be3b9df3f998f6a88507bf9cf745f43 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:31:37 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix(game-tests):=20=E6=B8=85=E7=90=86?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=85=8D=E7=BD=AE=E6=B6=88=E8=B4=B9=E8=80=85?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 GeneratedConfigConsumerIntegrationTests 的 raw string 缩进和方法边界,恢复编译通过\n- 重构生成配置消费者集成测试的断言辅助方法,清理该文件剩余 warning\n- 更新 analyzer warning reduction 的 tracking 与 trace,记录 RP-056 验证结果和当前分支体积 --- ...GeneratedConfigConsumerIntegrationTests.cs | 226 +++++++++++------- .../analyzer-warning-reduction-tracking.md | 25 +- .../analyzer-warning-reduction-trace.md | 23 ++ 3 files changed, 182 insertions(+), 92 deletions(-) diff --git a/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs b/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs index 98bf01d8..ca20065c 100644 --- a/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs +++ b/GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs @@ -50,65 +50,12 @@ public class GeneratedConfigConsumerIntegrationTests var loader = new YamlConfigLoader(_rootPath) .RegisterAllGeneratedConfigTables(); - await loader.LoadAsync(registry); + await loader.LoadAsync(registry).ConfigureAwait(false); var monsterTable = registry.GetMonsterTable(); - var dungeonMonsters = monsterTable.FindByFaction("dungeon"); var itemTable = registry.GetItemTable(); - Assert.Multiple(() => - { - Assert.That( - GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName), - Is.SupersetOf(new[] { "item", "monster" })); - Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True); - Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item")); - Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item")); - Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json")); - Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True); - Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster")); - Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster")); - Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json")); - Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item")); - Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item")); - Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster")); - Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster")); - Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster")); - Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json")); - Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain)); - Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName)); - Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath, - Is.EqualTo(MonsterConfigBindings.ConfigRelativePath)); - Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath, - Is.EqualTo(MonsterConfigBindings.SchemaRelativePath)); - Assert.That(MonsterConfigBindings.References.All, Is.Empty); - Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False); - Assert.That(monsterTable.Count, Is.EqualTo(2)); - Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime")); - Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30)); - Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 })); - Assert.That(dungeonMonsters.Select(static config => config.Name), - Is.EquivalentTo(new[] { "Slime", "Goblin" })); - Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True); - Assert.That(goblin, Is.Not.Null); - Assert.That(goblin!.Id, Is.EqualTo(2)); - Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True); - Assert.That(firstDungeonMonster, Is.Not.Null); - Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin")); - Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False); - Assert.That(missingMonster, Is.Null); - Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True); - Assert.That(generatedTable, Is.Not.Null); - Assert.That(generatedTable!.All().Select(static config => config.Name), - Is.EquivalentTo(new[] { "Slime", "Goblin" })); - Assert.That(itemTable.Count, Is.EqualTo(2)); - Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion")); - Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id), - Is.EquivalentTo(new[] { "potion", "ether" })); - Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True); - Assert.That(generatedItemTable, Is.Not.Null); - Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether")); - }); + AssertGeneratedBindingsLoadResults(registry, monsterTable, itemTable); } /// @@ -181,7 +128,7 @@ public class GeneratedConfigConsumerIntegrationTests { IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain } }); - await domainLoader.LoadAsync(domainRegistry); + await domainLoader.LoadAsync(domainRegistry).ConfigureAwait(false); var tableNameRegistry = new ConfigRegistry(); var tableNameLoader = new YamlConfigLoader(_rootPath) @@ -190,7 +137,7 @@ public class GeneratedConfigConsumerIntegrationTests { IncludedTableNames = new[] { ItemConfigBindings.TableName } }); - await tableNameLoader.LoadAsync(tableNameRegistry); + await tableNameLoader.LoadAsync(tableNameRegistry).ConfigureAwait(false); var emptyAllowListRegistry = new ConfigRegistry(); var emptyAllowListLoader = new YamlConfigLoader(_rootPath) @@ -200,7 +147,7 @@ public class GeneratedConfigConsumerIntegrationTests IncludedConfigDomains = Array.Empty(), IncludedTableNames = Array.Empty() }); - await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry); + await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry).ConfigureAwait(false); var monsterDomain = MonsterConfigBindings.ConfigDomain; var predicateRegistry = new ConfigRegistry(); @@ -211,28 +158,13 @@ public class GeneratedConfigConsumerIntegrationTests TableFilter = metadata => string.Equals(metadata.ConfigDomain, monsterDomain, StringComparison.Ordinal) }); - await predicateLoader.LoadAsync(predicateRegistry); + await predicateLoader.LoadAsync(predicateRegistry).ConfigureAwait(false); - Assert.Multiple(() => - { - Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True); - Assert.That(emptyAllowListMonsterTable, Is.Not.Null); - Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True); - Assert.That(emptyAllowListItemTable, Is.Not.Null); - - Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True); - Assert.That(domainMonsterTable, Is.Not.Null); - Assert.That(domainRegistry.TryGetItemTable(out _), Is.False); - - Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False); - Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True); - Assert.That(tableNameItemTable, Is.Not.Null); - Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion")); - - Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True); - Assert.That(predicateMonsterTable, Is.Not.Null); - Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False); - }); + AssertGeneratedRegistrationFilteringResults( + domainRegistry, + tableNameRegistry, + emptyAllowListRegistry, + predicateRegistry); } /// @@ -270,7 +202,7 @@ public class GeneratedConfigConsumerIntegrationTests MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml)); Assert.DoesNotThrowAsync(async () => - await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml)); + await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false)); var invalidYaml = """ id: 3 @@ -282,7 +214,7 @@ public class GeneratedConfigConsumerIntegrationTests var exception = Assert.Throws(() => MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", invalidYaml)); var asyncException = Assert.ThrowsAsync(async () => - await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml)); + await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml).ConfigureAwait(false)); Assert.Multiple(() => { @@ -367,6 +299,138 @@ public class GeneratedConfigConsumerIntegrationTests """); } + /// + /// 统一断言生成绑定加载后的目录元数据、查询入口与强类型表包装结果, + /// 以便缩短端到端测试主体并降低分析器对方法长度的告警。 + /// + private static void AssertGeneratedBindingsLoadResults( + ConfigRegistry registry, + MonsterTable monsterTable, + ItemTable itemTable) + { + AssertGeneratedCatalogMetadata(); + AssertGeneratedMonsterTableResults(registry, monsterTable); + AssertGeneratedItemTableResults(registry, itemTable); + } + + /// + /// 断言消费者项目的生成目录元数据与静态绑定常量保持一致。 + /// + private static void AssertGeneratedCatalogMetadata() + { + Assert.Multiple(() => + { + Assert.That( + GeneratedConfigCatalog.Tables.Select(static metadata => metadata.TableName), + Is.SupersetOf(new[] { "item", "monster" })); + Assert.That(GeneratedConfigCatalog.TryGetByTableName("item", out var itemCatalogEntry), Is.True); + Assert.That(itemCatalogEntry.ConfigDomain, Is.EqualTo("item")); + Assert.That(itemCatalogEntry.ConfigRelativePath, Is.EqualTo("item")); + Assert.That(itemCatalogEntry.SchemaRelativePath, Is.EqualTo("schemas/item.schema.json")); + Assert.That(GeneratedConfigCatalog.TryGetByTableName("monster", out var catalogEntry), Is.True); + Assert.That(catalogEntry.ConfigDomain, Is.EqualTo("monster")); + Assert.That(catalogEntry.ConfigRelativePath, Is.EqualTo("monster")); + Assert.That(catalogEntry.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json")); + Assert.That(ItemConfigBindings.ConfigDomain, Is.EqualTo("item")); + Assert.That(ItemConfigBindings.Metadata.TableName, Is.EqualTo("item")); + Assert.That(MonsterConfigBindings.ConfigDomain, Is.EqualTo("monster")); + Assert.That(MonsterConfigBindings.TableName, Is.EqualTo("monster")); + Assert.That(MonsterConfigBindings.ConfigRelativePath, Is.EqualTo("monster")); + Assert.That(MonsterConfigBindings.SchemaRelativePath, Is.EqualTo("schemas/monster.schema.json")); + Assert.That(MonsterConfigBindings.Metadata.ConfigDomain, Is.EqualTo(MonsterConfigBindings.ConfigDomain)); + Assert.That(MonsterConfigBindings.Metadata.TableName, Is.EqualTo(MonsterConfigBindings.TableName)); + Assert.That(MonsterConfigBindings.Metadata.ConfigRelativePath, + Is.EqualTo(MonsterConfigBindings.ConfigRelativePath)); + Assert.That(MonsterConfigBindings.Metadata.SchemaRelativePath, + Is.EqualTo(MonsterConfigBindings.SchemaRelativePath)); + Assert.That(MonsterConfigBindings.References.All, Is.Empty); + Assert.That(MonsterConfigBindings.References.TryGetByDisplayPath("dropItems", out _), Is.False); + }); + } + + /// + /// 断言 monster 绑定在注册表中的查询辅助、索引查询与强类型访问入口都可用。 + /// + private static void AssertGeneratedMonsterTableResults( + ConfigRegistry registry, + MonsterTable monsterTable) + { + var dungeonMonsters = monsterTable.FindByFaction("dungeon"); + + Assert.Multiple(() => + { + Assert.That(monsterTable.Count, Is.EqualTo(2)); + Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime")); + Assert.That(monsterTable.Get(2).Hp, Is.EqualTo(30)); + Assert.That(monsterTable.FindByName("Slime").Select(static config => config.Id), Is.EqualTo(new[] { 1 })); + Assert.That(dungeonMonsters.Select(static config => config.Name), + Is.EquivalentTo(new[] { "Slime", "Goblin" })); + Assert.That(monsterTable.TryFindFirstByName("Goblin", out var goblin), Is.True); + Assert.That(goblin, Is.Not.Null); + Assert.That(goblin!.Id, Is.EqualTo(2)); + Assert.That(monsterTable.TryFindFirstByFaction("dungeon", out var firstDungeonMonster), Is.True); + Assert.That(firstDungeonMonster, Is.Not.Null); + Assert.That(firstDungeonMonster!.Name, Is.AnyOf("Slime", "Goblin")); + Assert.That(monsterTable.TryFindFirstByFaction("forest", out var missingMonster), Is.False); + Assert.That(missingMonster, Is.Null); + Assert.That(registry.TryGetMonsterTable(out var generatedTable), Is.True); + Assert.That(generatedTable, Is.Not.Null); + Assert.That(generatedTable!.All().Select(static config => config.Name), + Is.EquivalentTo(new[] { "Slime", "Goblin" })); + }); + } + + /// + /// 断言 item 绑定的强类型表包装与按分类查询在聚合注册路径下可正常工作。 + /// + private static void AssertGeneratedItemTableResults( + ConfigRegistry registry, + ItemTable itemTable) + { + Assert.Multiple(() => + { + Assert.That(itemTable.Count, Is.EqualTo(2)); + Assert.That(itemTable.Get("potion").Name, Is.EqualTo("Potion")); + Assert.That(itemTable.FindByCategory("consumable").Select(static config => config.Id), + Is.EquivalentTo(new[] { "potion", "ether" })); + Assert.That(registry.TryGetItemTable(out var generatedItemTable), Is.True); + Assert.That(generatedItemTable, Is.Not.Null); + Assert.That(generatedItemTable!.Get("ether").Name, Is.EqualTo("Ether")); + }); + } + + /// + /// 汇总断言不同聚合注册筛选条件下的装载结果, + /// 让测试主体聚焦于注册参数本身而不是展开大量重复断言。 + /// + private static void AssertGeneratedRegistrationFilteringResults( + ConfigRegistry domainRegistry, + ConfigRegistry tableNameRegistry, + ConfigRegistry emptyAllowListRegistry, + ConfigRegistry predicateRegistry) + { + Assert.Multiple(() => + { + Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True); + Assert.That(emptyAllowListMonsterTable, Is.Not.Null); + Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True); + Assert.That(emptyAllowListItemTable, Is.Not.Null); + + Assert.That(domainRegistry.TryGetMonsterTable(out var domainMonsterTable), Is.True); + Assert.That(domainMonsterTable, Is.Not.Null); + Assert.That(domainRegistry.TryGetItemTable(out _), Is.False); + + Assert.That(tableNameRegistry.TryGetMonsterTable(out _), Is.False); + Assert.That(tableNameRegistry.TryGetItemTable(out var tableNameItemTable), Is.True); + Assert.That(tableNameItemTable, Is.Not.Null); + Assert.That(tableNameItemTable!.Get("potion").Name, Is.EqualTo("Potion")); + + Assert.That(predicateRegistry.TryGetMonsterTable(out var predicateMonsterTable), Is.True); + Assert.That(predicateMonsterTable, Is.Not.Null); + Assert.That(predicateRegistry.TryGetItemTable(out _), Is.False); + }); + } + /// /// 在临时消费者目录中创建 item schema 与 YAML 测试数据,用于验证多表聚合注册和筛选行为。 /// 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 94fe2a80..00b10d77 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,22 +6,22 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-055` -- 当前阶段:`Phase 55` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-056` +- 当前阶段:`Phase 56` - 当前焦点: - - `2026-04-24` 本轮先纠正了 batch stop-condition 的计算口径:应使用 `origin/main` 与 `HEAD` 的 merge-base 分支 diff,而不是工作树 diff - - 在该正确口径下,`RP-054` 提交后的真实 branch 体积是 `23` 个文件、`603` 行;当前这批提交后的投影体积是 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75` - - 本轮已完成 `ArchitectureConfigIntegrationTests`、`GameConfigBootstrapTests`、`JsonSerializerTests` 的小热点清理,并顺手补齐 `YamlConfigLoaderAllOfTests` / `PersistenceTests` 的残余 warning - - 当前仍在 `GFramework.Game.Tests` 内推进,但剩余热点已经越来越集中到 `YamlConfigLoaderTests.cs` 与 `GeneratedConfigConsumerIntegrationTests.cs` 这类高上下文文件 + - `2026-04-24` 本轮延续 `RP-055` 的 `GFramework.Game.Tests` 小热点批次,修复了 `GeneratedConfigConsumerIntegrationTests.cs` 中 raw string 缩进导致的编译错误 + - 进一步将 `GeneratedConfigConsumerIntegrationTests.cs` 的长断言逻辑拆分为多个辅助方法,并补齐异步等待的 `.ConfigureAwait(false)`,使该文件不再出现在项目构建 warning 输出中 + - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 已从上一轮收尾的 `63 Warning(s)` 进一步收敛到 `59 Warning(s)` + - 按当前工作树投影重新计算后,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75` ## 当前活跃事实 - 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值 - 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线 - 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release` 的 `116 warning(s)` -- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 已从上一批入口的 `116 warning(s)` 继续收敛到本轮收尾的 `63 warning(s)` -- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`,结果为 `Passed: 19` -- `GFramework.Game.Tests` 当前剩余 warning 主要集中在未触碰的 `YamlConfigLoaderTests.cs`、`GeneratedConfigConsumerIntegrationTests.cs`,以及少量未处理的 `GameConfigBootstrapTests` 之外热点 +- `RP-055` 后续补批已验证 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`,结果为 `59 Warning(s)`、`0 Error(s)` +- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`,结果为 `Passed: 4` +- `GeneratedConfigConsumerIntegrationTests.cs` 当前已不再出现在项目 build warning 输出中;`GFramework.Game.Tests` 剩余热点进一步集中到未触碰的 `YamlConfigLoaderTests.cs` 等高上下文文件 ## 当前风险 @@ -55,11 +55,14 @@ - `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - 结果:失败;clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)`、`0 Error(s)`,未输出额外错误文本 - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 结果:成功;`63 Warning(s)`、`0 Error(s)` + - `RP-055` 收尾结果:成功;`63 Warning(s)`、`0 Error(s)` + - `RP-056` 当前结果:成功;`59 Warning(s)`、`0 Error(s)` - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"` - 结果:成功;`Passed: 19`、`Failed: 0` +- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"` + - 结果:成功;`Passed: 4`、`Failed: 0` ## 下一步建议 -1. 提交当前 `GFramework.Game.Tests` 小热点批次与 `RP-055` tracking 更新,继续保持只纳入本 topic 相关文件 +1. 提交 `GeneratedConfigConsumerIntegrationTests.cs` 与 `RP-056` tracking/trace 更新,继续保持只纳入本 topic 相关文件 2. 下一轮若继续 warning reduction,应优先决定是否接受进入 `YamlConfigLoaderTests.cs` 的高上下文批次 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 b00458f6..164df0dc 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 @@ -2,6 +2,29 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-24 — RP-056 + +### 阶段:修复 `GeneratedConfigConsumerIntegrationTests` 编译错误并清零该文件 warning + +- 触发背景: + - `RP-055` 继续推进时,`GeneratedConfigConsumerIntegrationTests.cs` 在 raw string `invalidYaml` 段落附近出现 `CS8999`,导致 `GFramework.Game.Tests` 暂时无法编译 + - 该文件同时仍是项目内少数残留 warning 热点之一,因此适合作为同一批次中的单文件收尾 +- 主线程实施: + - 修复 `GeneratedConfigConsumerIntegrationTests.cs` 中损坏的 `CreateMonsterFiles` raw string 与方法边界,恢复文件可编译状态 + - 保留并整理上一轮已开始的 `.ConfigureAwait(false)` 与断言 helper 抽取 + - 继续将 `AssertGeneratedBindingsLoadResults` 再拆分为 catalog / monster / item 三个辅助方法,清除该文件剩余 `MA0051` + - 更新 active tracking / trace,沿用 `merge-base(origin/main, HEAD)` 作为 `$gframework-batch-boot 75` 的唯一 stop-condition 口径 +- 验证里程碑: + - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 结果:成功;`59 Warning(s)`、`0 Error(s)` + - 结论:`GeneratedConfigConsumerIntegrationTests.cs` 不再出现在 warning 输出中 + - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"` + - 结果:成功;`Passed: 4`、`Failed: 0` +- 当前结论: + - `GFramework.Game.Tests` 已从 `RP-055` 收尾时的 `63 warning(s)` 进一步收敛到 `59 warning(s)` + - 当前工作树投影下,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75` + - 后续若继续自动推进,最自然的下一批将进入 `YamlConfigLoaderTests.cs` 这类高上下文大文件 + ## 2026-04-24 — RP-055 ### 阶段:修正 stop-condition 口径并继续 `GFramework.Game.Tests` 小热点 From 2b707343577193fc9904517e6078149653e95698 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 24 Apr 2026 20:05:14 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix(game-tests):=20=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E6=B5=8B=E8=AF=95=E6=AE=8B=E4=BD=99?= =?UTF-8?q?=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 PersistenceTests 中统一设置仓库失败场景测试的剩余 ConfigureAwait 告警\n- 验证 PersistenceTests 不再出现在非增量 GFramework.Game.Tests 构建告警输出中\n- 更新 analyzer warning reduction 的 tracking 与 trace,记录 RP-057 验证结果和当前分支体积 --- .../Data/PersistenceTests.cs | 19 +++++++------ .../analyzer-warning-reduction-tracking.md | 28 +++++++++++-------- .../analyzer-warning-reduction-trace.md | 23 +++++++++++++++ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/GFramework.Game.Tests/Data/PersistenceTests.cs b/GFramework.Game.Tests/Data/PersistenceTests.cs index 4cbcc5e0..4daefddc 100644 --- a/GFramework.Game.Tests/Data/PersistenceTests.cs +++ b/GFramework.Game.Tests/Data/PersistenceTests.cs @@ -595,11 +595,11 @@ public class PersistenceTests Assert.ThrowsAsync( async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }).ConfigureAwait(false)); - var cachedAfterFailure = await repository.LoadAsync(primaryLocation); + var cachedAfterFailure = await repository.LoadAsync(primaryLocation).ConfigureAwait(false); Assert.That(cachedAfterFailure.Value, Is.EqualTo(1)); throwingStorage.ThrowOnWrite = false; - await repository.SaveAsync(secondaryLocation, new TestSimpleData { Value = 7 }); + await repository.SaveAsync(secondaryLocation, new TestSimpleData { Value = 7 }).ConfigureAwait(false); using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json"); var verifyRepository = new UnifiedSettingsDataRepository( @@ -609,8 +609,8 @@ public class PersistenceTests verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData)); verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData)); - var persistedPrimary = await verifyRepository.LoadAsync(primaryLocation); - var persistedSecondary = await verifyRepository.LoadAsync(secondaryLocation); + var persistedPrimary = await verifyRepository.LoadAsync(primaryLocation).ConfigureAwait(false); + var persistedSecondary = await verifyRepository.LoadAsync(secondaryLocation).ConfigureAwait(false); Assert.Multiple(() => { @@ -655,12 +655,13 @@ public class PersistenceTests repository.RegisterDataType(secondaryLocation, typeof(TestSimpleData)); throwingStorage.ThrowOnWrite = true; - Assert.ThrowsAsync(async () => await repository.DeleteAsync(secondaryLocation)); + Assert.ThrowsAsync( + async () => await repository.DeleteAsync(secondaryLocation).ConfigureAwait(false)); - Assert.That(await repository.ExistsAsync(secondaryLocation), Is.True); + Assert.That(await repository.ExistsAsync(secondaryLocation).ConfigureAwait(false), Is.True); throwingStorage.ThrowOnWrite = false; - await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 9 }); + await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 9 }).ConfigureAwait(false); using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json"); var verifyRepository = new UnifiedSettingsDataRepository( @@ -670,8 +671,8 @@ public class PersistenceTests verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData)); verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData)); - var persistedPrimary = await verifyRepository.LoadAsync(primaryLocation); - var persistedSecondary = await verifyRepository.LoadAsync(secondaryLocation); + var persistedPrimary = await verifyRepository.LoadAsync(primaryLocation).ConfigureAwait(false); + var persistedSecondary = await verifyRepository.LoadAsync(secondaryLocation).ConfigureAwait(false); Assert.Multiple(() => { 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 00b10d77..41fe7647 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,22 +6,23 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-056` -- 当前阶段:`Phase 56` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-057` +- 当前阶段:`Phase 57` - 当前焦点: - - `2026-04-24` 本轮延续 `RP-055` 的 `GFramework.Game.Tests` 小热点批次,修复了 `GeneratedConfigConsumerIntegrationTests.cs` 中 raw string 缩进导致的编译错误 - - 进一步将 `GeneratedConfigConsumerIntegrationTests.cs` 的长断言逻辑拆分为多个辅助方法,并补齐异步等待的 `.ConfigureAwait(false)`,使该文件不再出现在项目构建 warning 输出中 - - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 已从上一轮收尾的 `63 Warning(s)` 进一步收敛到 `59 Warning(s)` - - 按当前工作树投影重新计算后,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75` + - `2026-04-24` 本轮继续吃掉 `GFramework.Game.Tests` 中仍然独立且低风险的 `PersistenceTests.cs` 残余 `MA0004` + - 已补齐两个失败缓存一致性测试中的剩余 `.ConfigureAwait(false)`,使 `PersistenceTests.cs` 不再出现在非增量构建 warning 输出中 + - 非增量 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 从 `253 Warning(s)` 进一步收敛到 `249 Warning(s)`,剩余热点几乎全部集中在 `YamlConfigLoaderTests.cs` + - 按当前工作树投影重新计算后,分支体积为 `27` 个文件、`991` 行,仍低于 `$gframework-batch-boot 75` ## 当前活跃事实 - 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值 - 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线 - 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release` 的 `116 warning(s)` -- `RP-055` 后续补批已验证 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`,结果为 `59 Warning(s)`、`0 Error(s)` -- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`,结果为 `Passed: 4` -- `GeneratedConfigConsumerIntegrationTests.cs` 当前已不再出现在项目 build warning 输出中;`GFramework.Game.Tests` 剩余热点进一步集中到未触碰的 `YamlConfigLoaderTests.cs` 等高上下文文件 +- `RP-056` 已验证 `GeneratedConfigConsumerIntegrationTests.cs` 不再出现在项目 build warning 输出中 +- `RP-057` 已验证 `PersistenceTests.cs` 不再出现在 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 的 warning 输出中 +- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`,结果为 `Passed: 2` +- `GFramework.Game.Tests` 当前剩余热点已经几乎完全集中到 `YamlConfigLoaderTests.cs` 这一高上下文文件 ## 当前风险 @@ -57,12 +58,17 @@ - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - `RP-055` 收尾结果:成功;`63 Warning(s)`、`0 Error(s)` - `RP-056` 当前结果:成功;`59 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` + - `RP-057` 热点重排前:成功;`253 Warning(s)`、`0 Error(s)` + - `RP-057` 当前结果:成功;`249 Warning(s)`、`0 Error(s)` - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"` - 结果:成功;`Passed: 19`、`Failed: 0` - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"` - 结果:成功;`Passed: 4`、`Failed: 0` +- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"` + - 结果:成功;`Passed: 2`、`Failed: 0` ## 下一步建议 -1. 提交 `GeneratedConfigConsumerIntegrationTests.cs` 与 `RP-056` tracking/trace 更新,继续保持只纳入本 topic 相关文件 -2. 下一轮若继续 warning reduction,应优先决定是否接受进入 `YamlConfigLoaderTests.cs` 的高上下文批次 +1. 提交 `PersistenceTests.cs` 与 `RP-057` tracking/trace 更新,继续保持只纳入本 topic 相关文件 +2. 若继续 warning reduction,应把 `YamlConfigLoaderTests.cs` 视为独立的高上下文批次,不再与其它文件混批 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 164df0dc..5363a4d3 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 @@ -2,6 +2,29 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-24 — RP-057 + +### 阶段:清理 `PersistenceTests.cs` 残余 `MA0004` + +- 触发背景: + - `RP-056` 提交后重新做非增量热点排序时,`GFramework.Game.Tests` 的剩余测试项目 warning 已明显收敛,只剩 `PersistenceTests.cs` 少量 `MA0004` 与 `YamlConfigLoaderTests.cs` 大量 warning + - 为避免在同一轮直接进入 `YamlConfigLoaderTests.cs` 的大文件高上下文批次,先吃掉 `PersistenceTests.cs` 这个独立小切片 +- 主线程实施: + - 在 `PersistenceTests.cs` 中为统一设置仓库失败缓存一致性相关测试补齐剩余 `.ConfigureAwait(false)` + - 覆盖保存失败与删除失败两个测试场景中的缓存读取、存在性检查、后续保存和最终验证读取 + - 更新 active tracking / trace,明确下一批若继续推进应单独进入 `YamlConfigLoaderTests.cs` +- 验证里程碑: + - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` + - 热点重排前:成功;`253 Warning(s)`、`0 Error(s)` + - 修复后:成功;`249 Warning(s)`、`0 Error(s)` + - 结论:`PersistenceTests.cs` 不再出现在 warning 输出中 + - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"` + - 结果:成功;`Passed: 2`、`Failed: 0` +- 当前结论: + - `PersistenceTests.cs` 的残余 warning 已清零,`GFramework.Game.Tests` 剩余热点几乎全部压缩到了 `YamlConfigLoaderTests.cs` + - 当前工作树投影下,分支体积为 `27` 个文件、`991` 行,仍低于 `$gframework-batch-boot 75` + - 按 batch skill 的低风险边界,这一轮应在提交后收口;下一轮再把 `YamlConfigLoaderTests.cs` 作为单独批次处理 + ## 2026-04-24 — RP-056 ### 阶段:修复 `GeneratedConfigConsumerIntegrationTests` 编译错误并清零该文件 warning From 1e5ca14620d6f13178aa19bf6925885d5eed3ae0 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:18:23 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix(godot):=20=E4=BF=AE=E5=A4=8D=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E8=A1=8C=E4=B8=BA=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 SceneBehaviorBase 中暂停、恢复、卸载方法的缩进,消除 PR review 指向的格式问题 - 更新 analyzer-warning-reduction 的 tracking 与 trace,记录 PR #286 latest-head review 跟进和验证结果 - 补充 GFramework.Godot 的 Release build 与 dotnet format verify 结论,保留后续 warning reduction 恢复点 --- GFramework.Godot/Scene/SceneBehaviorBase.cs | 34 +++++++++---------- .../analyzer-warning-reduction-tracking.md | 28 ++++++++++----- .../analyzer-warning-reduction-trace.md | 25 ++++++++++++++ 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/GFramework.Godot/Scene/SceneBehaviorBase.cs b/GFramework.Godot/Scene/SceneBehaviorBase.cs index fee000f5..826be58f 100644 --- a/GFramework.Godot/Scene/SceneBehaviorBase.cs +++ b/GFramework.Godot/Scene/SceneBehaviorBase.cs @@ -141,11 +141,11 @@ public abstract class SceneBehaviorBase : ISceneBehavior /// 当场景被其他场景覆盖或失去焦点时调用。 /// /// 表示暂停操作完成的ValueTask。 - public virtual async ValueTask OnPauseAsync() - { - if (_scene != null) - // 暂停后紧接着会修改 Owner 的处理开关,必须回到 Godot 主线程继续执行。 - await _scene.OnPauseAsync().ConfigureAwait(true); + public virtual async ValueTask OnPauseAsync() + { + if (_scene != null) + // 暂停后紧接着会修改 Owner 的处理开关,必须回到 Godot 主线程继续执行。 + await _scene.OnPauseAsync().ConfigureAwait(true); // 暂停处理 Owner.SetProcess(false); @@ -160,14 +160,14 @@ public abstract class SceneBehaviorBase : ISceneBehavior /// 当场景重新获得焦点或从暂停状态恢复时调用。 /// /// 表示恢复操作完成的ValueTask。 - public virtual async ValueTask OnResumeAsync() - { - if (Owner.IsInvalidNode()) - return; + public virtual async ValueTask OnResumeAsync() + { + if (Owner.IsInvalidNode()) + return; - if (_scene != null) - // 恢复完成后要立刻重新启用节点处理流程,因此显式保留当前同步上下文。 - await _scene.OnResumeAsync().ConfigureAwait(true); + if (_scene != null) + // 恢复完成后要立刻重新启用节点处理流程,因此显式保留当前同步上下文。 + await _scene.OnResumeAsync().ConfigureAwait(true); // 恢复处理 Owner.SetProcess(true); @@ -197,11 +197,11 @@ public abstract class SceneBehaviorBase : ISceneBehavior /// 在场景完全退出后调用,释放占用的内存和资源。 /// /// 表示卸载操作完成的ValueTask。 - public virtual async ValueTask OnUnloadAsync() - { - if (_scene != null) - // 卸载后的 QueueFreeX 必须在 Godot 节点线程上调用,不能切走同步上下文。 - await _scene.OnUnloadAsync().ConfigureAwait(true); + public virtual async ValueTask OnUnloadAsync() + { + if (_scene != null) + // 卸载后的 QueueFreeX 必须在 Godot 节点线程上调用,不能切走同步上下文。 + await _scene.OnUnloadAsync().ConfigureAwait(true); // 释放节点 Owner.QueueFreeX(); 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 41fe7647..cfeb8489 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,13 +6,13 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-057` -- 当前阶段:`Phase 57` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-058` +- 当前阶段:`Phase 58` - 当前焦点: - - `2026-04-24` 本轮继续吃掉 `GFramework.Game.Tests` 中仍然独立且低风险的 `PersistenceTests.cs` 残余 `MA0004` - - 已补齐两个失败缓存一致性测试中的剩余 `.ConfigureAwait(false)`,使 `PersistenceTests.cs` 不再出现在非增量构建 warning 输出中 - - 非增量 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 从 `253 Warning(s)` 进一步收敛到 `249 Warning(s)`,剩余热点几乎全部集中在 `YamlConfigLoaderTests.cs` - - 按当前工作树投影重新计算后,分支体积为 `27` 个文件、`991` 行,仍低于 `$gframework-batch-boot 75` + - `2026-04-24` 使用 `$gframework-pr-review` 复核当前分支 PR #286 的 latest-head review threads、MegaLinter 与测试状态 + - 已确认最新 head 上唯一未解决的实质代码线程指向 `GFramework.Godot/Scene/SceneBehaviorBase.cs` 中 `OnPauseAsync` 的缩进异常,并顺带对齐 `OnResumeAsync`、`OnUnloadAsync` + - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` 通过,结果为 `565 Warning(s)`、`0 Error(s)`;当前跟进只处理 PR review 指向的格式问题,不扩散到既有 warning 基线 + - `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` 已通过,当前文件不再残留格式差异 ## 当前活跃事实 @@ -23,6 +23,11 @@ - `RP-057` 已验证 `PersistenceTests.cs` 不再出现在 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 的 warning 输出中 - 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`,结果为 `Passed: 2` - `GFramework.Game.Tests` 当前剩余热点已经几乎完全集中到 `YamlConfigLoaderTests.cs` 这一高上下文文件 +- PR #286 当前标题为 `Fix/analyzer warning reduction batch`;最新抓取时间点的 PR 状态仍为 `OPEN` +- 最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698`,CodeRabbit 于 `2026-04-24T12:44:12Z` 给出 `CHANGES_REQUESTED` +- latest-head review threads 中只有 `1` 个未解决线程,内容是 `SceneBehaviorBase.OnPauseAsync` 的缩进不一致;本地源码已修复并扩展到同段的 `OnResumeAsync` / `OnUnloadAsync` +- MegaLinter 的 `dotnet-format` 详细问题与上述格式异常一致;本地 `dotnet format --verify-no-changes` 已通过 +- PR 上其余 nitpick 仅为可选建议或已明确留待后续批次处理,当前没有额外需要立即修复的 latest-head 代码线程 ## 当前风险 @@ -34,6 +39,8 @@ - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交 - 下一轮若继续深入 `GFramework.Game.Tests`,很可能需要进入 `YamlConfigLoaderTests.cs` 这种高上下文大文件 - 缓解措施:把它单独作为一个明确的新批次处理,不与其它 warning family 混批 +- PR 标题检查当前仍显示 `Inconclusive` + - 缓解措施:如需让该检查转绿,需要单独更新 GitHub PR 标题;这不属于本地代码修改范围 ## 活跃文档 @@ -67,8 +74,13 @@ - 结果:成功;`Passed: 4`、`Failed: 0` - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"` - 结果:成功;`Passed: 2`、`Failed: 0` +- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` + - 结果:成功;`565 Warning(s)`、`0 Error(s)` +- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` + - 首次运行:失败;restore 阶段异常退出,未进入格式验证 + - 第二次运行(同命令追加 sandbox 提权):成功;workspace 仅提示加载 warning,无格式差异 ## 下一步建议 -1. 提交 `PersistenceTests.cs` 与 `RP-057` tracking/trace 更新,继续保持只纳入本 topic 相关文件 -2. 若继续 warning reduction,应把 `YamlConfigLoaderTests.cs` 视为独立的高上下文批次,不再与其它文件混批 +1. 提交 `SceneBehaviorBase.cs` 与 `RP-058` tracking/trace 更新,清掉 PR #286 当前 latest-head 上的格式类 review thread +2. 若继续 warning reduction 主线,应回到 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`,把它作为独立高上下文批次处理 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 5363a4d3..bbed0ac9 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 @@ -2,6 +2,31 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-24 — RP-058 + +### 阶段:PR #286 latest-head review 格式跟进 + +- 触发背景: + - 用户要求执行 `$gframework-pr-review`,需要以当前分支 PR 页面而不是本地记忆为准,重新核对 CodeRabbit、MegaLinter 和测试状态 + - 抓取脚本当前解析到的 PR 是 `#286`,最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698` + - 最新 head 上真正未解决的代码线程只剩 `GFramework.Godot/Scene/SceneBehaviorBase.cs:148` 的缩进问题;其余 nitpick 为可选建议或已留待后续批次 +- 主线程实施: + - 运行 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`,确认 PR `OPEN`、测试 `2156/2156` 通过、MegaLinter 仅剩 `dotnet-format` 警告 + - 复核 `SceneBehaviorBase.cs` 后确认 `OnPauseAsync` 的方法签名与方法体缩进异常仍存在于本地源码;同段的 `OnResumeAsync`、`OnUnloadAsync` 也有同类偏差 + - 在不改变行为的前提下统一修正三个方法的缩进,保持现有 XML 注释、`ConfigureAwait(true)` 语义与 Godot 主线程说明不变 + - 更新 active tracking / trace,记录当前 PR review follow-up 已完成,本地剩余外部信号只剩 PR 标题检查 +- 验证里程碑: + - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` + - 结果:成功;`565 Warning(s)`、`0 Error(s)` + - 结论:当前格式修复未引入编译错误;模块既有 warning 基线仍存在,但不属于本次 PR review 跟进范围 + - `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` + - 首次运行:失败;sandbox 环境下在 build host / pipe 建立阶段报错,未进入真实格式比较 + - 提权复验:成功;仅提示 workspace load warning,无格式差异 +- 当前结论: + - PR #286 当前 latest-head 上唯一未解决的实质代码 review thread 已在本地修复 + - MegaLinter 暴露的 `dotnet-format` 问题已被本地 `verify-no-changes` 复验覆盖 + - `Title check: Inconclusive` 仍然存在,但属于 GitHub PR 标题元数据问题,不能通过本地代码提交直接消除 + ## 2026-04-24 — RP-057 ### 阶段:清理 `PersistenceTests.cs` 残余 `MA0004`