refactor(source-generators-tests): 收口 ContextGetGeneratorTests 的 MA0051

- 重构 ContextGetGeneratorTests 的长测试方法为场景常量与验证 helper,保持生成输入和断言语义不变

- 补充测试类与 helper 的 XML 文档,并统一生成源码与诊断断言的复用路径

- 验证 GFramework.SourceGenerators.Tests Release build 与 ContextGetGeneratorTests 过滤测试通过
This commit is contained in:
gewuyou 2026-04-23 13:17:07 +08:00
parent 18c595a72f
commit 8cd492506d

View File

@ -3,13 +3,18 @@ using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Rule; namespace GFramework.SourceGenerators.Tests.Rule;
/// <summary>
/// 验证 <see cref="ContextGetGenerator" /> 在显式特性、<c>GetAll</c> 推断与诊断场景下的生成契约。
/// </summary>
[TestFixture] [TestFixture]
public class ContextGetGeneratorTests public class ContextGetGeneratorTests
{ {
[Test] private const string InventoryPanelGeneratedFileName = "TestApp_InventoryPanel.ContextGet.g.cs";
public async Task Generates_Bindings_For_ContextAwareAttribute_Class() private const string BattlePanelGeneratedFileName = "TestApp_BattlePanel.ContextGet.g.cs";
{ private const string GameplayHudGeneratedFileName = "TestApp_GameplayHud.ContextGet.g.cs";
var source = """ private const string StrategyHostGeneratedFileName = "TestApp_StrategyHost.ContextGet.g.cs";
private const string ContextAwareAttributeClassSource = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -72,7 +77,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string ContextAwareAttributeClassExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -91,16 +96,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string FullyQualifiedFieldAttributesSource = """
source,
("TestApp_InventoryPanel.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
{
var source = """
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -154,7 +150,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string FullyQualifiedFieldAttributesExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -172,16 +168,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string InferredGetAllClassSource = """
source,
("TestApp_InventoryPanel.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Generates_Inferred_Bindings_For_GetAll_Class()
{
var source = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -259,7 +246,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string InferredGetAllClassExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -280,16 +267,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string ExplicitServiceGetAllClassSource = """
source,
("TestApp_BattlePanel.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Generates_Explicit_Service_Binding_For_GetAll_Class()
{
var source = """
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -352,7 +330,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string ExplicitServiceGetAllClassExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -371,16 +349,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string GeneratedInjectionMethodAlreadyExistsMarkupSource = """
source,
("TestApp_BattlePanel.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
{
var source = """
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -438,27 +407,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string IgnoreConstFieldGetAllSource = """
{
TestState =
{
Sources = { source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" },
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
};
test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
.WithLocation(0)
.WithArguments("InventoryPanel", "__InjectContextBindings_Generated"));
await test.RunAsync();
}
[Test]
public async Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
{
var source = """
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -514,7 +463,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string IgnoreConstFieldGetAllExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -532,16 +481,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string ReadonlyInferredFieldGetAllMarkupSource = """
source,
("TestApp_BattlePanel.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
{
var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -597,9 +537,9 @@ public class ContextGetGeneratorTests
private ICombatSystem _system = null!; private ICombatSystem _system = null!;
} }
} }
"""); """;
const string expected = """ private const string SkipInvalidGetAllFieldExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -617,32 +557,7 @@ public class ContextGetGeneratorTests
"""; """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string StaticInferredFieldGetAllMarkupSource = """
{
TestState =
{
Sources = { source.Source },
GeneratedSources =
{
(typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", NormalizeLineEndings(expected))
}
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
test.ExpectedDiagnostics.Add(source.WithSpan(
new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning),
"0")
.WithArguments("_model"));
await test.RunAsync();
Assert.Pass();
}
[Test]
public async Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
{
var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -698,60 +613,9 @@ public class ContextGetGeneratorTests
private ICombatSystem _system = null!; private ICombatSystem _system = null!;
} }
} }
""");
const string expected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class BattlePanel
{
private void __InjectContextBindings_Generated()
{
_system = this.GetSystem<global::TestApp.ICombatSystem>();
}
}
"""; """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string SkipNullableServiceLikeFieldSource = """
{
TestState =
{
Sources = { source.Source },
GeneratedSources =
{
(typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", NormalizeLineEndings(expected))
}
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
test.ExpectedDiagnostics.Add(source.WithSpan(
new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning),
"0")
.WithArguments("_model"));
await test.RunAsync();
Assert.Pass();
}
private static string NormalizeLineEndings(string content)
{
return content
.Replace("\r\n", "\n", StringComparison.Ordinal)
.Replace("\r", "\n", StringComparison.Ordinal)
.Replace("\n", Environment.NewLine, StringComparison.Ordinal);
}
[Test]
public async Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
{
var source = """
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -815,7 +679,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string SkipNullableServiceLikeFieldExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -834,16 +698,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string IContextAwareClassSource = """
source,
("TestApp_GameplayHud.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Generates_Bindings_For_IContextAware_Class()
{
var source = """
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -893,7 +748,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string IContextAwareClassExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -911,16 +766,7 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( private const string NonContextAwareClassMarkupSource = """
source,
("TestApp_StrategyHost.ContextGet.g.cs", expected));
Assert.Pass();
}
[Test]
public async Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
{
var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -963,30 +809,9 @@ public class ContextGetGeneratorTests
private IInventoryModel {|#0:_model|} = null!; private IInventoryModel {|#0:_model|} = null!;
} }
} }
"""); """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string GetModelsFieldNotIReadOnlyListMarkupSource = """
{
TestState =
{
Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
test.ExpectedDiagnostics.Add(source.WithSpan(
new DiagnosticResult("GF_ContextGet_005", DiagnosticSeverity.Error),
"0")
.WithArguments("InventoryPanel"));
await test.RunAsync();
Assert.Pass();
}
[Test]
public async Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
{
var source = MarkupTestSource.Parse("""
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -1035,30 +860,9 @@ public class ContextGetGeneratorTests
private List<IInventoryModel> {|#0:_models|} = new(); private List<IInventoryModel> {|#0:_models|} = new();
} }
} }
"""); """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string ReadonlyExplicitGetModelFieldMarkupSource = """
{
TestState =
{
Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
test.ExpectedDiagnostics.Add(source.WithSpan(
new DiagnosticResult("GF_ContextGet_004", DiagnosticSeverity.Error),
"0")
.WithArguments("_models", "System.Collections.Generic.List<TestApp.IInventoryModel>", "GetModels"));
await test.RunAsync();
Assert.Pass();
}
[Test]
public async Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
{
var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -1106,30 +910,9 @@ public class ContextGetGeneratorTests
private readonly IInventoryModel {|#0:_model|} = null!; private readonly IInventoryModel {|#0:_model|} = null!;
} }
} }
"""); """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string StaticExplicitGetModelFieldMarkupSource = """
{
TestState =
{
Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
test.ExpectedDiagnostics.Add(source.WithSpan(
new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error),
"0")
.WithArguments("_model"));
await test.RunAsync();
Assert.Pass();
}
[Test]
public async Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
{
var source = MarkupTestSource.Parse("""
using System; using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -1177,30 +960,9 @@ public class ContextGetGeneratorTests
private static IInventoryModel {|#0:_model|} = null!; private static IInventoryModel {|#0:_model|} = null!;
} }
} }
"""); """;
var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> private const string GetModelsAssignableSource = """
{
TestState =
{
Sources = { source.Source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
test.ExpectedDiagnostics.Add(source.WithSpan(
new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error),
"0")
.WithArguments("_model"));
await test.RunAsync();
Assert.Pass();
}
[Test]
public async Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
{
var source = """
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule; using GFramework.Core.SourceGenerators.Abstractions.Rule;
@ -1251,7 +1013,7 @@ public class ContextGetGeneratorTests
} }
"""; """;
const string expected = """ private const string GetModelsAssignableExpected = """
// <auto-generated /> // <auto-generated />
#nullable enable #nullable enable
@ -1269,9 +1031,328 @@ public class ContextGetGeneratorTests
"""; """;
await GeneratorTest<ContextGetGenerator>.RunAsync( /// <summary>
/// 验证 <c>[ContextAware]</c> 类上的显式字段特性会生成模型与服务绑定。
/// </summary>
[Test]
public Task Generates_Bindings_For_ContextAwareAttribute_Class()
{
return VerifyGeneratedSourceAsync(
ContextAwareAttributeClassSource,
InventoryPanelGeneratedFileName,
ContextAwareAttributeClassExpected);
}
/// <summary>
/// 验证字段使用 fully-qualified 特性名时仍能生成绑定。
/// </summary>
[Test]
public Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
{
return VerifyGeneratedSourceAsync(
FullyQualifiedFieldAttributesSource,
InventoryPanelGeneratedFileName,
FullyQualifiedFieldAttributesExpected);
}
/// <summary>
/// 验证 <c>GetAll</c> 会仅为可推断的 model、models、system 与 utility 字段生成绑定。
/// </summary>
[Test]
public Task Generates_Inferred_Bindings_For_GetAll_Class()
{
return VerifyGeneratedSourceAsync(
InferredGetAllClassSource,
BattlePanelGeneratedFileName,
InferredGetAllClassExpected);
}
/// <summary>
/// 验证 <c>GetAll</c> 与显式 <c>[GetService]</c> 可以组合生成绑定。
/// </summary>
[Test]
public Task Generates_Explicit_Service_Binding_For_GetAll_Class()
{
return VerifyGeneratedSourceAsync(
ExplicitServiceGetAllClassSource,
BattlePanelGeneratedFileName,
ExplicitServiceGetAllClassExpected);
}
/// <summary>
/// 验证目标类已声明注入方法时会报告冲突诊断。
/// </summary>
[Test]
public Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
{
var source = MarkupTestSource.Parse(GeneratedInjectionMethodAlreadyExistsMarkupSource);
return VerifyDiagnosticAsync(
source, source,
("TestApp_InventoryPanel.ContextGet.g.cs", expected)); CreateSingleSpanDiagnostic(
Assert.Pass(); source,
"GF_Common_Class_002",
DiagnosticSeverity.Error,
"InventoryPanel",
"__InjectContextBindings_Generated"));
}
/// <summary>
/// 验证 <c>GetAll</c> 会忽略不可推断的常量字段且不报告诊断。
/// </summary>
[Test]
public Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
{
return VerifyGeneratedSourceAsync(
IgnoreConstFieldGetAllSource,
BattlePanelGeneratedFileName,
IgnoreConstFieldGetAllExpected);
}
/// <summary>
/// 验证只读推断字段会被跳过并报告 warning同时其他字段保持生成。
/// </summary>
[Test]
public Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
{
var source = MarkupTestSource.Parse(ReadonlyInferredFieldGetAllMarkupSource);
return VerifyDiagnosticAndGeneratedSourceAsync(
source,
BattlePanelGeneratedFileName,
SkipInvalidGetAllFieldExpected,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_008",
DiagnosticSeverity.Warning,
"_model"));
}
/// <summary>
/// 验证静态推断字段会被跳过并报告 warning同时其他字段保持生成。
/// </summary>
[Test]
public Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
{
var source = MarkupTestSource.Parse(StaticInferredFieldGetAllMarkupSource);
return VerifyDiagnosticAndGeneratedSourceAsync(
source,
BattlePanelGeneratedFileName,
SkipInvalidGetAllFieldExpected,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_007",
DiagnosticSeverity.Warning,
"_model"));
}
/// <summary>
/// 验证 nullable 的服务样字段不会被 <c>GetAll</c> 误判为可注入字段。
/// </summary>
[Test]
public Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
{
return VerifyGeneratedSourceAsync(
SkipNullableServiceLikeFieldSource,
GameplayHudGeneratedFileName,
SkipNullableServiceLikeFieldExpected);
}
/// <summary>
/// 验证实现 <c>IContextAware</c> 的类型无需 <c>[ContextAware]</c> 也能生成显式绑定。
/// </summary>
[Test]
public Task Generates_Bindings_For_IContextAware_Class()
{
return VerifyGeneratedSourceAsync(
IContextAwareClassSource,
StrategyHostGeneratedFileName,
IContextAwareClassExpected);
}
/// <summary>
/// 验证缺少上下文感知契约的类型会报告错误诊断。
/// </summary>
[Test]
public Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
{
var source = MarkupTestSource.Parse(NonContextAwareClassMarkupSource);
return VerifyDiagnosticAsync(
source,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_005",
DiagnosticSeverity.Error,
"InventoryPanel"));
}
/// <summary>
/// 验证 <c>[GetModels]</c> 字段若不是可赋值自 <c>IReadOnlyList&lt;T&gt;</c> 会报告错误。
/// </summary>
[Test]
public Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
{
var source = MarkupTestSource.Parse(GetModelsFieldNotIReadOnlyListMarkupSource);
return VerifyDiagnosticAsync(
source,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_004",
DiagnosticSeverity.Error,
"_models",
"System.Collections.Generic.List<TestApp.IInventoryModel>",
"GetModels"));
}
/// <summary>
/// 验证显式 <c>[GetModel]</c> 作用于只读字段时会报告错误。
/// </summary>
[Test]
public Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
{
var source = MarkupTestSource.Parse(ReadonlyExplicitGetModelFieldMarkupSource);
return VerifyDiagnosticAsync(
source,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_003",
DiagnosticSeverity.Error,
"_model"));
}
/// <summary>
/// 验证显式 <c>[GetModel]</c> 作用于静态字段时会报告错误。
/// </summary>
[Test]
public Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
{
var source = MarkupTestSource.Parse(StaticExplicitGetModelFieldMarkupSource);
return VerifyDiagnosticAsync(
source,
CreateSingleSpanDiagnostic(
source,
"GF_ContextGet_002",
DiagnosticSeverity.Error,
"_model"));
}
/// <summary>
/// 验证 <c>[GetModels]</c> 字段可以赋值到更宽的可枚举接口上。
/// </summary>
[Test]
public Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
{
return VerifyGeneratedSourceAsync(
GetModelsAssignableSource,
InventoryPanelGeneratedFileName,
GetModelsAssignableExpected);
}
/// <summary>
/// 运行单个生成源码断言,保持文件名与文本快照语义不变。
/// </summary>
/// <param name="source">测试输入源码。</param>
/// <param name="generatedFileName">期望生成文件名。</param>
/// <param name="expectedGeneratedSource">期望生成源码。</param>
/// <returns>表示测试执行的异步任务。</returns>
private static Task VerifyGeneratedSourceAsync(
string source,
string generatedFileName,
string expectedGeneratedSource)
{
return GeneratorTest<ContextGetGenerator>.RunAsync(
source,
(generatedFileName, expectedGeneratedSource));
}
/// <summary>
/// 运行仅关注诊断输出的生成器测试。
/// </summary>
/// <param name="source">包含 markup span 的测试源码。</param>
/// <param name="expectedDiagnostic">期望诊断。</param>
/// <returns>表示测试执行的异步任务。</returns>
private static Task VerifyDiagnosticAsync(
MarkupTestSource source,
DiagnosticResult expectedDiagnostic)
{
var test = CreateGeneratorTest(source.Source);
test.ExpectedDiagnostics.Add(expectedDiagnostic);
return test.RunAsync();
}
/// <summary>
/// 运行同时断言诊断与部分生成输出的生成器测试。
/// </summary>
/// <param name="source">包含 markup span 的测试源码。</param>
/// <param name="generatedFileName">期望生成文件名。</param>
/// <param name="expectedGeneratedSource">期望生成源码。</param>
/// <param name="expectedDiagnostic">期望诊断。</param>
/// <returns>表示测试执行的异步任务。</returns>
private static Task VerifyDiagnosticAndGeneratedSourceAsync(
MarkupTestSource source,
string generatedFileName,
string expectedGeneratedSource,
DiagnosticResult expectedDiagnostic)
{
var test = CreateGeneratorTest(source.Source);
test.TestState.GeneratedSources.Add(
(typeof(ContextGetGenerator), generatedFileName, NormalizeLineEndings(expectedGeneratedSource)));
test.ExpectedDiagnostics.Add(expectedDiagnostic);
return test.RunAsync();
}
/// <summary>
/// 为单一 markup span 场景构造诊断结果,统一保持定位键与参数组装方式。
/// </summary>
/// <param name="source">包含 markup span 的测试源码。</param>
/// <param name="diagnosticId">诊断 ID。</param>
/// <param name="severity">诊断严重级别。</param>
/// <param name="arguments">诊断参数。</param>
/// <returns>绑定到 markup key <c>0</c> 的期望诊断。</returns>
private static DiagnosticResult CreateSingleSpanDiagnostic(
MarkupTestSource source,
string diagnosticId,
DiagnosticSeverity severity,
params string[] arguments)
{
return source.WithSpan(
new DiagnosticResult(diagnosticId, severity),
"0")
.WithArguments(arguments);
}
/// <summary>
/// 创建禁用 trace 诊断的通用源生成器测试实例,避免各场景重复样板配置。
/// </summary>
/// <param name="source">测试输入源码。</param>
/// <returns>配置完成的生成器测试对象。</returns>
private static CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> CreateGeneratorTest(string source)
{
return new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
{
TestState =
{
Sources = { source }
},
DisabledDiagnostics = { "GF_Common_Trace_001" }
};
}
/// <summary>
/// 将手工声明的期望生成源码归一化到当前平台换行符,避免不同宿主上的伪差异。
/// </summary>
/// <param name="content">原始期望源码。</param>
/// <returns>已按当前平台换行符归一化的源码文本。</returns>
private static string NormalizeLineEndings(string content)
{
return content
.Replace("\r\n", "\n", StringComparison.Ordinal)
.Replace("\r", "\n", StringComparison.Ordinal)
.Replace("\n", Environment.NewLine, StringComparison.Ordinal);
} }
} }