mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
Merge pull request #286 from GeWuYou/fix/analyzer-warning-reduction-batch
Fix/analyzer warning reduction batch
This commit is contained in:
commit
9964962416
@ -39,7 +39,7 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
architecture = new ConsumerArchitecture(rootPath);
|
architecture = new ConsumerArchitecture(rootPath);
|
||||||
await architecture.InitializeAsync();
|
await architecture.InitializeAsync().ConfigureAwait(false);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
var table = architecture.MonsterTable;
|
var table = architecture.MonsterTable;
|
||||||
@ -63,7 +63,7 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
{
|
{
|
||||||
if (architecture is not null && initialized)
|
if (architecture is not null && initialized)
|
||||||
{
|
{
|
||||||
await architecture.DestroyAsync();
|
await architecture.DestroyAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteDirectoryIfExists(rootPath);
|
DeleteDirectoryIfExists(rootPath);
|
||||||
@ -83,7 +83,7 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
architecture = new ConsumerArchitecture(rootPath);
|
architecture = new ConsumerArchitecture(rootPath);
|
||||||
await architecture.InitializeAsync();
|
await architecture.InitializeAsync().ConfigureAwait(false);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
@ -97,7 +97,7 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
{
|
{
|
||||||
if (architecture is not null && initialized)
|
if (architecture is not null && initialized)
|
||||||
{
|
{
|
||||||
await architecture.DestroyAsync();
|
await architecture.DestroyAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteDirectoryIfExists(rootPath);
|
DeleteDirectoryIfExists(rootPath);
|
||||||
@ -119,16 +119,16 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
var module = CreateModule(rootPath);
|
var module = CreateModule(rootPath);
|
||||||
|
|
||||||
firstArchitecture = new ModuleOnlyArchitecture(module);
|
firstArchitecture = new ModuleOnlyArchitecture(module);
|
||||||
await firstArchitecture.InitializeAsync();
|
await firstArchitecture.InitializeAsync().ConfigureAwait(false);
|
||||||
var wasInitializedBeforeDestroy = module.IsInitialized;
|
var wasInitializedBeforeDestroy = module.IsInitialized;
|
||||||
await firstArchitecture.DestroyAsync();
|
await firstArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||||
firstDestroyed = true;
|
firstDestroyed = true;
|
||||||
firstArchitecture = null;
|
firstArchitecture = null;
|
||||||
GameContext.Clear();
|
GameContext.Clear();
|
||||||
|
|
||||||
var secondArchitecture = new ModuleOnlyArchitecture(module);
|
var secondArchitecture = new ModuleOnlyArchitecture(module);
|
||||||
var exception =
|
var exception =
|
||||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await secondArchitecture.InitializeAsync());
|
Assert.ThrowsAsync<InvalidOperationException>(async () => await secondArchitecture.InitializeAsync().ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -141,7 +141,7 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
{
|
{
|
||||||
if (firstArchitecture is not null && !firstDestroyed)
|
if (firstArchitecture is not null && !firstDestroyed)
|
||||||
{
|
{
|
||||||
await firstArchitecture.DestroyAsync();
|
await firstArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteDirectoryIfExists(rootPath);
|
DeleteDirectoryIfExists(rootPath);
|
||||||
@ -203,7 +203,7 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
var module = CreateModule(rootPath);
|
var module = CreateModule(rootPath);
|
||||||
|
|
||||||
readyArchitecture = new ReadyOnlyArchitecture();
|
readyArchitecture = new ReadyOnlyArchitecture();
|
||||||
await readyArchitecture.InitializeAsync();
|
await readyArchitecture.InitializeAsync().ConfigureAwait(false);
|
||||||
readyArchitectureInitialized = true;
|
readyArchitectureInitialized = true;
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => readyArchitecture.InstallModule(module));
|
var exception = Assert.Throws<InvalidOperationException>(() => readyArchitecture.InstallModule(module));
|
||||||
@ -216,13 +216,13 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
Assert.That(module.IsInitialized, Is.False);
|
Assert.That(module.IsInitialized, Is.False);
|
||||||
});
|
});
|
||||||
|
|
||||||
await readyArchitecture.DestroyAsync();
|
await readyArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||||
readyArchitectureInitialized = false;
|
readyArchitectureInitialized = false;
|
||||||
readyArchitecture = null;
|
readyArchitecture = null;
|
||||||
GameContext.Clear();
|
GameContext.Clear();
|
||||||
|
|
||||||
retryArchitecture = new ModuleOnlyArchitecture(module);
|
retryArchitecture = new ModuleOnlyArchitecture(module);
|
||||||
await retryArchitecture.InitializeAsync();
|
await retryArchitecture.InitializeAsync().ConfigureAwait(false);
|
||||||
retryArchitectureInitialized = true;
|
retryArchitectureInitialized = true;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
@ -235,12 +235,12 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
{
|
{
|
||||||
if (retryArchitecture is not null && retryArchitectureInitialized)
|
if (retryArchitecture is not null && retryArchitectureInitialized)
|
||||||
{
|
{
|
||||||
await retryArchitecture.DestroyAsync();
|
await retryArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readyArchitecture is not null && readyArchitectureInitialized)
|
if (readyArchitecture is not null && readyArchitectureInitialized)
|
||||||
{
|
{
|
||||||
await readyArchitecture.DestroyAsync();
|
await readyArchitecture.DestroyAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteDirectoryIfExists(rootPath);
|
DeleteDirectoryIfExists(rootPath);
|
||||||
|
|||||||
@ -50,7 +50,7 @@ public class GameConfigBootstrapTests
|
|||||||
var registry = new ConfigRegistry();
|
var registry = new ConfigRegistry();
|
||||||
using var bootstrap = CreateBootstrap(registry);
|
using var bootstrap = CreateBootstrap(registry);
|
||||||
|
|
||||||
await bootstrap.InitializeAsync();
|
await bootstrap.InitializeAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var monsterTable = registry.GetMonsterTable();
|
var monsterTable = registry.GetMonsterTable();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ public class GameConfigBootstrapTests
|
|||||||
CreateMonsterFiles();
|
CreateMonsterFiles();
|
||||||
|
|
||||||
using var bootstrap = CreateBootstrap();
|
using var bootstrap = CreateBootstrap();
|
||||||
await bootstrap.InitializeAsync();
|
await bootstrap.InitializeAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var reloadTaskSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var reloadTaskSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
bootstrap.StartHotReload(
|
bootstrap.StartHotReload(
|
||||||
@ -95,7 +95,7 @@ public class GameConfigBootstrapTests
|
|||||||
faction: dungeon
|
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();
|
var monsterTable = bootstrap.Registry.GetMonsterTable();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
@ -163,11 +163,11 @@ public class GameConfigBootstrapTests
|
|||||||
Is.True,
|
Is.True,
|
||||||
"The first initialization attempt did not reach the guarded lifecycle section.");
|
"The first initialization attempt did not reach the guarded lifecycle section.");
|
||||||
|
|
||||||
var secondCallerException = Assert.ThrowsAsync<InvalidOperationException>(async () => await bootstrap.InitializeAsync());
|
var secondCallerException = Assert.ThrowsAsync<InvalidOperationException>(async () => await bootstrap.InitializeAsync().ConfigureAwait(false));
|
||||||
|
|
||||||
continueInitialization.Set();
|
continueInitialization.Set();
|
||||||
|
|
||||||
Assert.DoesNotThrowAsync(async () => await firstInitializeTask);
|
Assert.DoesNotThrowAsync(async () => await firstInitializeTask.ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -202,7 +202,7 @@ public class GameConfigBootstrapTests
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await bootstrap.InitializeAsync());
|
var exception = Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await bootstrap.InitializeAsync().ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -311,12 +311,12 @@ public class GameConfigBootstrapTests
|
|||||||
/// <returns>任务结果。</returns>
|
/// <returns>任务结果。</returns>
|
||||||
private static async Task<T> WaitForTaskWithinAsync<T>(Task<T> task, TimeSpan timeout)
|
private static async Task<T> WaitForTaskWithinAsync<T>(Task<T> 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))
|
if (!ReferenceEquals(completedTask, task))
|
||||||
{
|
{
|
||||||
Assert.Fail($"Timed out after {timeout} while waiting for file watcher notification.");
|
Assert.Fail($"Timed out after {timeout} while waiting for file watcher notification.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await task;
|
return await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,65 +50,12 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
var loader = new YamlConfigLoader(_rootPath)
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
.RegisterAllGeneratedConfigTables();
|
.RegisterAllGeneratedConfigTables();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var monsterTable = registry.GetMonsterTable();
|
var monsterTable = registry.GetMonsterTable();
|
||||||
var dungeonMonsters = monsterTable.FindByFaction("dungeon");
|
|
||||||
var itemTable = registry.GetItemTable();
|
var itemTable = registry.GetItemTable();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
AssertGeneratedBindingsLoadResults(registry, monsterTable, itemTable);
|
||||||
{
|
|
||||||
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"));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -181,7 +128,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
{
|
{
|
||||||
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
|
IncludedConfigDomains = new[] { MonsterConfigBindings.ConfigDomain }
|
||||||
});
|
});
|
||||||
await domainLoader.LoadAsync(domainRegistry);
|
await domainLoader.LoadAsync(domainRegistry).ConfigureAwait(false);
|
||||||
|
|
||||||
var tableNameRegistry = new ConfigRegistry();
|
var tableNameRegistry = new ConfigRegistry();
|
||||||
var tableNameLoader = new YamlConfigLoader(_rootPath)
|
var tableNameLoader = new YamlConfigLoader(_rootPath)
|
||||||
@ -190,7 +137,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
{
|
{
|
||||||
IncludedTableNames = new[] { ItemConfigBindings.TableName }
|
IncludedTableNames = new[] { ItemConfigBindings.TableName }
|
||||||
});
|
});
|
||||||
await tableNameLoader.LoadAsync(tableNameRegistry);
|
await tableNameLoader.LoadAsync(tableNameRegistry).ConfigureAwait(false);
|
||||||
|
|
||||||
var emptyAllowListRegistry = new ConfigRegistry();
|
var emptyAllowListRegistry = new ConfigRegistry();
|
||||||
var emptyAllowListLoader = new YamlConfigLoader(_rootPath)
|
var emptyAllowListLoader = new YamlConfigLoader(_rootPath)
|
||||||
@ -200,7 +147,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
IncludedConfigDomains = Array.Empty<string>(),
|
IncludedConfigDomains = Array.Empty<string>(),
|
||||||
IncludedTableNames = Array.Empty<string>()
|
IncludedTableNames = Array.Empty<string>()
|
||||||
});
|
});
|
||||||
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry);
|
await emptyAllowListLoader.LoadAsync(emptyAllowListRegistry).ConfigureAwait(false);
|
||||||
|
|
||||||
var monsterDomain = MonsterConfigBindings.ConfigDomain;
|
var monsterDomain = MonsterConfigBindings.ConfigDomain;
|
||||||
var predicateRegistry = new ConfigRegistry();
|
var predicateRegistry = new ConfigRegistry();
|
||||||
@ -211,28 +158,13 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
TableFilter = metadata =>
|
TableFilter = metadata =>
|
||||||
string.Equals(metadata.ConfigDomain, monsterDomain, StringComparison.Ordinal)
|
string.Equals(metadata.ConfigDomain, monsterDomain, StringComparison.Ordinal)
|
||||||
});
|
});
|
||||||
await predicateLoader.LoadAsync(predicateRegistry);
|
await predicateLoader.LoadAsync(predicateRegistry).ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
AssertGeneratedRegistrationFilteringResults(
|
||||||
{
|
domainRegistry,
|
||||||
Assert.That(emptyAllowListRegistry.TryGetMonsterTable(out var emptyAllowListMonsterTable), Is.True);
|
tableNameRegistry,
|
||||||
Assert.That(emptyAllowListMonsterTable, Is.Not.Null);
|
emptyAllowListRegistry,
|
||||||
Assert.That(emptyAllowListRegistry.TryGetItemTable(out var emptyAllowListItemTable), Is.True);
|
predicateRegistry);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -270,7 +202,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml));
|
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml));
|
||||||
|
|
||||||
Assert.DoesNotThrowAsync(async () =>
|
Assert.DoesNotThrowAsync(async () =>
|
||||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml));
|
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false));
|
||||||
|
|
||||||
var invalidYaml = """
|
var invalidYaml = """
|
||||||
id: 3
|
id: 3
|
||||||
@ -282,7 +214,7 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
var exception = Assert.Throws<ConfigLoadException>(() =>
|
var exception = Assert.Throws<ConfigLoadException>(() =>
|
||||||
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", invalidYaml));
|
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", invalidYaml));
|
||||||
var asyncException = Assert.ThrowsAsync<ConfigLoadException>(async () =>
|
var asyncException = Assert.ThrowsAsync<ConfigLoadException>(async () =>
|
||||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml));
|
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", invalidYaml).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -367,6 +299,138 @@ public class GeneratedConfigConsumerIntegrationTests
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一断言生成绑定加载后的目录元数据、查询入口与强类型表包装结果,
|
||||||
|
/// 以便缩短端到端测试主体并降低分析器对方法长度的告警。
|
||||||
|
/// </summary>
|
||||||
|
private static void AssertGeneratedBindingsLoadResults(
|
||||||
|
ConfigRegistry registry,
|
||||||
|
MonsterTable monsterTable,
|
||||||
|
ItemTable itemTable)
|
||||||
|
{
|
||||||
|
AssertGeneratedCatalogMetadata();
|
||||||
|
AssertGeneratedMonsterTableResults(registry, monsterTable);
|
||||||
|
AssertGeneratedItemTableResults(registry, itemTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断言消费者项目的生成目录元数据与静态绑定常量保持一致。
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断言 monster 绑定在注册表中的查询辅助、索引查询与强类型访问入口都可用。
|
||||||
|
/// </summary>
|
||||||
|
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" }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断言 item 绑定的强类型表包装与按分类查询在聚合注册路径下可正常工作。
|
||||||
|
/// </summary>
|
||||||
|
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"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 汇总断言不同聚合注册筛选条件下的装载结果,
|
||||||
|
/// 让测试主体聚焦于注册参数本身而不是展开大量重复断言。
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在临时消费者目录中创建 item schema 与 YAML 测试数据,用于验证多表聚合注册和筛选行为。
|
/// 在临时消费者目录中创建 item schema 与 YAML 测试数据,用于验证多表聚合注册和筛选行为。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -90,7 +90,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterAllOfConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterAllOfConfigStub>("monster");
|
||||||
var reward = table.Get(1).Reward;
|
var reward = table.Get(1).Reward;
|
||||||
@ -165,7 +165,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
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)
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
.RegisterTable<int, MonsterTagConfigStub>(
|
.RegisterTable<int, MonsterTagConfigStub>(
|
||||||
@ -220,7 +223,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
static config => config.Id);
|
static config => config.Id);
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -255,7 +258,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -295,7 +298,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -335,7 +338,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -375,7 +378,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -415,7 +418,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -455,7 +458,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -499,7 +502,7 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -519,7 +522,10 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
||||||
private void CreateConfigFile(string relativePath, string content)
|
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 filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||||
var directoryPath = Path.GetDirectoryName(filePath);
|
var directoryPath = Path.GetDirectoryName(filePath);
|
||||||
@ -606,7 +612,10 @@ public sealed class YamlConfigLoaderAllOfTests
|
|||||||
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
||||||
private YamlConfigLoader CreateMonsterRewardLoader()
|
private YamlConfigLoader CreateMonsterRewardLoader()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
if (_rootPath is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Root path is not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
return new YamlConfigLoader(_rootPath)
|
return new YamlConfigLoader(_rootPath)
|
||||||
.RegisterTable<int, MonsterAllOfConfigStub>(
|
.RegisterTable<int, MonsterAllOfConfigStub>(
|
||||||
|
|||||||
@ -75,7 +75,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -124,7 +124,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||||
Assert.That(table.Count, Is.EqualTo(1));
|
Assert.That(table.Count, Is.EqualTo(1));
|
||||||
@ -169,7 +169,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||||
var reward = table.Get(1).Reward;
|
var reward = table.Get(1).Reward;
|
||||||
@ -217,7 +217,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -267,7 +267,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
|||||||
var loader = CreateCaseSensitiveRewardLoader();
|
var loader = CreateCaseSensitiveRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -317,7 +317,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -74,7 +74,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -106,7 +106,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
|
||||||
Assert.That(table.Count, Is.EqualTo(1));
|
Assert.That(table.Count, Is.EqualTo(1));
|
||||||
@ -133,7 +133,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterDependentSchemasConfigStub>("monster");
|
||||||
var reward = table.Get(1).Reward;
|
var reward = table.Get(1).Reward;
|
||||||
@ -174,7 +174,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -220,7 +220,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -264,7 +264,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -283,7 +283,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
/// <param name="content">要写入的 YAML 或 schema 内容。</param>
|
||||||
private void CreateConfigFile(string relativePath, string content)
|
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 filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||||
var directoryPath = Path.GetDirectoryName(filePath);
|
var directoryPath = Path.GetDirectoryName(filePath);
|
||||||
@ -353,7 +356,10 @@ public sealed class YamlConfigLoaderDependentSchemasTests
|
|||||||
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
||||||
private YamlConfigLoader CreateMonsterRewardLoader()
|
private YamlConfigLoader CreateMonsterRewardLoader()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
if (_rootPath is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Root path is not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
return new YamlConfigLoader(_rootPath)
|
return new YamlConfigLoader(_rootPath)
|
||||||
.RegisterTable<int, MonsterDependentSchemasConfigStub>(
|
.RegisterTable<int, MonsterDependentSchemasConfigStub>(
|
||||||
|
|||||||
@ -75,7 +75,7 @@ public class YamlConfigLoaderEnumTests
|
|||||||
var loader = CreateLoader<MonsterRewardConfigStub>();
|
var loader = CreateLoader<MonsterRewardConfigStub>();
|
||||||
var registry = new ConfigRegistry();
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
@ -127,7 +127,7 @@ public class YamlConfigLoaderEnumTests
|
|||||||
var loader = CreateLoader<MonsterRewardConfigStub>();
|
var loader = CreateLoader<MonsterRewardConfigStub>();
|
||||||
var registry = new ConfigRegistry();
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -176,7 +176,7 @@ public class YamlConfigLoaderEnumTests
|
|||||||
var loader = CreateLoader<MonsterDropItemIdsConfigStub>();
|
var loader = CreateLoader<MonsterDropItemIdsConfigStub>();
|
||||||
var registry = new ConfigRegistry();
|
var registry = new ConfigRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -105,7 +105,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -137,7 +137,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
|
||||||
var reward = table.Get(1).Reward;
|
var reward = table.Get(1).Reward;
|
||||||
@ -170,7 +170,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -202,7 +202,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterConditionalConfigStub>("monster");
|
||||||
var reward = table.Get(1).Reward;
|
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)
|
var loader = new YamlConfigLoader(_rootPath)
|
||||||
.RegisterTable<int, MonsterTagConfigStub>(
|
.RegisterTable<int, MonsterTagConfigStub>(
|
||||||
@ -260,7 +263,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
static config => config.Id);
|
static config => config.Id);
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -301,7 +304,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -342,7 +345,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -390,7 +393,7 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -409,7 +412,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
/// <param name="content">配置文件内容。</param>
|
/// <param name="content">配置文件内容。</param>
|
||||||
private void CreateConfigFile(string relativePath, string content)
|
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 filePath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||||
var directoryPath = Path.GetDirectoryName(filePath);
|
var directoryPath = Path.GetDirectoryName(filePath);
|
||||||
@ -496,7 +502,10 @@ public sealed class YamlConfigLoaderIfThenElseTests
|
|||||||
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
/// <returns>已注册测试表与 schema 路径的加载器。</returns>
|
||||||
private YamlConfigLoader CreateMonsterRewardLoader()
|
private YamlConfigLoader CreateMonsterRewardLoader()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_rootPath);
|
if (_rootPath is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Root path is not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
return new YamlConfigLoader(_rootPath)
|
return new YamlConfigLoader(_rootPath)
|
||||||
.RegisterTable<int, MonsterConditionalConfigStub>(
|
.RegisterTable<int, MonsterConditionalConfigStub>(
|
||||||
|
|||||||
@ -70,7 +70,7 @@ public sealed class YamlConfigLoaderNegationTests
|
|||||||
var loader = CreateMonsterLoader();
|
var loader = CreateMonsterLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -118,7 +118,7 @@ public sealed class YamlConfigLoaderNegationTests
|
|||||||
var loader = CreateMonsterLoader();
|
var loader = CreateMonsterLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterConfigStub>("monster");
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ public sealed class YamlConfigLoaderNegationTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -227,7 +227,7 @@ public sealed class YamlConfigLoaderNegationTests
|
|||||||
var loader = CreateMonsterRewardLoader();
|
var loader = CreateMonsterRewardLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
await loader.LoadAsync(registry);
|
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||||
|
|
||||||
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
var table = registry.GetTable<int, MonsterRewardConfigStub>("monster");
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ public sealed class YamlConfigLoaderNegationTests
|
|||||||
var loader = CreateMonsterLoader();
|
var loader = CreateMonsterLoader();
|
||||||
var registry = CreateRegistry();
|
var registry = CreateRegistry();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry));
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () => await loader.LoadAsync(registry).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -191,7 +191,10 @@ public sealed class YamlConfigSchemaValidatorTests
|
|||||||
string relativePath,
|
string relativePath,
|
||||||
string content)
|
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 fullPath = Path.Combine(_rootPath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||||
var directoryPath = Path.GetDirectoryName(fullPath);
|
var directoryPath = Path.GetDirectoryName(fullPath);
|
||||||
|
|||||||
@ -132,7 +132,7 @@ public sealed class YamlConfigTextValidatorTests
|
|||||||
"monster/generated.yaml",
|
"monster/generated.yaml",
|
||||||
"""
|
"""
|
||||||
id: 1
|
id: 1
|
||||||
"""));
|
""").ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,114 +0,0 @@
|
|||||||
using System;
|
|
||||||
using GFramework.Game.Abstractions.Data;
|
|
||||||
using GFramework.Game.Abstractions.Enums;
|
|
||||||
|
|
||||||
namespace GFramework.Game.Tests.Data;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 为持久化测试提供稳定的测试数据位置实现。
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class TestDataLocation : IDataLocation
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化测试数据位置。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">测试使用的存储键。</param>
|
|
||||||
/// <param name="kinds">测试使用的存储类型。</param>
|
|
||||||
/// <param name="namespaceValue">测试使用的命名空间。</param>
|
|
||||||
/// <param name="metadata">附加测试元数据。</param>
|
|
||||||
public TestDataLocation(
|
|
||||||
string key,
|
|
||||||
StorageKinds kinds = StorageKinds.Local,
|
|
||||||
string? namespaceValue = null,
|
|
||||||
IReadOnlyDictionary<string, string>? metadata = null)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
Kinds = kinds;
|
|
||||||
Namespace = namespaceValue;
|
|
||||||
Metadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取测试数据对应的存储键。
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取测试数据使用的存储类型。
|
|
||||||
/// </summary>
|
|
||||||
public StorageKinds Kinds { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取测试数据使用的命名空间。
|
|
||||||
/// </summary>
|
|
||||||
public string? Namespace { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取附加到测试位置上的元数据。
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyDictionary<string, string>? Metadata { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 为基础存档仓库测试提供的简单存档模型。
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class TestSaveData : IData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试存档中的名称字段。
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 为存档迁移测试提供的版本化存档模型。
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class TestVersionedSaveData : IVersionedData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试存档中的名称字段。
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试存档中的等级字段。
|
|
||||||
/// </summary>
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试存档中的经验字段。
|
|
||||||
/// </summary>
|
|
||||||
public int Experience { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置当前测试存档的版本号。
|
|
||||||
/// </summary>
|
|
||||||
public int Version { get; set; } = 3;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试存档的最后修改时间。
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LastModified { get; set; } = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 为通用持久化测试提供的简单数据模型。
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class TestSimpleData : IData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试数据中的整数值。
|
|
||||||
/// </summary>
|
|
||||||
public int Value { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class TestNamedData : IData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置测试数据中的名称值。
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@ -38,12 +38,12 @@ public class PersistenceTests
|
|||||||
using var storage = new FileStorage(root, new JsonSerializer(), ".json");
|
using var storage = new FileStorage(root, new JsonSerializer(), ".json");
|
||||||
|
|
||||||
var saved = new TestSimpleData { Value = 5 };
|
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<TestSimpleData>("folder/item");
|
var loaded = await storage.ReadAsync<TestSimpleData>("folder/item").ConfigureAwait(false);
|
||||||
Assert.That(loaded.Value, Is.EqualTo(saved.Value));
|
Assert.That(loaded.Value, Is.EqualTo(saved.Value));
|
||||||
|
|
||||||
Assert.ThrowsAsync<ArgumentException>(async () => await storage.WriteAsync("../escape", new TestSimpleData()));
|
Assert.ThrowsAsync<ArgumentException>(async () => await storage.WriteAsync("../escape", new TestSimpleData()).ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -108,7 +108,7 @@ public class PersistenceTests
|
|||||||
.RegisterMigration(new TestSaveMigrationV2ToV3());
|
.RegisterMigration(new TestSaveMigrationV2ToV3());
|
||||||
|
|
||||||
var loaded = await repository.LoadAsync(1);
|
var loaded = await repository.LoadAsync(1);
|
||||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
|
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -185,7 +185,7 @@ public class PersistenceTests
|
|||||||
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
|
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
|
||||||
.RegisterMigration(new TestSaveMigrationV1ToV2());
|
.RegisterMigration(new TestSaveMigrationV1ToV2());
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1));
|
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1).ConfigureAwait(false));
|
||||||
Assert.That(exception!.Message, Does.Contain("from version 2"));
|
Assert.That(exception!.Message, Does.Contain("from version 2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,8 +218,8 @@ public class PersistenceTests
|
|||||||
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
|
var repository = new SaveRepository<TestVersionedSaveData>(storage, config)
|
||||||
.RegisterMigration(new TestSaveMigrationV1ToV2ReturningV3());
|
.RegisterMigration(new TestSaveMigrationV1ToV2ReturningV3());
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1));
|
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.LoadAsync(1).ConfigureAwait(false));
|
||||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
|
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -270,8 +270,8 @@ public class PersistenceTests
|
|||||||
repository.RegisterMigration(new TestSaveMigrationV2ToV3());
|
repository.RegisterMigration(new TestSaveMigrationV2ToV3());
|
||||||
continueMigration.Set();
|
continueMigration.Set();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await loadTask);
|
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () => await loadTask.ConfigureAwait(false));
|
||||||
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save");
|
var persisted = await storage.ReadAsync<TestVersionedSaveData>("saves/slot_1/save").ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -593,13 +593,13 @@ public class PersistenceTests
|
|||||||
|
|
||||||
throwingStorage.ThrowOnWrite = true;
|
throwingStorage.ThrowOnWrite = true;
|
||||||
Assert.ThrowsAsync<InvalidOperationException>(
|
Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
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<TestSimpleData>(primaryLocation);
|
var cachedAfterFailure = await repository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
|
||||||
Assert.That(cachedAfterFailure.Value, Is.EqualTo(1));
|
Assert.That(cachedAfterFailure.Value, Is.EqualTo(1));
|
||||||
|
|
||||||
throwingStorage.ThrowOnWrite = false;
|
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");
|
using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json");
|
||||||
var verifyRepository = new UnifiedSettingsDataRepository(
|
var verifyRepository = new UnifiedSettingsDataRepository(
|
||||||
@ -609,8 +609,8 @@ public class PersistenceTests
|
|||||||
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
|
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
|
||||||
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
||||||
|
|
||||||
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation);
|
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
|
||||||
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation);
|
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation).ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -655,12 +655,13 @@ public class PersistenceTests
|
|||||||
repository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
repository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
||||||
|
|
||||||
throwingStorage.ThrowOnWrite = true;
|
throwingStorage.ThrowOnWrite = true;
|
||||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await repository.DeleteAsync(secondaryLocation));
|
Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
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;
|
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");
|
using var verifyStorage = new FileStorage(root, new JsonSerializer(), ".json");
|
||||||
var verifyRepository = new UnifiedSettingsDataRepository(
|
var verifyRepository = new UnifiedSettingsDataRepository(
|
||||||
@ -670,8 +671,8 @@ public class PersistenceTests
|
|||||||
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
|
verifyRepository.RegisterDataType(primaryLocation, typeof(TestSimpleData));
|
||||||
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
verifyRepository.RegisterDataType(secondaryLocation, typeof(TestSimpleData));
|
||||||
|
|
||||||
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation);
|
var persistedPrimary = await verifyRepository.LoadAsync<TestSimpleData>(primaryLocation).ConfigureAwait(false);
|
||||||
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation);
|
var persistedSecondary = await verifyRepository.LoadAsync<TestSimpleData>(secondaryLocation).ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
49
GFramework.Game.Tests/Data/TestDataLocation.cs
Normal file
49
GFramework.Game.Tests/Data/TestDataLocation.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using GFramework.Game.Abstractions.Data;
|
||||||
|
using GFramework.Game.Abstractions.Enums;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为持久化测试提供稳定的测试数据位置实现。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class TestDataLocation : IDataLocation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化测试数据位置。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">测试使用的存储键。</param>
|
||||||
|
/// <param name="kinds">测试使用的存储类型。</param>
|
||||||
|
/// <param name="namespaceValue">测试使用的命名空间。</param>
|
||||||
|
/// <param name="metadata">附加测试元数据。</param>
|
||||||
|
public TestDataLocation(
|
||||||
|
string key,
|
||||||
|
StorageKinds kinds = StorageKinds.Local,
|
||||||
|
string? namespaceValue = null,
|
||||||
|
IReadOnlyDictionary<string, string>? metadata = null)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Kinds = kinds;
|
||||||
|
Namespace = namespaceValue;
|
||||||
|
Metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取测试数据对应的存储键。
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取测试数据使用的存储类型。
|
||||||
|
/// </summary>
|
||||||
|
public StorageKinds Kinds { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取测试数据使用的命名空间。
|
||||||
|
/// </summary>
|
||||||
|
public string? Namespace { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取附加到测试位置上的元数据。
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string>? Metadata { get; }
|
||||||
|
}
|
||||||
14
GFramework.Game.Tests/Data/TestNamedData.cs
Normal file
14
GFramework.Game.Tests/Data/TestNamedData.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using GFramework.Game.Abstractions.Data;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为批量持久化测试提供的另一种数据模型,用于验证运行时类型不会在接口路径上退化。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class TestNamedData : IData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试数据中的名称值。
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
14
GFramework.Game.Tests/Data/TestSaveData.cs
Normal file
14
GFramework.Game.Tests/Data/TestSaveData.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using GFramework.Game.Abstractions.Data;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为基础存档仓库测试提供的简单存档模型。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class TestSaveData : IData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试存档中的名称字段。
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
14
GFramework.Game.Tests/Data/TestSimpleData.cs
Normal file
14
GFramework.Game.Tests/Data/TestSimpleData.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using GFramework.Game.Abstractions.Data;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为通用持久化测试提供的简单数据模型。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class TestSimpleData : IData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试数据中的整数值。
|
||||||
|
/// </summary>
|
||||||
|
public int Value { get; set; }
|
||||||
|
}
|
||||||
35
GFramework.Game.Tests/Data/TestVersionedSaveData.cs
Normal file
35
GFramework.Game.Tests/Data/TestVersionedSaveData.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using GFramework.Game.Abstractions.Data;
|
||||||
|
|
||||||
|
namespace GFramework.Game.Tests.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为存档迁移测试提供的版本化存档模型。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class TestVersionedSaveData : IVersionedData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试存档中的名称字段。
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试存档中的等级字段。
|
||||||
|
/// </summary>
|
||||||
|
public int Level { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试存档中的经验字段。
|
||||||
|
/// </summary>
|
||||||
|
public int Experience { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置当前测试存档的版本号。
|
||||||
|
/// </summary>
|
||||||
|
public int Version { get; set; } = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置测试存档的最后修改时间。
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastModified { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer;
|
using GameJsonSerializer = GFramework.Game.Serializer.JsonSerializer;
|
||||||
|
|
||||||
@ -182,8 +183,8 @@ public sealed class JsonSerializerTests
|
|||||||
var parts = raw.Split(':');
|
var parts = raw.Split(':');
|
||||||
return new CoordinateStub
|
return new CoordinateStub
|
||||||
{
|
{
|
||||||
X = int.Parse(parts[0]),
|
X = int.Parse(parts[0], CultureInfo.InvariantCulture),
|
||||||
Y = int.Parse(parts[1])
|
Y = int.Parse(parts[1], CultureInfo.InvariantCulture)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ public sealed class AbstractArchitectureModuleInstallationTests
|
|||||||
var module = new RecordingGodotModule();
|
var module = new RecordingGodotModule();
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
var exception = Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||||
await architecture.InstallGodotModuleForTestAsync(module));
|
await architecture.InstallGodotModuleForTestAsync(module).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -197,7 +197,7 @@ public sealed class GodotYamlConfigLoaderTests
|
|||||||
var loader = CreateLoader(isEditor: false);
|
var loader = CreateLoader(isEditor: false);
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () =>
|
var exception = Assert.ThrowsAsync<ConfigLoadException>(async () =>
|
||||||
await loader.LoadAsync(new ConfigRegistry()));
|
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
@ -225,7 +225,7 @@ public sealed class GodotYamlConfigLoaderTests
|
|||||||
configureLoader: static _ => { });
|
configureLoader: static _ => { });
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
|
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||||
await loader.LoadAsync(new ConfigRegistry()));
|
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
||||||
}
|
}
|
||||||
@ -254,7 +254,7 @@ public sealed class GodotYamlConfigLoaderTests
|
|||||||
configureLoader: static _ => { });
|
configureLoader: static _ => { });
|
||||||
|
|
||||||
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
|
var exception = Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||||
await loader.LoadAsync(new ConfigRegistry()));
|
await loader.LoadAsync(new ConfigRegistry()).ConfigureAwait(false));
|
||||||
|
|
||||||
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
Assert.That(exception!.ParamName, Is.EqualTo("relativePath"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using GFramework.Godot.Text;
|
using GFramework.Godot.Text;
|
||||||
|
|
||||||
namespace GFramework.Godot.Tests.Text;
|
namespace GFramework.Godot.Tests.Text;
|
||||||
@ -25,7 +26,7 @@ public sealed class RichTextMarkupTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Effect_Should_Sort_Environment_Parameters_By_Key()
|
public void Effect_Should_Sort_Environment_Parameters_By_Key()
|
||||||
{
|
{
|
||||||
var env = new Dictionary<string, object?>
|
var env = new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||||
{
|
{
|
||||||
["tick"] = 0.1f,
|
["tick"] = 0.1f,
|
||||||
["speed"] = 4
|
["speed"] = 4
|
||||||
@ -53,7 +54,7 @@ public sealed class RichTextMarkupTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Effect_Should_Reject_Invalid_Environment_Key_Tokens()
|
public void Effect_Should_Reject_Invalid_Environment_Key_Tokens()
|
||||||
{
|
{
|
||||||
var env = new Dictionary<string, object?>
|
var env = new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||||
{
|
{
|
||||||
["bad key"] = 1
|
["bad key"] = 1
|
||||||
};
|
};
|
||||||
|
|||||||
@ -112,8 +112,8 @@ public abstract class AbstractArchitecture(
|
|||||||
// 在附加流程完成前先登记模块,保证后续任一步失败时仍能参与架构销毁阶段的清理。
|
// 在附加流程完成前先登记模块,保证后续任一步失败时仍能参与架构销毁阶段的清理。
|
||||||
_extensions.Add(module);
|
_extensions.Add(module);
|
||||||
|
|
||||||
// 等待锚点准备就绪,并保持 Godot 同步上下文,以便后续附加逻辑安全访问节点 API。
|
// 显式保留 Godot 同步上下文,确保后续 AddChild 和 OnAttach 仍在节点可访问的主线程执行。
|
||||||
await anchor.WaitUntilReadyAsync();
|
await anchor.WaitUntilReadyAsync().ConfigureAwait(true);
|
||||||
|
|
||||||
// 延迟调用将扩展节点添加为锚点的子节点
|
// 延迟调用将扩展节点添加为锚点的子节点
|
||||||
anchor.CallDeferred(Node.MethodName.AddChild, module.Node);
|
anchor.CallDeferred(Node.MethodName.AddChild, module.Node);
|
||||||
|
|||||||
@ -104,41 +104,36 @@ internal sealed class GodotYamlConfigEnvironment
|
|||||||
|
|
||||||
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateDirectoryCore(string path)
|
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateDirectoryCore(string path)
|
||||||
{
|
{
|
||||||
if (!path.IsGodotPath())
|
return path.IsGodotPath()
|
||||||
|
? EnumerateGodotDirectory(path)
|
||||||
|
: EnumerateFileSystemDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateFileSystemDirectory(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
if (!Directory.Exists(path))
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(path))
|
return null;
|
||||||
{
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Directory
|
return Directory
|
||||||
.EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly)
|
.EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly)
|
||||||
.Select(static entryPath => new GodotYamlConfigDirectoryEntry(
|
.Select(static entryPath => new GodotYamlConfigDirectoryEntry(
|
||||||
Path.GetFileName(entryPath),
|
Path.GetFileName(entryPath),
|
||||||
Directory.Exists(entryPath)))
|
Directory.Exists(entryPath)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
// 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null,而不是泄漏底层异常。
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (NotSupportedException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) when (IsExpectedDirectoryEnumerationException(ex))
|
||||||
|
{
|
||||||
|
// 非 Godot 路径分支与公开契约保持一致:宿主无法访问目录时返回 null,而不是泄漏底层异常。
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<GodotYamlConfigDirectoryEntry>? EnumerateGodotDirectory(string path)
|
||||||
|
{
|
||||||
using var directory = DirAccess.Open(path);
|
using var directory = DirAccess.Open(path);
|
||||||
if (directory == null)
|
if (directory == null)
|
||||||
{
|
{
|
||||||
@ -170,9 +165,15 @@ internal sealed class GodotYamlConfigEnvironment
|
|||||||
// 目录枚举句柄必须成对结束,避免未来循环体扩展后在异常路径上遗留引擎状态。
|
// 目录枚举句柄必须成对结束,避免未来循环体扩展后在异常路径上遗留引擎状态。
|
||||||
directory.ListDirEnd();
|
directory.ListDirEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsExpectedDirectoryEnumerationException(Exception exception)
|
||||||
|
{
|
||||||
|
return exception is IOException or UnauthorizedAccessException or ArgumentException or NotSupportedException;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool FileExistsCore(string path)
|
private static bool FileExistsCore(string path)
|
||||||
{
|
{
|
||||||
return path.IsGodotPath()
|
return path.IsGodotPath()
|
||||||
|
|||||||
@ -144,7 +144,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
|
|||||||
public virtual async ValueTask OnPauseAsync()
|
public virtual async ValueTask OnPauseAsync()
|
||||||
{
|
{
|
||||||
if (_scene != null)
|
if (_scene != null)
|
||||||
await _scene.OnPauseAsync();
|
// 暂停后紧接着会修改 Owner 的处理开关,必须回到 Godot 主线程继续执行。
|
||||||
|
await _scene.OnPauseAsync().ConfigureAwait(true);
|
||||||
|
|
||||||
// 暂停处理
|
// 暂停处理
|
||||||
Owner.SetProcess(false);
|
Owner.SetProcess(false);
|
||||||
@ -165,7 +166,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (_scene != null)
|
if (_scene != null)
|
||||||
await _scene.OnResumeAsync();
|
// 恢复完成后要立刻重新启用节点处理流程,因此显式保留当前同步上下文。
|
||||||
|
await _scene.OnResumeAsync().ConfigureAwait(true);
|
||||||
|
|
||||||
// 恢复处理
|
// 恢复处理
|
||||||
Owner.SetProcess(true);
|
Owner.SetProcess(true);
|
||||||
@ -198,7 +200,8 @@ public abstract class SceneBehaviorBase<T> : ISceneBehavior
|
|||||||
public virtual async ValueTask OnUnloadAsync()
|
public virtual async ValueTask OnUnloadAsync()
|
||||||
{
|
{
|
||||||
if (_scene != null)
|
if (_scene != null)
|
||||||
await _scene.OnUnloadAsync();
|
// 卸载后的 QueueFreeX 必须在 Godot 节点线程上调用,不能切走同步上下文。
|
||||||
|
await _scene.OnUnloadAsync().ConfigureAwait(true);
|
||||||
|
|
||||||
// 释放节点
|
// 释放节点
|
||||||
Owner.QueueFreeX();
|
Owner.QueueFreeX();
|
||||||
|
|||||||
@ -6,36 +6,41 @@
|
|||||||
|
|
||||||
## 当前恢复点
|
## 当前恢复点
|
||||||
|
|
||||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-052`
|
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-058`
|
||||||
- 当前阶段:`Phase 52`
|
- 当前阶段:`Phase 58`
|
||||||
- 当前焦点:
|
- 当前焦点:
|
||||||
- `2026-04-24` 本轮从当前 PR review 的未解决线程回切到 `GFramework.Game` / `GFramework.Godot.SourceGenerators.Tests`
|
- `2026-04-24` 使用 `$gframework-pr-review` 复核当前分支 PR #286 的 latest-head review threads、MegaLinter 与测试状态
|
||||||
- `UnifiedSettingsFile.Sections` 与 `CloneFile` fallback 已对齐为“可保留原 comparer 时保留,否则显式回退到 `StringComparer.Ordinal`”的文档与实现契约
|
- 已确认最新 head 上唯一未解决的实质代码线程指向 `GFramework.Godot/Scene/SceneBehaviorBase.cs` 中 `OnPauseAsync` 的缩进异常,并顺带对齐 `OnResumeAsync`、`OnUnloadAsync`
|
||||||
- `AutoRegisterExportedCollectionsGeneratorTests` 中剩余的 `await test.RunAsync();` 已统一补齐 `.ConfigureAwait(false)`,并同步让 `VerifyDiagnosticsAsync` 内部消费异步等待
|
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` 通过,结果为 `565 Warning(s)`、`0 Error(s)`;当前跟进只处理 PR review 指向的格式问题,不扩散到既有 warning 基线
|
||||||
- 当前批次仍需避免混入与 analyzer-warning-reduction 无关的既有工作树改动
|
- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` 已通过,当前文件不再残留格式差异
|
||||||
|
|
||||||
## 当前活跃事实
|
## 当前活跃事实
|
||||||
|
|
||||||
- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值
|
- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值
|
||||||
- 本轮已完成 `GFramework.Godot.SourceGenerators` warning 清理:clean `Release` build 从 9 个 warning 降至 0 个 warning
|
- 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线
|
||||||
- 当前已确认解决的文件包括 `BindNodeSignalGenerator.cs`、`GetNodeGenerator.cs`、`GodotProjectMetadataGenerator.cs`、`Registration/AutoRegisterExportedCollectionsGenerator.cs`
|
- 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release` 的 `116 warning(s)`
|
||||||
- 本轮直接执行仓库根目录 `dotnet clean` 仍在 `ValidateSolutionConfiguration` 阶段失败,输出未提供具体 error 文本
|
- `RP-056` 已验证 `GeneratedConfigConsumerIntegrationTests.cs` 不再出现在项目 build warning 输出中
|
||||||
- 本轮直接执行仓库根目录 `dotnet build` 成功,并给出 `1184 warning(s)` 的真实输出
|
- `RP-057` 已验证 `PersistenceTests.cs` 不再出现在 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 的 warning 输出中
|
||||||
- `GFramework.Godot.SourceGenerators.Tests` 已通过测试辅助模板抽取与 `ConfigureAwait(false)` 修正,当前 `Debug` / `Release` 构建均为 `0 Warning(s)`
|
- 本轮已验证 `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`
|
||||||
- 本轮已验证 `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`,结果为 `Passed: 48`
|
- `GFramework.Game.Tests` 当前剩余热点已经几乎完全集中到 `YamlConfigLoaderTests.cs` 这一高上下文文件
|
||||||
- 本轮已把 PR #283 中仍打开的 `UnifiedSettingsDataRepository.cs` comparer 契约线程落到代码与 XML 注释,避免 fallback 语义继续依赖隐式默认 comparer
|
- PR #286 当前标题为 `Fix/analyzer warning reduction batch`;最新抓取时间点的 PR 状态仍为 `OPEN`
|
||||||
- 本轮已确认 `AutoRegisterExportedCollectionsGeneratorTests` 的 5 处裸 `await test.RunAsync();` 不是当前 Release build 告警来源,但仍作为 PR review 一致性项一并修正
|
- 最新 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 代码线程
|
||||||
|
|
||||||
## 当前风险
|
## 当前风险
|
||||||
|
|
||||||
- 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0
|
- 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0
|
||||||
- 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build`
|
- 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build`
|
||||||
- 仓库根目录 `dotnet clean` 目前仍然无法给出新的 clean 基线
|
- 仓库根目录与 `GFramework.Game.Tests` 的 `dotnet clean` 目前都无法给出新的 clean 基线
|
||||||
- 缓解措施:若下一轮继续做整仓 warning reduction,先定位 `dotnet clean` 的 solution-level 失败原因,或明确继续沿用用户确认的 `1193 warning(s)` clean 基线与本轮 `1184 warning(s)` direct build 观测值
|
- 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值
|
||||||
- 当前 worktree 已存在与本批次无关的未提交改动
|
- 当前 worktree 仍存在未跟踪的 `.codex` 目录
|
||||||
- 缓解措施:提交当前批次时只暂存 `GFramework.Godot.SourceGenerators.Tests` 与对应 `ai-plan` 文件,避免混入其他 topic 变更
|
- 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交
|
||||||
- `GFramework.Game` 当前 `Release` build 仍带有既有 analyzer warning 基线
|
- 下一轮若继续深入 `GFramework.Game.Tests`,很可能需要进入 `YamlConfigLoaderTests.cs` 这种高上下文大文件
|
||||||
- 缓解措施:本轮仅验证改动未新增 `UnifiedSettingsDataRepository` / `UnifiedSettingsFile` 相关 warning;若继续在该模块做 warning reduction,需要另开切片处理现存基线
|
- 缓解措施:把它单独作为一个明确的新批次处理,不与其它 warning family 混批
|
||||||
|
- PR 标题检查当前仍显示 `Inconclusive`
|
||||||
|
- 缓解措施:如需让该检查转绿,需要单独更新 GitHub PR 标题;这不属于本地代码修改范围
|
||||||
|
|
||||||
## 活跃文档
|
## 活跃文档
|
||||||
|
|
||||||
@ -51,25 +56,31 @@
|
|||||||
|
|
||||||
## 验证说明
|
## 验证说明
|
||||||
|
|
||||||
- `dotnet clean`
|
- `dotnet clean GFramework.sln -c Release`
|
||||||
- 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本
|
- 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本
|
||||||
- `dotnet build`
|
- `dotnet build GFramework.sln -c Release`
|
||||||
- 结果:成功;`1184 Warning(s)`、`0 Error(s)`
|
- 结果:成功;`116 Warning(s)`、`0 Error(s)`
|
||||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj`
|
- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||||
- 初始结果:成功;`24 Warning(s)`、`0 Error(s)`
|
- 结果:失败;clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)`、`0 Error(s)`,未输出额外错误文本
|
||||||
- 本轮收尾结果:成功;`0 Warning(s)`、`0 Error(s)`
|
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
|
- `RP-055` 收尾结果:成功;`63 Warning(s)`、`0 Error(s)`
|
||||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
- `RP-056` 当前结果:成功;`59 Warning(s)`、`0 Error(s)`
|
||||||
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
|
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental`
|
||||||
- 结果:成功;`Passed: 48`、`Failed: 0`
|
- `RP-057` 热点重排前:成功;`253 Warning(s)`、`0 Error(s)`
|
||||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
- `RP-057` 当前结果:成功;`249 Warning(s)`、`0 Error(s)`
|
||||||
- 结果:成功;`533 Warning(s)`、`0 Error(s)`;模块仍存在既有 warning 基线,本轮 follow-up 仅处理 PR review 指向的 comparer 契约与测试异步等待一致性
|
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"`
|
||||||
- `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release`
|
- 结果:成功;`Passed: 19`、`Failed: 0`
|
||||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"`
|
||||||
- `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build`
|
- 结果:成功;`Passed: 4`、`Failed: 0`
|
||||||
- 结果:成功;`Passed: 48`、`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. 提交当前 comparer 契约与 `ConfigureAwait(false)` PR follow-up,并确认只纳入本 topic 相关文件
|
1. 提交 `SceneBehaviorBase.cs` 与 `RP-058` tracking/trace 更新,清掉 PR #286 当前 latest-head 上的格式类 review thread
|
||||||
2. 视 PR review 反馈决定是否继续收敛 `GFramework.Game` 现有 warning 基线,或返回下一轮整仓 warning 热点筛选
|
2. 若继续 warning reduction 主线,应回到 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`,把它作为独立高上下文批次处理
|
||||||
|
|||||||
@ -2,6 +2,163 @@
|
|||||||
|
|
||||||
# Analyzer Warning Reduction 追踪
|
# 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`
|
||||||
|
|
||||||
|
- 触发背景:
|
||||||
|
- `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
|
||||||
|
|
||||||
|
- 触发背景:
|
||||||
|
- `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` 小热点
|
||||||
|
|
||||||
|
- 触发背景:
|
||||||
|
- `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 批次(触发文件数停止阈值)
|
||||||
|
|
||||||
|
- 触发背景:
|
||||||
|
- 用户要求“直接进入下一批”,继续沿 `$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 清理
|
||||||
|
|
||||||
|
- 触发背景:
|
||||||
|
- 用户以 `$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
|
## 2026-04-24 — RP-052
|
||||||
|
|
||||||
### 阶段:PR review follow-up(comparer 契约 + `ConfigureAwait(false)` 收尾)
|
### 阶段:PR review follow-up(comparer 契约 + `ConfigureAwait(false)` 收尾)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user