From 3600f7436d662f86a249bd91e8b70144a430d6e6 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 13 Mar 2026 08:52:30 +0100 Subject: [PATCH 1/4] Cleanup AssemblyInitialize failure handling --- .../Execution/TestAssemblyInfo.cs | 37 ++++++++----------- .../Execution/UnitTestRunner.cs | 10 ++--- .../Execution/TestAssemblyInfoTests.cs | 31 ++++++++++------ 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs index 3f8488795b..6e5c1ce773 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs @@ -86,7 +86,7 @@ internal set /// /// Gets or sets the assembly initialization exception. /// - public Exception? AssemblyInitializationException { get; internal set; } + public TestFailedException? AssemblyInitializationException { get; internal set; } /// /// Gets the assembly cleanup exception. @@ -112,13 +112,13 @@ internal set /// /// The test context. /// Throws a test failed exception if the initialization method throws an exception. - public async Task RunAssemblyInitializeAsync(TestContext testContext) + public async Task RunAssemblyInitializeAsync(TestContext testContext) { // No assembly initialize => nothing to do. if (AssemblyInitializeMethod == null) { IsAssemblyInitializeExecuted = true; - return; + return new TestResult { Outcome = UnitTestOutcome.Passed }; } // If assembly initialization is not done, then do it. @@ -161,7 +161,7 @@ public async Task RunAssemblyInitializeAsync(TestContext testContext) } catch (Exception ex) { - AssemblyInitializationException = ex; + AssemblyInitializationException = GetTestFailedExceptionFromAssemblyInitializeException(ex, AssemblyInitializeMethod); } finally { @@ -176,37 +176,30 @@ public async Task RunAssemblyInitializeAsync(TestContext testContext) } // If assemblyInitialization was successful, then don't do anything - if (AssemblyInitializationException == null) - { - return; - } - - // If the exception is already a `TestFailedException` we throw it as-is - if (AssemblyInitializationException is TestFailedException) - { - throw AssemblyInitializationException; - } + return AssemblyInitializationException is null + ? new TestResult { Outcome = UnitTestOutcome.Passed } + : new TestResult { TestFailureException = AssemblyInitializationException, Outcome = AssemblyInitializationException.Outcome }; + } - Exception realException = AssemblyInitializationException.GetRealException(); + private static TestFailedException GetTestFailedExceptionFromAssemblyInitializeException(Exception ex, MethodInfo assemblyInitializeMethod) + { + Exception realException = ex.GetRealException(); UnitTestOutcome outcome = realException is AssertInconclusiveException ? UnitTestOutcome.Inconclusive : UnitTestOutcome.Failed; // Do not use StackTraceHelper.GetFormattedExceptionMessage(realException) as it prefixes the message with the exception type name. string exceptionMessage = realException.TryGetMessage(); - DebugEx.Assert(AssemblyInitializeMethod.DeclaringType?.FullName is not null, "AssemblyInitializeMethod.DeclaringType.FullName is null"); + DebugEx.Assert(assemblyInitializeMethod.DeclaringType?.FullName is not null, "AssemblyInitializeMethod.DeclaringType.FullName is null"); string errorMessage = string.Format( CultureInfo.CurrentCulture, Resource.UTA_AssemblyInitMethodThrows, - AssemblyInitializeMethod.DeclaringType.FullName, - AssemblyInitializeMethod.Name, + assemblyInitializeMethod.DeclaringType.FullName, + assemblyInitializeMethod.Name, realException.GetType().ToString(), exceptionMessage); StackTraceInformation? exceptionStackTraceInfo = realException.GetStackTraceInformation(); - var testFailedException = new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo, realException); - AssemblyInitializationException = testFailedException; - - throw testFailedException; + return new TestFailedException(outcome, errorMessage, exceptionStackTraceInfo, realException); } /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs index 6608e4f9d2..501f3f8976 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs @@ -244,15 +244,11 @@ internal async Task RunSingleTestAsync(UnitTestElement unitTestEle private static async Task RunAssemblyInitializeIfNeededAsync(TestMethodInfo testMethodInfo, ITestContext testContext) { - var result = new TestResult { Outcome = UnitTestOutcome.Passed }; + TestResult? result = null; try { - await testMethodInfo.Parent.Parent.RunAssemblyInitializeAsync(testContext.Context).ConfigureAwait(false); - } - catch (TestFailedException ex) - { - result = new TestResult { TestFailureException = ex, Outcome = ex.Outcome }; + result = await testMethodInfo.Parent.Parent.RunAssemblyInitializeAsync(testContext.Context).ConfigureAwait(false); } catch (Exception ex) { @@ -262,7 +258,7 @@ private static async Task RunAssemblyInitializeIfNeededAsync(TestMet finally { var testContextImpl = testContext.Context as TestContextImplementation; - result.LogOutput = testContextImpl?.GetOut(); + result!.LogOutput = testContextImpl?.GetOut(); result.LogError = testContextImpl?.GetErr(); result.DebugTrace = testContextImpl?.GetTrace(); result.TestContextMessages = testContext.GetAndClearDiagnosticMessages(); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs index 66beaace09..70762510e5 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs @@ -126,9 +126,9 @@ public async Task RunAssemblyInitializeShouldSetAssemblyInitializationExceptionO #pragma warning restore RS0030 // Do not use banned APIs _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - Func action = () => _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); - - await action.Should().ThrowAsync(); + TestResult testResult = await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); + testResult.Should().NotBeNull(); + testResult.TestFailureException.Should().NotBeNull(); _testAssemblyInfo.AssemblyInitializationException.Should().NotBeNull(); } @@ -139,7 +139,8 @@ public async Task RunAssemblyInitializeShouldThrowTestFailedExceptionOnAssertion #pragma warning restore RS0030 // Do not use banned APIs _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - TestFailedException exception = (await new Func(() => _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).Should().ThrowAsync()).Which; + var exception = (await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).TestFailureException as TestFailedException; + exception.Should().NotBeNull(); exception.Outcome.Should().Be(UnitTestOutcome.Failed); exception.Message.Should().Be("Assembly Initialization method Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution.TestAssemblyInfoTests+DummyTestClass.AssemblyInitializeMethod threw exception. Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Assert.Fail failed. Test failure. Aborting test execution."); exception.StackTraceInformation!.ErrorStackTrace.Should().Contain( @@ -154,7 +155,8 @@ public async Task RunAssemblyInitializeShouldThrowTestFailedExceptionWithInconcl #pragma warning restore RS0030 // Do not use banned APIs _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - TestFailedException exception = (await new Func(() => _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).Should().ThrowAsync()).Which; + var exception = (await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).TestFailureException as TestFailedException; + exception.Should().NotBeNull(); exception.Outcome.Should().Be(UnitTestOutcome.Inconclusive); exception.Message.Should().Be("Assembly Initialization method Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution.TestAssemblyInfoTests+DummyTestClass.AssemblyInitializeMethod threw exception. Microsoft.VisualStudio.TestTools.UnitTesting.AssertInconclusiveException: Assert.Inconclusive failed. Test Inconclusive. Aborting test execution."); exception.StackTraceInformation!.ErrorStackTrace.Should().Contain( @@ -167,8 +169,8 @@ public async Task RunAssemblyInitializeShouldThrowTestFailedExceptionWithNonAsse DummyTestClass.AssemblyInitializeMethodBody = tc => throw new ArgumentException("Some actualErrorMessage message", new InvalidOperationException("Inner actualErrorMessage message")); _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - TestFailedException exception = (await new Func(() => _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).Should().ThrowAsync()).Which; - + var exception = (await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).TestFailureException as TestFailedException; + exception.Should().NotBeNull(); exception.Outcome.Should().Be(UnitTestOutcome.Failed); exception.Message.Should().Be("Assembly Initialization method Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution.TestAssemblyInfoTests+DummyTestClass.AssemblyInitializeMethod threw exception. System.ArgumentException: Some actualErrorMessage message. Aborting test execution."); exception.StackTraceInformation!.ErrorStackTrace.Should().Contain( @@ -186,8 +188,8 @@ public async Task RunAssemblyInitializeShouldThrowTheInnerMostExceptionWhenThere FailingStaticHelper.DoWork(); _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - TestFailedException exception = (await new Func(() => _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).Should().ThrowAsync()).Which; - + var exception = (await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).TestFailureException as TestFailedException; + exception.Should().NotBeNull(); exception.Outcome.Should().Be(UnitTestOutcome.Failed); exception.Message.Should().Be("Assembly Initialization method Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Execution.TestAssemblyInfoTests+DummyTestClass.AssemblyInitializeMethod threw exception. System.InvalidOperationException: I fail.. Aborting test execution."); exception.StackTraceInformation!.ErrorStackTrace.StartsWith( @@ -202,17 +204,24 @@ public async Task RunAssemblyInitializeShouldThrowForAlreadyExecutedTestAssembly _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; _testAssemblyInfo.AssemblyInitializationException = new TestFailedException(UnitTestOutcome.Failed, "Cached Test failure"); - TestFailedException exception = (await new Func(() => _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).Should().ThrowAsync()).Which; + var exception = (await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext)).TestFailureException as TestFailedException; + exception.Should().NotBeNull(); exception.Outcome.Should().Be(UnitTestOutcome.Failed); exception.Message.Should().Be("Cached Test failure"); } public async Task RunAssemblyInitializeShouldPassOnTheTestContextToAssemblyInitMethod() { - DummyTestClass.AssemblyInitializeMethodBody = tc => (tc == _testContext).Should().BeTrue(); + bool hasExecuted = false; + DummyTestClass.AssemblyInitializeMethodBody = tc => + { + (tc == _testContext).Should().BeTrue(); + hasExecuted = true; + }; _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); + hasExecuted.Should().BeTrue(); } #endregion From 1b6179fca66b49fe0eb07d806ace84f7e7d37d89 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 13 Mar 2026 09:46:54 +0100 Subject: [PATCH 2/4] Fix build error --- .../Execution/TestAssemblyInfoTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs index 70762510e5..b0a800d4ab 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs @@ -58,7 +58,7 @@ void Action() public void TestAssemblyHasExecutableCleanupMethodShouldReturnTrueEvenIfAssemblyInitializationThrewAnException() { _testAssemblyInfo.AssemblyCleanupMethod = _dummyMethodInfo; - _testAssemblyInfo.AssemblyInitializationException = new NotImplementedException(); + _testAssemblyInfo.AssemblyInitializationException = new TestFailedException(UnitTestOutcome.Error, "ERROR"); _testAssemblyInfo.HasExecutableCleanupMethod.Should().BeTrue(); } From a16f4b3a8b7473fe33ea4ec76d9c19d0bec5a369 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 13 Mar 2026 12:48:21 +0100 Subject: [PATCH 3/4] Fix doc comment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Execution/TestAssemblyInfo.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs index 6e5c1ce773..d0d55e17ce 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestAssemblyInfo.cs @@ -110,8 +110,12 @@ internal set /// /// Runs assembly initialize method. /// - /// The test context. - /// Throws a test failed exception if the initialization method throws an exception. + /// The test context. + /// + /// A whose is + /// when the assembly initialization succeeds, or the failure outcome with + /// set when the initialization fails. + /// public async Task RunAssemblyInitializeAsync(TestContext testContext) { // No assembly initialize => nothing to do. From 43a80e8098c3ce7aee93c344b87502fed3a840de Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:15:58 +0100 Subject: [PATCH 4/4] Add `Passed` outcome assertions to success-path `RunAssemblyInitializeAsync` tests (#7550) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../Execution/TestAssemblyInfoTests.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs index b0a800d4ab..fe081e28cd 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs @@ -79,9 +79,10 @@ public async Task RunAssemblyInitializeShouldNotInvokeIfAssemblyInitializeIsNull _testAssemblyInfo.AssemblyInitializeMethod = null; - await _testAssemblyInfo.RunAssemblyInitializeAsync(null!); + TestResult result = await _testAssemblyInfo.RunAssemblyInitializeAsync(null!); assemblyInitCallCount.Should().Be(0); + result.Outcome.Should().Be(UnitTestOutcome.Passed); } public async Task RunAssemblyInitializeShouldNotExecuteAssemblyInitializeIfItHasAlreadyExecuted() @@ -92,9 +93,10 @@ public async Task RunAssemblyInitializeShouldNotExecuteAssemblyInitializeIfItHas _testAssemblyInfo.IsAssemblyInitializeExecuted = true; _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); + TestResult result = await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); assemblyInitCallCount.Should().Be(0); + result.Outcome.Should().Be(UnitTestOutcome.Passed); } public async Task RunAssemblyInitializeShouldExecuteAssemblyInitialize() @@ -103,9 +105,10 @@ public async Task RunAssemblyInitializeShouldExecuteAssemblyInitialize() DummyTestClass.AssemblyInitializeMethodBody = _ => assemblyInitCallCount++; _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); + TestResult result = await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); assemblyInitCallCount.Should().Be(1); + result.Outcome.Should().Be(UnitTestOutcome.Passed); } public async Task RunAssemblyInitializeShouldSetAssemblyInitializeExecutedFlag() @@ -114,9 +117,10 @@ public async Task RunAssemblyInitializeShouldSetAssemblyInitializeExecutedFlag() _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); + TestResult result = await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); _testAssemblyInfo.IsAssemblyInitializeExecuted.Should().BeTrue(); + result.Outcome.Should().Be(UnitTestOutcome.Passed); } public async Task RunAssemblyInitializeShouldSetAssemblyInitializationExceptionOnException() @@ -220,8 +224,9 @@ public async Task RunAssemblyInitializeShouldPassOnTheTestContextToAssemblyInitM }; _testAssemblyInfo.AssemblyInitializeMethod = typeof(DummyTestClass).GetMethod("AssemblyInitializeMethod")!; - await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); + TestResult result = await _testAssemblyInfo.RunAssemblyInitializeAsync(_testContext); hasExecuted.Should().BeTrue(); + result.Outcome.Should().Be(UnitTestOutcome.Passed); } #endregion