diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs index d914105015..5f245da83d 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; using UTF = Microsoft.VisualStudio.TestTools.UnitTesting; @@ -37,22 +38,30 @@ public class TestMethodInfo : ITestMethod internal TestMethodInfo( MethodInfo testMethod, TestClassInfo parent, - TestMethodOptions testMethodOptions) + ITestContext testContext) { DebugEx.Assert(testMethod != null, "TestMethod should not be null"); DebugEx.Assert(parent != null, "Parent should not be null"); TestMethod = testMethod; Parent = parent; - TestMethodOptions = testMethodOptions; + TestContext = testContext; ExpectedException = ResolveExpectedException(); RetryAttribute = GetRetryAttribute(); + TimeoutInfo = GetTestTimeout(); + Executor = GetTestMethodAttribute(); } + internal TimeoutInfo TimeoutInfo { get; } + + internal TestMethodAttribute Executor { get; } + + internal ITestContext TestContext { get; } + /// /// Gets a value indicating whether timeout is set. /// - public bool IsTimeoutSet => TestMethodOptions.TimeoutInfo.Timeout != TimeoutWhenNotSet; + public bool IsTimeoutSet => TimeoutInfo.Timeout != TimeoutWhenNotSet; /// /// Gets the reason why the test is not runnable. @@ -86,11 +95,6 @@ internal TestMethodInfo( /// internal TestClassInfo Parent { get; } - /// - /// Gets the options for the test method in this environment. - /// - internal TestMethodOptions TestMethodOptions { get; } - internal ExpectedExceptionBaseAttribute? ExpectedException { get; set; /*set for testing only*/ } internal RetryBaseAttribute? RetryAttribute { get; } @@ -116,7 +120,7 @@ public virtual TestResult Invoke(object?[]? arguments) // check if arguments are set for data driven tests arguments ??= Arguments; - using LogMessageListener listener = new(TestMethodOptions.CaptureDebugTraces); + using LogMessageListener listener = new(MSTestSettings.CurrentSettings.CaptureDebugTraces); watch.Start(); try { @@ -133,8 +137,8 @@ public virtual TestResult Invoke(object?[]? arguments) result.DebugTrace = listener.GetAndClearDebugTrace(); result.LogOutput = listener.GetAndClearStandardOutput(); result.LogError = listener.GetAndClearStandardError(); - result.TestContextMessages = TestMethodOptions.TestContext?.GetAndClearDiagnosticMessages(); - result.ResultFiles = TestMethodOptions.TestContext?.GetResultFiles(); + result.TestContextMessages = TestContext?.GetAndClearDiagnosticMessages(); + result.ResultFiles = TestContext?.GetResultFiles(); } } @@ -218,6 +222,43 @@ public virtual TestResult Invoke(object?[]? arguments) return newParameters; } + /// + /// Gets the test timeout for the test method. + /// + /// The timeout value if defined in milliseconds. 0 if not defined. + private TimeoutInfo GetTestTimeout() + { + DebugEx.Assert(TestMethod != null, "TestMethod should be non-null"); + TimeoutAttribute? timeoutAttribute = ReflectHelper.Instance.GetFirstNonDerivedAttributeOrDefault(TestMethod, inherit: false); + if (timeoutAttribute is null) + { + return TimeoutInfo.FromTestTimeoutSettings(); + } + + if (!timeoutAttribute.HasCorrectTimeout) + { + string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, TestMethod.DeclaringType!.FullName, TestMethod.Name); + throw new TypeInspectionException(message); + } + + return TimeoutInfo.FromTimeoutAttribute(timeoutAttribute); + } + + /// + /// Provides the Test Method Extension Attribute of the TestClass. + /// + /// Test Method Attribute. + 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.GetFirstDerivedAttributeOrDefault(TestMethod, inherit: false)!; + + // 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; + } + /// /// Resolves the expected exception attribute. The function will try to /// get all the expected exception attributes defined for a testMethod. @@ -354,7 +395,7 @@ private TestResult ExecuteInternal(object?[]? arguments, CancellationTokenSource // Expected Exception was thrown, so Pass the test result.Outcome = UTF.UnitTestOutcome.Passed; } - else if (realException.IsOperationCanceledExceptionFromToken(TestMethodOptions.TestContext!.Context.CancellationTokenSource.Token)) + else if (realException.IsOperationCanceledExceptionFromToken(TestContext!.Context.CancellationTokenSource.Token)) { result.Outcome = UTF.UnitTestOutcome.Timeout; result.TestFailureException = new TestFailedException( @@ -364,7 +405,7 @@ private TestResult ExecuteInternal(object?[]? arguments, CancellationTokenSource CultureInfo.InvariantCulture, Resource.Execution_Test_Timeout, TestMethodName, - TestMethodOptions.TimeoutInfo.Timeout) + TimeoutInfo.Timeout) : string.Format( CultureInfo.InvariantCulture, Resource.Execution_Test_Cancelled, @@ -403,7 +444,7 @@ private TestResult ExecuteInternal(object?[]? arguments, CancellationTokenSource } // Update TestContext with outcome and exception so it can be used in the cleanup logic. - if (TestMethodOptions.TestContext is { } testContext) + if (TestContext is { } testContext) { testContext.SetOutcome(result.Outcome); // Uwnrap the exception if it's a TestFailedException @@ -579,12 +620,12 @@ private void RunTestCleanupMethod(TestResult result, CancellationTokenSource? ti try { // Reset the cancellation token source to avoid cancellation of cleanup methods because of the init or test method cancellation. - TestMethodOptions.TestContext!.Context.CancellationTokenSource = new CancellationTokenSource(); + TestContext!.Context.CancellationTokenSource = new CancellationTokenSource(); // If we are running with a method timeout, we need to cancel the cleanup when the overall timeout expires. If it already expired, nothing to do. if (timeoutTokenSource is { IsCancellationRequested: false }) { - timeoutTokenSource?.Token.Register(TestMethodOptions.TestContext.Context.CancellationTokenSource.Cancel); + timeoutTokenSource?.Token.Register(TestContext.Context.CancellationTokenSource.Cancel); } // Test cleanups are called in the order of discovery @@ -786,7 +827,7 @@ private bool RunTestInitializeMethod(object classInstance, TestResult result, Ca return FixtureMethodRunner.RunWithTimeoutAndCancellation( () => methodInfo.InvokeAsSynchronousTask(classInstance, null), - TestMethodOptions.TestContext!.Context.CancellationTokenSource, + TestContext!.Context.CancellationTokenSource, timeout, methodInfo, new InstanceExecutionContextScope(classInstance, Parent.ClassType), @@ -794,7 +835,7 @@ private bool RunTestInitializeMethod(object classInstance, TestResult result, Ca Resource.TestInitializeTimedOut, timeoutTokenSource is null ? null - : (timeoutTokenSource, TestMethodOptions.TimeoutInfo.Timeout)); + : (timeoutTokenSource, TimeoutInfo.Timeout)); } private TestFailedException? InvokeCleanupMethod(MethodInfo methodInfo, object classInstance, int remainingCleanupCount, CancellationTokenSource? timeoutTokenSource) @@ -807,7 +848,7 @@ timeoutTokenSource is null return FixtureMethodRunner.RunWithTimeoutAndCancellation( () => methodInfo.InvokeAsSynchronousTask(classInstance, null), - TestMethodOptions.TestContext!.Context.CancellationTokenSource, + TestContext!.Context.CancellationTokenSource, timeout, methodInfo, new InstanceExecutionContextScope(classInstance, Parent.ClassType, remainingCleanupCount), @@ -815,7 +856,7 @@ timeoutTokenSource is null Resource.TestCleanupTimedOut, timeoutTokenSource is null ? null - : (timeoutTokenSource, TestMethodOptions.TimeoutInfo.Timeout)); + : (timeoutTokenSource, TimeoutInfo.Timeout)); } /// @@ -840,7 +881,7 @@ private bool SetTestContext(object classInstance, TestResult result) { if (Parent.TestContextProperty != null && Parent.TestContextProperty.CanWrite) { - Parent.TestContextProperty.SetValue(classInstance, TestMethodOptions.TestContext); + Parent.TestContextProperty.SetValue(classInstance, TestContext); } return true; @@ -878,7 +919,7 @@ private bool SetTestContext(object classInstance, TestResult result) object? classInstance = null; try { - classInstance = Parent.Constructor.Invoke(Parent.IsParameterlessConstructor ? null : [TestMethodOptions.TestContext]); + classInstance = Parent.Constructor.Invoke(Parent.IsParameterlessConstructor ? null : [TestContext]); } catch (Exception ex) { @@ -901,10 +942,10 @@ private bool SetTestContext(object classInstance, TestResult result) // It also seems that in rare cases the ex can be null. Exception realException = ex.GetRealException(); - if (realException.IsOperationCanceledExceptionFromToken(TestMethodOptions.TestContext!.Context.CancellationTokenSource.Token)) + if (realException.IsOperationCanceledExceptionFromToken(TestContext!.Context.CancellationTokenSource.Token)) { result.Outcome = UTF.UnitTestOutcome.Timeout; - result.TestFailureException = new TestFailedException(UTFUnitTestOutcome.Timeout, string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TestMethodOptions.TimeoutInfo.Timeout)); + result.TestFailureException = new TestFailedException(UTFUnitTestOutcome.Timeout, string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TimeoutInfo.Timeout)); } else { @@ -935,13 +976,13 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments) { DebugEx.Assert(IsTimeoutSet, "Timeout should be set"); - if (TestMethodOptions.TimeoutInfo.CooperativeCancellation) + if (TimeoutInfo.CooperativeCancellation) { CancellationTokenSource? timeoutTokenSource = null; try { - timeoutTokenSource = new(TestMethodOptions.TimeoutInfo.Timeout); - timeoutTokenSource.Token.Register(TestMethodOptions.TestContext!.Context.CancellationTokenSource.Cancel); + timeoutTokenSource = new(TimeoutInfo.Timeout); + timeoutTokenSource.Token.Register(TestContext!.Context.CancellationTokenSource.Cancel); if (timeoutTokenSource.Token.IsCancellationRequested) { return new() @@ -949,7 +990,7 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments) Outcome = UTF.UnitTestOutcome.Timeout, TestFailureException = new TestFailedException( UTFUnitTestOutcome.Timeout, - string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TestMethodOptions.TimeoutInfo.Timeout)), + string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TimeoutInfo.Timeout)), }; } @@ -967,7 +1008,7 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments) TestFailureException = new TestFailedException( UTFUnitTestOutcome.Timeout, timeoutTokenSource.Token.IsCancellationRequested - ? string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TestMethodOptions.TimeoutInfo.Timeout) + ? string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TimeoutInfo.Timeout) : string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Cancelled, TestMethodName)), }; } @@ -982,7 +1023,7 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments) TestResult? result = null; Exception? failure = null; - if (PlatformServiceProvider.Instance.ThreadOperations.Execute(ExecuteAsyncAction, TestMethodOptions.TimeoutInfo.Timeout, TestMethodOptions.TestContext!.Context.CancellationTokenSource.Token)) + if (PlatformServiceProvider.Instance.ThreadOperations.Execute(ExecuteAsyncAction, TimeoutInfo.Timeout, TestContext!.Context.CancellationTokenSource.Token)) { if (failure != null) { @@ -998,15 +1039,15 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments) } // Timed out or canceled - string errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TestMethodOptions.TimeoutInfo.Timeout); - if (TestMethodOptions.TestContext.Context.CancellationTokenSource.IsCancellationRequested) + string errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Timeout, TestMethodName, TimeoutInfo.Timeout); + if (TestContext.Context.CancellationTokenSource.IsCancellationRequested) { errorMessage = string.Format(CultureInfo.CurrentCulture, Resource.Execution_Test_Cancelled, TestMethodName); } else { // Cancel the token source as test has timed out - TestMethodOptions.TestContext.Context.CancellationTokenSource.Cancel(); + TestContext.Context.CancellationTokenSource.Cancel(); } TestResult timeoutResult = new() { Outcome = UTF.UnitTestOutcome.Timeout, TestFailureException = new TestFailedException(UTFUnitTestOutcome.Timeout, errorMessage) }; diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs index c2a5f1738e..2258f725f8 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs @@ -64,7 +64,7 @@ public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, IT internal TestResult[] Execute(string initializationLogs, string initializationErrorLogs, string initializationTrace, string initializationTestContextMessages) { bool isSTATestClass = AttributeComparer.IsDerived(_testMethodInfo.Parent.ClassAttribute); - bool isSTATestMethod = AttributeComparer.IsDerived(_testMethodInfo.TestMethodOptions.Executor); + bool isSTATestMethod = AttributeComparer.IsDerived(_testMethodInfo.Executor); bool isSTARequested = isSTATestClass || isSTATestMethod; bool isWindowsOS = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); if (isSTARequested && isWindowsOS && Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) @@ -161,7 +161,7 @@ internal TestResult[] RunTestMethod() DebugEx.Assert(_testMethodInfo.TestMethod != null, "Test method should not be null."); List results = []; - if (_testMethodInfo.TestMethodOptions.Executor == null) + if (_testMethodInfo.Executor == null) { throw ApplicationStateGuard.Unreachable(); } @@ -427,7 +427,7 @@ private TestResult[] ExecuteTest(TestMethodInfo testMethodInfo) { try { - return _testMethodInfo.TestMethodOptions.Executor.Execute(testMethodInfo); + return _testMethodInfo.Executor.Execute(testMethodInfo); } catch (Exception ex) { @@ -439,7 +439,7 @@ private TestResult[] ExecuteTest(TestMethodInfo testMethodInfo) string.Format( CultureInfo.CurrentCulture, Resource.UTA_ExecuteThrewException, - _testMethodInfo.TestMethodOptions.Executor.GetType().FullName, + _testMethodInfo.Executor.GetType().FullName, ex.ToString()), ex), }, diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs index 2b13c8384d..9478bd74c7 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs @@ -80,7 +80,7 @@ public IEnumerable AssemblyInfoListWithExecutableCleanupMethod /// Get the test method info corresponding to the parameter test Element. /// /// The . - public TestMethodInfo? GetTestMethodInfo(TestMethod testMethod, ITestContext testContext, bool captureDebugTraces) + public TestMethodInfo? GetTestMethodInfo(TestMethod testMethod, ITestContext testContext) { Guard.NotNull(testMethod); Guard.NotNull(testContext); @@ -96,7 +96,7 @@ public IEnumerable AssemblyInfoListWithExecutableCleanupMethod } // Get the testMethod - return ResolveTestMethodInfo(testMethod, testClassInfo, testContext, captureDebugTraces); + return ResolveTestMethodInfo(testMethod, testClassInfo, testContext); } /// @@ -678,16 +678,14 @@ private void UpdateInfoIfTestInitializeOrCleanupMethod( /// /// The TestMethodInfo for the given test method. Null if the test method could not be found. /// - private TestMethodInfo ResolveTestMethodInfo(TestMethod testMethod, TestClassInfo testClassInfo, ITestContext testContext, bool captureDebugTraces) + private TestMethodInfo ResolveTestMethodInfo(TestMethod testMethod, TestClassInfo testClassInfo, ITestContext testContext) { DebugEx.Assert(testMethod != null, "testMethod is Null"); DebugEx.Assert(testClassInfo != null, "testClassInfo is Null"); MethodInfo methodInfo = GetMethodInfoForTestMethod(testMethod, testClassInfo); - TimeoutInfo timeout = GetTestTimeout(methodInfo, testMethod); - var testMethodOptions = new TestMethodOptions(timeout, testContext, captureDebugTraces, GetTestMethodAttribute(methodInfo, testClassInfo)); - var testMethodInfo = new TestMethodInfo(methodInfo, testClassInfo, testMethodOptions); + var testMethodInfo = new TestMethodInfo(methodInfo, testClassInfo, testContext); SetCustomProperties(testMethodInfo, testContext); @@ -699,26 +697,7 @@ private TestMethodInfo ResolveTestMethodInfoForDiscovery(TestMethod testMethod, MethodInfo methodInfo = GetMethodInfoForTestMethod(testMethod, testClassInfo); // Let's build a fake options type as it won't be used. - return new TestMethodInfo(methodInfo, testClassInfo, new(TimeoutInfo.FromTimeout(-1), null, false, null!)); - } - - /// - /// Provides the Test Method Extension Attribute of the TestClass. - /// - /// The method info. - /// The test class info. - /// Test Method Attribute. - private TestMethodAttribute GetTestMethodAttribute(MethodInfo methodInfo, TestClassInfo testClassInfo) - { - // Get the derived TestMethod attribute from reflection. - // It should be non-null as it was already validated by IsValidTestMethod. - TestMethodAttribute testMethodAttribute = _reflectionHelper.GetFirstDerivedAttributeOrDefault(methodInfo, inherit: false)!; - - // 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 - testMethodAttribute = testClassInfo.ClassAttribute.GetTestMethodAttribute(testMethodAttribute) ?? testMethodAttribute; - - return testMethodAttribute; + return new TestMethodInfo(methodInfo, testClassInfo, null!); } /// @@ -798,31 +777,6 @@ private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassIn return methods.FirstOrDefault(method => method.DeclaringType!.FullName == testMethod.DeclaringClassFullName); } - /// - /// Gets the test timeout for the parameter test method. - /// - /// The method Info. - /// The test Method. - /// The timeout value if defined in milliseconds. 0 if not defined. - private TimeoutInfo GetTestTimeout(MethodInfo methodInfo, TestMethod testMethod) - { - DebugEx.Assert(methodInfo != null, "TestMethod should be non-null"); - TimeoutAttribute? timeoutAttribute = _reflectionHelper.GetFirstNonDerivedAttributeOrDefault(methodInfo, inherit: false); - - if (timeoutAttribute != null) - { - if (!timeoutAttribute.HasCorrectTimeout) - { - string message = string.Format(CultureInfo.CurrentCulture, Resource.UTA_ErrorInvalidTimeout, testMethod.FullClassName, testMethod.Name); - throw new TypeInspectionException(message); - } - - return TimeoutInfo.FromTimeoutAttribute(timeoutAttribute); - } - - return TimeoutInfo.FromTestTimeoutSettings(); - } - /// /// Set custom properties. /// diff --git a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs index 99c46cdded..d1dcf28a85 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs @@ -148,8 +148,7 @@ internal async Task RunSingleTestAsync(TestMethod testMethod, IDic // Get the testMethod TestMethodInfo? testMethodInfo = _typeCache.GetTestMethodInfo( testMethod, - testContextForTestExecution, - MSTestSettings.CurrentSettings.CaptureDebugTraces); + testContextForTestExecution); TestResult[] result; if (!IsTestMethodRunnable(testMethod, testMethodInfo, out TestResult[]? notRunnableResult)) diff --git a/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethodOptions.cs b/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethodOptions.cs deleted file mode 100644 index 8b1804ce83..0000000000 --- a/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethodOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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.Execution; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; - -/// -/// A facade service for options passed to a test method. -/// -internal sealed record TestMethodOptions(TimeoutInfo TimeoutInfo, ITestContext? TestContext, bool CaptureDebugTraces, TestMethodAttribute Executor);