mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(source-generators-tests): 收敛 schema 快照测试超长方法
- 重构 SchemaConfigGeneratorSnapshotTests 的 monster 场景输入与快照辅助逻辑,消除单个超长测试方法 - 更新 analyzer warning reduction 的 tracking 与 trace,记录 RP-033 基线和验证结果
This commit is contained in:
parent
9d251ab1f8
commit
3203239726
@ -8,171 +8,189 @@ namespace GFramework.SourceGenerators.Tests.Config;
|
||||
[TestFixture]
|
||||
public class SchemaConfigGeneratorSnapshotTests
|
||||
{
|
||||
private const string RuntimeContractsSource = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Config
|
||||
{
|
||||
public interface IConfigTable
|
||||
{
|
||||
Type KeyType { get; }
|
||||
Type ValueType { get; }
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
public interface IConfigTable<TKey, TValue> : IConfigTable
|
||||
where TKey : notnull
|
||||
{
|
||||
TValue Get(TKey key);
|
||||
bool TryGet(TKey key, out TValue? value);
|
||||
bool ContainsKey(TKey key);
|
||||
IReadOnlyCollection<TValue> All();
|
||||
}
|
||||
|
||||
public interface IConfigRegistry
|
||||
{
|
||||
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
|
||||
where TKey : notnull;
|
||||
|
||||
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
|
||||
where TKey : notnull;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Config
|
||||
{
|
||||
public sealed class YamlConfigLoader
|
||||
{
|
||||
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||
string tableName,
|
||||
string relativePath,
|
||||
string schemaRelativePath,
|
||||
Func<TValue, TKey> keySelector,
|
||||
IEqualityComparer<TKey>? comparer = null)
|
||||
where TKey : notnull
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MonsterSchema = """
|
||||
{
|
||||
"title": "Monster Config",
|
||||
"description": "Represents one monster entry generated from schema metadata.",
|
||||
"type": "object",
|
||||
"minProperties": 4,
|
||||
"maxProperties": 8,
|
||||
"required": ["id", "name", "reward", "phases"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "Unique monster identifier."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Monster Name",
|
||||
"description": "Localized monster display name.",
|
||||
"x-gframework-index": true,
|
||||
"minLength": 3,
|
||||
"maxLength": 16,
|
||||
"pattern": "^[A-Z][a-z]+$",
|
||||
"default": "Slime",
|
||||
"enum": ["Slime", "Goblin"]
|
||||
},
|
||||
"hp": {
|
||||
"type": "integer",
|
||||
"const": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 999,
|
||||
"exclusiveMinimum": 0,
|
||||
"exclusiveMaximum": 1000,
|
||||
"multipleOf": 5,
|
||||
"default": 10
|
||||
},
|
||||
"dropItems": {
|
||||
"description": "Referenced drop ids.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"minContains": 1,
|
||||
"maxContains": 2,
|
||||
"uniqueItems": true,
|
||||
"contains": {
|
||||
"type": "string",
|
||||
"const": "potion"
|
||||
},
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 12,
|
||||
"enum": ["potion", "slime_gel"]
|
||||
},
|
||||
"default": ["potion"],
|
||||
"x-gframework-ref-table": "item"
|
||||
},
|
||||
"reward": {
|
||||
"type": "object",
|
||||
"description": "Reward payload.",
|
||||
"minProperties": 2,
|
||||
"maxProperties": 2,
|
||||
"required": ["gold", "currency"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 10
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"enum": ["coin", "gem"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"phases": {
|
||||
"type": "array",
|
||||
"description": "Encounter phases.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["wave", "monsterId"],
|
||||
"properties": {
|
||||
"wave": {
|
||||
"type": "integer"
|
||||
},
|
||||
"monsterId": {
|
||||
"type": "string",
|
||||
"description": "Monster reference id.",
|
||||
"minLength": 2,
|
||||
"maxLength": 32,
|
||||
"x-gframework-ref-table": "monster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 验证一个最小 monster schema 能生成配置类型、表包装和注册辅助。
|
||||
/// </summary>
|
||||
/// 验证一个最小 monster schema 能生成配置类型、表包装和注册辅助。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public Task Snapshot_SchemaConfigGenerator()
|
||||
{
|
||||
const string source = """
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GFramework.Game.Abstractions.Config
|
||||
{
|
||||
public interface IConfigTable
|
||||
{
|
||||
Type KeyType { get; }
|
||||
Type ValueType { get; }
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
public interface IConfigTable<TKey, TValue> : IConfigTable
|
||||
where TKey : notnull
|
||||
{
|
||||
TValue Get(TKey key);
|
||||
bool TryGet(TKey key, out TValue? value);
|
||||
bool ContainsKey(TKey key);
|
||||
IReadOnlyCollection<TValue> All();
|
||||
}
|
||||
|
||||
public interface IConfigRegistry
|
||||
{
|
||||
IConfigTable<TKey, TValue> GetTable<TKey, TValue>(string name)
|
||||
where TKey : notnull;
|
||||
|
||||
bool TryGetTable<TKey, TValue>(string name, out IConfigTable<TKey, TValue>? table)
|
||||
where TKey : notnull;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Game.Config
|
||||
{
|
||||
public sealed class YamlConfigLoader
|
||||
{
|
||||
public YamlConfigLoader RegisterTable<TKey, TValue>(
|
||||
string tableName,
|
||||
string relativePath,
|
||||
string schemaRelativePath,
|
||||
Func<TValue, TKey> keySelector,
|
||||
IEqualityComparer<TKey>? comparer = null)
|
||||
where TKey : notnull
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
const string schema = """
|
||||
{
|
||||
"title": "Monster Config",
|
||||
"description": "Represents one monster entry generated from schema metadata.",
|
||||
"type": "object",
|
||||
"minProperties": 4,
|
||||
"maxProperties": 8,
|
||||
"required": ["id", "name", "reward", "phases"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "Unique monster identifier."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Monster Name",
|
||||
"description": "Localized monster display name.",
|
||||
"x-gframework-index": true,
|
||||
"minLength": 3,
|
||||
"maxLength": 16,
|
||||
"pattern": "^[A-Z][a-z]+$",
|
||||
"default": "Slime",
|
||||
"enum": ["Slime", "Goblin"]
|
||||
},
|
||||
"hp": {
|
||||
"type": "integer",
|
||||
"const": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 999,
|
||||
"exclusiveMinimum": 0,
|
||||
"exclusiveMaximum": 1000,
|
||||
"multipleOf": 5,
|
||||
"default": 10
|
||||
},
|
||||
"dropItems": {
|
||||
"description": "Referenced drop ids.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 3,
|
||||
"minContains": 1,
|
||||
"maxContains": 2,
|
||||
"uniqueItems": true,
|
||||
"contains": {
|
||||
"type": "string",
|
||||
"const": "potion"
|
||||
},
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 12,
|
||||
"enum": ["potion", "slime_gel"]
|
||||
},
|
||||
"default": ["potion"],
|
||||
"x-gframework-ref-table": "item"
|
||||
},
|
||||
"reward": {
|
||||
"type": "object",
|
||||
"description": "Reward payload.",
|
||||
"minProperties": 2,
|
||||
"maxProperties": 2,
|
||||
"required": ["gold", "currency"],
|
||||
"properties": {
|
||||
"gold": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 10
|
||||
},
|
||||
"currency": {
|
||||
"type": "string",
|
||||
"enum": ["coin", "gem"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"phases": {
|
||||
"type": "array",
|
||||
"description": "Encounter phases.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["wave", "monsterId"],
|
||||
"properties": {
|
||||
"wave": {
|
||||
"type": "integer"
|
||||
},
|
||||
"monsterId": {
|
||||
"type": "string",
|
||||
"description": "Monster reference id.",
|
||||
"minLength": 2,
|
||||
"maxLength": 32,
|
||||
"x-gframework-ref-table": "monster"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
var generatedSources = GenerateSourcesForMonsterSchema();
|
||||
var snapshotFolder = GetSchemaSnapshotFolder();
|
||||
return AssertAllSnapshotsAsync(generatedSources, snapshotFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行 monster schema 场景,并把生成结果转换为按 hint name 索引的字典。
|
||||
/// </summary>
|
||||
/// <returns>当前快照场景的全部生成文件内容。</returns>
|
||||
private static IReadOnlyDictionary<string, string> GenerateSourcesForMonsterSchema()
|
||||
{
|
||||
var result = SchemaGeneratorTestDriver.Run(
|
||||
source,
|
||||
("monster.schema.json", schema));
|
||||
RuntimeContractsSource,
|
||||
("monster.schema.json", MonsterSchema));
|
||||
|
||||
var generatedSources = result.Results
|
||||
return result.Results
|
||||
.Single()
|
||||
.GeneratedSources
|
||||
.ToDictionary(
|
||||
static sourceResult => sourceResult.HintName,
|
||||
static sourceResult => sourceResult.SourceText.ToString(),
|
||||
StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 schema 生成器快照目录,确保断言始终落在仓库内已提交的 snapshot 资产上。
|
||||
/// </summary>
|
||||
/// <returns>schema 生成器快照目录的绝对路径。</returns>
|
||||
private static string GetSchemaSnapshotFolder()
|
||||
{
|
||||
var snapshotFolder = Path.Combine(
|
||||
TestContext.CurrentContext.TestDirectory,
|
||||
"..",
|
||||
@ -181,9 +199,7 @@ public class SchemaConfigGeneratorSnapshotTests
|
||||
"Config",
|
||||
"snapshots",
|
||||
"SchemaConfigGenerator");
|
||||
snapshotFolder = Path.GetFullPath(snapshotFolder);
|
||||
|
||||
return AssertAllSnapshotsAsync(generatedSources, snapshotFolder);
|
||||
return Path.GetFullPath(snapshotFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-032`
|
||||
- 当前阶段:`Phase 32`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-033`
|
||||
- 当前阶段:`Phase 33`
|
||||
- 当前焦点:
|
||||
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
|
||||
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
|
||||
@ -61,10 +61,15 @@
|
||||
将内联测试源码与期望快照抽到类级常量、补齐测试类 XML 文档,并将仅作转发的异步测试改为直接返回 `Task`
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `43` 条降到 `40` 条;
|
||||
`AutoRegisterModuleGeneratorTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- 已完成 `GFramework.SourceGenerators.Tests/Config/SchemaConfigGeneratorSnapshotTests.cs` 的 `MA0051` 收口:
|
||||
将 monster 场景的运行时契约与 schema 输入提取为类级常量,并把生成结果与快照目录解析拆成小 helper,保持
|
||||
生成文件名、快照目录和断言语义不变
|
||||
- 当前 `GFramework.SourceGenerators.Tests` Release build 基线已从 `40` 条降到 `39` 条;
|
||||
`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
|
||||
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
|
||||
- 下一轮默认继续拆分 `GFramework.SourceGenerators.Tests` 的 `MA0051` 热点,优先处理
|
||||
`GeneratorSnapshotTest` 或 `ContextGetGeneratorTests`
|
||||
`GeneratorSnapshotTest` 或 `ContextRegistrationAnalyzerTests`
|
||||
- 单次 `boot` 的工作树改动上限控制在约 `100` 个文件以内,避免 recovery context 与 review 面同时失控
|
||||
- 若任务边界互不冲突,允许使用不同模型的 subagent 并行处理不同 warning 类型或不同目录,但必须遵守显式 ownership
|
||||
|
||||
@ -104,6 +109,8 @@
|
||||
`43` 条,并通过 focused snapshot tests 保持行为不变
|
||||
- 已完成 `AutoRegisterModuleGeneratorTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests` Release build 基线已降到
|
||||
`40` 条,并通过 focused generator tests 保持输出契约不变
|
||||
- 已完成 `SchemaConfigGeneratorSnapshotTests` 的单文件 `MA0051` 收口;当前 `GFramework.SourceGenerators.Tests`
|
||||
Release build 基线已降到 `39` 条,并通过 focused snapshot test 保持生成输出契约不变
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
@ -146,6 +153,8 @@
|
||||
并用 focused schema generator tests 验证 50 个用例通过
|
||||
- `RP-032` 已完成 `AutoRegisterModuleGeneratorTests` 的 3 个 `MA0051` 收口:通过提取类级常量承载测试源码与快照,保持
|
||||
生成文件名、断言路径与源生成输出不变;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `43` 降至 `40`
|
||||
- `RP-033` 已完成 `SchemaConfigGeneratorSnapshotTests` 的 `MA0051` 收口:monster schema 运行时契约与 schema 输入已提取为
|
||||
类级常量,生成结果映射与快照目录解析已拆为小 helper;`GFramework.SourceGenerators.Tests` warnings-only 基线由 `40` 降至 `39`
|
||||
- `RP-021` 使用 `$gframework-pr-review` 复核当前分支 PR #269 后,修复仍在本地成立的 4 个项:将
|
||||
`CqrsHandlerRegistryGenerator` 拆分为职责清晰的 partial 文件、为 `ContextAwareGenerator` 生成字段增加稳定前缀并补上
|
||||
`SetContextProvider` 的运行时 null 校验、为 `Option<T>` 补齐 `<remarks>`,并新增字段重名场景的生成器快照测试
|
||||
@ -362,13 +371,18 @@
|
||||
- 结果:`43 Warning(s)`,`0 Error(s)`;`LoggerGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~LoggerGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`6 Passed`,`0 Failed`
|
||||
- `RP-033` 的验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`39 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
|
||||
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests` 的 `MA0051` 收口,先在 `AutoRegisterModuleGeneratorTests`、
|
||||
`GeneratorSnapshotTest` 或 `ContextGetGeneratorTests` 中选择一个单写集推进,不再把已清零的 `MA0004` / `MA0048` 混回写集
|
||||
2. 下一轮优先继续 `GFramework.SourceGenerators.Tests` 的 `MA0051` 收口,先在 `GeneratorSnapshotTest`、
|
||||
`ContextRegistrationAnalyzerTests` 或 `ContextGetGeneratorTests` 中选择一个单写集推进,不再把已清零的 `MA0004` / `MA0048` 混回写集
|
||||
3. 若改回推进 `MA0158`,先设计 `net8.0` / `net9.0` / `net10.0` 多 target 条件编译方案,不直接批量替换共享源码中的
|
||||
`object` lock
|
||||
4. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build
|
||||
|
||||
@ -1,5 +1,34 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-23 — RP-033
|
||||
|
||||
### 阶段:`SchemaConfigGeneratorSnapshotTests.cs` `MA0051` 收口(RP-033)
|
||||
|
||||
- 启动复核:
|
||||
- 按 `gframework-boot` 流程恢复当前 worktree,读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、
|
||||
`ai-plan/public/README.md` 与 active topic 跟踪文件,确认当前分支 `fix/analyzer-warning-reduction-batch`
|
||||
仍映射到 `analyzer-warning-reduction`
|
||||
- 用
|
||||
`dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
复核当前 `MA0051` 热点,确认 `SchemaConfigGeneratorSnapshotTests.cs` 仍保留 1 个超长方法,适合作为单文件低风险写集
|
||||
- 决策:
|
||||
- 保持 monster schema 场景的输入源码、schema 文本、生成文件名与快照目录不变,只收敛测试方法长度
|
||||
- 沿用前几轮 snapshot test 的收口策略:提取类级常量承载大段 fixture 输入,再用小 helper 封装生成结果映射与快照目录解析
|
||||
- 同一测试项目的 build/test 继续采用串行验证;并行执行会在 WSL worktree 上制造瞬时输出缺失,导致 `MSB3030` / `CS0006`
|
||||
- 实施调整:
|
||||
- 为 `SchemaConfigGeneratorSnapshotTests` 新增 `RuntimeContractsSource` 与 `MonsterSchema` 类级常量,保留既有 monster 场景内容
|
||||
- 把生成结果字典构造拆到 `GenerateSourcesForMonsterSchema()`,把快照目录解析拆到 `GetSchemaSnapshotFolder()`
|
||||
- 保持 `AssertAllSnapshotsAsync(...)`、快照文件名与断言流程不变,不改生成器逻辑和 snapshot 资产
|
||||
- 验证结果:
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release -t:Rebuild --no-restore --disable-build-servers -m:1 -p:UseSharedCompilation=false -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`39 Warning(s)`,`0 Error(s)`;`SchemaConfigGeneratorSnapshotTests.cs` 已不再出现在 `MA0051` 列表中
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --disable-build-servers --filter FullyQualifiedName~SchemaConfigGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`1 Passed`,`0 Failed`
|
||||
- 下一步建议:
|
||||
- 若继续压缩 `GFramework.SourceGenerators.Tests` 的 `MA0051`,优先处理只剩单个超长方法的 `GeneratorSnapshotTest` 或
|
||||
`ContextRegistrationAnalyzerTests`
|
||||
- 若希望继续按 warning 数量收敛,则回到 `ContextGetGeneratorTests.cs`,但需要接受更大的单文件写集
|
||||
|
||||
## 2026-04-23 — RP-032
|
||||
|
||||
### 阶段:`AutoRegisterModuleGeneratorTests.cs` `MA0051` 收口(RP-032)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user