Skip to content
Draft
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 number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;

#pragma warning disable CA1852 // Seal internal types - This class is inherited in tests.
internal partial class TestMethodInfo
{
internal void SetArguments(object?[]? arguments) => Arguments = arguments == null ? null : ResolveArguments(arguments);

internal object?[] ResolveArguments(object?[] arguments)
{
ParameterInfo[] parametersInfo = MethodInfo.GetParameters();
int requiredParameterCount = 0;
bool hasParamsValue = false;
object? paramsValues = null;
foreach (ParameterInfo parameter in parametersInfo)
{
// If this is a params array parameter, create an instance to
// populate with any extra values provided. Don't increment
// required parameter count - params arguments are not actually required
if (parameter.GetCustomAttribute<ParamArrayAttribute>() != null)
{
hasParamsValue = true;
break;
}

// Count required parameters from method
if (!parameter.IsOptional)
{
requiredParameterCount++;
}
}

// If all the parameters are required, we have fewer arguments
// supplied than required, or more arguments than the method takes
// and it doesn't have a params parameter don't try and resolve anything
if (requiredParameterCount == parametersInfo.Length ||
arguments.Length < requiredParameterCount ||
(!hasParamsValue && arguments.Length > parametersInfo.Length))
{
return arguments;
}

object?[] newParameters = new object[parametersInfo.Length];
for (int argumentIndex = 0; argumentIndex < arguments.Length; argumentIndex++)
{
// We have reached the end of the regular parameters and any additional
// values will go in a params array
if (argumentIndex >= parametersInfo.Length - 1 && hasParamsValue)
{
// If this is the params parameter, instantiate a new object of that type
if (argumentIndex == parametersInfo.Length - 1)
{
paramsValues = PlatformServiceProvider.Instance.ReflectionOperations.CreateInstance(parametersInfo[argumentIndex].ParameterType, [arguments.Length - argumentIndex]);
newParameters[argumentIndex] = paramsValues;
}

// The params parameters is an array but the type is not known
// set the values as a generic array
if (paramsValues is Array paramsArray)
{
paramsArray.SetValue(arguments[argumentIndex], argumentIndex - (parametersInfo.Length - 1));
}
}
else
{
newParameters[argumentIndex] = arguments[argumentIndex];
}
}

// If arguments supplied are less than total possible arguments set
// the values supplied to the default values for those parameters
for (int parameterNotProvidedIndex = arguments.Length; parameterNotProvidedIndex < parametersInfo.Length; parameterNotProvidedIndex++)
{
// If this is the params parameters, set it to an empty
// array of that type as DefaultValue is DBNull
newParameters[parameterNotProvidedIndex] = hasParamsValue && parameterNotProvidedIndex == parametersInfo.Length - 1
? PlatformServiceProvider.Instance.ReflectionOperations.CreateInstance(parametersInfo[parameterNotProvidedIndex].ParameterType, [0])
: parametersInfo[parameterNotProvidedIndex].DefaultValue;
}

return newParameters;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution;

#pragma warning disable CA1852 // Seal internal types - This class is inherited in tests.
internal partial class TestMethodInfo
{
/// <summary>
/// Handles the exception that is thrown by a test method. The exception can either
/// be expected or not expected.
/// </summary>
/// <param name="ex">Exception that was thrown.</param>
/// <param name="realException">Real exception thrown by the test method.</param>
/// <param name="className">The class name.</param>
/// <param name="methodName">The method name.</param>
/// <returns>Test framework exception with details.</returns>
private TestFailedException HandleMethodException(Exception ex, Exception realException, string className, string methodName)
{
DebugEx.Assert(ex != null, "exception should not be null.");

string errorMessage;
if (ex is TargetInvocationException && ex.InnerException == null)
{
errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.UTA_FailedToGetTestMethodException, className, methodName);
return new TestFailedException(UnitTestOutcome.Error, errorMessage);
}

if (ex is TestFailedException testFailedException)
{
return testFailedException;
}

// If we are in hot reload context and the exception is a MissingMethodException and the first line of the stack
// trace contains the method name then it's likely that the current method was removed and the test is failing.
// For cases where the content of the test would throw a MissingMethodException, the first line of the stack trace
// would not be the test method name, so we can safely assume this is a proper test failure.
if (ex is MissingMethodException missingMethodException
&& RuntimeContext.IsHotReloadEnabled
&& missingMethodException.StackTrace?.IndexOf(Environment.NewLine, StringComparison.Ordinal) is { } lineReturnIndex
&& lineReturnIndex >= 0
#pragma warning disable IDE0057 // Use range operator
&& missingMethodException.StackTrace.Substring(0, lineReturnIndex).Contains($"{className}.{methodName}"))
#pragma warning restore IDE0057 // Use range operator
{
return new TestFailedException(UnitTestOutcome.NotFound, missingMethodException.Message, missingMethodException);
}

// Get the real exception thrown by the test method
if (realException.TryGetUnitTestAssertException(out UnitTestOutcome outcome, out string? exceptionMessage, out StackTraceInformation? exceptionStackTraceInfo))
{
return new TestFailedException(outcome, exceptionMessage, exceptionStackTraceInfo, realException);
}

errorMessage = _classInstance is null
? string.Format(
CultureInfo.CurrentCulture,
Resource.UTA_InstanceCreationError,
TestClassName,
realException.GetFormattedExceptionMessage())
: string.Format(
CultureInfo.CurrentCulture,
Resource.UTA_TestMethodThrows,
className,
methodName,
realException.GetFormattedExceptionMessage());

// Handle special case of UI objects in TestMethod to suggest UITestMethod
if (realException.HResult == -2147417842)
{
errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.UTA_WrongThread, errorMessage);
}

StackTraceInformation? stackTrace = null;

// For ThreadAbortException (that can be thrown only by aborting a thread as there's no public constructor)
// there's no inner exception and exception itself contains reflection-related stack trace
// (_RuntimeMethodHandle.InvokeMethodFast <- _RuntimeMethodHandle.Invoke <- UnitTestExecuter.RunTestMethod)
// which has no meaningful info for the user. Thus, we do not show call stack for ThreadAbortException.
if (realException.GetType().Name != "ThreadAbortException")
{
stackTrace = realException.GetStackTraceInformation();
}

return new TestFailedException(UnitTestOutcome.Failed, errorMessage, stackTrace, realException);
}
}
Loading