using System.Text.Json.Serialization; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEExtension.NetCore.XUnit.Runtime; /// /// 表示生成器创建的强类型异步调用适配器。 /// /// 实例方法的目标对象;静态方法为 。 /// 按方法形参顺序排列的调用参数。 /// 表示方法完成的值任务;结果为装箱后的返回值, 方法返回 public delegate ValueTask XfeInvoker(object? instance, object?[] arguments); /// /// 保存测试和基准各阶段的强类型生命周期调用器。 /// public sealed class XfeLifecycleHooks { /// /// 获取测试类全部用例开始前执行一次的调用器。 /// public IReadOnlyList BeforeAll { get; init; } = []; /// /// 获取测试类全部用例结束后执行一次的调用器。 /// public IReadOnlyList AfterAll { get; init; } = []; /// /// 获取每个普通测试用例开始前执行的调用器。 /// public IReadOnlyList BeforeEach { get; init; } = []; /// /// 获取每个普通测试用例结束后执行的调用器。 /// public IReadOnlyList AfterEach { get; init; } = []; /// /// 获取单个基准工作进程开始测量前执行一次的调用器。 /// public IReadOnlyList GlobalSetup { get; init; } = []; /// /// 获取单个基准工作进程完成全部测量后执行一次的调用器。 /// public IReadOnlyList GlobalCleanup { get; init; } = []; /// /// 获取每轮基准实际测量前执行的调用器。 /// public IReadOnlyList IterationSetup { get; init; } = []; /// /// 获取每轮基准实际测量后执行的调用器。 /// public IReadOnlyList IterationCleanup { get; init; } = []; } /// /// 描述一个已经展开参数、可由运行器直接执行的普通测试用例。 /// public sealed class TestDescriptor { /// /// 获取在生成注册表和工作进程通信中唯一标识该用例的字符串。 /// public required string Id { get; init; } /// /// 获取控制台、筛选和报告使用的测试显示名称。 /// public required string DisplayName { get; init; } /// /// 获取测试方法声明类型的完全限定名称。 /// public required string TypeName { get; init; } /// /// 获取测试方法名称。 /// public required string MethodName { get; init; } /// /// 获取传递给测试调用器的参数。 /// public object?[] Arguments { get; init; } = []; /// /// 获取从测试类和方法合并得到的分类名称。 /// public IReadOnlyList Categories { get; init; } = []; /// /// 获取从测试类和方法合并得到的结构化特征。 /// public IReadOnlyDictionary Traits { get; init; } = new Dictionary(); /// /// 获取跳过原因;为空表示测试未被无条件跳过。 /// public string? SkipReason { get; init; } /// /// 获取该用例是否仅在启用显式测试时运行。 /// public bool Explicit { get; init; } /// /// 获取该用例是否禁止与其他测试并行执行。 /// public bool NonParallel { get; init; } /// /// 获取该用例是否必须在独立工作进程中执行。 /// public bool Isolated { get; init; } /// /// 获取该用例是否来自 3.x 兼容特性。 /// public bool IsLegacy { get; init; } /// /// 获取该用例是否来自 3.x 的 单次执行特性。 /// public bool IsLegacySingleRun { get; init; } /// /// 获取测试集合名称;为空时按声明类型分组。 /// public string? Collection { get; init; } /// /// 获取硬超时毫秒数;0 表示使用全局默认值或不超时。 /// public int TimeoutMilliseconds { get; init; } /// /// 获取首次失败后允许重新执行的次数。 /// public int RetryCount { get; init; } /// /// 获取兼容执行器是否需要比较方法返回值。 /// public bool HasExpectedResult { get; init; } /// /// 获取兼容执行器期望的方法返回值。 /// public object? ExpectedResult { get; init; } /// /// 获取创建测试类实例的工厂;该成员不会序列化到结果文件。 /// [JsonIgnore] public required Func Factory { get; init; } /// /// 获取直接调用测试方法的强类型适配器;该成员不会序列化到结果文件。 /// [JsonIgnore] public required XfeInvoker Invoker { get; init; } /// /// 获取测试类的生命周期调用器集合。 /// [JsonIgnore] public XfeLifecycleHooks Lifecycle { get; init; } = new(); } /// /// 描述一个已经展开方法参数和 组合的基准用例。 /// public sealed class BenchmarkDescriptor { /// /// 获取在生成注册表和工作进程通信中唯一标识该基准的字符串。 /// public required string Id { get; init; } /// /// 获取控制台、筛选和报告使用的基准显示名称。 /// public required string DisplayName { get; init; } /// /// 获取基准方法声明类型的完全限定名称。 /// public required string TypeName { get; init; } /// /// 获取基准方法名称。 /// public required string MethodName { get; init; } /// /// 获取传递给基准调用器的方法参数。 /// public object?[] Arguments { get; init; } = []; /// /// 获取从基准类和方法合并得到的分类名称。 /// public IReadOnlyList Categories { get; init; } = []; /// /// 获取该基准是否为相同参数组合的比率基线。 /// public bool Baseline { get; init; } /// /// 获取该基准是否来自 3.x 单方法计时兼容特性。 /// public bool IsLegacy { get; init; } /// /// 获取用于匹配相同 组合基线的稳定键。 /// public string ParameterKey { get; init; } = string.Empty; /// /// 获取该基准采用的测量策略。 /// public BenchmarkStrategy Strategy { get; init; } = BenchmarkStrategy.Throughput; /// /// 获取创建基准类实例的工厂。 /// [JsonIgnore] public required Func Factory { get; init; } /// /// 获取直接调用基准方法的强类型适配器。 /// [JsonIgnore] public required XfeInvoker Invoker { get; init; } /// /// 获取具有相同返回形状但不执行用户工作负载的开销调用器。 /// [JsonIgnore] public XfeInvoker OverheadInvoker { get; init; } = static (_, _) => new ValueTask((object?)null); /// /// 获取把当前 组合应用到基准实例的委托。 /// [JsonIgnore] public Action ApplyParameters { get; init; } = static _ => { }; /// /// 获取基准类的全局和迭代生命周期调用器。 /// [JsonIgnore] public XfeLifecycleHooks Lifecycle { get; init; } = new(); } /// /// 保存生成器发现的普通测试和基准描述符。 /// public sealed class XfeRegistry { private readonly List _tests = []; private readonly List _benchmarks = []; /// /// 获取注册的普通测试用例只读列表。 /// public IReadOnlyList Tests => _tests; /// /// 获取注册的基准用例只读列表。 /// public IReadOnlyList Benchmarks => _benchmarks; /// /// 向注册表添加一个普通测试用例。 /// /// 要注册的测试描述符。 public void AddTest(TestDescriptor descriptor) => _tests.Add(descriptor); /// /// 向注册表添加一个基准用例。 /// /// 要注册的基准描述符。 public void AddBenchmark(BenchmarkDescriptor descriptor) => _benchmarks.Add(descriptor); } /// /// 连接增量生成器输出与运行时的进程内注册表入口。 /// public static class XfeGeneratedRegistry { private static Func? s_factory; /// /// 设置用于创建当前程序集测试注册表的生成工厂。 /// /// 生成器发出的注册表工厂。 public static void SetFactory(Func factory) => s_factory = factory; /// /// 创建当前程序集的测试注册表;没有生成工厂时返回空注册表。 /// /// 新创建的测试和基准注册表。 public static XfeRegistry Create() => s_factory?.Invoke() ?? new XfeRegistry(); } /// /// 为生成代码提供测试类、基准类和 Fixture 的构造及释放支持。 /// public static class XfeObjectFactory { private static readonly AsyncLocal?> s_fixtures = new(); private static readonly AsyncLocal s_activator = new(); internal static IDisposable UseActivator(ITestActivator? activator) { var previous = s_activator.Value; s_activator.Value = activator; return new ActivatorScope(previous); } internal static IDisposable BeginFixtureScope(out IReadOnlyCollection fixtures) { var previous = s_fixtures.Value; var current = new Dictionary(); s_fixtures.Value = current; fixtures = current.Values; return new FixtureScope(previous); } /// /// 创建指定类型,优先使用已注册的 ,然后匹配构造参数或注入 Fixture。 /// /// 要创建的测试、基准或 Fixture 类型。 /// 显式传递给构造函数的参数。 /// 创建的实例;静态类返回 /// 没有构造函数能够接收给定参数,且无法使用 Fixture 构造。 public static object? Create(Type type, object?[] arguments) { if (type.IsAbstract && type.IsSealed) return null; if (arguments.Length == 0 && s_activator.Value?.CreateInstance(type) is { } activated) return activated; var constructors = type.GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); foreach (var constructor in constructors) { var parameters = constructor.GetParameters(); if (parameters.Length != arguments.Length) continue; var compatible = true; for (var index = 0; index < parameters.Length; index++) { var value = arguments[index]; if (value is null ? parameters[index].ParameterType.IsValueType && Nullable.GetUnderlyingType(parameters[index].ParameterType) is null : !parameters[index].ParameterType.IsInstanceOfType(value)) { compatible = false; break; } } if (compatible) return constructor.Invoke(arguments); } if (arguments.Length == 0) { var fixtureConstructor = constructors.OrderByDescending(static constructor => constructor.GetParameters().Length).FirstOrDefault(); if (fixtureConstructor is not null) { var fixtureArguments = fixtureConstructor.GetParameters().Select(parameter => GetOrCreateFixture(parameter.ParameterType)).ToArray(); return fixtureConstructor.Invoke(fixtureArguments); } } throw new MissingMethodException($"No constructor on {type.FullName} accepts {arguments.Length} supplied argument(s)."); } /// /// 使用已注册激活器或标准释放接口异步释放实例。 /// /// 要释放的对象;静态测试可传入 。 /// 表示释放完成的值任务。 public static async ValueTask DisposeAsync(object? instance) { if (s_activator.Value is { } activator) { await activator.DisposeAsync(instance).ConfigureAwait(false); return; } if (instance is IAsyncDisposable asyncDisposable) await asyncDisposable.DisposeAsync().ConfigureAwait(false); else if (instance is IDisposable disposable) disposable.Dispose(); } private static object GetOrCreateFixture(Type type) { var fixtures = s_fixtures.Value ?? throw new InvalidOperationException("Fixture construction is only available inside an active test collection."); if (fixtures.TryGetValue(type, out var fixture)) return fixture; fixture = s_activator.Value?.CreateInstance(type) ?? Activator.CreateInstance(type, true) ?? throw new InvalidOperationException($"Could not construct fixture {type.FullName}."); fixtures.Add(type, fixture); return fixture; } private sealed class FixtureScope(Dictionary? previous) : IDisposable { public void Dispose() => s_fixtures.Value = previous; } private sealed class ActivatorScope(ITestActivator? previous) : IDisposable { public void Dispose() => s_activator.Value = previous; } } /// /// 为生成代码解析 指定的静态数据成员。 /// public static class XfeMemberData { /// /// 读取静态字段、属性或无参方法,并把数据源规范化为参数数组序列。 /// /// 声明数据成员的类型。 /// 静态数据成员名称。 /// 测试参数行序列。 /// 找不到指定的静态成员。 /// 成员值既不是 也不是可枚举数据。 public static IEnumerable Get(Type sourceType, string memberName) { const System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic; object? value = sourceType.GetProperty(memberName, flags)?.GetValue(null) ?? sourceType.GetField(memberName, flags)?.GetValue(null) ?? sourceType.GetMethod(memberName, flags, null, Type.EmptyTypes, null)?.Invoke(null, null) ?? throw new MissingMemberException(sourceType.FullName, memberName); if (value is ITestCaseDataSource dataSource) { foreach (var row in dataSource.GetData()) yield return row; yield break; } if (value is not System.Collections.IEnumerable rows) throw new InvalidOperationException($"Member data source {sourceType.FullName}.{memberName} must implement IEnumerable."); foreach (var row in rows) { if (row is object?[] array) yield return array; else if (row is System.Runtime.CompilerServices.ITuple tuple) { var values = new object?[tuple.Length]; for (var index = 0; index < tuple.Length; index++) values[index] = tuple[index]; yield return values; } else yield return [row]; } } } /// /// 为生成代码把 的值应用到基准实例成员。 /// public static class XfeParameterBinder { /// /// 设置基准实例上的字段或属性值。 /// /// 包含目标成员的基准实例。 /// 要设置的字段或属性名称。 /// 要应用的参数值。 /// /// 实例类型上不存在指定字段或属性。 public static void Set(object? instance, string memberName, object? value) { if (instance is null) throw new InvalidOperationException($"Cannot set benchmark parameter {memberName} on a static fixture."); const System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic; var type = instance.GetType(); if (type.GetProperty(memberName, flags) is { } property) { property.SetValue(instance, value); return; } if (type.GetField(memberName, flags) is { } field) { field.SetValue(instance, value); return; } throw new MissingMemberException(type.FullName, memberName); } }