Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ private static GeneratedRegistrySourceShape CreateGeneratedRegistrySourceShape(
/// 从可直接表达 handler 接口的注册描述中提取 request invoker 发射计划。
/// </summary>
/// <param name="supportsRequestInvokerProvider">
/// 指示当前 runtime 是否同时暴露 <c>ICqrsRequestInvokerProvider</c> 与
/// <c>IEnumeratesCqrsRequestInvokerDescriptors</c> 契约;若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
/// 指示当前 runtime 是否完整暴露 request invoker provider、descriptor 与 descriptor entry 契约;
/// 若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
/// </param>
/// <param name="registrations">已按稳定顺序整理完成的 handler 注册描述。</param>
/// <returns>
Expand Down Expand Up @@ -136,8 +136,8 @@ private static ImmutableArray<RequestInvokerEmissionSpec> CreateRequestInvokerEm
/// 从可直接表达 handler 接口的注册描述中提取 stream invoker 发射计划。
/// </summary>
/// <param name="supportsStreamInvokerProvider">
/// 指示当前 runtime 是否同时暴露 <c>ICqrsStreamInvokerProvider</c> 与
/// <c>IEnumeratesCqrsStreamInvokerDescriptors</c> 契约;若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
/// 指示当前 runtime 是否完整暴露 stream invoker provider、descriptor 与 descriptor entry 契约;
/// 若不支持,则本方法必须返回空结果并让后续发射路径整体跳过。
/// </param>
/// <param name="registrations">已按稳定顺序整理完成的 handler 注册描述。</param>
/// <returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2332,6 +2332,66 @@ await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
("CqrsHandlerRegistry.g.cs", AssemblyLevelCqrsHandlerRegistryExpected));
}

/// <summary>
/// 验证当 runtime 缺少 generated registry 需要依赖的基础合同时,
/// 生成器会整体跳过发射,避免产出无法承载运行时注册合同的半成品源码。
/// </summary>
/// <param name="startMarker">待移除 runtime 合同块的起始标记。</param>
/// <param name="endMarker">待移除 runtime 合同块之后的下一个稳定标记。</param>
[TestCase(
"public interface ICqrsHandlerRegistry",
"[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]")]
[TestCase(
"public interface INotificationHandler",
"public interface IStreamRequestHandler")]
[TestCase(
"public interface IRequestHandler",
"rename:MissingIRequestHandler")]
[TestCase(
"public interface IStreamRequestHandler",
"rename:MissingIStreamRequestHandler")]
[TestCase(
"[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]",
"[AttributeUsage(AttributeTargets.Assembly)]")]
[TestCase(
"public interface ILogger",
"rename:MissingILogger")]
[TestCase(
"public interface IServiceCollection",
"rename:MissingServiceCollection")]
public void Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract(
string startMarker,
string endMarker)
{
var source = endMarker.StartsWith("rename:", StringComparison.Ordinal)
? RenameTypeIdentifier(
HiddenNestedHandlerSelfRegistrationSource,
startMarker.Replace("public interface ", string.Empty, StringComparison.Ordinal),
endMarker["rename:".Length..])
: RemoveBlock(
HiddenNestedHandlerSelfRegistrationSource,
startMarker,
endMarker);
var execution = ExecuteGenerator(source);
var inputCompilationErrors = execution.InputCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();

Assert.Multiple(() =>
{
Assert.That(inputCompilationErrors, Is.Empty);
Assert.That(generatedCompilationErrors, Is.Empty);
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Is.Empty);
});
}

/// <summary>
/// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器会在生成注册器内部执行定向反射注册,
/// 不再依赖程序集级 fallback marker。
Expand Down Expand Up @@ -2870,6 +2930,50 @@ public void
});
}

