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 批次(触发文件数停止阈值)