mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-08 09:34:30 +08:00
test(cqrs): 补强数组类型生成回归
- 新增多维数组、交错数组与外部程序集隐藏元素类型的 precise runtime type lookup 回归 - 更新 cqrs-rewrite 跟踪与追踪,记录 RP-053 到 RP-054 的并行批次收口与验证结果
This commit is contained in:
parent
e81a43680d
commit
7b5efde3bd
@ -314,6 +314,128 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
|
||||
""";
|
||||
|
||||
private const string HiddenMultiDimensionalArrayResponseSource = """
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public interface IServiceCollection { }
|
||||
|
||||
public static class ServiceCollectionServiceExtensions
|
||||
{
|
||||
public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Debug(string msg);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs
|
||||
{
|
||||
public interface IRequest<TResponse> { }
|
||||
public interface INotification { }
|
||||
public interface IStreamRequest<TResponse> { }
|
||||
|
||||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||
}
|
||||
|
||||
namespace GFramework.Cqrs
|
||||
{
|
||||
public interface ICqrsHandlerRegistry
|
||||
{
|
||||
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public sealed class CqrsHandlerRegistryAttribute : Attribute
|
||||
{
|
||||
public CqrsHandlerRegistryAttribute(Type registryType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
public sealed class Container
|
||||
{
|
||||
private sealed record HiddenResponse();
|
||||
|
||||
private sealed record HiddenRequest() : IRequest<HiddenResponse[,]>;
|
||||
|
||||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenResponse[,]> { }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HiddenJaggedArrayResponseSource = """
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public interface IServiceCollection { }
|
||||
|
||||
public static class ServiceCollectionServiceExtensions
|
||||
{
|
||||
public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Core.Abstractions.Logging
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void Debug(string msg);
|
||||
}
|
||||
}
|
||||
|
||||
namespace GFramework.Cqrs.Abstractions.Cqrs
|
||||
{
|
||||
public interface IRequest<TResponse> { }
|
||||
public interface INotification { }
|
||||
public interface IStreamRequest<TResponse> { }
|
||||
|
||||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||
}
|
||||
|
||||
namespace GFramework.Cqrs
|
||||
{
|
||||
public interface ICqrsHandlerRegistry
|
||||
{
|
||||
void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public sealed class CqrsHandlerRegistryAttribute : Attribute
|
||||
{
|
||||
public CqrsHandlerRegistryAttribute(Type registryType) { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp
|
||||
{
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
public sealed class Container
|
||||
{
|
||||
private sealed record HiddenResponse();
|
||||
|
||||
private sealed record HiddenRequest() : IRequest<HiddenResponse[][]>;
|
||||
|
||||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenResponse[][]> { }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string HiddenGenericEnvelopeResponseSource = """
|
||||
using System;
|
||||
|
||||
@ -963,6 +1085,24 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
}
|
||||
""";
|
||||
|
||||
private const string ExternalProtectedMultiDimensionalTypeDependencySource = """
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace Dep;
|
||||
|
||||
public abstract class VisibilityScope
|
||||
{
|
||||
protected internal sealed record ProtectedResponse();
|
||||
|
||||
protected internal sealed record ProtectedRequest() : IRequest<ProtectedResponse[,]>;
|
||||
}
|
||||
|
||||
public abstract class HandlerBase :
|
||||
IRequestHandler<VisibilityScope.ProtectedRequest, VisibilityScope.ProtectedResponse[,]>
|
||||
{
|
||||
}
|
||||
""";
|
||||
|
||||
private const string LegacyFallbackMarkerHiddenHandlerSource = """
|
||||
using System;
|
||||
|
||||
@ -1590,6 +1730,52 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
("CqrsHandlerRegistry.g.cs", HiddenGenericEnvelopeResponseExpected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证精确重建路径会保留隐藏元素类型的多维数组秩信息,
|
||||
/// 使生成注册器继续走定向运行时类型重建,而不是退回宽松接口发现。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Generates_Precise_Service_Type_For_Hidden_MultiDimensional_Array_Type_Arguments()
|
||||
{
|
||||
var generatedSource = RunGenerator(HiddenMultiDimensionalArrayResponseSource);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain(
|
||||
"var serviceType0_0Argument1Element = registryAssembly.GetType(\"TestApp.Container+HiddenResponse\", throwOnError: false, ignoreCase: false);"));
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain(
|
||||
"var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType(2));"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces("));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证精确重建路径会递归覆盖交错数组,
|
||||
/// 确保隐藏元素类型的每一层数组都继续通过数组发射分支稳定重建。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Generates_Precise_Service_Type_For_Hidden_Jagged_Array_Type_Arguments()
|
||||
{
|
||||
var generatedSource = RunGenerator(HiddenJaggedArrayResponseSource);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain(
|
||||
"var serviceType0_0Argument1ElementElement = registryAssembly.GetType(\"TestApp.Container+HiddenResponse\", throwOnError: false, ignoreCase: false);"));
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain(
|
||||
"var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1ElementElement.MakeArrayType().MakeArrayType());"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces("));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 handler 合同把 pointer 响应类型放进 CQRS 泛型参数时,
|
||||
/// 生成器会保守回退而不是继续发射不可构造的精确注册代码。
|
||||
@ -1682,6 +1868,39 @@ public class CqrsHandlerRegistryGeneratorTests
|
||||
Is.EqualTo(ExternalAssemblyPreciseLookupExpected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当外部程序集隐藏元素类型以多维数组形式参与 CQRS 合同时,
|
||||
/// 生成器仍会保留外部程序集定向查找与数组秩信息,而不是退回 fallback 元数据。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Generates_Precise_Assembly_Type_Lookups_For_Inaccessible_External_MultiDimensional_Array_Elements()
|
||||
{
|
||||
var contractsReference = MetadataReferenceTestBuilder.CreateFromSource(
|
||||
"Contracts",
|
||||
ExternalProtectedTypeContractsSource);
|
||||
var dependencyReference = MetadataReferenceTestBuilder.CreateFromSource(
|
||||
"Dependency",
|
||||
ExternalProtectedMultiDimensionalTypeDependencySource,
|
||||
contractsReference);
|
||||
var generatedSource = RunGenerator(
|
||||
ExternalProtectedTypeLookupSource,
|
||||
contractsReference,
|
||||
dependencyReference);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain(
|
||||
"var serviceType0_0Argument1Element = ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedResponse\");"));
|
||||
Assert.That(
|
||||
generatedSource,
|
||||
Does.Contain(
|
||||
"var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType(2));"));
|
||||
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute("));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证即使 runtime 仍暴露旧版无参 fallback marker,生成器也会优先在生成注册器内部处理隐藏 handler,
|
||||
/// 不再输出 fallback marker。
|
||||
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-052`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-054`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
||||
@ -15,6 +15,8 @@ CQRS 迁移与收敛。
|
||||
- `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出
|
||||
- 已将 generator 的程序集级 fallback 元数据进一步收敛:当全部 fallback handlers 都可直接引用且 runtime 暴露 `params Type[]` 合同时,生成器现优先发射 `typeof(...)` 形式的 fallback 元数据
|
||||
- 当 runtime 不支持多实例 fallback 特性或缺少对应构造函数时,mixed fallback 场景仍会整体保守回退到字符串元数据,避免仅部分 handler 走 `Type[]` 时漏掉剩余需按名称恢复的 handlers
|
||||
- 已完成 request pipeline executor 形状缓存:`CqrsDispatcher` 现会在单个 request binding 内按 `behaviorCount` 复用强类型 pipeline executor,而不是每次 `SendAsync` 都重建整条 `next` 委托链
|
||||
- 已补充 dispatcher pipeline executor 缓存与双行为顺序回归,锁定缓存复用后仍保持现有行为执行顺序
|
||||
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
|
||||
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
|
||||
- 已修正 pointer / function pointer 泛型合同的错误覆盖:生成器不再为这两类类型发射 precise runtime type 重建代码
|
||||
@ -71,6 +73,17 @@ CQRS 迁移与收敛。
|
||||
- latest reviewed commit 当前剩余 `3` 条 open AI review threads:`2` 条 Greptile、`1` 条 CodeRabbit
|
||||
- 本地核对后确认 `dotnet-format` 仍只有 `Restore operation failed` 噪音,没有附带当前仍成立的文件级格式诊断
|
||||
- 已按 review triage 修正 generator source preamble 的多实例 fallback 特性排版、移除死参数,并补强 mixed/direct fallback 发射回归断言与 XML 文档
|
||||
- `2026-04-29` 已完成一轮 precise runtime type lookup 的数组回归补强:
|
||||
- `GFramework.SourceGenerators.Tests` 已新增多维数组、交错数组、外部程序集隐藏元素类型三类回归
|
||||
- 当前生成器在 precise runtime type lookup 下已稳定保留数组秩信息,并递归发射交错数组的 `MakeArrayType()` 链
|
||||
- 本轮定向测试未暴露数组发射缺陷,因此未改动 fallback 合同选择逻辑,也未调整 direct / named / mixed fallback 排版路径
|
||||
- `2026-04-29` 已完成一轮 request pipeline executor 形状缓存:
|
||||
- `CqrsDispatcher` 现会继续按 `requestType + responseType` 缓存 request dispatch binding,并在 binding 内按 `behaviorCount` 缓存强类型 pipeline executor
|
||||
- 每次分发只绑定当前 handler / behaviors 实例,不缓存容器解析结果,因此不改变 transient 生命周期与上下文注入语义
|
||||
- `GFramework.Cqrs.Tests` 已补充 executor 首次创建 / 后续复用与双行为顺序回归
|
||||
- `2026-04-29` 已完成一轮 CQRS 入口文档对齐:
|
||||
- `GFramework.Cqrs/README.md`、`docs/zh-CN/core/cqrs.md` 与 `docs/zh-CN/api-reference/index.md` 现已明确 generated registry 优先、targeted fallback 补齐剩余 handler 的当前语义
|
||||
- 当前工作区相对 `origin/main` 的累计 diff 已达到 `13 files / 709 lines`,仍低于本轮 `gframework-batch-boot 50` 的主要 stop condition
|
||||
- 当前主线优先级:
|
||||
- generator 覆盖面继续扩大
|
||||
- dispatch/invoker 反射占比继续下降
|
||||
@ -133,9 +146,18 @@ CQRS 迁移与收敛。
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||
- 结果:通过
|
||||
- 备注:`13/13` 测试通过;本轮确认 mixed fallback metadata 的 registrar 消费路径未回归
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||
- 结果:通过
|
||||
- 备注:`21/21` 测试通过;本轮新增多维数组、交错数组与外部程序集隐藏元素类型的 precise runtime type lookup 回归
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"`
|
||||
- 结果:通过
|
||||
- 备注:`4/4` 测试通过;本轮覆盖 request pipeline executor 的首次创建、复用与双行为顺序回归
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`;本轮确认 dispatcher request pipeline 形状缓存未破坏 `net8.0` / `net9.0` / `net10.0` 目标构建
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 继续 `Phase 8` 主线,优先再找一个收益明确的 generator 覆盖缺口,继续减少仍必须依赖字符串 fallback 元数据的 handler 类型形态
|
||||
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
|
||||
1. 继续 `Phase 8` 主线,优先再找一个收益明确且写集独立的 generator 或 dispatch 热点;当前累计 diff 为 `13 files / 709 lines`,距离 `50 files` stop condition 仍有余量
|
||||
2. 若继续文档主线,优先再扫教程入口页与 API 参考中的 CQRS 采用说明,确认是否还有旧 Command / Query 迁移口径残留
|
||||
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
|
||||
|
||||
@ -2,6 +2,50 @@
|
||||
|
||||
## 2026-04-29
|
||||
|
||||
### 阶段:低风险并行批次收口(CQRS-REWRITE-RP-054)
|
||||
|
||||
- 继续按 `gframework-batch-boot 50` 推进 `Phase 8`,本轮先完成批次评估后再并行拆分写集,避免把 generator、runtime 与 docs 改动揉进同一片上下文
|
||||
- 先复核当前 worktree、active tracking 与 `origin/main` 基线后确认:
|
||||
- 当前分支头最初与 `origin/main` 对齐,批次阈值从 `0 files / 0 lines` 起算
|
||||
- 本轮可以安全拆成三个互不冲突的切片:request pipeline executor 形状缓存、precise runtime type lookup 数组回归补强、CQRS 入口文档对齐
|
||||
- 主线程保留集成与验证职责,subagent 只负责各自写集
|
||||
- 已接受并整合的并行写集:
|
||||
- docs 切片:更新 `GFramework.Cqrs/README.md`、`docs/zh-CN/core/cqrs.md`、`docs/zh-CN/api-reference/index.md`,明确 generated registry 优先、targeted fallback 只补剩余 handler
|
||||
- generator 切片:在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 新增多维数组、交错数组、外部程序集隐藏元素类型三组 precise lookup 回归
|
||||
- dispatcher 切片:在 `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 中将 request pipeline 从“每次分发重建 next 链”收敛为“binding 内按 behaviorCount 缓存 executor 形状”,并补充 dispatcher cache / 顺序回归
|
||||
- docs 切片已作为独立提交落地:
|
||||
- `66830ba2` `docs(cqrs): 更新入口与回退语义说明`
|
||||
- 本轮定向验证已通过:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- `0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"`
|
||||
- `4/4` passed
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||
- `21/21` passed
|
||||
- 本轮停止时,当前工作区相对 `origin/main` 的累计 diff 为 `13 files / 709 lines`
|
||||
- 结论:
|
||||
- primary stop condition `50 files` 尚未触发,本轮停止是因为三条低风险切片已收口完毕
|
||||
- 下一批更适合重新做一轮热点筛选,而不是在同一轮继续扩写集
|
||||
|
||||
### 阶段:precise runtime type lookup 数组回归补强(CQRS-REWRITE-RP-053)
|
||||
|
||||
- 延续 `gframework-batch-boot 50` 的 `Phase 8` 主线,本轮选择一个更窄的 generator 覆盖缺口:锁定 precise runtime type lookup 下数组类型形态的回归
|
||||
- 先复核当前实现后确认:
|
||||
- `TryCreateRuntimeTypeReference` 已会把 `IArrayTypeSymbol` 递归建模为 `RuntimeTypeReferenceSpec.FromArray(element, rank)`
|
||||
- `AppendArrayRuntimeTypeReferenceResolution` 已按 `ArrayRank == 1` 发射 `MakeArrayType()`,按 `rank > 1` 发射 `MakeArrayType(rank)`
|
||||
- 当前缺口主要是测试面不足,尚未显式覆盖多维数组、交错数组、外部程序集隐藏元素类型这三类 precise lookup 场景
|
||||
- 已在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 补充三组回归:
|
||||
- 隐藏元素类型的多维数组响应,锁定 `MakeArrayType(2)` 发射
|
||||
- 隐藏元素类型的交错数组响应,锁定递归 `MakeArrayType().MakeArrayType()` 发射
|
||||
- 外部程序集隐藏元素类型的多维数组响应,锁定 `ResolveReferencedAssemblyType(...)` 与 `MakeArrayType(2)` 的组合
|
||||
- 本轮定向测试全部通过,未暴露数组发射缺陷:
|
||||
- 因此没有修改 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs`
|
||||
- 也没有改动 `CqrsHandlerRegistryGenerator.RuntimeTypeReferences.cs`
|
||||
- fallback 合同选择逻辑与 direct / named / mixed fallback 排版路径保持不变
|
||||
- 定向验证已通过:
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||
- `21/21` passed
|
||||
|
||||
### 阶段:mixed fallback 元数据拆分(CQRS-REWRITE-RP-052)
|
||||
|
||||
- 延续 `gframework-batch-boot 50` 的 `Phase 8` 主线,本轮把上一批的“全部可直接引用 fallback handlers 走 `Type[]`”继续推进到 mixed 场景
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user