/// <summary>
/// 验证当 runtime 同时支持直接 <see cref="Type" /> 与字符串 fallback 元数据、但不允许多个 fallback 特性实例时,
/// mixed 场景会整体回退到单个字符串特性,避免生成会违反 runtime attribute usage 的多实例元数据。
/// </summary>
[Test]
public void
Emits_String_Fallback_Metadata_For_Mixed_Fallback_When_Runtime_Disallows_Multiple_Fallback_Attributes()
{
var source = ReplaceAttributeUsageForType(
AssemblyLevelMixedFallbackMetadataSource,
"CqrsReflectionFallbackAttribute",
"[AttributeUsage(AttributeTargets.Assembly)]");
var execution = ExecuteGenerator(
source,
allowUnsafe: true);
var inputCompilationErrors = execution.InputCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
Assert.Multiple(() =>
{
Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
Assert.That(generatedCompilationErrors, Is.Empty);
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
var generatedSource = execution.GeneratedSources[0].content;
Assert.That(
generatedSource,
Does.Contain(
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]"));
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute(typeof("));
Assert.That(
CountOccurrences(
generatedSource,
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"),
Is.EqualTo(1));
});
}

/// <summary>
/// 验证当 runtime 暴露 request invoker provider 契约时,生成器会让 generated registry 同时发射
/// request invoker 描述符与对应的开放静态 invoker 方法。
Expand Down Expand Up @@ -2998,6 +3102,58 @@ public void Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_R
});
}

/// <summary>
/// 验证当 runtime 缺少 <c>CqrsRequestInvokerDescriptor</c> 时,
/// 生成器不会继续发射依赖描述符类型的 request provider 元数据。
/// </summary>
[Test]
public void Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Type()
{
var source = RenameTypeIdentifier(
RequestInvokerProviderSource,
"CqrsRequestInvokerDescriptor",
"MissingCqrsRequestInvokerDescriptor");
var generatedSource = RunGenerator(source);

Assert.Multiple(() =>
{
Assert.That(
generatedSource,
Does.Contain(
"internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry"));
Assert.That(generatedSource, Does.Not.Contain("ICqrsRequestInvokerProvider"));
Assert.That(generatedSource, Does.Not.Contain("IEnumeratesCqrsRequestInvokerDescriptors"));
Assert.That(generatedSource, Does.Not.Contain("CqrsRequestInvokerDescriptorEntry("));
Assert.That(generatedSource, Does.Not.Contain("InvokeRequestHandler0"));
});
}

/// <summary>
/// 验证当 runtime 缺少 <c>CqrsRequestInvokerDescriptorEntry</c> 时,
/// 生成器不会继续保留 request provider 的枚举接口或静态 invoker 元数据。
/// </summary>
[Test]
public void Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type()
{
var source = RenameTypeIdentifier(
RequestInvokerProviderSource,
"CqrsRequestInvokerDescriptorEntry",
"MissingCqrsRequestInvokerDescriptorEntry");
var generatedSource = RunGenerator(source);

Assert.Multiple(() =>
{
Assert.That(
generatedSource,
Does.Contain(
"internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry"));
Assert.That(generatedSource, Does.Not.Contain("ICqrsRequestInvokerProvider"));
Assert.That(generatedSource, Does.Not.Contain("IEnumeratesCqrsRequestInvokerDescriptors"));
Assert.That(generatedSource, Does.Not.Contain("CqrsRequestInvokerDescriptorEntry("));
Assert.That(generatedSource, Does.Not.Contain("InvokeRequestHandler0"));
});
}

/// <summary>
/// 验证当 request handler 仍需走 precise reflected 注册时,
/// 生成器即使检测到 request invoker provider runtime 合同,也不会错误发射无法稳定表达隐藏请求/响应类型的 provider 元数据。
Expand Down Expand Up @@ -3373,6 +3529,47 @@ private static bool IsIdentifierBoundary(string source, int index)
return !char.IsLetterOrDigit(character) && character != '_';
}

/// <summary>
/// 替换指定测试类型紧邻的 <c>AttributeUsage</c> 声明,用于构造 runtime contract 的 attribute usage 变体。
/// </summary>
/// <param name="source">原始测试源码。</param>
/// <param name="typeName">需要定位的类型名。</param>
/// <param name="replacementAttributeUsage">替换后的完整 <c>AttributeUsage</c> 声明。</param>
/// <returns>完成 attribute usage 替换后的源码。</returns>
private static string ReplaceAttributeUsageForType(
string source,
string typeName,
string replacementAttributeUsage)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(typeName);
ArgumentNullException.ThrowIfNull(replacementAttributeUsage);

var typeIndex = source.IndexOf($"public sealed class {typeName}", StringComparison.Ordinal);
if (typeIndex < 0)
{
throw new InvalidOperationException("The requested type declaration was not found in the generator test input.");
}

const string attributeUsagePrefix = "[AttributeUsage(";
var attributeUsageStartIndex = source.LastIndexOf(attributeUsagePrefix, typeIndex, StringComparison.Ordinal);
if (attributeUsageStartIndex < 0)
{
throw new InvalidOperationException("The requested AttributeUsage declaration was not found in the generator test input.");
}

var attributeUsageEndIndex = source.IndexOf(']', attributeUsageStartIndex);
if (attributeUsageEndIndex < 0 || attributeUsageEndIndex > typeIndex)
{
throw new InvalidOperationException("The requested AttributeUsage declaration is malformed.");
}

return source.Remove(
attributeUsageStartIndex,
attributeUsageEndIndex - attributeUsageStartIndex + 1)
.Insert(attributeUsageStartIndex, replacementAttributeUsage);
}

/// <summary>
/// 统计生成源码中某个固定片段的出现次数,用于锁定程序集级 fallback 特性的发射个数。
/// </summary>
Expand Down
Loading
Loading