diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs index 6515ea57fa..26530800e4 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs @@ -144,7 +144,7 @@ private async Task ExecuteInternalAsync(object?[]? arguments, Cancel { if (_executionContext is null) { - Task? invokeResult = MethodInfo.GetInvokeResultAsync(_classInstance, arguments); + Task? invokeResult = MethodInfo.GetInvokeResultWithParametersAsync(_classInstance, ParameterTypes, arguments); if (invokeResult is not null) { await invokeResult.ConfigureAwait(true); @@ -161,7 +161,7 @@ private async Task ExecuteInternalAsync(object?[]? arguments, Cancel #if NETFRAMEWORK CallContext.HostContext = _hostContext; #endif - Task? invokeResult = MethodInfo.GetInvokeResultAsync(_classInstance, arguments); + Task? invokeResult = MethodInfo.GetInvokeResultWithParametersAsync(_classInstance, ParameterTypes, arguments); if (invokeResult is not null) { await invokeResult.ConfigureAwait(false); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index 2349db7f15..6456ba31bf 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -117,12 +117,17 @@ private static bool IsValueTask(this MethodInfo method) } internal static Task? GetInvokeResultAsync(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments) - { - ParameterInfo[]? methodParameters = methodInfo.GetParameters(); + // MethodInfo.GetParameters() allocates a fresh ParameterInfo[] on every call (CLR safety + // guarantee). Non-hot-path callers go through this thin wrapper; the hot path (data-driven + // test invocation) calls GetInvokeResultWithParametersAsync directly with an already-cached + // array to avoid the per-invocation allocation. + => methodInfo.GetInvokeResultWithParametersAsync(classInstance, methodInfo.GetParameters(), arguments); + internal static Task? GetInvokeResultWithParametersAsync(this MethodInfo methodInfo, object? classInstance, ParameterInfo[] methodParameters, params object?[]? arguments) + { // check if test method expected parameter values but no test data was provided, // throw error with appropriate message. - if (methodParameters is { Length: > 0 } && arguments == null) + if (methodParameters is { Length: > 0 } && arguments is null) { throw new TestFailedException( UnitTestOutcome.Error, @@ -150,7 +155,7 @@ private static bool IsValueTask(this MethodInfo method) // IndexOutOfRangeException. Mirror the reflection path's friendly diagnostic instead. We // only validate the count here — a type mismatch surfaces as the test's own exception and // must not be reinterpreted as an arguments error. - int expectedParameterCount = methodParameters?.Length ?? 0; + int expectedParameterCount = methodParameters.Length; int providedArgumentCount = sourceGeneratedArguments?.Length ?? 0; if (expectedParameterCount != providedArgumentCount) { @@ -162,7 +167,7 @@ private static bool IsValueTask(this MethodInfo method) methodInfo.DeclaringType!.FullName, methodInfo.Name, expectedParameterCount, - string.Join(", ", methodParameters?.Select(p => p.ParameterType.Name) ?? []), + string.Join(", ", methodParameters.Select(p => p.ParameterType.Name)), providedArgumentCount, string.Join(", ", sourceGeneratedArguments?.Select(a => a?.GetType().Name ?? "null") ?? []))); } @@ -184,7 +189,7 @@ private static bool IsValueTask(this MethodInfo method) } else { - int methodParametersLengthOrZero = methodParameters?.Length ?? 0; + int methodParametersLengthOrZero = methodParameters.Length; int argumentsLengthOrZero = arguments?.Length ?? 0; #if WINDOWS_UWP @@ -203,7 +208,7 @@ private static bool IsValueTask(this MethodInfo method) { if (methodInfo.IsGenericMethod) { - methodInfo = ConstructGenericMethod(methodInfo, arguments); + methodInfo = ConstructGenericMethod(methodInfo, methodParameters, arguments); } invokeResult = methodInfo.Invoke(classInstance, arguments); @@ -218,7 +223,7 @@ private static bool IsValueTask(this MethodInfo method) methodInfo.DeclaringType!.FullName, methodInfo.Name, methodParametersLengthOrZero, - string.Join(", ", methodParameters?.Select(p => p.ParameterType.Name) ?? []), + string.Join(", ", methodParameters.Select(p => p.ParameterType.Name)), argumentsLengthOrZero, string.Join(", ", arguments?.Select(a => a?.GetType().Name ?? "null") ?? [])), ex); } @@ -285,7 +290,7 @@ private static void InferGenerics(Type parameterType, Type argumentType, List<(T // public void TestMethod(T2 p0, T1, p1) { } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:Call to 'System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.", Justification = "Generic test methods with substituted type arguments are part of MSTest's reflection-mode adapter. Native AOT support relies on MSTest source-generated metadata, not on this code path.")] [UnconditionalSuppressMessage("Aot", "IL3050:Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT", Justification = "Generic test methods with substituted type arguments are part of MSTest's reflection-mode adapter. Native AOT support relies on MSTest source-generated metadata, not on this code path.")] - private static MethodInfo ConstructGenericMethod(MethodInfo methodInfo, object?[]? arguments) + private static MethodInfo ConstructGenericMethod(MethodInfo methodInfo, ParameterInfo[] parameters, object?[]? arguments) { DebugEx.Assert(methodInfo.IsGenericMethod, "ConstructGenericMethod should only be called for a generic method."); @@ -304,7 +309,6 @@ private static MethodInfo ConstructGenericMethod(MethodInfo methodInfo, object?[ map[i] = (genericDefinitions[i], null); } - ParameterInfo[] parameters = methodInfo.GetParameters(); for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType;