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,89 @@
// 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.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>
/// Gets the test timeout for the test method.
/// </summary>
/// <returns> The timeout value if defined in milliseconds. 0 if not defined. </returns>
private TimeoutInfo GetTestTimeout()
{
DebugEx.Assert(MethodInfo != null, "TestMethod should be non-null");
TimeoutAttribute? timeoutAttribute = ReflectHelper.Instance.GetFirstAttributeOrDefault<TimeoutAttribute>(MethodInfo);
if (timeoutAttribute is null)
{
return TimeoutInfo.FromTestTimeoutSettings();
}

if (!timeoutAttribute.HasCorrectTimeout)
{
string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, MethodInfo.DeclaringType!.FullName, MethodInfo.Name);
throw new TypeInspectionException(message);
}

return TimeoutInfo.FromTimeoutAttribute(timeoutAttribute);
}

/// <summary>
/// Provides the Test Method Extension Attribute of the TestClass.
/// </summary>
/// <returns>Test Method Attribute.</returns>
private TestMethodAttribute GetTestMethodAttribute()
{
// Get the derived TestMethod attribute from reflection.
// It should be non-null as it was already validated by IsValidTestMethod.
TestMethodAttribute testMethodAttribute = ReflectHelper.Instance.GetSingleAttributeOrDefault<TestMethodAttribute>(MethodInfo)!;

// Get the derived TestMethod attribute from Extended TestClass Attribute
// If the extended TestClass Attribute doesn't have extended TestMethod attribute then base class returns back the original testMethod Attribute
return Parent.ClassAttribute.GetTestMethodAttribute(testMethodAttribute) ?? testMethodAttribute;
}

/// <summary>
/// Gets the number of retries this test method should make in case of failure.
/// </summary>
/// <returns>
/// The number of retries, which is always greater than or equal to 1.
/// If RetryAttribute is not present, returns 1.
/// </returns>
private RetryBaseAttribute? GetRetryAttribute()
{
IEnumerable<RetryBaseAttribute> attributes = ReflectHelper.Instance.GetAttributes<RetryBaseAttribute>(MethodInfo);
using IEnumerator<RetryBaseAttribute> enumerator = attributes.GetEnumerator();
if (!enumerator.MoveNext())
{
return null;
}

RetryBaseAttribute attribute = enumerator.Current;

if (enumerator.MoveNext())
{
ThrowMultipleAttributesException(nameof(RetryBaseAttribute));
}

return attribute;
}

[DoesNotReturn]
private void ThrowMultipleAttributesException(string attributeName)
{
// Note: even if the given attribute has AllowMultiple = false, we can
// still reach here if a derived attribute authored by the user re-defines AttributeUsage
string errorMessage = string.Format(
CultureInfo.CurrentCulture,
Resource.UTA_MultipleAttributesOnTestMethod,
Parent.ClassType.FullName,
MethodInfo.Name,
attributeName);
throw new TypeInspectionException(errorMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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.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>
/// Sets the <see cref="TestContext"/> on <paramref name="classInstance"/>.
/// </summary>
/// <param name="classInstance">
/// Reference to instance of TestClass.
/// </param>
/// <param name="result">
/// Reference to instance of <see cref="TestResult"/>.
/// </param>
/// <returns>
/// True if there no exceptions during set context operation.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
private bool SetTestContext(object classInstance, TestResult result)
{
DebugEx.Assert(classInstance != null, "classInstance != null");
DebugEx.Assert(result != null, "result != null");

try
{
if (Parent.TestContextProperty != null && Parent.TestContextProperty.CanWrite)
{
Parent.TestContextProperty.SetValue(classInstance, TestContext);
}

return true;
}
catch (Exception ex)
{
Exception realException = ex.GetRealException();
string errorMessage = string.Format(
CultureInfo.CurrentCulture,
Resource.UTA_TestContextSetError,
TestClassName,
realException.GetFormattedExceptionMessage());

result.Outcome = UnitTestOutcome.Failed;
StackTraceInformation? stackTraceInfo = realException.GetStackTraceInformation();
result.TestFailureException = new TestFailedException(UnitTestOutcome.Failed, errorMessage, stackTraceInfo);
}

return false;
}

/// <summary>
/// Creates an instance of TestClass. The TestMethod is invoked on this instance.
/// </summary>
/// <returns>
/// An instance of the TestClass.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
private object? CreateTestClassInstance()
=> Parent.Constructor.Invoke(Parent.IsParameterlessConstructor ? null : [TestContext]);
}
Loading