diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs index 875c198231..34aaa264cf 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TestAssemblyInfo.cs @@ -234,4 +234,58 @@ public void RunAssemblyInitialize(TestContext testContext) } } } + + /// + /// Calls the assembly cleanup method in a thread-safe. + /// + /// + /// It is a replacement for RunAssemblyCleanup but as we are in a bug-fix version, we do not want to touch + /// public API and so we introduced this method. + /// + internal void ExecuteAssemblyCleanup() + { + if (AssemblyCleanupMethod == null) + { + return; + } + + lock (_assemblyInfoExecuteSyncObject) + { + try + { + AssemblyCleanupMethod.InvokeAsSynchronousTask(null); + } + catch (Exception ex) + { + var realException = ex.InnerException ?? ex; + + string errorMessage; + + // special case AssertFailedException to trim off part of the stack trace + if (realException is AssertFailedException or AssertInconclusiveException) + { + errorMessage = realException.Message; + } + else + { + errorMessage = StackTraceHelper.GetExceptionMessage(realException); + } + + var exceptionStackTraceInfo = StackTraceHelper.GetStackTraceInformation(realException); + DebugEx.Assert(AssemblyCleanupMethod.DeclaringType?.Name is not null, "AssemblyCleanupMethod.DeclaringType.Name is null"); + + throw new TestFailedException( + UnitTestOutcome.Failed, + string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_AssemblyCleanupMethodWasUnsuccesful, + AssemblyCleanupMethod.DeclaringType.Name, + AssemblyCleanupMethod.Name, + errorMessage, + exceptionStackTraceInfo?.ErrorStackTrace), + exceptionStackTraceInfo, + realException); + } + } + } } diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs index 2f8d73d0e3..7fe9a419dc 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs @@ -410,4 +410,74 @@ public void RunClassInitialize(TestContext testContext) return null; } + + /// + /// Execute current and base class cleanups. + /// + /// + /// This is a replacement for RunClassCleanup but as we are on a bug fix version, we do not want to change + /// the public API, hence this method. + /// + internal void ExecuteClassCleanup() + { + if ((ClassCleanupMethod is null && BaseClassInitAndCleanupMethods.All(p => p.Item2 == null)) + || IsClassCleanupExecuted) + { + return; + } + + lock (_testClassExecuteSyncObject) + { + if (IsClassCleanupExecuted + || (!IsClassInitializeExecuted && ClassInitializeMethod is not null)) + { + return; + } + + MethodInfo? classCleanupMethod = null; + + try + { + classCleanupMethod = ClassCleanupMethod; + classCleanupMethod?.InvokeAsSynchronousTask(null); + var baseClassCleanupQueue = new Queue(BaseClassCleanupMethodsStack); + while (baseClassCleanupQueue.Count > 0) + { + classCleanupMethod = baseClassCleanupQueue.Dequeue(); + classCleanupMethod?.InvokeAsSynchronousTask(null); + } + + IsClassCleanupExecuted = true; + + return; + } + catch (Exception exception) + { + var realException = exception.InnerException ?? exception; + ClassCleanupException = realException; + + // special case AssertFailedException to trim off part of the stack trace + string errorMessage = realException is AssertFailedException or AssertInconclusiveException + ? realException.Message + : StackTraceHelper.GetExceptionMessage(realException); + + var exceptionStackTraceInfo = realException.TryGetStackTraceInformation(); + + var testFailedException = new TestFailedException( + ObjectModelUnitTestOutcome.Failed, + string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_ClassCleanupMethodWasUnsuccesful, + classCleanupMethod!.DeclaringType!.Name, + classCleanupMethod.Name, + errorMessage, + exceptionStackTraceInfo?.ErrorStackTrace), + exceptionStackTraceInfo, + realException); + ClassCleanupException = testFailedException; + + throw testFailedException; + } + } + } } diff --git a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs index 619dda79cf..0041b7497f 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs @@ -141,7 +141,7 @@ internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary - /// Runs the class cleanup method. - /// It returns any error information during the execution of the cleanup method. - /// - /// The . - internal RunCleanupResult? RunClassAndAssemblyCleanup() - { - // No cleanup methods to execute, then return. - var assemblyInfoCache = _typeCache.AssemblyInfoListWithExecutableCleanupMethods; - var classInfoCache = _typeCache.ClassInfoListWithExecutableCleanupMethods; - if (assemblyInfoCache.Length == 0 && classInfoCache.Length == 0) - { - return null; - } - - var result = new RunCleanupResult { Warnings = new List() }; - - using (var redirector = new LogMessageListener(MSTestSettings.CurrentSettings.CaptureDebugTraces)) - { - try - { - RunClassCleanupMethods(classInfoCache, result.Warnings); - RunAssemblyCleanup(assemblyInfoCache, result.Warnings); - } - finally - { - // Replacing the null character with a string.replace should work. - // If this does not work for a specific dotnet version a custom function doing the same needs to be put in place. - result.StandardOut = redirector.GetAndClearStandardOutput()?.Replace("\0", "\\0"); - result.StandardError = redirector.GetAndClearStandardError()?.Replace("\0", "\\0"); - result.DebugTrace = redirector.GetAndClearDebugTrace()?.Replace("\0", "\\0"); - } - } - - return result; - } - - private void RunCleanupsIfNeeded(ITestContext testContext, TestMethodInfo testMethodInfo, TestMethod testMethod, UnitTestResult[] results) + private void RunRequiredCleanups(ITestContext testContext, TestMethodInfo testMethodInfo, TestMethod testMethod, UnitTestResult[] results) { bool shouldRunClassCleanup = false; bool shouldRunClassAndAssemblyCleanup = false; _classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup, out shouldRunClassAndAssemblyCleanup); + using LogMessageListener logListener = new(MSTestSettings.CurrentSettings.CaptureDebugTraces); try { - using LogMessageListener logListener = new(MSTestSettings.CurrentSettings.CaptureDebugTraces); if (shouldRunClassCleanup) { - try - { - // Class cleanup can throw exceptions in which case we need to ensure that we fail the test. - testMethodInfo.Parent.RunClassCleanup(ClassCleanupBehavior.EndOfClass); - } - finally - { - string cleanupLogs = logListener.StandardOutput; - string? cleanupTrace = logListener.DebugTrace; - string cleanupErrorLogs = logListener.StandardError; - var cleanupTestContextMessages = testContext.GetAndClearDiagnosticMessages(); - - if (results.Length > 0) - { - var lastResult = results[results.Length - 1]; - lastResult.StandardOut += cleanupLogs; - lastResult.StandardError += cleanupErrorLogs; - lastResult.DebugTrace += cleanupTrace; - lastResult.TestContextMessages += cleanupTestContextMessages; - } - } + testMethodInfo.Parent.ExecuteClassCleanup(); } if (shouldRunClassAndAssemblyCleanup) { - try + var classInfoCache = _typeCache.ClassInfoListWithExecutableCleanupMethods; + foreach (var classInfo in classInfoCache) { - RunClassAndAssemblyCleanup(); + classInfo.ExecuteClassCleanup(); } - finally - { - string cleanupLogs = logListener.StandardOutput; - string? cleanupTrace = logListener.DebugTrace; - string cleanupErrorLogs = logListener.StandardError; - var cleanupTestContextMessages = testContext.GetAndClearDiagnosticMessages(); - if (results.Length > 0) - { - var lastResult = results[results.Length - 1]; - lastResult.StandardOut += cleanupLogs; - lastResult.StandardError += cleanupErrorLogs; - lastResult.DebugTrace += cleanupTrace; - lastResult.TestContextMessages += cleanupTestContextMessages; - } + var assemblyInfoCache = _typeCache.AssemblyInfoListWithExecutableCleanupMethods; + foreach (var assemblyInfo in assemblyInfoCache) + { + assemblyInfo.ExecuteAssemblyCleanup(); } } } - catch (Exception e) + catch (Exception ex) { + // We mainly expect TestFailedException here as each cleanup method is executed in a try-catch block but + // for the sake of the catch-all mechanism, let's keep it as Exception. + if (results.Length == 0) + { + return; + } + + var lastResult = results[results.Length - 1]; + lastResult.Outcome = ObjectModel.UnitTestOutcome.Failed; + lastResult.ErrorMessage = ex.Message; + lastResult.ErrorStackTrace = ex.StackTrace; + } + finally + { + var cleanupTestContextMessages = testContext.GetAndClearDiagnosticMessages(); + if (results.Length > 0) { - results[results.Length - 1].Outcome = ObjectModel.UnitTestOutcome.Failed; - results[results.Length - 1].ErrorMessage = e.Message; - results[results.Length - 1].ErrorStackTrace = e.StackTrace; + var lastResult = results[results.Length - 1]; + lastResult.StandardOut += logListener.StandardOutput; + lastResult.StandardError += logListener.StandardError; + lastResult.DebugTrace += logListener.DebugTrace; + lastResult.TestContextMessages += cleanupTestContextMessages; } } } @@ -331,44 +283,6 @@ private bool IsTestMethodRunnable( return true; } - /// - /// Run assembly cleanup methods. - /// - /// The assembly Info Cache. - /// The warnings. - private static void RunAssemblyCleanup(IEnumerable assemblyInfoCache, IList warnings) - { - foreach (var assemblyInfo in assemblyInfoCache) - { - DebugEx.Assert(assemblyInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true."); - - var warning = assemblyInfo.RunAssemblyCleanup(); - if (warning != null) - { - warnings.Add(warning); - } - } - } - - /// - /// Run class cleanup methods. - /// - /// The class Info Cache. - /// The warnings. - private static void RunClassCleanupMethods(IEnumerable classInfoCache, IList warnings) - { - foreach (var classInfo in classInfoCache) - { - DebugEx.Assert(classInfo.HasExecutableCleanupMethod, "HasExecutableCleanupMethod should be true."); - - var warning = classInfo.RunClassCleanup(); - if (warning != null) - { - warnings.Add(warning); - } - } - } - private class ClassCleanupManager { private readonly ClassCleanupBehavior? _lifecycleFromMsTest; @@ -385,30 +299,33 @@ public ClassCleanupManager( _remainingTestsByClass = testsToRun.GroupBy(t => t.TestMethod.FullClassName) .ToDictionary( g => g.Key, - g => new HashSet(g.Select(t => t.DisplayName!))); + g => new HashSet(g.Select(t => t.TestMethod.UniqueName))); _lifecycleFromMsTest = lifecycleFromMsTest; _lifecycleFromAssembly = lifecycleFromAssembly; _reflectHelper = reflectHelper ?? new ReflectHelper(); } - public void MarkTestComplete(TestMethodInfo testMethodInfo, TestMethod testMethod, out bool shouldRunClassCleanup, out bool shouldRunAssemblyCleanup) + public void MarkTestComplete(TestMethodInfo testMethodInfo, TestMethod testMethod, out bool shouldRunEndOfClassCleanup, out bool shouldRunEndOfAssemblyCleanup) { - shouldRunClassCleanup = false; + shouldRunEndOfClassCleanup = false; var testsByClass = _remainingTestsByClass[testMethodInfo.TestClassName]; lock (testsByClass) { - testsByClass.Remove(testMethod.DisplayName!); - if (testsByClass.Count == 0 && testMethodInfo.Parent.HasExecutableCleanupMethod) + testsByClass.Remove(testMethod.UniqueName); + if (testsByClass.Count == 0) { - var cleanupLifecycle = _reflectHelper.GetClassCleanupBehavior(testMethodInfo.Parent) - ?? _lifecycleFromMsTest - ?? _lifecycleFromAssembly; - - shouldRunClassCleanup = cleanupLifecycle == ClassCleanupBehavior.EndOfClass; _remainingTestsByClass.Remove(testMethodInfo.TestClassName); + if (testMethodInfo.Parent.HasExecutableCleanupMethod) + { + var cleanupLifecycle = _reflectHelper.GetClassCleanupBehavior(testMethodInfo.Parent) + ?? _lifecycleFromMsTest + ?? _lifecycleFromAssembly; + + shouldRunEndOfClassCleanup = cleanupLifecycle == ClassCleanupBehavior.EndOfClass; + } } - shouldRunAssemblyCleanup = _remainingTestsByClass.Count == 0; + shouldRunEndOfAssemblyCleanup = _remainingTestsByClass.Count == 0; } } } diff --git a/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethod.cs b/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethod.cs index d1a5da3a6b..cc51df2107 100644 --- a/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethod.cs +++ b/src/Adapter/MSTest.TestAdapter/ObjectModel/TestMethod.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using Microsoft.TestPlatform.AdapterUtilities; @@ -173,5 +174,10 @@ public string? DeclaringClassFullName /// internal string? DisplayName { get; set; } + internal string UniqueName + => HasManagedMethodAndTypeProperties + ? $"{ManagedTypeName}.{ManagedMethodName}->{string.Join(", ", SerializedData ?? Array.Empty())}" + : $"{FullClassName}.{Name}->{string.Join(", ", SerializedData ?? Array.Empty())}"; + internal TestMethod Clone() => (TestMethod)MemberwiseClone(); } diff --git a/test/E2ETests/Smoke.E2E.Tests/SuiteLifeCycleTests.cs b/test/E2ETests/Smoke.E2E.Tests/SuiteLifeCycleTests.cs index 2306f26dc9..e66180f368 100644 --- a/test/E2ETests/Smoke.E2E.Tests/SuiteLifeCycleTests.cs +++ b/test/E2ETests/Smoke.E2E.Tests/SuiteLifeCycleTests.cs @@ -1423,7 +1423,7 @@ LifeCycleClassCleanupEndOfClassAndNone.TestCleanup was called // Locally, netfx calls seems to be respecting the order of the cleanup while it is not stable for netcore. // But local order is not the same on various machines. I am not sure whether we should be committing to a // specific order. - caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod.Messages[0].Text.Should().Be( + var expectedStart = $""" Console: LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ctor was called Console: LifeCycleDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ctor was called @@ -1436,8 +1436,41 @@ LifeCycleClassCleanupEndOfClassAndNone.TestCleanup was called ? "Console: LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.DisposeAsync was called\r\nConsole: LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.Dispose was called" : "Console: LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.Dispose was called")} - """); - caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod.Messages[1].Text.Should().Be( + """; + caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod + .Messages[0].Text + .Should().StartWith(expectedStart); + + var expectedRemainingMessages = + """ + Console: LifeCycleDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleClassCleanup.ClassCleanup was called + Console: LifeCycleClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleDerivedClassCleanupEndOfAssemblyAndNone.ClassCleanup was called + Console: LifeCycleDerivedClassInitializeAndCleanupNone.ClassCleanup was called + Console: LifeCycleClassInitializeAndCleanupNone.ClassCleanup was called + Console: LifeCycleDerivedClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleDerivedClassCleanupEndOfClassAndBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleDerivedClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called + Console: LifeCycleDerivedClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called + Console: LifeCycleDerivedClassCleanupEndOfClassAndNone.ClassCleanup was called + Console: LifeCycleClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called + Console: LifeCycleClassCleanupEndOfAssembly.ClassCleanup was called + Console: LifeCycleClassCleanupEndOfAssemblyAndNone.ClassCleanup was called + Console: AssemblyCleanup was called + + """ + .Split(new[] { "\r\n" }, StringSplitOptions.None); + caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod + .Messages[0].Text + .Substring(expectedStart.Length) + .Split(new[] { "\r\n" }, StringSplitOptions.None) + .Should().BeEquivalentTo(expectedRemainingMessages); + + expectedStart = $""" @@ -1455,9 +1488,41 @@ LifeCycleClassCleanupEndOfClassAndNone.TestCleanup was called + GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.Dispose was called") : GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.Dispose was called"))} - """); + """; + caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod + .Messages[1].Text + .Should().StartWith(expectedStart); + + expectedRemainingMessages = + $""" + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassCleanupEndOfClassAndNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeAndCleanupNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassCleanupEndOfAssemblyAndNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassCleanup.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassInitializeAndCleanupNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassCleanupEndOfAssembly.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassCleanupEndOfClassAndBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleDerivedClassCleanupEndOfAssemblyAndNone.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called")} + {GenerateTraceDebugPrefixedMessage("AssemblyCleanup was called")} + + """ + .Split(new[] { "\r\n" }, StringSplitOptions.None); + caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod + .Messages[1].Text + .Substring(expectedStart.Length) + .Split(new[] { "\r\n" }, StringSplitOptions.None) + .Should().BeEquivalentTo(expectedRemainingMessages); - var expectedStart = + expectedStart = $""" @@ -1474,30 +1539,33 @@ LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.TestCleanup wa : "LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.Dispose was called")} """; - caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod.Messages[2].Text.Should().StartWith(expectedStart); - var expectedRemainingMessages = new List - { - "LifeCycleDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleDerivedClassCleanupEndOfAssemblyAndNone.ClassCleanup was called", - "LifeCycleDerivedClassCleanupEndOfClassAndBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleDerivedClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleDerivedClassCleanupEndOfClassAndNone.ClassCleanup was called", - "LifeCycleClassCleanupEndOfAssemblyAndNone.ClassCleanup was called", - "LifeCycleClassCleanup.ClassCleanup was called", - "LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleDerivedClassInitializeAndCleanupNone.ClassCleanup was called", - "LifeCycleClassInitializeAndCleanupNone.ClassCleanup was called", - "LifeCycleDerivedClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called", - "LifeCycleDerivedClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called", - "LifeCycleClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called", - "LifeCycleClassCleanupEndOfAssembly.ClassCleanup was called", - "AssemblyCleanup was called", - string.Empty, - }; - + caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod + .Messages[2].Text + .Should().StartWith(expectedStart); + + expectedRemainingMessages = + """ + LifeCycleDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called + LifeCycleClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called + LifeCycleDerivedClassCleanupEndOfAssemblyAndNone.ClassCleanup was called + LifeCycleDerivedClassCleanupEndOfClassAndBeforeEachDerivedClass.ClassCleanup was called + LifeCycleDerivedClassCleanupEndOfAssemblyAndBeforeEachDerivedClass.ClassCleanup was called + LifeCycleDerivedClassCleanupEndOfClassAndNone.ClassCleanup was called + LifeCycleClassCleanupEndOfAssemblyAndNone.ClassCleanup was called + LifeCycleClassCleanup.ClassCleanup was called + LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.ClassCleanup was called + LifeCycleDerivedClassInitializeAndCleanupNone.ClassCleanup was called + LifeCycleClassInitializeAndCleanupNone.ClassCleanup was called + LifeCycleDerivedClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called + LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called + LifeCycleClassInitializeAndCleanupBeforeEachDerivedClass.ClassCleanup was called + LifeCycleDerivedClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called + LifeCycleClassInitializeBeforeEachDerivedClassAndClassCleanupNone.ClassCleanup was called + LifeCycleClassCleanupEndOfAssembly.ClassCleanup was called + AssemblyCleanup was called + + """ + .Split(new[] { "\r\n" }, StringSplitOptions.None); caseDerivedClassInitializeNoneAndClassCleanupBeforeEachDerivedClassParentTestMethod .Messages[2].Text .Substring(expectedStart.Length) @@ -1505,7 +1573,7 @@ LifeCycleClassInitializeNoneAndClassCleanupBeforeEachDerivedClass.TestCleanup wa .Should().BeEquivalentTo(expectedRemainingMessages); } - private string GenerateTraceDebugPrefixedMessage(string message) + private static string GenerateTraceDebugPrefixedMessage(string message) { string prefixedMessage = $"Trace: {message}"; diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs index dafaff85ed..5ddb830611 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.UnitTests/Execution/UnitTestRunnerTests.cs @@ -318,49 +318,6 @@ public void RunSingleTestShouldCallAssemblyInitializeAndClassInitializeMethodsIn #endregion - #region RunCleanup Tests - - public void RunCleanupShouldReturnNullOnNoCleanUpMethods() - { - Verify(_unitTestRunner.RunClassAndAssemblyCleanup() is null); - } - - public void RunCleanupShouldReturnCleanupResultsForAssemblyAndClassCleanupMethods() - { - var type = typeof(DummyTestClassWithCleanupMethods); - var methodInfo = type.GetMethod("TestMethod"); - var testMethod = new TestMethod(methodInfo.Name, type.FullName, "A", isAsync: false); - - _testablePlatformServiceProvider.MockFileOperations.Setup(fo => fo.LoadAssembly("A", It.IsAny())) - .Returns(Assembly.GetExecutingAssembly()); - - _unitTestRunner.RunSingleTest(testMethod, _testRunParameters); - - var assemblyCleanupCount = 0; - var classCleanupCount = 0; - - DummyTestClassWithCleanupMethods.AssemblyCleanupMethodBody = () => - { - assemblyCleanupCount++; - throw new NotImplementedException(); - }; - - DummyTestClassWithCleanupMethods.ClassCleanupMethodBody = () => - { - classCleanupCount++; - throw new NotImplementedException(); - }; - - var cleanupresult = _unitTestRunner.RunClassAndAssemblyCleanup(); - - Verify(assemblyCleanupCount == 1); - Verify(classCleanupCount == 1); - Verify(cleanupresult.Warnings.Count == 2); - Verify(cleanupresult.Warnings.All(w => w.Contains("NotImplemented"))); - } - - #endregion - #region private helpers private MSTestSettings GetSettingsWithDebugTrace(bool captureDebugTraceValue)