mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-11 20:38:58 +08:00
test: 增强快照测试基础设施,添加安全验证和覆盖率改进
- 在 EnumExtensionsGeneratorSnapshotTests.cs 中补充 snapshotFileNameSelector 的 null 分支覆盖,新增默认快照文件名选择器用例及对应快照资产 - 强化 GeneratorSnapshotTest.cs 的快照路径校验,拒绝空白文件名、绝对路径和目录遍历攻击;将辅助器改为通过 Roslyn GeneratorDriver 读取真实生成结果并验证编译,消除仅依赖 TestState.GeneratedSources 导致的空跑风险 - 新增 GeneratorSnapshotTestSecurityTests.cs 安全回归测试,覆盖绝对路径拒绝和目录逃逸防护两个分支 - 将 Priority、Logger、ContextAware 三组生成器测试统一指向仓库内快照目录,并补齐缺失的快照资产以支持现在强制执行的生成验证
This commit is contained in:
parent
1d6ff223d5
commit
d0b4946bba
@ -50,12 +50,7 @@ public class PriorityGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("BasicPriority"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"bases",
|
|
||||||
"snapshots",
|
|
||||||
"PriorityGenerator",
|
|
||||||
"BasicPriority"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -98,12 +93,7 @@ public class PriorityGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("NegativePriority"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"bases",
|
|
||||||
"snapshots",
|
|
||||||
"PriorityGenerator",
|
|
||||||
"NegativePriority"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -156,12 +146,7 @@ public class PriorityGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("PriorityGroup"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"bases",
|
|
||||||
"snapshots",
|
|
||||||
"PriorityGenerator",
|
|
||||||
"PriorityGroup"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -204,11 +189,25 @@ public class PriorityGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
await GeneratorSnapshotTest<PriorityGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
|
GetSnapshotFolder("GenericClass"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将运行时测试目录映射回仓库内已提交的 Priority 生成器快照目录。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scenarioName">快照场景名称。</param>
|
||||||
|
/// <returns>场景对应的绝对快照目录。</returns>
|
||||||
|
private static string GetSnapshotFolder(string scenarioName)
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(
|
||||||
Path.Combine(
|
Path.Combine(
|
||||||
TestContext.CurrentContext.TestDirectory,
|
TestContext.CurrentContext.TestDirectory,
|
||||||
"bases",
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"Bases",
|
||||||
"snapshots",
|
"snapshots",
|
||||||
"PriorityGenerator",
|
"PriorityGenerator",
|
||||||
"GenericClass"));
|
scenarioName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取优先级值: 10
|
||||||
|
/// </summary>
|
||||||
|
public int Priority => 10;
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class GenericSystem<T> : global::GFramework.Core.Abstractions.Bases.IPrioritized
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取优先级值: 20
|
||||||
|
/// </summary>
|
||||||
|
public int Priority => 20;
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class CriticalSystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取优先级值: -100
|
||||||
|
/// </summary>
|
||||||
|
public int Priority => -100;
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class HighPrioritySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取优先级值: -50
|
||||||
|
/// </summary>
|
||||||
|
public int Priority => -50;
|
||||||
|
}
|
||||||
@ -21,25 +21,45 @@ public static class GeneratorSnapshotTest<TGenerator>
|
|||||||
string snapshotFolder,
|
string snapshotFolder,
|
||||||
Func<string, string>? snapshotFileNameSelector = null)
|
Func<string, string>? snapshotFileNameSelector = null)
|
||||||
{
|
{
|
||||||
var test = new CSharpSourceGeneratorTest<TGenerator, DefaultVerifier>
|
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||||
{
|
var compilation = CSharpCompilation.Create(
|
||||||
TestState =
|
$"{typeof(TGenerator).Name}SnapshotTests",
|
||||||
{
|
[syntaxTree],
|
||||||
Sources = { source }
|
MetadataReferenceTestBuilder.GetRuntimeMetadataReferences(),
|
||||||
},
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||||
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck,
|
GeneratorDriver driver = CSharpGeneratorDriver.Create(
|
||||||
DisabledDiagnostics = { "GF_Common_Trace_001" }
|
generators: [CreateGenerator()],
|
||||||
};
|
parseOptions: (CSharpParseOptions)syntaxTree.Options);
|
||||||
|
driver = driver.RunGeneratorsAndUpdateCompilation(
|
||||||
|
compilation,
|
||||||
|
out var updatedCompilation,
|
||||||
|
out _);
|
||||||
|
|
||||||
await test.RunAsync();
|
var compilationErrors = updatedCompilation.GetDiagnostics()
|
||||||
|
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||||
|
.ToArray();
|
||||||
|
Assert.That(
|
||||||
|
compilationErrors,
|
||||||
|
Is.Empty,
|
||||||
|
() =>
|
||||||
|
$"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}");
|
||||||
|
|
||||||
var generated = test.TestState.GeneratedSources;
|
var runResult = driver.GetRunResult();
|
||||||
|
var generated = runResult.Results
|
||||||
|
.SelectMany(static result => result.GeneratedSources)
|
||||||
|
.OrderBy(static source => source.HintName, StringComparer.Ordinal)
|
||||||
|
.Select(static source => (filename: source.HintName, content: source.SourceText.ToString()))
|
||||||
|
.ToArray();
|
||||||
|
Assert.That(
|
||||||
|
generated,
|
||||||
|
Is.Not.Empty,
|
||||||
|
$"Generator '{typeof(TGenerator).FullName}' did not produce any sources.");
|
||||||
|
|
||||||
foreach (var (filename, content) in generated)
|
foreach (var (filename, content) in generated)
|
||||||
{
|
{
|
||||||
// 不同测试套件可能需要将生成文件映射到非 .cs 快照,以避免测试资产被当作可编译源码参与构建。
|
// 不同测试套件可能需要将生成文件映射到非 .cs 快照,以避免测试资产被当作可编译源码参与构建。
|
||||||
var snapshotFileName = snapshotFileNameSelector?.Invoke(filename) ?? filename;
|
var snapshotFileName = snapshotFileNameSelector?.Invoke(filename) ?? filename;
|
||||||
var path = Path.Combine(
|
var path = ResolveSnapshotPath(
|
||||||
snapshotFolder,
|
snapshotFolder,
|
||||||
snapshotFileName);
|
snapshotFileName);
|
||||||
|
|
||||||
@ -71,4 +91,52 @@ public static class GeneratorSnapshotTest<TGenerator>
|
|||||||
{
|
{
|
||||||
return text.Replace("\r\n", "\n").Trim();
|
return text.Replace("\r\n", "\n").Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建可由 Roslyn 驱动直接执行的源生成器实例,并统一兼容经典与增量生成器。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>适配后的源生成器实例。</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">当测试类型既不是源生成器也不是增量生成器时抛出。</exception>
|
||||||
|
private static ISourceGenerator CreateGenerator()
|
||||||
|
{
|
||||||
|
var generator = new TGenerator();
|
||||||
|
return generator switch
|
||||||
|
{
|
||||||
|
ISourceGenerator sourceGenerator => sourceGenerator,
|
||||||
|
IIncrementalGenerator incrementalGenerator => incrementalGenerator.AsSourceGenerator(),
|
||||||
|
_ => throw new InvalidOperationException(
|
||||||
|
$"Generator type '{typeof(TGenerator).FullName}' must implement {nameof(ISourceGenerator)} or {nameof(IIncrementalGenerator)}.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析并验证快照路径,确保文件名映射不会逃逸出当前快照根目录。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="snapshotFolder">快照根目录。</param>
|
||||||
|
/// <param name="snapshotFileName">映射后的快照文件名。</param>
|
||||||
|
/// <returns>可安全访问的快照绝对路径。</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// 当映射结果为空白、为绝对路径,或通过相对路径越界到快照目录之外时抛出。
|
||||||
|
/// </exception>
|
||||||
|
private static string ResolveSnapshotPath(string snapshotFolder, string snapshotFileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(snapshotFileName) || Path.IsPathRooted(snapshotFileName))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Invalid snapshot file name: {snapshotFileName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先规范化根目录再做包含关系判断,避免 `..` 或平台大小写差异导致的目录逃逸。
|
||||||
|
var snapshotRoot = Path.TrimEndingDirectorySeparator(Path.GetFullPath(snapshotFolder));
|
||||||
|
var snapshotPath = Path.GetFullPath(Path.Combine(snapshotRoot, snapshotFileName));
|
||||||
|
var comparison = OperatingSystem.IsWindows()
|
||||||
|
? StringComparison.OrdinalIgnoreCase
|
||||||
|
: StringComparison.Ordinal;
|
||||||
|
|
||||||
|
if (!snapshotPath.StartsWith(snapshotRoot + Path.DirectorySeparatorChar, comparison))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Snapshot path escapes root folder: {snapshotFileName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshotPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,91 @@
|
|||||||
|
using System.IO;
|
||||||
|
using GFramework.Core.SourceGenerators.Enums;
|
||||||
|
|
||||||
|
namespace GFramework.SourceGenerators.Tests.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证快照测试辅助器对快照文件路径映射的安全约束。
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class GeneratorSnapshotTestSecurityTests
|
||||||
|
{
|
||||||
|
private const string EnumAttributeNamespace = "GFramework.Core.SourceGenerators.Abstractions.Enums";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证快照文件名映射返回绝对路径时,会在访问文件系统前被拒绝。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void RunAsync_SnapshotFileNameSelectorReturnsAbsolutePath_ThrowsInvalidOperationException()
|
||||||
|
{
|
||||||
|
var snapshotRoot = CreateSnapshotRoot();
|
||||||
|
var source = BuildSource();
|
||||||
|
|
||||||
|
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||||
|
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
snapshotRoot,
|
||||||
|
_ => Path.Combine(snapshotRoot, "Status.EnumExtensions.g.cs")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证快照文件名映射尝试通过父级目录片段逃逸根目录时,会在访问文件系统前被拒绝。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void RunAsync_SnapshotFileNameSelectorEscapesSnapshotRoot_ThrowsInvalidOperationException()
|
||||||
|
{
|
||||||
|
var snapshotRoot = CreateSnapshotRoot();
|
||||||
|
var source = BuildSource();
|
||||||
|
|
||||||
|
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||||
|
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
snapshotRoot,
|
||||||
|
_ => Path.Combine("..", "escaped", "Status.EnumExtensions.g.cs")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为安全测试创建隔离的快照根目录路径,避免不同用例共享状态。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>当前用例专属的快照根目录绝对路径。</returns>
|
||||||
|
private static string CreateSnapshotRoot()
|
||||||
|
{
|
||||||
|
return Path.Combine(
|
||||||
|
TestContext.CurrentContext.WorkDirectory,
|
||||||
|
"temp-snapshots",
|
||||||
|
TestContext.CurrentContext.Test.ID,
|
||||||
|
Guid.NewGuid().ToString("N"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造可稳定触发枚举扩展生成器输出的最小测试源码。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>包含测试属性与目标枚举的完整源码。</returns>
|
||||||
|
private static string BuildSource()
|
||||||
|
{
|
||||||
|
return $$"""
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace {{EnumAttributeNamespace}}
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Enum)]
|
||||||
|
public sealed class GenerateEnumExtensionsAttribute : Attribute
|
||||||
|
{
|
||||||
|
public bool GenerateIsMethods { get; set; } = true;
|
||||||
|
public bool GenerateIsInMethod { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using {{EnumAttributeNamespace}};
|
||||||
|
|
||||||
|
[GenerateEnumExtensions]
|
||||||
|
public enum Status
|
||||||
|
{
|
||||||
|
Active,
|
||||||
|
Inactive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,6 +34,26 @@ public class EnumExtensionsGeneratorSnapshotTests
|
|||||||
GetSnapshotFileName);
|
GetSnapshotFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证未提供快照文件名映射时,会直接按生成文件名进行快照比对。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public async Task Snapshot_BasicEnum_IsMethods_DefaultSnapshotFileNameSelector()
|
||||||
|
{
|
||||||
|
var source = BuildSource(
|
||||||
|
"""
|
||||||
|
public enum Status
|
||||||
|
{
|
||||||
|
Active,
|
||||||
|
Inactive
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
|
||||||
|
source,
|
||||||
|
GetSnapshotFolder("BasicEnum_IsMethods_DefaultSnapshotFileNameSelector"));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证默认配置在较小枚举上仍会生成集合判断方法。
|
/// 验证默认配置在较小枚举上仍会生成集合判断方法。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
public static partial class StatusExtensions
|
||||||
|
{
|
||||||
|
/// <summary>是否为 Active</summary>
|
||||||
|
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
|
||||||
|
|
||||||
|
/// <summary>是否为 Inactive</summary>
|
||||||
|
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
|
||||||
|
|
||||||
|
/// <summary>判断是否属于指定集合</summary>
|
||||||
|
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
|
||||||
|
{
|
||||||
|
if (values == null) return false;
|
||||||
|
foreach (var v in values) if (value == v) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -96,12 +96,7 @@ public class LoggerGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("DefaultConfiguration_Class"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"logging",
|
|
||||||
"snapshots",
|
|
||||||
"LoggerGenerator",
|
|
||||||
"DefaultConfiguration_Class"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -193,12 +188,7 @@ public class LoggerGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("CustomName_Class"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"logging",
|
|
||||||
"snapshots",
|
|
||||||
"LoggerGenerator",
|
|
||||||
"CustomName_Class"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -290,12 +280,7 @@ public class LoggerGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("CustomFieldName_Class"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"logging",
|
|
||||||
"snapshots",
|
|
||||||
"LoggerGenerator",
|
|
||||||
"CustomFieldName_Class"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -387,12 +372,7 @@ public class LoggerGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("InstanceField_Class"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"logging",
|
|
||||||
"snapshots",
|
|
||||||
"LoggerGenerator",
|
|
||||||
"InstanceField_Class"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -484,12 +464,7 @@ public class LoggerGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
Path.Combine(
|
GetSnapshotFolder("PublicField_Class"));
|
||||||
TestContext.CurrentContext.TestDirectory,
|
|
||||||
"logging",
|
|
||||||
"snapshots",
|
|
||||||
"LoggerGenerator",
|
|
||||||
"PublicField_Class"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -581,11 +556,25 @@ public class LoggerGeneratorSnapshotTests
|
|||||||
|
|
||||||
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
|
GetSnapshotFolder("GenericClass"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将运行时测试目录映射回仓库内已提交的日志生成器快照目录。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scenarioName">快照场景名称。</param>
|
||||||
|
/// <returns>场景对应的绝对快照目录。</returns>
|
||||||
|
private static string GetSnapshotFolder(string scenarioName)
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(
|
||||||
Path.Combine(
|
Path.Combine(
|
||||||
TestContext.CurrentContext.TestDirectory,
|
TestContext.CurrentContext.TestDirectory,
|
||||||
"logging",
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"Logging",
|
||||||
"snapshots",
|
"snapshots",
|
||||||
"LoggerGenerator",
|
"LoggerGenerator",
|
||||||
"GenericClass"));
|
scenarioName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
using GFramework.Core.Logging;
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyService
|
||||||
|
{
|
||||||
|
/// <summary>Auto-generated logger</summary>
|
||||||
|
private static readonly ILogger MyLogger = LoggerFactoryResolver.Provider.CreateLogger("MyService");
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
using GFramework.Core.Logging;
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyService
|
||||||
|
{
|
||||||
|
/// <summary>Auto-generated logger</summary>
|
||||||
|
private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
using GFramework.Core.Logging;
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyService
|
||||||
|
{
|
||||||
|
/// <summary>Auto-generated logger</summary>
|
||||||
|
private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
using GFramework.Core.Logging;
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyService<T>
|
||||||
|
{
|
||||||
|
/// <summary>Auto-generated logger</summary>
|
||||||
|
private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
using GFramework.Core.Logging;
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyService
|
||||||
|
{
|
||||||
|
/// <summary>Auto-generated logger</summary>
|
||||||
|
private readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
using GFramework.Core.Logging;
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyService
|
||||||
|
{
|
||||||
|
/// <summary>Auto-generated logger</summary>
|
||||||
|
public static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
|
||||||
|
}
|
||||||
@ -86,9 +86,22 @@ public class ContextAwareGeneratorSnapshotTests
|
|||||||
// 执行生成器快照测试,将生成的代码与预期快照进行比较
|
// 执行生成器快照测试,将生成的代码与预期快照进行比较
|
||||||
await GeneratorSnapshotTest<ContextAwareGenerator>.RunAsync(
|
await GeneratorSnapshotTest<ContextAwareGenerator>.RunAsync(
|
||||||
source,
|
source,
|
||||||
|
GetSnapshotFolder());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将运行时测试目录映射回仓库内已提交的上下文感知生成器快照目录。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>快照目录的绝对路径。</returns>
|
||||||
|
private static string GetSnapshotFolder()
|
||||||
|
{
|
||||||
|
return Path.GetFullPath(
|
||||||
Path.Combine(
|
Path.Combine(
|
||||||
TestContext.CurrentContext.TestDirectory,
|
TestContext.CurrentContext.TestDirectory,
|
||||||
"rule",
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"Rule",
|
||||||
"snapshots",
|
"snapshots",
|
||||||
"ContextAwareGenerator"));
|
"ContextAwareGenerator"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace TestApp;
|
||||||
|
|
||||||
|
partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
|
||||||
|
{
|
||||||
|
private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _context;
|
||||||
|
private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider)
|
||||||
|
/// </summary>
|
||||||
|
protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_context == null)
|
||||||
|
{
|
||||||
|
_contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();
|
||||||
|
_context = _contextProvider.GetContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置上下文提供者(用于测试或多架构场景)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider">上下文提供者实例</param>
|
||||||
|
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)
|
||||||
|
{
|
||||||
|
_contextProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置上下文提供者为默认值(用于测试清理)
|
||||||
|
/// </summary>
|
||||||
|
public static void ResetContextProvider()
|
||||||
|
{
|
||||||
|
_contextProvider = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architectures.IArchitectureContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
global::GFramework.Core.Abstractions.Architectures.IArchitectureContext global::GFramework.Core.Abstractions.Rule.IContextAware.GetContext()
|
||||||
|
{
|
||||||
|
return Context;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user