Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -144,7 +144,7 @@ private async Task<TestResult> 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);
Expand All@@ -161,7 +161,7 @@ private async Task<TestResult> 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);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,
Expand DownExpand Up@@ -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)
{
Expand All@@ -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") ?? [])));
}
Expand All@@ -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
Expand All@@ -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);
Expand All@@ -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);
}
Expand DownExpand Up@@ -285,7 +290,7 @@ private static void InferGenerics(Type parameterType, Type argumentType, List<(T
// public void TestMethod<T1, T2>(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.");

Expand All@@ -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;
Expand Down