mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
Merge pull request #297 from GeWuYou/fix/analyzer-warning-reduction-batch
Fix/analyzer warning reduction batch
This commit is contained in:
commit
7cfdd2cf21
10
GFramework.Core.Tests/Architectures/IMixedTestSystem.cs
Normal file
10
GFramework.Core.Tests/Architectures/IMixedTestSystem.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 定义用于混合优先级排序测试的系统契约。
|
||||
/// </summary>
|
||||
public interface IMixedTestSystem : ISystem
|
||||
{
|
||||
}
|
||||
10
GFramework.Core.Tests/Architectures/IPriorityTestModel.cs
Normal file
10
GFramework.Core.Tests/Architectures/IPriorityTestModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 定义用于优先级排序测试的模型契约。
|
||||
/// </summary>
|
||||
public interface IPriorityTestModel : IModel
|
||||
{
|
||||
}
|
||||
10
GFramework.Core.Tests/Architectures/IPriorityTestSystem.cs
Normal file
10
GFramework.Core.Tests/Architectures/IPriorityTestSystem.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 定义用于优先级排序测试的系统契约。
|
||||
/// </summary>
|
||||
public interface IPriorityTestSystem : ISystem
|
||||
{
|
||||
}
|
||||
10
GFramework.Core.Tests/Architectures/IPriorityTestUtility.cs
Normal file
10
GFramework.Core.Tests/Architectures/IPriorityTestUtility.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 定义用于优先级排序测试的工具契约。
|
||||
/// </summary>
|
||||
public interface IPriorityTestUtility : IUtility
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示显式声明负优先级的混合测试系统。
|
||||
/// </summary>
|
||||
public class MixedTestSystemNegativePriority : AbstractSystem, IMixedTestSystem, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试系统的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => -10;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示显式声明正优先级的混合测试系统。
|
||||
/// </summary>
|
||||
public class MixedTestSystemWithPriority : AbstractSystem, IMixedTestSystem, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试系统的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 10;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示未声明优先级、依赖默认排序值的混合测试系统。
|
||||
/// </summary>
|
||||
public class MixedTestSystemWithoutPriority : AbstractSystem, IMixedTestSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,7 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Model;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -120,129 +115,3 @@ public class PriorityServiceTests
|
||||
Assert.That(systems[2], Is.InstanceOf<MixedTestSystemWithPriority>()); // 10
|
||||
}
|
||||
}
|
||||
|
||||
#region Test Interfaces
|
||||
|
||||
public interface IPriorityTestSystem : ISystem
|
||||
{
|
||||
}
|
||||
|
||||
public interface IPriorityTestModel : IModel
|
||||
{
|
||||
}
|
||||
|
||||
public interface IPriorityTestUtility : IUtility
|
||||
{
|
||||
}
|
||||
|
||||
public interface IMixedTestSystem : ISystem
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Systems
|
||||
|
||||
public class PriorityTestSystemA : AbstractSystem, IPriorityTestSystem, IPrioritized
|
||||
{
|
||||
public int Priority => 10;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class PriorityTestSystemB : AbstractSystem, IPriorityTestSystem, IPrioritized
|
||||
{
|
||||
public int Priority => 20;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class PriorityTestSystemC : AbstractSystem, IPriorityTestSystem, IPrioritized
|
||||
{
|
||||
public int Priority => 30;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class MixedTestSystemWithPriority : AbstractSystem, IMixedTestSystem, IPrioritized
|
||||
{
|
||||
public int Priority => 10;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class MixedTestSystemWithoutPriority : AbstractSystem, IMixedTestSystem
|
||||
{
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class MixedTestSystemNegativePriority : AbstractSystem, IMixedTestSystem, IPrioritized
|
||||
{
|
||||
public int Priority => -10;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Models
|
||||
|
||||
public class PriorityTestModelA : AbstractModel, IPriorityTestModel, IPrioritized
|
||||
{
|
||||
public int Priority => 10;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class PriorityTestModelB : AbstractModel, IPriorityTestModel, IPrioritized
|
||||
{
|
||||
public int Priority => 20;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class PriorityTestModelC : AbstractModel, IPriorityTestModel, IPrioritized
|
||||
{
|
||||
public int Priority => 30;
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Utilities
|
||||
|
||||
public class PriorityTestUtilityA : IPriorityTestUtility, IPrioritized
|
||||
{
|
||||
public int Priority => 10;
|
||||
}
|
||||
|
||||
public class PriorityTestUtilityB : IPriorityTestUtility, IPrioritized
|
||||
{
|
||||
public int Priority => 20;
|
||||
}
|
||||
|
||||
public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized
|
||||
{
|
||||
public int Priority => 30;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
22
GFramework.Core.Tests/Architectures/PriorityTestModelA.cs
Normal file
22
GFramework.Core.Tests/Architectures/PriorityTestModelA.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Model;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 10 的测试模型。
|
||||
/// </summary>
|
||||
public class PriorityTestModelA : AbstractModel, IPriorityTestModel, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试模型的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 10;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
22
GFramework.Core.Tests/Architectures/PriorityTestModelB.cs
Normal file
22
GFramework.Core.Tests/Architectures/PriorityTestModelB.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Model;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 20 的测试模型。
|
||||
/// </summary>
|
||||
public class PriorityTestModelB : AbstractModel, IPriorityTestModel, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试模型的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 20;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
22
GFramework.Core.Tests/Architectures/PriorityTestModelC.cs
Normal file
22
GFramework.Core.Tests/Architectures/PriorityTestModelC.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Model;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 30 的测试模型。
|
||||
/// </summary>
|
||||
public class PriorityTestModelC : AbstractModel, IPriorityTestModel, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试模型的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 30;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
21
GFramework.Core.Tests/Architectures/PriorityTestSystemA.cs
Normal file
21
GFramework.Core.Tests/Architectures/PriorityTestSystemA.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 10 的测试系统。
|
||||
/// </summary>
|
||||
public class PriorityTestSystemA : AbstractSystem, IPriorityTestSystem, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试系统的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 10;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
21
GFramework.Core.Tests/Architectures/PriorityTestSystemB.cs
Normal file
21
GFramework.Core.Tests/Architectures/PriorityTestSystemB.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 20 的测试系统。
|
||||
/// </summary>
|
||||
public class PriorityTestSystemB : AbstractSystem, IPriorityTestSystem, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试系统的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 20;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
21
GFramework.Core.Tests/Architectures/PriorityTestSystemC.cs
Normal file
21
GFramework.Core.Tests/Architectures/PriorityTestSystemC.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 30 的测试系统。
|
||||
/// </summary>
|
||||
public class PriorityTestSystemC : AbstractSystem, IPriorityTestSystem, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试系统的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 30;
|
||||
|
||||
/// <summary>
|
||||
/// 保持空初始化,以便测试仅覆盖优先级排序行为。
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
14
GFramework.Core.Tests/Architectures/PriorityTestUtilityA.cs
Normal file
14
GFramework.Core.Tests/Architectures/PriorityTestUtilityA.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 10 的测试工具。
|
||||
/// </summary>
|
||||
public class PriorityTestUtilityA : IPriorityTestUtility, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试工具的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 10;
|
||||
}
|
||||
14
GFramework.Core.Tests/Architectures/PriorityTestUtilityB.cs
Normal file
14
GFramework.Core.Tests/Architectures/PriorityTestUtilityB.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 20 的测试工具。
|
||||
/// </summary>
|
||||
public class PriorityTestUtilityB : IPriorityTestUtility, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试工具的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 20;
|
||||
}
|
||||
14
GFramework.Core.Tests/Architectures/PriorityTestUtilityC.cs
Normal file
14
GFramework.Core.Tests/Architectures/PriorityTestUtilityC.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
/// <summary>
|
||||
/// 表示优先级为 30 的测试工具。
|
||||
/// </summary>
|
||||
public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前测试工具的排序优先级。
|
||||
/// </summary>
|
||||
public int Priority => 30;
|
||||
}
|
||||
8
GFramework.Core.Tests/Ioc/AliasAwareService.cs
Normal file
8
GFramework.Core.Tests/Ioc/AliasAwareService.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 同时实现多个别名接口的测试服务。
|
||||
/// </summary>
|
||||
public sealed class AliasAwareService : IPrimaryAliasService, ISecondaryAliasService
|
||||
{
|
||||
}
|
||||
12
GFramework.Core.Tests/Ioc/IMixedService.cs
Normal file
12
GFramework.Core.Tests/Ioc/IMixedService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 混合服务接口(用于测试优先级和非优先级混合)
|
||||
/// </summary>
|
||||
public interface IMixedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置服务名称。
|
||||
/// </summary>
|
||||
string? Name { get; set; }
|
||||
}
|
||||
6
GFramework.Core.Tests/Ioc/IPrimaryAliasService.cs
Normal file
6
GFramework.Core.Tests/Ioc/IPrimaryAliasService.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 主服务别名接口。
|
||||
/// </summary>
|
||||
public interface IPrimaryAliasService : ISharedAliasService;
|
||||
10
GFramework.Core.Tests/Ioc/IPrioritizedService.cs
Normal file
10
GFramework.Core.Tests/Ioc/IPrioritizedService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 优先级服务接口
|
||||
/// </summary>
|
||||
public interface IPrioritizedService : IPrioritized, IMixedService
|
||||
{
|
||||
}
|
||||
6
GFramework.Core.Tests/Ioc/ISecondaryAliasService.cs
Normal file
6
GFramework.Core.Tests/Ioc/ISecondaryAliasService.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 次级兼容别名接口。
|
||||
/// </summary>
|
||||
public interface ISecondaryAliasService : ISharedAliasService;
|
||||
6
GFramework.Core.Tests/Ioc/IService.cs
Normal file
6
GFramework.Core.Tests/Ioc/IService.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 服务接口定义
|
||||
/// </summary>
|
||||
public interface IService;
|
||||
6
GFramework.Core.Tests/Ioc/ISharedAliasService.cs
Normal file
6
GFramework.Core.Tests/Ioc/ISharedAliasService.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证未冻结查询路径中的服务别名去重行为。
|
||||
/// </summary>
|
||||
public interface ISharedAliasService;
|
||||
@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
@ -734,74 +733,3 @@ public class MicrosoftDiContainerTests
|
||||
Assert.That(((IPrioritizedService)services[1]).Priority, Is.EqualTo(30));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务接口定义
|
||||
/// </summary>
|
||||
public interface IService;
|
||||
|
||||
/// <summary>
|
||||
/// 测试服务类,实现 IService 接口
|
||||
/// </summary>
|
||||
public sealed class TestService : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置优先级
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优先级服务接口
|
||||
/// </summary>
|
||||
public interface IPrioritizedService : IPrioritized
|
||||
{
|
||||
string? Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 混合服务接口(用于测试优先级和非优先级混合)
|
||||
/// </summary>
|
||||
public interface IMixedService
|
||||
{
|
||||
string? Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于验证未冻结查询路径中的服务别名去重行为。
|
||||
/// </summary>
|
||||
public interface ISharedAliasService;
|
||||
|
||||
/// <summary>
|
||||
/// 主服务别名接口。
|
||||
/// </summary>
|
||||
public interface IPrimaryAliasService : ISharedAliasService;
|
||||
|
||||
/// <summary>
|
||||
/// 次级兼容别名接口。
|
||||
/// </summary>
|
||||
public interface ISecondaryAliasService : ISharedAliasService;
|
||||
|
||||
/// <summary>
|
||||
/// 同时实现多个别名接口的测试服务。
|
||||
/// </summary>
|
||||
public sealed class AliasAwareService : IPrimaryAliasService, ISecondaryAliasService
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实现优先级的服务
|
||||
/// </summary>
|
||||
public sealed class PrioritizedService : IPrioritizedService, IMixedService
|
||||
{
|
||||
public int Priority { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不实现优先级的服务
|
||||
/// </summary>
|
||||
public sealed class NonPrioritizedService : IMixedService
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
||||
12
GFramework.Core.Tests/Ioc/NonPrioritizedService.cs
Normal file
12
GFramework.Core.Tests/Ioc/NonPrioritizedService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 不实现优先级的服务
|
||||
/// </summary>
|
||||
public sealed class NonPrioritizedService : IMixedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置服务名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
17
GFramework.Core.Tests/Ioc/PrioritizedService.cs
Normal file
17
GFramework.Core.Tests/Ioc/PrioritizedService.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 实现优先级的服务
|
||||
/// </summary>
|
||||
public sealed class PrioritizedService : IPrioritizedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置优先级
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置服务名称
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
12
GFramework.Core.Tests/Ioc/TestService.cs
Normal file
12
GFramework.Core.Tests/Ioc/TestService.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace GFramework.Core.Tests.Ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 测试服务类,实现 IService 接口
|
||||
/// </summary>
|
||||
public sealed class TestService : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置优先级
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
}
|
||||
@ -6,7 +6,6 @@ using GFramework.Core.Environment;
|
||||
using GFramework.Core.Events;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Query;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
@ -236,179 +235,3 @@ public class AbstractAsyncQueryTests
|
||||
Assert.That(result2, Is.EqualTo(40));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用异步查询输入类V2
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryInputV2 : IQueryInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置值
|
||||
/// </summary>
|
||||
public int Value { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 整数类型测试异步查询类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncQueryV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncQueryV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>查询结果,将输入值乘以2</returns>
|
||||
protected override Task<int> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
return Task.FromResult(input.Value * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串类型测试异步查询类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncStringQueryV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncStringQueryV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncStringQueryV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>格式化的字符串结果</returns>
|
||||
protected override Task<string> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
return Task.FromResult($"Value: {input.Value * 2}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复杂对象类型测试异步查询类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncComplexQueryV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, TestAsyncQueryResultV2>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncComplexQueryV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncComplexQueryV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>复杂对象查询结果</returns>
|
||||
protected override Task<TestAsyncQueryResultV2> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
var result = new TestAsyncQueryResultV2
|
||||
{
|
||||
Value = input.Value * 2,
|
||||
DoubleValue = input.Value * 3
|
||||
};
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用异步查询类(抛出异常)
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryWithExceptionV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncQueryWithExceptionV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncQueryWithExceptionV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作并抛出异常
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <exception cref="InvalidOperationException">总是抛出异常</exception>
|
||||
protected override Task<int> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
throw new InvalidOperationException("Test exception");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用异步查询子类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryChildV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncQueryChildV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncQueryChildV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现(子类实现,乘以3)
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>查询结果,将输入值乘以3</returns>
|
||||
protected override Task<int> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
return Task.FromResult(input.Value * 3);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用复杂查询结果类V2
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryResultV2
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置值
|
||||
/// </summary>
|
||||
public int Value { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置双倍值
|
||||
/// </summary>
|
||||
public int DoubleValue { get; init; }
|
||||
}
|
||||
|
||||
38
GFramework.Core.Tests/Query/TestAsyncComplexQueryV4.cs
Normal file
38
GFramework.Core.Tests/Query/TestAsyncComplexQueryV4.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using GFramework.Core.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 复杂对象类型测试异步查询类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncComplexQueryV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, TestAsyncQueryResultV2>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncComplexQueryV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncComplexQueryV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>复杂对象查询结果</returns>
|
||||
protected override Task<TestAsyncQueryResultV2> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
var result = new TestAsyncQueryResultV2
|
||||
{
|
||||
Value = input.Value * 2,
|
||||
DoubleValue = input.Value * 3
|
||||
};
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
33
GFramework.Core.Tests/Query/TestAsyncQueryChildV4.cs
Normal file
33
GFramework.Core.Tests/Query/TestAsyncQueryChildV4.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using GFramework.Core.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 测试用异步查询子类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryChildV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncQueryChildV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncQueryChildV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现(子类实现,乘以3)
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>查询结果,将输入值乘以3</returns>
|
||||
protected override Task<int> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
return Task.FromResult(input.Value * 3);
|
||||
}
|
||||
}
|
||||
14
GFramework.Core.Tests/Query/TestAsyncQueryInputV2.cs
Normal file
14
GFramework.Core.Tests/Query/TestAsyncQueryInputV2.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using GFramework.Cqrs.Abstractions.Cqrs.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 测试用异步查询输入类V2
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryInputV2 : IQueryInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置值
|
||||
/// </summary>
|
||||
public int Value { get; init; }
|
||||
}
|
||||
17
GFramework.Core.Tests/Query/TestAsyncQueryResultV2.cs
Normal file
17
GFramework.Core.Tests/Query/TestAsyncQueryResultV2.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 测试用复杂查询结果类V2
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryResultV2
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置值
|
||||
/// </summary>
|
||||
public int Value { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置双倍值
|
||||
/// </summary>
|
||||
public int DoubleValue { get; init; }
|
||||
}
|
||||
33
GFramework.Core.Tests/Query/TestAsyncQueryV4.cs
Normal file
33
GFramework.Core.Tests/Query/TestAsyncQueryV4.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using GFramework.Core.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 整数类型测试异步查询类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncQueryV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncQueryV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>查询结果,将输入值乘以2</returns>
|
||||
protected override Task<int> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
return Task.FromResult(input.Value * 2);
|
||||
}
|
||||
}
|
||||
28
GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs
Normal file
28
GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using GFramework.Core.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 测试用异步查询类(抛出异常)
|
||||
/// </summary>
|
||||
public sealed class TestAsyncQueryWithExceptionV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncQueryWithExceptionV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncQueryWithExceptionV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作并抛出异常
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>返回一个不会正常完成的 <see cref="Task{TResult}" />,因为该方法始终抛出异常。</returns>
|
||||
/// <exception cref="InvalidOperationException">总是抛出异常</exception>
|
||||
protected override Task<int> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
throw new InvalidOperationException("Test exception");
|
||||
}
|
||||
}
|
||||
33
GFramework.Core.Tests/Query/TestAsyncStringQueryV4.cs
Normal file
33
GFramework.Core.Tests/Query/TestAsyncStringQueryV4.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using GFramework.Core.Query;
|
||||
|
||||
namespace GFramework.Core.Tests.Query;
|
||||
|
||||
/// <summary>
|
||||
/// 字符串类型测试异步查询类V4,继承AbstractAsyncQuery
|
||||
/// </summary>
|
||||
public sealed class TestAsyncStringQueryV4 : AbstractAsyncQuery<TestAsyncQueryInputV2, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化TestAsyncStringQueryV4的新实例
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
public TestAsyncStringQueryV4(TestAsyncQueryInputV2 input) : base(input)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取查询是否已执行
|
||||
/// </summary>
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行异步查询操作的具体实现
|
||||
/// </summary>
|
||||
/// <param name="input">查询输入参数</param>
|
||||
/// <returns>格式化的字符串结果</returns>
|
||||
protected override Task<string> OnDoAsync(TestAsyncQueryInputV2 input)
|
||||
{
|
||||
Executed = true;
|
||||
return Task.FromResult($"Value: {input.Value * 2}");
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Game.Abstractions.Config;
|
||||
using GFramework.Game.Config;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace GFramework.Game.Tests.Config;
|
||||
|
||||
@ -2784,6 +2787,130 @@ public class YamlConfigLoaderTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证底层文件读取在取消时会保留 <see cref="OperationCanceledException" />,
|
||||
/// 避免热重载把会话级取消误报为配置读取失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
|
||||
{
|
||||
CreateConfigFile(
|
||||
"monster/slime.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""");
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", static config => config.Id);
|
||||
var registration = GetSingleYamlTableRegistration(loader);
|
||||
var readYamlAsyncMethod = registration.GetType()
|
||||
.GetMethod("ReadYamlAsync", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(readYamlAsyncMethod, Is.Not.Null);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
// 通过反射直接命中注册项的文件读取路径,稳定回归本次取消语义修复。
|
||||
var readTask = (Task<string>)readYamlAsyncMethod!.Invoke(
|
||||
registration,
|
||||
new object?[]
|
||||
{
|
||||
Path.Combine(_rootPath, "monster"),
|
||||
Path.Combine(_rootPath, "monster", "slime.yaml"),
|
||||
null,
|
||||
cancellationTokenSource.Token
|
||||
})!;
|
||||
|
||||
Assert.That(
|
||||
async () => await readTask.ConfigureAwait(false),
|
||||
Throws.InstanceOf<OperationCanceledException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证同步反序列化阶段遇到已取消 token 时会直接透传 <see cref="OperationCanceledException" />,
|
||||
/// 避免把停止加载误报为 YAML 解析失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DeserializeValue_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
|
||||
{
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", static config => config.Id);
|
||||
var registration = GetSingleYamlTableRegistration(loader);
|
||||
var deserializeValueMethod = registration.GetType()
|
||||
.GetMethod("DeserializeValue", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(deserializeValueMethod, Is.Not.Null);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
var deserializer = new DeserializerBuilder().Build();
|
||||
var exception = Assert.Throws<TargetInvocationException>(() =>
|
||||
deserializeValueMethod!.Invoke(
|
||||
registration,
|
||||
new object?[]
|
||||
{
|
||||
deserializer,
|
||||
Path.Combine(_rootPath, "monster"),
|
||||
Path.Combine(_rootPath, "monster", "slime.yaml"),
|
||||
null,
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""",
|
||||
cancellationTokenSource.Token
|
||||
}));
|
||||
|
||||
// 反射调用同步私有方法时会把原始异常包装为 TargetInvocationException。
|
||||
Assert.That(exception!.InnerException, Is.InstanceOf<OperationCanceledException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证构建最终配置表阶段遇到已取消 token 时会继续透传 <see cref="OperationCanceledException" />,
|
||||
/// 避免热重载把提交前取消记录成构表失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BuildLoadResult_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
|
||||
{
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", static config => config.Id);
|
||||
var registration = GetSingleYamlTableRegistration(loader);
|
||||
var buildLoadResultMethod = registration.GetType()
|
||||
.GetMethod("BuildLoadResult", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(buildLoadResultMethod, Is.Not.Null);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
var exception = Assert.Throws<TargetInvocationException>(() =>
|
||||
buildLoadResultMethod!.Invoke(
|
||||
registration,
|
||||
new object?[]
|
||||
{
|
||||
Path.Combine(_rootPath, "monster"),
|
||||
null,
|
||||
new List<MonsterConfigStub>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Slime",
|
||||
Hp = 10
|
||||
}
|
||||
},
|
||||
new List<YamlConfigReferenceUsage>(),
|
||||
cancellationTokenSource.Token
|
||||
}));
|
||||
|
||||
// 反射调用同步私有方法时会把原始异常包装为 TargetInvocationException。
|
||||
Assert.That(exception!.InnerException, Is.InstanceOf<OperationCanceledException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证依赖关系仅来自 <c>contains</c> 子 schema 时,热重载仍会追踪该依赖并在目标表破坏引用后回滚。
|
||||
/// </summary>
|
||||
@ -2928,7 +3055,7 @@ public class YamlConfigLoaderTests
|
||||
|
||||
Assert.That(exception!.ParamName, Is.EqualTo("options"));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 验证热重载失败时会保留旧表状态,并通过失败回调暴露诊断信息。
|
||||
/// </summary>
|
||||
@ -3372,6 +3499,22 @@ public class YamlConfigLoaderTests
|
||||
CreateConfigFile(relativePath, content);
|
||||
}
|
||||
|
||||
private static object GetSingleYamlTableRegistration(YamlConfigLoader loader)
|
||||
{
|
||||
var registrationsField = typeof(YamlConfigLoader).GetField(
|
||||
"_registrations",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(registrationsField, Is.Not.Null);
|
||||
|
||||
var registrations = registrationsField!.GetValue(loader) as System.Collections.IList;
|
||||
|
||||
Assert.That(registrations, Is.Not.Null);
|
||||
Assert.That(registrations!.Count, Is.EqualTo(1));
|
||||
|
||||
return registrations[0]!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在限定时间内等待异步任务完成,避免文件监听测试无限挂起。
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Game.Abstractions.Config;
|
||||
using YamlDotNet.Serialization;
|
||||
@ -472,93 +474,178 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
IDeserializer deserializer,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var directoryPath = Path.Combine(rootPath, RelativePath);
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConfigDirectoryNotFound,
|
||||
Name,
|
||||
$"Config directory '{directoryPath}' was not found for table '{Name}'.",
|
||||
configDirectoryPath: directoryPath);
|
||||
}
|
||||
|
||||
YamlConfigSchema? schema = null;
|
||||
IReadOnlyCollection<string> referencedTableNames = Array.Empty<string>();
|
||||
if (!string.IsNullOrEmpty(SchemaRelativePath))
|
||||
{
|
||||
var schemaPath = Path.Combine(rootPath, SchemaRelativePath);
|
||||
schema = await YamlConfigSchemaValidator.LoadAsync(Name, schemaPath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
referencedTableNames = schema.ReferencedTableNames;
|
||||
}
|
||||
|
||||
var directoryPath = GetValidatedDirectoryPath(rootPath);
|
||||
var schema = await LoadSchemaAsync(rootPath, cancellationToken).ConfigureAwait(false);
|
||||
var referenceUsages = new List<YamlConfigReferenceUsage>();
|
||||
var values = await LoadValuesAsync(
|
||||
directoryPath,
|
||||
deserializer,
|
||||
schema,
|
||||
referenceUsages,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return BuildLoadResult(directoryPath, schema, values, referenceUsages, cancellationToken);
|
||||
}
|
||||
|
||||
private string GetValidatedDirectoryPath(string rootPath)
|
||||
{
|
||||
var directoryPath = Path.Combine(rootPath, RelativePath);
|
||||
if (Directory.Exists(directoryPath))
|
||||
{
|
||||
return directoryPath;
|
||||
}
|
||||
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConfigDirectoryNotFound,
|
||||
Name,
|
||||
$"Config directory '{directoryPath}' was not found for table '{Name}'.",
|
||||
configDirectoryPath: directoryPath);
|
||||
}
|
||||
|
||||
private async Task<YamlConfigSchema?> LoadSchemaAsync(string rootPath, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SchemaRelativePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var schemaPath = Path.Combine(rootPath, SchemaRelativePath);
|
||||
return await YamlConfigSchemaValidator.LoadAsync(Name, schemaPath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<List<TValue>> LoadValuesAsync(
|
||||
string directoryPath,
|
||||
IDeserializer deserializer,
|
||||
YamlConfigSchema? schema,
|
||||
List<YamlConfigReferenceUsage> referenceUsages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var values = new List<TValue>();
|
||||
var files = Directory
|
||||
foreach (var file in GetYamlFiles(directoryPath))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var yaml = await ReadYamlAsync(directoryPath, file, schema, cancellationToken).ConfigureAwait(false);
|
||||
CollectReferenceUsages(referenceUsages, schema, file, yaml);
|
||||
values.Add(DeserializeValue(deserializer, directoryPath, file, schema, yaml, cancellationToken));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static string[] GetYamlFiles(string directoryPath)
|
||||
{
|
||||
return Directory
|
||||
.EnumerateFiles(directoryPath, "*.*", SearchOption.TopDirectoryOnly)
|
||||
.Where(static path =>
|
||||
path.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith(".yml", StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(static path => path, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string yaml;
|
||||
try
|
||||
{
|
||||
yaml = await File.ReadAllTextAsync(file, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConfigFileReadFailed,
|
||||
Name,
|
||||
$"Failed to read config file '{file}' for table '{Name}'.",
|
||||
configDirectoryPath: directoryPath,
|
||||
yamlPath: file,
|
||||
schemaPath: schema?.SchemaPath,
|
||||
innerException: exception);
|
||||
}
|
||||
|
||||
if (schema != null)
|
||||
{
|
||||
// 先按 schema 拒绝结构问题并提取跨表引用,避免被 IgnoreUnmatchedProperties 或默认值掩盖配置错误。
|
||||
referenceUsages.AddRange(
|
||||
YamlConfigSchemaValidator.ValidateAndCollectReferences(Name, schema, file, yaml));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var value = deserializer.Deserialize<TValue>(yaml);
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException("YAML content was deserialized to null.");
|
||||
}
|
||||
|
||||
values.Add(value);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.DeserializationFailed,
|
||||
Name,
|
||||
$"Failed to deserialize config file '{file}' for table '{Name}' as '{typeof(TValue).Name}'.",
|
||||
configDirectoryPath: directoryPath,
|
||||
yamlPath: file,
|
||||
schemaPath: schema?.SchemaPath,
|
||||
detail: $"Target CLR type: {typeof(TValue).FullName}.",
|
||||
innerException: exception);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> ReadYamlAsync(
|
||||
string directoryPath,
|
||||
string file,
|
||||
YamlConfigSchema? schema,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await File.ReadAllTextAsync(file, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// 保留原始取消语义,避免热重载把会话级取消误报为配置读取失败。
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.ConfigFileReadFailed,
|
||||
Name,
|
||||
$"Failed to read config file '{file}' for table '{Name}'.",
|
||||
configDirectoryPath: directoryPath,
|
||||
yamlPath: file,
|
||||
schemaPath: schema?.SchemaPath,
|
||||
innerException: exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectReferenceUsages(
|
||||
List<YamlConfigReferenceUsage> referenceUsages,
|
||||
YamlConfigSchema? schema,
|
||||
string file,
|
||||
string yaml)
|
||||
{
|
||||
if (schema == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 先按 schema 拒绝结构问题并提取跨表引用,避免被 IgnoreUnmatchedProperties 或默认值掩盖配置错误。
|
||||
referenceUsages.AddRange(
|
||||
YamlConfigSchemaValidator.ValidateAndCollectReferences(Name, schema, file, yaml));
|
||||
}
|
||||
|
||||
private TValue DeserializeValue(
|
||||
IDeserializer deserializer,
|
||||
string directoryPath,
|
||||
string file,
|
||||
YamlConfigSchema? schema,
|
||||
string yaml,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var value = deserializer.Deserialize<TValue>(yaml);
|
||||
if (value != null)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("YAML content was deserialized to null.");
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// 同步反序列化阶段也要透传会话级取消,避免把停止加载误报为 YAML 解析失败。
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
ConfigLoadFailureKind.DeserializationFailed,
|
||||
Name,
|
||||
$"Failed to deserialize config file '{file}' for table '{Name}' as '{typeof(TValue).Name}'.",
|
||||
configDirectoryPath: directoryPath,
|
||||
yamlPath: file,
|
||||
schemaPath: schema?.SchemaPath,
|
||||
detail: $"Target CLR type: {typeof(TValue).FullName}.",
|
||||
innerException: exception);
|
||||
}
|
||||
}
|
||||
|
||||
private YamlTableLoadResult BuildLoadResult(
|
||||
string directoryPath,
|
||||
YamlConfigSchema? schema,
|
||||
List<TValue> values,
|
||||
List<YamlConfigReferenceUsage> referenceUsages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var table = new InMemoryConfigTable<TKey, TValue>(values, _keySelector, _comparer);
|
||||
return new YamlTableLoadResult(Name, table, referencedTableNames, referenceUsages);
|
||||
return new YamlTableLoadResult(
|
||||
Name,
|
||||
table,
|
||||
schema?.ReferencedTableNames ?? Array.Empty<string>(),
|
||||
referenceUsages);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// 构建最终配置表时继续保留原始取消语义,避免热重载把提交前取消记录成构表失败。
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -630,6 +717,12 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
/// </summary>
|
||||
private static class CrossTableReferenceValidator
|
||||
{
|
||||
private delegate bool IntegerTryParseDelegate<T>(
|
||||
string? value,
|
||||
NumberStyles style,
|
||||
IFormatProvider? provider,
|
||||
out T result);
|
||||
|
||||
/// <summary>
|
||||
/// 使用本轮新加载结果与注册表中保留的旧表,一起验证跨表引用是否全部有效。
|
||||
/// </summary>
|
||||
@ -754,59 +847,15 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
convertedKey = null;
|
||||
errorMessage = string.Empty;
|
||||
|
||||
if (targetKeyType == typeof(int) &&
|
||||
int.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
|
||||
if (TryConvertIntegerKey<int>(rawValue, targetKeyType, typeof(int), int.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<long>(rawValue, targetKeyType, typeof(long), long.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<short>(rawValue, targetKeyType, typeof(short), short.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<byte>(rawValue, targetKeyType, typeof(byte), byte.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<uint>(rawValue, targetKeyType, typeof(uint), uint.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<ulong>(rawValue, targetKeyType, typeof(ulong), ulong.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<ushort>(rawValue, targetKeyType, typeof(ushort), ushort.TryParse, out convertedKey) ||
|
||||
TryConvertIntegerKey<sbyte>(rawValue, targetKeyType, typeof(sbyte), sbyte.TryParse, out convertedKey))
|
||||
{
|
||||
convertedKey = intValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(long) &&
|
||||
long.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
|
||||
{
|
||||
convertedKey = longValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(short) &&
|
||||
short.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var shortValue))
|
||||
{
|
||||
convertedKey = shortValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(byte) &&
|
||||
byte.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var byteValue))
|
||||
{
|
||||
convertedKey = byteValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(uint) &&
|
||||
uint.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uintValue))
|
||||
{
|
||||
convertedKey = uintValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(ulong) &&
|
||||
ulong.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var ulongValue))
|
||||
{
|
||||
convertedKey = ulongValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(ushort) &&
|
||||
ushort.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var ushortValue))
|
||||
{
|
||||
convertedKey = ushortValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetKeyType == typeof(sbyte) &&
|
||||
sbyte.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var sbyteValue))
|
||||
{
|
||||
convertedKey = sbyteValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -815,6 +864,25 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryConvertIntegerKey<T>(
|
||||
string rawValue,
|
||||
Type targetKeyType,
|
||||
Type supportedType,
|
||||
IntegerTryParseDelegate<T> tryParse,
|
||||
out object? convertedKey)
|
||||
where T : struct
|
||||
{
|
||||
convertedKey = null;
|
||||
if (targetKeyType != supportedType ||
|
||||
!tryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedKey = parsedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ContainsKey(IConfigTable table, object key)
|
||||
{
|
||||
var tableInterface = table.GetType()
|
||||
@ -838,7 +906,13 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
new(StringComparer.Ordinal);
|
||||
|
||||
private readonly IDeserializer _deserializer;
|
||||
#if NET9_0_OR_GREATER
|
||||
// net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。
|
||||
private readonly Lock _gate = new();
|
||||
#else
|
||||
// net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。
|
||||
private readonly object _gate = new();
|
||||
#endif
|
||||
private readonly Action<string>? _onTableReloaded;
|
||||
private readonly Action<string, Exception>? _onTableReloadFailed;
|
||||
private readonly Dictionary<string, IYamlTableRegistration> _registrations = new(StringComparer.Ordinal);
|
||||
@ -1121,7 +1195,7 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
|
||||
foreach (var dependency in _dependenciesByTable)
|
||||
{
|
||||
if (!dependency.Value.Contains(currentTableName))
|
||||
if (!ContainsDependency(dependency.Value, currentTableName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -1138,6 +1212,14 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static bool ContainsDependency(
|
||||
IReadOnlyCollection<string> dependencies,
|
||||
string tableName)
|
||||
{
|
||||
return dependencies.Any(
|
||||
dependency => string.Equals(dependency, tableName, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private void InvokeReloaded(string tableName)
|
||||
{
|
||||
if (_onTableReloaded == null)
|
||||
|
||||
@ -6,54 +6,43 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-081`
|
||||
- 当前阶段:`Phase 81`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-084`
|
||||
- 当前阶段:`Phase 84`
|
||||
- 当前焦点:
|
||||
- `2026-04-27` 已复核 PR `#295` 的 latest-head review,确认 `ThrowShouldNotRetry` 的 `ParamName` open thread 属于 stale finding,本地代码已经使用传入值而非 `nameof(parameterName)`
|
||||
- 已清理 `AsyncExtensionsTests.WithRetry_Should_Respect_ShouldRetry_Predicate` 中的冗余 `Task.Delay(50)`,保留 `ParamName == nameof(taskFactory)` 断言锁定契约
|
||||
- 已增强 `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 的 failed-test 表格解析,允许 `Name` / `Failure Message` 后出现尾随额外列
|
||||
- 已新增 Python `unittest` 回归用例覆盖“尾随额外列不影响前两列提取”的场景
|
||||
- 当前剩余 warning 热点仍集中在 `YamlConfigSchemaValidator*`、`YamlConfigLoader.cs` 与大批量 `MA0048` 文件名拆分;这些 slice 仍高于本轮 PR review follow-up 的低风险边界
|
||||
- `2026-04-27` 已完成 PR `#297` 的 CodeRabbit follow-up,修复 `YamlConfigLoader` 的取消语义与 `IntegerTryParseDelegate` 可空性问题
|
||||
- 已补齐 `GFramework.Core.Tests/Ioc` 与 `GFramework.Core.Tests/Query` 中 review 指向的 XML 文档缺口,并让 `IPrioritizedService` 复用 `IMixedService.Name` 契约
|
||||
- 已新增 `YamlConfigLoaderTests` 回归测试,锁定“取消时保留 `OperationCanceledException`”这一行为
|
||||
- 当前分支的下一波 warning reduction 仍建议回到 `ArchitectureContextTests.cs`、`AsyncQueryExecutorTests.cs` 或 `YamlConfigSchemaValidator*` 的后续 slice
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前 `origin/main` 基线提交为 `617e0bf`(`2026-04-26T12:17:15+08:00`)。
|
||||
- 当前 PR review 真值:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output <current-pr-review-json>`
|
||||
- 最新结果:成功;当前分支对应 PR 为 `#295`
|
||||
- 当前测试报告输出已能显示 `Summary` 统计、失败测试名称,以及 `Name / Failure Message` 表格中的关键信息
|
||||
- 当前 GitHub latest-head review 仍显示 `1` 条 open thread,但该线程指向的 `nameof(parameterName)` 问题已不在本地代码中成立,属于 stale finding
|
||||
- 当前 latest review 中仍有 `2` 条与本地工作树一致的 nitpick:`AsyncExtensionsTests` 冗余等待,以及 failed-test 表格解析对尾随列不鲁棒
|
||||
- 当前 `origin/main` 基线提交为 `b6a9fef`(`2026-04-27T10:53:34+08:00`)。
|
||||
- 当前直接验证结果:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/test_fetch_current_pr_review.py`
|
||||
- 最新结果:成功;`Ran 1 test in 0.000s`, `OK`
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --section tests --json-output /tmp/current-pr-review-postfix.json`
|
||||
- 最新结果:成功;真实 PR 评论抓取仍能输出 `2` 份测试报告,失败用例详情保持可见
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~WithRetry_Should_Respect_ShouldRetry_Predicate"`
|
||||
- 最新结果:成功;`Failed: 0, Passed: 1, Skipped: 0, Total: 1`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~RegisterMigration_During_Cache_Rebuild_Should_Not_Leave_Stale_Type_Cache"`
|
||||
- 最新结果:成功;`Failed: 0, Passed: 1, Skipped: 0, Total: 1`
|
||||
- 当前分支 stop-condition 指标:
|
||||
- `git diff --name-only refs/remotes/origin/main...HEAD | wc -l`
|
||||
- 最新结果:`35`
|
||||
- `git diff --numstat refs/remotes/origin/main...HEAD`
|
||||
- 最新结果:`642` changed lines
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
|
||||
- 最新结果:成功;`1` 通过、`0` 失败
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`
|
||||
- 最新结果:成功;`1` 通过、`0` 失败
|
||||
- `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigLoader.cs GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs GFramework.Core.Tests/Ioc/IMixedService.cs GFramework.Core.Tests/Ioc/IPrioritizedService.cs GFramework.Core.Tests/Ioc/PrioritizedService.cs GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs`
|
||||
- 最新结果:成功;本次 PR follow-up 改动文件无需额外格式化
|
||||
- 当前批次摘要:
|
||||
- 三轮低风险 warning 清理已在此前验证中将仓库根 warning 从 `639` 降到 `397`
|
||||
- 当前批次的已完成 slice 明细已迁移到归档,active todo 仅保留恢复真值
|
||||
- 本轮新增内容为 PR review nitpick 收口与脚本回归测试补齐,不扩展 warning reduction 的热点清理边界
|
||||
- 本轮完成 PR `#297` 最新 head review 中仍然有效的 `3` 个 open threads 修复:`YamlConfigLoader` 取消语义、`IMixedService.Name` XML 文档、`IPrioritizedService` 相关契约整理
|
||||
- 本轮同时吸收 CodeRabbit folded nitpick 中仍然成立的 `2` 个点:`IntegerTryParseDelegate` 可空性对齐、`TestAsyncQueryWithExceptionV4.OnDoAsync` 的 `<returns>` 文档
|
||||
- 本轮新增一条精确回归测试,确保底层 YAML 文件读取在取消时继续抛出 `OperationCanceledException` 系列异常,而不是包装成 `ConfigLoadException`
|
||||
- 当前建议保留到下一波次的候选:
|
||||
- `GFramework.Game/Config/YamlConfigLoader.cs` 的 `MA0158`(单点可修,但文件本身同时承载其他高耦合 warning)
|
||||
- 测试项目中的 `MA0048` 文件名拆分波次(会显著增加 changed-file 数)
|
||||
- `GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs` 的 `7` 个 `MA0048`
|
||||
- `GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs` 的 `7` 个 `MA0048`
|
||||
- `GFramework.Game/Config/YamlConfigSchemaValidator.cs` 与 `YamlConfigSchemaValidator.ObjectKeywords.cs` 的高耦合 warning 热点
|
||||
|
||||
## 当前风险
|
||||
|
||||
- `GFramework.Game/Config/YamlConfigSchemaValidator*.cs` 仍然聚集多类高耦合 warning。
|
||||
- 缓解措施:本轮先避开该热点,只清理低风险且 ownership 清晰的文件集合。
|
||||
- `MA0158` 迁移涉及 `net8.0` / `net9.0` / `net10.0` 多目标兼容。
|
||||
- 缓解措施:复用 `StoreSelection.cs` 已存在的 `#if NET9_0_OR_GREATER` 专用锁模式,不在 `net8.0` 引入不兼容 API。
|
||||
- 当前 PR open thread 与 CI 失败信号仍依赖新提交进入远端 PR head 才能复核。
|
||||
- 缓解措施:本轮提交并推送后重新执行 `$gframework-pr-review`,确认 stale open thread 是否被 GitHub 收口,以及两条 nitpick 是否从 latest review 中消失。
|
||||
- `GFramework.Cqrs.Tests/Mediator/*` 仍有 `47` / `44` / `34` 个唯一 warning 位点,属于高 changed-file 风险的 `MA0048` 大波次。
|
||||
- 缓解措施:优先继续处理 `6-7` 个 warning 的小文件切片,避免一次性推高文件数。
|
||||
- `YamlConfigSchemaValidator*` 仍然聚集多类高耦合 warning。
|
||||
- 缓解措施:继续把它们留在独立波次,不与测试项目的低风险拆分混提。
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -73,11 +62,11 @@
|
||||
## 验证说明
|
||||
|
||||
- 权威验证结果统一维护在“当前活跃事实”。
|
||||
- `GFramework.Core.Tests` 当前仍有既有 analyzer / nullable warning 基线,因此本轮验证只证明 PR review 修复未引入构建错误,未将该项目 warning 清零。
|
||||
- 后续若刷新构建或 PR review 真值,只更新上述权威区块,不在本节重复抄录。
|
||||
- `GFramework.Core.Tests` 项目级 Release 构建已在本轮清零,但仓库根 non-incremental 构建仍保留大量既有 warning。
|
||||
- warning reduction 的仓库级真值只以同轮 `dotnet clean` 后的 `dotnet build` 为准。
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 提交本轮 `AsyncExtensionsTests` / `$gframework-pr-review` nitpick 修复、Python 回归测试与 `ai-plan` 同步。
|
||||
2. 推送后重新执行 `$gframework-pr-review`,确认 PR `#295` 的 stale open thread、nitpick 与测试报告是否已刷新为新 head 真值。
|
||||
3. 若后续继续推进 warning reduction,建议另开下一波次处理 `YamlConfigLoader.cs` 热点或测试项目 `MA0048` 拆分波次。
|
||||
1. 提交本轮 PR `#297` review follow-up 与 `ai-plan` 同步。
|
||||
2. 下一波优先挑选 `ArchitectureContextTests.cs` 或 `AsyncQueryExecutorTests.cs` 这类 `7`-warning 的纯 `MA0048` 单文件切片。
|
||||
3. 继续将 `YamlConfigSchemaValidator*` 与 `GFramework.Cqrs.Tests/Mediator/*` 作为独立高风险波次处理。
|
||||
|
||||
@ -1,39 +1,75 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-27 — RP-081
|
||||
## 2026-04-27 — RP-084
|
||||
|
||||
### 阶段:核实 PR `#295` 的剩余 nitpick,并补齐脚本解析回归测试
|
||||
### 阶段:收敛 PR #297 的 CodeRabbit follow-up
|
||||
|
||||
- 触发背景:
|
||||
- 用户再次执行 `$gframework-pr-review`,需要根据当前 PR `#295` 的 latest-head review 继续核实哪些反馈仍需在本地处理
|
||||
- 远端 review 显示 `1` 条 open thread 与 `2` 条 nitpick,需要区分 stale finding 与仍然成立的本地问题
|
||||
- 用户执行 `$gframework-pr-review`,要求以当前分支对应 PR 为准,提取并核对 AI review / check 信号
|
||||
- `fetch_current_pr_review.py` 返回 PR `#297` 的最新 head review 中仍有 `3` 个 open threads,另有 `2` 个 folded nitpick 仍然适用
|
||||
- 主线程实施:
|
||||
- 复核 `/tmp/current-pr-review.json` 与本地 `AsyncExtensionsTests.cs`,确认 open thread 指向的 `nameof(parameterName)` 问题已在现有代码中修复,属于 stale finding
|
||||
- 删除 `GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs` 中 `WithRetry_Should_Respect_ShouldRetry_Predicate` 的冗余 `Task.Delay(50)`,将测试改回同步断言路径
|
||||
- 调整 `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 的 `parse_failed_test_details`,允许 failed-test HTML 表格在 `Name` / `Failure Message` 后追加额外列
|
||||
- 新增 `.agents/skills/gframework-pr-review/scripts/test_fetch_current_pr_review.py`,以 `unittest` 覆盖“尾随额外列不影响前两列提取”的回归场景
|
||||
- 校验 `GFramework.Game/Config/YamlConfigLoader.cs` 后,保留 `ReadYamlAsync` 的原始取消语义,并把 `IntegerTryParseDelegate<T>` 第一个参数改为 `string?`
|
||||
- 校验 `GFramework.Core.Tests/Ioc/*` 与 `Query/TestAsyncQueryWithExceptionV4.cs` 后,补齐缺失 XML 文档,让 `IPrioritizedService` 继承 `IMixedService` 复用 `Name` 契约,并补上 `<returns>` 文档
|
||||
- 新增 `YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested`,用反射直接命中私有读取路径,稳定回归本次取消语义修复
|
||||
- 用 `dotnet format --verify-no-changes --include ...` 清理并验证本次改动文件的格式状态
|
||||
- 验证里程碑:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/test_fetch_current_pr_review.py`
|
||||
- 结果:成功;`Ran 1 test in 0.000s`, `OK`
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --section tests --json-output /tmp/current-pr-review-postfix.json`
|
||||
- 结果:成功;真实 PR 评论抓取仍显示 `2` 份测试报告,失败测试名与 failure message 摘要保持可见
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~WithRetry_Should_Respect_ShouldRetry_Predicate"`
|
||||
- 结果:成功;`Failed: 0, Passed: 1, Skipped: 0, Total: 1`
|
||||
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
|
||||
- 结果:成功;`1` 通过、`0` 失败
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`
|
||||
- 结果:成功;`1` 通过、`0` 失败
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigLoader.cs GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs GFramework.Core.Tests/Ioc/IMixedService.cs GFramework.Core.Tests/Ioc/IPrioritizedService.cs GFramework.Core.Tests/Ioc/PrioritizedService.cs GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs`
|
||||
- 结果:成功
|
||||
- 当前结论:
|
||||
- 本轮 latest-head review 中只有 `AsyncExtensionsTests` 冗余等待与 failed-test 表格尾随列容错性两个 nitpick 仍与本地代码一致,现已修复
|
||||
- `ThrowShouldNotRetry` 的 `ParamName` open thread 属于 stale finding,本地代码已经符合预期,只需等待新提交进入远端后复核 thread 状态
|
||||
- PR `#297` 当前仍然有效的 CodeRabbit open threads 与 folded nitpick 已在本地全部核对并收敛
|
||||
- 当前恢复点完成后,分支可以回到 `ArchitectureContextTests.cs` / `AsyncQueryExecutorTests.cs` / `YamlConfigSchemaValidator*` 的 warning reduction 主线
|
||||
- 下一步:
|
||||
1. 提交本轮 PR review follow-up。
|
||||
2. 继续执行下一波 `MA0048` 小切片,优先避免一次性进入 `Mediator*` 的高 changed-file 风险波次。
|
||||
|
||||
## 2026-04-27 — RP-083
|
||||
|
||||
### 阶段:修复 `YamlConfigLoader` 单文件 warning,并拆分 `MicrosoftDiContainerTests` 的辅助类型
|
||||
|
||||
- 触发背景:
|
||||
- 用户执行 `$gframework-batch-boot 50`,要求先拿仓库根构建 warning,再按 bounded slice 分派给不同 subagent 并持续推进
|
||||
- 当前分支在本轮开始时与 `origin/main@b6a9fef` 零提交差异,适合从低风险 warning slice 起步
|
||||
- 主线程实施:
|
||||
- 先执行 non-incremental 仓库根基线:`dotnet clean` + `dotnet build`,得到 `397 Warning(s)` / `316` 个唯一位点
|
||||
- 主线程修复 `GFramework.Game/Config/YamlConfigLoader.cs` 的 `MA0051`、`MA0002` 与 `MA0158`
|
||||
- 接受一个 worker batch:将 `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 末尾的 `10` 个测试辅助接口/类拆分到 `Ioc/` 同目录独立文件
|
||||
- 接受第二波 worker 的已落地结果:将 `GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs` 末尾的 `7` 个测试辅助类型拆分到 `Query/` 同目录独立文件
|
||||
- 启动 `ArchitectureContextTests.cs` 候选 worker,但在共享工作树落地前主动停止,以避免本轮上下文与 review 面积继续膨胀
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`111 Warning(s)`、`0 Error(s)`
|
||||
- 观察:构建输出未再报告 `GFramework.Game/Config/YamlConfigLoader.cs`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet clean`
|
||||
- 结果:成功;刷新最终 non-incremental 仓库根 warning 基线
|
||||
- `dotnet build`
|
||||
- 结果:成功;`353 Warning(s)`、`0 Error(s)`,唯一位点 `279`
|
||||
- 观察:构建输出未再报告 `GFramework.Game/Config/YamlConfigLoader.cs`、`GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 与 `GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs`
|
||||
- 当前结论:
|
||||
- 本轮已完成一个主线程单文件 slice 和两个 worker 拆分 slice;仓库根 non-incremental warning 从 `397` 降到 `353`
|
||||
- 当前共享工作树 footprint 为 `22` 个 changed files,仍低于 `$gframework-batch-boot 50` 的停止线
|
||||
- 下一波更适合继续处理 `7` 个 `MA0048` 的小文件,而不是立即进入 `Mediator*` 或 `YamlConfigSchemaValidator*` 的高耦合热点
|
||||
|
||||
## 活跃风险
|
||||
|
||||
- PR 上的 latest-head review thread 与测试报告仍需要等新提交进入远端后再复核。
|
||||
- 缓解措施:提交并推送后重新执行 `$gframework-pr-review`,只以新的 latest-head 和 test report 为准。
|
||||
- `YamlConfigSchemaValidator*`、`YamlConfigLoader.cs` 与 `MA0048` 拆分仍是下一波次的高耦合候选。
|
||||
- 缓解措施:保持本轮边界只处理 PR review nitpick follow-up,不顺手扩展 warning reduction 范围。
|
||||
- `GFramework.Cqrs.Tests/Mediator/*` 的 `MA0048` 位点密度很高,一次性拆分会迅速推高 changed-file 数。
|
||||
- 缓解措施:下一波优先继续拿 `7` warning 级别的小切片。
|
||||
- `YamlConfigSchemaValidator*` 仍然聚集多类高耦合 warning。
|
||||
- 缓解措施:继续维持为独立波次,不与测试项目拆分混提。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 完成本轮提交。
|
||||
2. 推送后重新执行 `$gframework-pr-review`,确认 PR `#295` 的 stale open thread 与 nitpick 是否已刷新。
|
||||
1. 完成本轮 `YamlConfigLoader.cs`、`MicrosoftDiContainerTests.cs` 与 `ai-plan` 的提交。
|
||||
2. 下一波优先从 `ArchitectureContextTests.cs` 或 `AsyncQueryExecutorTests.cs` 继续拆分纯 `MA0048`。
|
||||
|
||||
## 历史归档指针
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user