GFramework/GFramework.SourceGenerators.Tests/Rule/ContextGetGeneratorTests.cs
gewuyou 8cd492506d refactor(source-generators-tests): 收口 ContextGetGeneratorTests 的 MA0051
- 重构 ContextGetGeneratorTests 的长测试方法为场景常量与验证 helper,保持生成输入和断言语义不变

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

- 验证 GFramework.SourceGenerators.Tests Release build 与 ContextGetGeneratorTests 过滤测试通过
2026-04-23 13:17:07 +08:00

1359 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using GFramework.Core.SourceGenerators.Rule;
using GFramework.SourceGenerators.Tests.Core;
namespace GFramework.SourceGenerators.Tests.Rule;
/// <summary>
/// 验证 <see cref="ContextGetGenerator" /> 在显式特性、<c>GetAll</c> 推断与诊断场景下的生成契约。
/// </summary>
[TestFixture]
public class ContextGetGeneratorTests
{
private const string InventoryPanelGeneratedFileName = "TestApp_InventoryPanel.ContextGet.g.cs";
private const string BattlePanelGeneratedFileName = "TestApp_BattlePanel.ContextGet.g.cs";
private const string GameplayHudGeneratedFileName = "TestApp_GameplayHud.ContextGet.g.cs";
private const string StrategyHostGeneratedFileName = "TestApp_StrategyHost.ContextGet.g.cs";
private const string ContextAwareAttributeClassSource = """
using System;
using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetServicesAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
public static IReadOnlyList<T> GetServices<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public interface IInventoryStrategy { }
[ContextAware]
public partial class InventoryPanel
{
[GetModel]
private IInventoryModel _model = null!;
[GetServices]
private IReadOnlyList<IInventoryStrategy> _strategies = null!;
}
}
""";
private const string ContextAwareAttributeClassExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class InventoryPanel
{
private void __InjectContextBindings_Generated()
{
_model = this.GetModel<global::TestApp.IInventoryModel>();
_strategies = this.GetServices<global::TestApp.IInventoryStrategy>();
}
}
""";
private const string FullyQualifiedFieldAttributesSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
[ContextAware]
public partial class InventoryPanel
{
[global::GFramework.Core.SourceGenerators.Abstractions.Rule.GetModel]
private IInventoryModel _model = null!;
}
}
""";
private const string FullyQualifiedFieldAttributesExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class InventoryPanel
{
private void __InjectContextBindings_Generated()
{
_model = this.GetModel<global::TestApp.IInventoryModel>();
}
}
""";
private const string InferredGetAllClassSource = """
using System;
using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Architectures
{
public interface IArchitectureContext { }
}
namespace GFramework.Core.Rule
{
public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
public static T GetSystem<T>(this object contextAware) => default!;
public static T GetUtility<T>(this object contextAware) => default!;
}
}
namespace Godot
{
public class Node { }
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
public interface IUiUtility : GFramework.Core.Abstractions.Utility.IUtility { }
public interface IStrategy { }
[GetAll]
public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
{
private IInventoryModel _model = null!;
private IReadOnlyList<IInventoryModel> _models = null!;
private ICombatSystem _system = null!;
private IUiUtility _utility = null!;
private IStrategy _service = null!;
private IReadOnlyList<IStrategy> _services = null!;
private Godot.Node _node = null!;
}
}
""";
private const string InferredGetAllClassExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class BattlePanel
{
private void __InjectContextBindings_Generated()
{
_model = this.GetModel<global::TestApp.IInventoryModel>();
_models = this.GetModels<global::TestApp.IInventoryModel>();
_system = this.GetSystem<global::TestApp.ICombatSystem>();
_utility = this.GetUtility<global::TestApp.IUiUtility>();
}
}
""";
private const string ExplicitServiceGetAllClassSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetServiceAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Rule
{
public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
public static T GetService<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public interface IStrategy { }
[GetAll]
public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
{
private IInventoryModel _model = null!;
[GetService]
private IStrategy _service = null!;
}
}
""";
private const string ExplicitServiceGetAllClassExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class BattlePanel
{
private void __InjectContextBindings_Generated()
{
_model = this.GetModel<global::TestApp.IInventoryModel>();
_service = this.GetService<global::TestApp.IStrategy>();
}
}
""";
private const string GeneratedInjectionMethodAlreadyExistsMarkupSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
[ContextAware]
public partial class InventoryPanel
{
[GetModel]
private IInventoryModel _model = null!;
private void {|#0:__InjectContextBindings_Generated|}()
{
}
}
}
""";
private const string IgnoreConstFieldGetAllSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Rule
{
public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
[GetAll]
public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
{
private const double LogicStep = 0.2;
private IInventoryModel _model = null!;
}
}
""";
private const string IgnoreConstFieldGetAllExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class BattlePanel
{
private void __InjectContextBindings_Generated()
{
_model = this.GetModel<global::TestApp.IInventoryModel>();
}
}
""";
private const string ReadonlyInferredFieldGetAllMarkupSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Rule
{
public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
public static T GetSystem<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
[GetAll]
public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
{
private readonly IInventoryModel {|#0:_model|} = null!;
private ICombatSystem _system = null!;
}
}
""";
private const string SkipInvalidGetAllFieldExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class BattlePanel
{
private void __InjectContextBindings_Generated()
{
_system = this.GetSystem<global::TestApp.ICombatSystem>();
}
}
""";
private const string StaticInferredFieldGetAllMarkupSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Rule
{
public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
public static T GetSystem<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
[GetAll]
public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
{
private static IInventoryModel {|#0:_model|} = null!;
private ICombatSystem _system = null!;
}
}
""";
private const string SkipNullableServiceLikeFieldSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class GetAllAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
public static T GetSystem<T>(this object contextAware) => default!;
}
}
namespace Godot
{
public class Control { }
}
namespace TestApp
{
public interface IGridModel : GFramework.Core.Abstractions.Model.IModel { }
public interface IRunLoopSystem : GFramework.Core.Abstractions.Systems.ISystem { }
public interface IUiPageBehavior { }
[ContextAware]
[GetAll]
public partial class GameplayHud : Godot.Control
{
private IGridModel _gridModel = null!;
private IUiPageBehavior? _page;
private IRunLoopSystem _runLoopSystem = null!;
}
}
""";
private const string SkipNullableServiceLikeFieldExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class GameplayHud
{
private void __InjectContextBindings_Generated()
{
_gridModel = this.GetModel<global::TestApp.IGridModel>();
_runLoopSystem = this.GetSystem<global::TestApp.IRunLoopSystem>();
}
}
""";
private const string IContextAwareClassSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetServiceAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetService<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IStrategy { }
public partial class StrategyHost : GFramework.Core.Abstractions.Rule.IContextAware
{
[GetService]
private IStrategy _strategy = null!;
}
}
""";
private const string IContextAwareClassExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class StrategyHost
{
private void __InjectContextBindings_Generated()
{
_strategy = this.GetService<global::TestApp.IStrategy>();
}
}
""";
private const string NonContextAwareClassMarkupSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public partial class InventoryPanel
{
[GetModel]
private IInventoryModel {|#0:_model|} = null!;
}
}
""";
private const string GetModelsFieldNotIReadOnlyListMarkupSource = """
using System;
using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelsAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
{
[GetModels]
private List<IInventoryModel> {|#0:_models|} = new();
}
}
""";
private const string ReadonlyExplicitGetModelFieldMarkupSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
{
[GetModel]
private readonly IInventoryModel {|#0:_model|} = null!;
}
}
""";
private const string StaticExplicitGetModelFieldMarkupSource = """
using System;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static T GetModel<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
{
[GetModel]
private static IInventoryModel {|#0:_model|} = null!;
}
}
""";
private const string GetModelsAssignableSource = """
using System;
using System.Collections.Generic;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
public sealed class GetModelsAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware { }
}
namespace GFramework.Core.Abstractions.Model
{
public interface IModel { }
}
namespace GFramework.Core.Abstractions.Systems
{
public interface ISystem { }
}
namespace GFramework.Core.Abstractions.Utility
{
public interface IUtility { }
}
namespace GFramework.Core.Extensions
{
public static class ContextAwareServiceExtensions
{
public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
}
}
namespace TestApp
{
public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
{
[GetModels]
private IEnumerable<IInventoryModel> _models = null!;
}
}
""";
private const string GetModelsAssignableExpected = """
// <auto-generated />
#nullable enable
using GFramework.Core.Extensions;
namespace TestApp;
partial class InventoryPanel
{
private void __InjectContextBindings_Generated()
{
_models = this.GetModels<global::TestApp.IInventoryModel>();
}
}
""";
/// <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,
CreateSingleSpanDiagnostic(
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);
}
}