From 668335abc02abd9fd073e5c46d6c3223a1c914fa Mon Sep 17 00:00:00 2001 From: Amaury Leveque Date: Sun, 5 Jul 2026 12:03:37 +0200 Subject: [PATCH 1/2] Remove IFrameworkHandle from the PlatformServices execution engine (Phase 6e-1) The execution engine used the VSTest IFrameworkHandle exclusively to obtain an IAdapterMessageLogger via ToAdapterMessageLogger(). Replace the IFrameworkHandle parameter with the neutral IAdapterMessageLogger throughout TestExecutionManager (RunTestsAsync both overloads, ExecuteTestsAsync, ExecuteTestsInSourceAsync, Deploy); the adapter boundary (MSTestExecutor) now calls frameworkHandle.ToAdapterMessageLogger() once and injects the result. This removes the last VSTest ObjectModel.Adapter reference from the execution engine. No behavior change: the logger wrapper is stateless, so injecting one instance is identical to building one per call site. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../VSTestAdapter/MSTestExecutor.cs | 4 +- .../TestExecutionManager.Parallelization.cs | 15 ++--- .../Execution/TestExecutionManager.cs | 28 ++++---- .../Utilities/CLITestBase.discovery.cs | 4 +- .../Execution/TestExecutionManagerTests.cs | 66 +++++++++---------- 5 files changed, 57 insertions(+), 60 deletions(-) diff --git a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs index 0db5ce45f8b..f1fd235958e 100644 --- a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs +++ b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs @@ -161,7 +161,7 @@ internal async Task RunTestsAsync(IEnumerable? tests, IRunContext? run // this boundary so the execution engine no longer depends on the VSTest run context. var deploymentContext = new DeploymentContext(runContext?.TestRunDirectory, runContext?.RunSettings?.SettingsXml); - await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(testElements, deploymentContext, frameworkHandle, testResultRecorder, new TestElementFilterProvider(runContext), testRunToken).ConfigureAwait(false)).ConfigureAwait(false); + await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(testElements, deploymentContext, frameworkHandle.ToAdapterMessageLogger(), testResultRecorder, new TestElementFilterProvider(runContext), testRunToken).ConfigureAwait(false)).ConfigureAwait(false); } finally { @@ -223,7 +223,7 @@ internal async Task RunTestsAsync(IEnumerable? sources, IRunContext? run // this boundary so the execution engine no longer depends on the VSTest run context. var deploymentContext = new DeploymentContext(runContext?.TestRunDirectory, runContext?.RunSettings?.SettingsXml); - await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, deploymentContext, frameworkHandle, testResultRecorder, new TestElementFilterProvider(runContext), testSourceHandler, isMTP, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); + await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, deploymentContext, frameworkHandle.ToAdapterMessageLogger(), testResultRecorder, new TestElementFilterProvider(runContext), testSourceHandler, isMTP, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); } finally { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs index 379f5308b8e..9f7b4ffdd30 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs @@ -6,7 +6,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; using ExecutionScope = Microsoft.VisualStudio.TestTools.UnitTesting.ExecutionScope; @@ -20,16 +19,16 @@ internal partial class TestExecutionManager /// /// Tests to execute. /// Host-provided test-run directory and run settings XML. - /// Handle to record test start/end/results. + /// Logger used to report test messages back to the host. /// Recorder used to report test results back to the host. /// Provider for the test filter, or for no filter. /// Indicates if deployment is done. - internal virtual async Task ExecuteTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone) + internal virtual async Task ExecuteTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone) { _testResultRecorder = testResultRecorder; _testElementFilterProvider = filterProvider; - InitializeRandomTestOrder(frameworkHandle.ToAdapterMessageLogger()); + InitializeRandomTestOrder(messageLogger); var testsBySource = (from test in tests group test by test.TestMethod.AssemblyName into testGroup @@ -43,7 +42,7 @@ group test by test.TestMethod.AssemblyName into testGroup foreach (var group in testsBySource) { _testRunCancellationToken?.ThrowIfCancellationRequested(); - await ExecuteTestsInSourceAsync(group.Tests, deploymentContext, frameworkHandle, group.Source, isDeploymentDone).ConfigureAwait(false); + await ExecuteTestsInSourceAsync(group.Tests, deploymentContext, messageLogger, group.Source, isDeploymentDone).ConfigureAwait(false); } } @@ -52,10 +51,10 @@ group test by test.TestMethod.AssemblyName into testGroup /// /// Tests to execute. /// Host-provided test-run directory and run settings XML. - /// Handle to record test start/end/results. + /// Logger used to report test messages back to the host. /// The test container for the tests. /// Indicates if deployment is done. - private async Task ExecuteTestsInSourceAsync(IEnumerable tests, DeploymentContext deploymentContext, IFrameworkHandle frameworkHandle, string source, bool isDeploymentDone) + private async Task ExecuteTestsInSourceAsync(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, string source, bool isDeploymentDone) { DebugEx.Assert(!StringEx.IsNullOrEmpty(source), "Source cannot be empty"); @@ -66,7 +65,7 @@ private async Task ExecuteTestsInSourceAsync(IEnumerable tests, } #endif - IAdapterMessageLogger adapterMessageLogger = frameworkHandle.ToAdapterMessageLogger(); + IAdapterMessageLogger adapterMessageLogger = messageLogger; using ITestSourceHost isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(source, deploymentContext.RunSettingsXml); bool usesAppDomains = isolationHost is TestSourceHost { UsesAppDomain: true }; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs index f08ddb0a524..d8858cdaea6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs @@ -2,11 +2,9 @@ // 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; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -89,31 +87,31 @@ private static Task DefaultFactoryAsync(Func taskGetter) /// /// Tests to be run. /// Host-provided test-run directory and run settings XML. - /// Handle to the framework to record results and to do framework operations. + /// Logger used to report test messages back to the host. /// Recorder used to report test results back to the host. /// Provider for the test filter, or for no filter. /// Test run cancellation token. - internal async Task RunTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, TestRunCancellationToken runCancellationToken) + internal async Task RunTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, TestRunCancellationToken runCancellationToken) { DebugEx.Assert(tests != null, "tests"); DebugEx.Assert(deploymentContext != null, "deploymentContext"); - DebugEx.Assert(frameworkHandle != null, "frameworkHandle"); + DebugEx.Assert(messageLogger != null, "messageLogger"); DebugEx.Assert(runCancellationToken != null, "runCancellationToken"); _testRunCancellationToken = runCancellationToken; PlatformServiceProvider.Instance.TestRunCancellationToken = _testRunCancellationToken; #if !WINDOWS_UWP && !WIN_UI - bool isDeploymentDone = Deploy(tests, deploymentContext, frameworkHandle); + bool isDeploymentDone = Deploy(tests, deploymentContext, messageLogger); #else const bool isDeploymentDone = false; #endif // Placing this after deployment since we need information post deployment that we pass in as properties. - CacheSessionParameters(deploymentContext.RunSettingsXml, frameworkHandle.ToAdapterMessageLogger()); + CacheSessionParameters(deploymentContext.RunSettingsXml, messageLogger); // Execute the tests - await ExecuteTestsAsync(tests, deploymentContext, frameworkHandle, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false); + await ExecuteTestsAsync(tests, deploymentContext, messageLogger, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false); #if !WINDOWS_UWP && !WIN_UI if (!_hasAnyTestFailed) @@ -123,7 +121,7 @@ internal async Task RunTestsAsync(IEnumerable tests, Deployment #endif } - internal async Task RunTestsAsync(IEnumerable sources, DeploymentContext deploymentContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, ITestSourceHandler testSourceHandler, bool isMTP, TestRunCancellationToken cancellationToken) + internal async Task RunTestsAsync(IEnumerable sources, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, ITestSourceHandler testSourceHandler, bool isMTP, TestRunCancellationToken cancellationToken) { _testRunCancellationToken = cancellationToken; PlatformServiceProvider.Instance.TestRunCancellationToken = _testRunCancellationToken; @@ -132,7 +130,7 @@ internal async Task RunTestsAsync(IEnumerable sources, DeploymentContext var tests = new List(); - IAdapterMessageLogger logger = frameworkHandle.ToAdapterMessageLogger(); + IAdapterMessageLogger logger = messageLogger; // deploy everything first. foreach (string source in sources) @@ -148,16 +146,16 @@ internal async Task RunTestsAsync(IEnumerable sources, DeploymentContext } #if !WINDOWS_UWP && !WIN_UI - bool isDeploymentDone = Deploy(tests, deploymentContext, frameworkHandle); + bool isDeploymentDone = Deploy(tests, deploymentContext, messageLogger); #else const bool isDeploymentDone = false; #endif // Placing this after deployment since we need information post deployment that we pass in as properties. - CacheSessionParameters(deploymentContext.RunSettingsXml, frameworkHandle.ToAdapterMessageLogger()); + CacheSessionParameters(deploymentContext.RunSettingsXml, messageLogger); // Run tests. - await ExecuteTestsAsync(tests, deploymentContext, frameworkHandle, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false); + await ExecuteTestsAsync(tests, deploymentContext, messageLogger, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false); #if !WINDOWS_UWP && !WIN_UI if (!_hasAnyTestFailed) @@ -172,8 +170,8 @@ internal async Task RunTestsAsync(IEnumerable sources, DeploymentContext /// Runs deployment for the given tests via the platform-agnostic deployment service using the neutral /// supplied by the adapter boundary. /// - private static bool Deploy(IEnumerable tests, DeploymentContext deploymentContext, IFrameworkHandle frameworkHandle) - => PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, deploymentContext, frameworkHandle.ToAdapterMessageLogger()); + private static bool Deploy(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger) + => PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, deploymentContext, messageLogger); #endif internal virtual UnitTestDiscoverer GetUnitTestDiscoverer(ITestSourceHandler testSourceHandler) => new(testSourceHandler); diff --git a/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs b/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs index f80f61eeba7..40326955f64 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs +++ b/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs @@ -42,7 +42,7 @@ internal static async Task> RunTestsAsync(IEnumerable var frameworkHandle = new InternalFrameworkHandle(); ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings); - await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), new DeploymentContext(null, null), frameworkHandle, testResultRecorder, filterProvider: null, false); + await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), new DeploymentContext(null, null), frameworkHandle.ToAdapterMessageLogger(), testResultRecorder, filterProvider: null, false); return frameworkHandle.GetFlattenedTestResults(); } @@ -55,7 +55,7 @@ internal static async Task> RunTestsAsync(IEnumerable var runContext = new InternalRunContext(runSettingsXml, testCaseFilter); ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings); - await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), new DeploymentContext(runContext.TestRunDirectory, runContext.RunSettings?.SettingsXml), frameworkHandle, testResultRecorder, new TestElementFilterProvider(runContext), false); + await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), new DeploymentContext(runContext.TestRunDirectory, runContext.RunSettings?.SettingsXml), frameworkHandle.ToAdapterMessageLogger(), testResultRecorder, new TestElementFilterProvider(runContext), false); return frameworkHandle.GetFlattenedTestResults(); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs index 12395a78d3b..4e15c282a43 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs @@ -107,7 +107,7 @@ public async Task RunTestsForTestWithFilterErrorShouldSendZeroResults() // Causing the FilterExpressionError _runContext = new TestableRunContextTestExecutionTests(() => throw new TestPlatformFormatException()); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); // No Results _frameworkHandle.TestCaseStartList.Count.Should().Be(0); @@ -123,7 +123,7 @@ public async Task RunTestsForTestWithFilterShouldSendResultsForFilteredTests() _runContext = new TestableRunContextTestExecutionTests(() => new TestableTestCaseFilterExpression(p => p.DisplayName == "PassingTest")); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); // FailingTest should be skipped because it does not match the filter criteria. List expectedTestCaseStartList = ["PassingTest"]; @@ -151,7 +151,7 @@ public async Task RunTestsForIgnoredTestShouldSendResultsMarkingIgnoredTestsAsSk TestCase testCase = GetTestCase(typeof(DummyTestClass), "IgnoredTest"); TestCase[] tests = [testCase]; - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); _frameworkHandle.TestCaseStartList[0].Should().Be("IgnoredTest"); _frameworkHandle.TestCaseEndList[0].Should().Be("IgnoredTest:Skipped"); @@ -164,7 +164,7 @@ public async Task RunTestsForASingleTestShouldSendSingleResult() TestCase[] tests = [testCase]; - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); List expectedTestCaseStartList = ["PassingTest"]; List expectedTestCaseEndList = ["PassingTest:Passed"]; @@ -181,7 +181,7 @@ public async Task RunTestsForMultipleTestShouldSendMultipleResults() TestCase failingTestCase = GetTestCase(typeof(DummyTestClass), "FailingTest"); TestCase[] tests = [testCase, failingTestCase]; - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); List expectedTestCaseStartList = ["PassingTest", "FailingTest"]; List expectedTestCaseEndList = ["PassingTest:Passed", "FailingTest:Failed"]; @@ -201,7 +201,7 @@ public async Task RunTestsForCancellationTokenCanceledSetToTrueShouldSendZeroRes // Cancel the test run _cancellationToken.Cancel(); - Func func = () => _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); + Func func = () => _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); await func.Should().ThrowAsync(); // No Results @@ -224,7 +224,7 @@ public async Task RunTestsForTestShouldDeployBeforeExecution() await _testExecutionManager.RunTestsAsync( ToUnitTestElements(tests), CurrentDeploymentContext, - _frameworkHandle, + _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _callers[0].Should().Be("Deploy", "Deploy should be called before execution."); @@ -245,7 +245,7 @@ public async Task RunTestsForTestShouldCleanupAfterExecution() td => td.Cleanup()).Callback(() => SetCaller("Cleanup")); #endif - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _callers[0].Should().Be("LoadAssembly", "Cleanup should be called after execution."); @@ -262,7 +262,7 @@ public async Task RunTestsForTestShouldNotCleanupOnTestFailure() TestCase[] tests = [testCase, failingTestCase]; TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); testablePlatformService.MockTestDeployment.Verify(td => td.Cleanup(), Times.Never); } @@ -281,7 +281,7 @@ public async Task RunTestsForTestShouldLoadSourceFromDeploymentDirectoryIfDeploy testablePlatformService.MockTestDeployment.Setup(td => td.GetDeploymentDirectory()) .Returns(@"C:\temp"); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); testablePlatformService.MockFileOperations.Verify( fo => fo.LoadAssembly(It.Is(s => s.StartsWith("C:\\temp"))), @@ -307,7 +307,7 @@ public async Task RunTestsForTestShouldPassInTestRunParametersInformationAsPrope """); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClass.TestContextProperties!.Contains( new KeyValuePair("webAppUrl", "http://localhost")).Should().BeTrue(); @@ -329,7 +329,7 @@ public async Task RunTestsForTestShouldPassInTcmPropertiesAsPropertiesToTheTest( """); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); VerifyTcmProperties(DummyTestClass.TestContextProperties, testCase); } @@ -342,7 +342,7 @@ public async Task RunTestsForTestShouldPassInDeploymentInformationAsPropertiesTo // Setup mocks. TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); testablePlatformService.MockSettingsProvider.Verify(sp => sp.GetProperties(It.IsAny()), Times.Once); } @@ -366,7 +366,7 @@ public async Task RunTestsShouldClearSessionParametersAcrossRuns() """); // Trigger First Run - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); // Update runsettings to have different values for similar keys _runContext.MockRunSettings.Setup(rs => rs.SettingsXml).Returns( @@ -383,7 +383,7 @@ public async Task RunTestsShouldClearSessionParametersAcrossRuns() """); // Trigger another Run - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); "http://updatedLocalHost".Equals(DummyTestClass.TestContextProperties!["webAppUrl"]).Should().BeTrue(); } @@ -397,7 +397,7 @@ private async Task RunTestsForSourceShouldRunTestsInASource() { var sources = new List { Assembly.GetExecutingAssembly().Location }; - await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); + await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); _frameworkHandle.TestCaseStartList.Contains("PassingTest").Should().BeTrue(); _frameworkHandle.TestCaseEndList.Contains("PassingTest:Passed").Should().BeTrue(); @@ -419,7 +419,7 @@ private async Task RunTestsForSourceShouldPassInTestRunParametersInformationAsPr """); - await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); + await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); DummyTestClass.TestContextProperties!.Contains( new KeyValuePair("webAppUrl", "http://localhost")).Should().BeTrue(); @@ -430,7 +430,7 @@ private async Task RunTestsForSourceShouldPassInDeploymentInformationAsPropertie { var sources = new List { Assembly.GetExecutingAssembly().Location }; - await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); + await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); DummyTestClass.TestContextProperties.Should().NotBeNull(); } @@ -444,7 +444,7 @@ public async Task RunTestsForMultipleSourcesShouldRunEachTestJustOnce() ExecuteTestsWrapper = (tests, runContext, frameworkHandle, testResultRecorder, isDeploymentDone) => testsCount += tests.Count(), }; - await testableTestExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); + await testableTestExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); testsCount.Should().Be(4); } @@ -477,7 +477,7 @@ public async Task RunTestsForTestShouldRunTestsInParallelWhenEnabledInRunsetting try { MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassForParallelize.ThreadIds.Count.Should().Be(1); DummyTestClassForParallelize2.ThreadIds.Count.Should().Be(1); @@ -514,7 +514,7 @@ public async Task RunTestsForTestShouldRunTestsByMethodLevelWhenSpecified() try { MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _enqueuedParallelTestsCount.Should().Be(2); @@ -551,7 +551,7 @@ public async Task RunTestsForTestShouldRunTestsWithSpecifiedNumberOfWorkers() try { MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassForParallelize.ThreadIds.Count.Should().Be(1); DummyTestClassForParallelize2.ThreadIds.Count.Should().Be(1); @@ -614,7 +614,7 @@ public async Task RunTestsForTestShouldNotRunTestsInParallelWhenDisabledFromRuns testablePlatformService.MockReflectionOperations.Setup(fo => fo.GetRuntimeMethods(It.IsAny())) .Returns((Type t) => originalReflectionOperation.GetRuntimeMethods(t)); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassForParallelize.ThreadIds.Count.Should().Be(1); } @@ -673,7 +673,7 @@ public async Task RunTestsForTestShouldNotRunTestsInParallelWhenDisabledFromSour testablePlatformService.MockReflectionOperations.Setup(fo => fo.GetRuntimeMethods(It.IsAny())) .Returns((Type t) => originalReflectionOperation.GetRuntimeMethods(t)); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassForParallelize.ThreadIds.Count.Should().Be(1); } @@ -712,7 +712,7 @@ public async Task RunTestsForTestShouldRunNonParallelizableTestsSeparately() try { MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _enqueuedParallelTestsCount.Should().Be(2); DummyTestClassWithDoNotParallelizeMethods.ParallelizableTestsThreadIds.Count.Should().BeOneOf(1, 2); @@ -778,7 +778,7 @@ public async Task RunTestsForTestShouldPreferParallelSettingsFromRunSettingsOver testablePlatformService.MockReflectionOperations.Setup(fo => fo.GetRuntimeMethods(It.IsAny())) .Returns((Type t) => originalReflectionOperation.GetRuntimeMethods(t)); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _enqueuedParallelTestsCount.Should().Be(2); @@ -818,7 +818,7 @@ private async Task RunTestsForTestShouldRunTestsInTheParentDomainsApartmentState try { MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassWithDoNotParallelizeMethods.ThreadApartmentStates.Count.Should().Be(1); DummyTestClassWithDoNotParallelizeMethods.ThreadApartmentStates.ToArray()[0].Should().Be(Thread.CurrentThread.GetApartmentState()); @@ -861,11 +861,11 @@ static TestCase[] BuildTests() => var firstHandle = new TestableFrameworkHandle(); var firstManager = new TestExecutionManager(task => task()); - await firstManager.RunTestsAsync(ToUnitTestElements(BuildTests()), CurrentDeploymentContext, firstHandle, firstHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings), new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await firstManager.RunTestsAsync(ToUnitTestElements(BuildTests()), CurrentDeploymentContext, firstHandle.ToAdapterMessageLogger(), firstHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings), new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); var secondHandle = new TestableFrameworkHandle(); var secondManager = new TestExecutionManager(task => task()); - await secondManager.RunTestsAsync(ToUnitTestElements(BuildTests()), CurrentDeploymentContext, secondHandle, secondHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings), new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await secondManager.RunTestsAsync(ToUnitTestElements(BuildTests()), CurrentDeploymentContext, secondHandle.ToAdapterMessageLogger(), secondHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings), new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); // Same seed must produce the same order across separate runs. firstHandle.TestCaseStartList.Should().Equal(secondHandle.TestCaseStartList); @@ -902,7 +902,7 @@ public async Task RunTestsWithoutRandomizeTestOrderShouldPreserveInputOrder() MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle, TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _frameworkHandle.TestCaseStartList.Should().Equal("TestA", "TestB", "TestC"); } @@ -1278,11 +1278,11 @@ internal sealed class TestableTestCaseFilterExpression : ITestCaseFilterExpressi internal class TestableTestExecutionManager : TestExecutionManager { - internal Action, DeploymentContext, IFrameworkHandle, ITestResultRecorder, bool> ExecuteTestsWrapper { get; set; } = null!; + internal Action, DeploymentContext, IAdapterMessageLogger, ITestResultRecorder, bool> ExecuteTestsWrapper { get; set; } = null!; - internal override Task ExecuteTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IFrameworkHandle frameworkHandle, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone) + internal override Task ExecuteTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone) { - ExecuteTestsWrapper?.Invoke(tests, deploymentContext, frameworkHandle, testResultRecorder, isDeploymentDone); + ExecuteTestsWrapper?.Invoke(tests, deploymentContext, messageLogger, testResultRecorder, isDeploymentDone); return Task.CompletedTask; } From 1e61c137f12ce415ccd2aa479d877f69297bedfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sun, 5 Jul 2026 12:30:12 +0200 Subject: [PATCH 2/2] Relocate VSTest logger/sink bridges to the adapter (Phase 6e-2) Move the three remaining VSTest-object-model bridge helpers out of MSTestAdapter.PlatformServices and into MSTest.TestAdapter: AdapterMessageLoggerExtensions, MessageLevel (ToTestMessageLevel), and UnitTestElementSinkExtensions. These are the last code references to Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging / ITestCaseDiscoverySink in PlatformServices; only doc comments now mention the VSTest types. The logical namespace is unchanged so callers at the adapter boundary and the integration harness are unaffected. PlatformServices.UnitTests calls the ToAdapterMessageLogger bridge, which now lives in MSTest.TestAdapter; touching that module runs its [ModuleInitializer] (MSTestExecutor.SetPlatformLogger), which assigns PlatformServiceProvider.Instance.AdapterTraceLogger. Make the test double's setter tolerate the assignment (as the real PlatformServiceProvider does) instead of throwing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Services/AdapterMessageLoggerExtensions.cs | 0 .../Services/MessageLevel.cs | 0 .../Services/UnitTestElementSinkExtensions.cs | 0 .../TestablePlatformServiceProvider.cs | 7 ++++++- 4 files changed, 6 insertions(+), 1 deletion(-) rename src/Adapter/{MSTestAdapter.PlatformServices => MSTest.TestAdapter}/Services/AdapterMessageLoggerExtensions.cs (100%) rename src/Adapter/{MSTestAdapter.PlatformServices => MSTest.TestAdapter}/Services/MessageLevel.cs (100%) rename src/Adapter/{MSTestAdapter.PlatformServices => MSTest.TestAdapter}/Services/UnitTestElementSinkExtensions.cs (100%) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/AdapterMessageLoggerExtensions.cs b/src/Adapter/MSTest.TestAdapter/Services/AdapterMessageLoggerExtensions.cs similarity index 100% rename from src/Adapter/MSTestAdapter.PlatformServices/Services/AdapterMessageLoggerExtensions.cs rename to src/Adapter/MSTest.TestAdapter/Services/AdapterMessageLoggerExtensions.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/MessageLevel.cs b/src/Adapter/MSTest.TestAdapter/Services/MessageLevel.cs similarity index 100% rename from src/Adapter/MSTestAdapter.PlatformServices/Services/MessageLevel.cs rename to src/Adapter/MSTest.TestAdapter/Services/MessageLevel.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/UnitTestElementSinkExtensions.cs b/src/Adapter/MSTest.TestAdapter/Services/UnitTestElementSinkExtensions.cs similarity index 100% rename from src/Adapter/MSTestAdapter.PlatformServices/Services/UnitTestElementSinkExtensions.cs rename to src/Adapter/MSTest.TestAdapter/Services/UnitTestElementSinkExtensions.cs diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs index 57abe652784..baf709a8e77 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs @@ -40,7 +40,12 @@ internal class TestablePlatformServiceProvider : IPlatformServiceProvider public IFileOperations FileOperations => MockFileOperations.Object; - public ITraceLogger AdapterTraceLogger { get => MockTraceLogger.Object; set => throw new NotSupportedException(); } + // The getter always returns the mock so tests can verify trace logging. The setter is a no-op: + // MSTest.TestAdapter's [ModuleInitializer] (MSTestExecutor.SetPlatformLogger) legitimately assigns + // PlatformServiceProvider.Instance.AdapterTraceLogger when that module is first touched (e.g. via the + // ToAdapterMessageLogger bridge), so this double must tolerate the assignment exactly as the real + // PlatformServiceProvider does, rather than throwing. + public ITraceLogger AdapterTraceLogger { get => MockTraceLogger.Object; set => _ = value; } #if !WINDOWS_UWP && !WIN_UI public ITestDeployment TestDeployment => MockTestDeployment.Object;