diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestResultExtensions.cs b/src/Adapter/MSTest.TestAdapter/Extensions/TestResultExtensions.cs similarity index 98% rename from src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestResultExtensions.cs rename to src/Adapter/MSTest.TestAdapter/Extensions/TestResultExtensions.cs index 0eac8b99a2..0a4824f828 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestResultExtensions.cs +++ b/src/Adapter/MSTest.TestAdapter/Extensions/TestResultExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; using VSTestAttachmentSet = Microsoft.VisualStudio.TestPlatform.ObjectModel.AttachmentSet; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/UnitTestOutcomeHelper.cs b/src/Adapter/MSTest.TestAdapter/Helpers/UnitTestOutcomeHelper.cs similarity index 100% rename from src/Adapter/MSTestAdapter.PlatformServices/Helpers/UnitTestOutcomeHelper.cs rename to src/Adapter/MSTest.TestAdapter/Helpers/UnitTestOutcomeHelper.cs diff --git a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj index 20616c2118..b4792d9cad 100644 --- a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj +++ b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj @@ -193,6 +193,7 @@ + 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/TestResultRecorderExtensions.cs b/src/Adapter/MSTest.TestAdapter/Services/TestResultRecorderExtensions.cs similarity index 71% rename from src/Adapter/MSTestAdapter.PlatformServices/Services/TestResultRecorderExtensions.cs rename to src/Adapter/MSTest.TestAdapter/Services/TestResultRecorderExtensions.cs index 911f563609..d0eba71eb2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestResultRecorderExtensions.cs +++ b/src/Adapter/MSTest.TestAdapter/Services/TestResultRecorderExtensions.cs @@ -1,25 +1,27 @@ // 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; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using FrameworkTestResult = Microsoft.VisualStudio.TestTools.UnitTesting.TestResult; -namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; /// /// Bridges a VSTest to the platform-agnostic . /// /// -/// This is the single translation point between the VSTest result object model (TestResult, -/// TestOutcome, attachments, ...) and the platform services result-reporting abstraction. It is -/// expected to move entirely into the adapter layer once the execution pipeline no longer flows VSTest -/// recorders through the platform services. +/// This is the single translation point between the neutral execution model ( and +/// the framework ) and the VSTest result object model (TestResult, +/// TestOutcome, attachments, ...). It lives in the adapter layer; the platform services engine reports +/// through the neutral only. The element's host test case is resolved (and +/// materialized/cached on demand for internally discovered tests) via +/// so recorded results preserve host-injected data (test-case-management / data-collector properties) with full fidelity. /// internal static class TestResultRecorderExtensions { @@ -46,14 +48,15 @@ public HostTestResultRecorder(ITestExecutionRecorder testExecutionRecorder, stri _settings = settings; } - public void RecordStart(TestCase testCase) - => _testExecutionRecorder.RecordStart(testCase); + public void RecordStart(UnitTestElement testElement) + => _testExecutionRecorder.RecordStart(testElement.GetOrCreateHostTestCase()); - public void RecordEmptyResult(TestCase testCase) - => _testExecutionRecorder.RecordEnd(testCase, TestOutcome.None); + public void RecordEmptyResult(UnitTestElement testElement) + => _testExecutionRecorder.RecordEnd(testElement.GetOrCreateHostTestCase(), TestOutcome.None); - public bool RecordResult(TestCase testCase, FrameworkTestResult unitTestResult, DateTimeOffset startTime, DateTimeOffset endTime) + public bool RecordResult(UnitTestElement testElement, FrameworkTestResult unitTestResult, DateTimeOffset startTime, DateTimeOffset endTime) { + TestCase testCase = testElement.GetOrCreateHostTestCase(); var testResult = unitTestResult.ToTestResult(testCase, startTime, endTime, _computerName, _settings); _testExecutionRecorder.RecordEnd(testCase, testResult.Outcome); 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/src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs b/src/Adapter/MSTest.TestAdapter/TestMethodFilter.cs similarity index 90% rename from src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs rename to src/Adapter/MSTest.TestAdapter/TestMethodFilter.cs index ecae88d02e..7e2a054cc6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs +++ b/src/Adapter/MSTest.TestAdapter/TestMethodFilter.cs @@ -182,3 +182,20 @@ public bool Matches(UnitTestElement testElement) } } } + +/// +/// Adapter-boundary that closes over the VSTest discovery/run context +/// and produces the neutral via . Created at the +/// boundary and passed into the platform services engine/discoverer so the filter (and any parse-error report) +/// is built at the exact point the engine needs it, preserving the original per-source timing. +/// +internal sealed class TestElementFilterProvider : ITestElementFilterProvider +{ + private readonly TestMethodFilter _testMethodFilter = new(); + private readonly IDiscoveryContext? _context; + + public TestElementFilterProvider(IDiscoveryContext? context) => _context = context; + + public ITestElementFilter? GetTestElementFilter(IAdapterMessageLogger logger, out bool filterHasError) + => _testMethodFilter.GetTestElementFilter(_context, logger, out filterHasError); +} diff --git a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs index b57182075e..ebe26f397c 100644 --- a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs +++ b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs @@ -85,9 +85,9 @@ internal async Task DiscoverTestsAsync(IEnumerable sources, IDiscoveryCo try { IAdapterMessageLogger adapterLogger = logger.ToAdapterMessageLogger(); - if (MSTestDiscovererHelpers.InitializeDiscovery(sources, discoveryContext, adapterLogger, configuration, _testSourceHandler)) + if (MSTestDiscovererHelpers.InitializeDiscovery(sources, discoveryContext?.RunSettings?.SettingsXml, adapterLogger, configuration, _testSourceHandler)) { - new UnitTestDiscoverer(_testSourceHandler).DiscoverTests(sources, adapterLogger, discoverySink.ToUnitTestElementSink(), discoveryContext, isMTP); + new UnitTestDiscoverer(_testSourceHandler).DiscoverTests(sources, adapterLogger, discoverySink.ToUnitTestElementSink(), discoveryContext?.RunSettings?.SettingsXml, new TestElementFilterProvider(discoveryContext), isMTP); } } finally diff --git a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs index a8ada1235f..f1fd235958 100644 --- a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs +++ b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs @@ -2,8 +2,11 @@ // 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.MSTest.TestAdapter.Extensions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.VSTestAdapter; 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.MSTestAdapter.PlatformServices.Resources; @@ -139,12 +142,26 @@ internal async Task RunTestsAsync(IEnumerable? tests, IRunContext? run try { - if (!MSTestDiscovererHelpers.InitializeDiscovery(from test in tests select test.Source, runContext, frameworkHandle.ToAdapterMessageLogger(), configuration, new TestSourceHandler())) + if (!MSTestDiscovererHelpers.InitializeDiscovery(from test in tests select test.Source, runContext?.RunSettings?.SettingsXml, frameworkHandle.ToAdapterMessageLogger(), configuration, new TestSourceHandler())) { return; } - await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(tests, runContext, frameworkHandle, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); + // Convert the host test cases into the neutral model at this boundary so the execution engine runs + // on UnitTestElement end-to-end. Each element keeps its originating test case as an opaque handle + // (for full-fidelity result recording and deployment) plus the host execution-context (TCM) + // properties surfaced through TestContext. + UnitTestElement[] testElements = [.. tests.Select(ToUnitTestElement)]; + + // Translate the VSTest recorder into the platform-agnostic result recorder at this boundary; the + // execution engine reports results through this neutral recorder and never constructs VSTest results. + ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings); + + // Extract the neutral run inputs (test-run directory + run settings XML) from the host run context at + // 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.ToAdapterMessageLogger(), testResultRecorder, new TestElementFilterProvider(runContext), testRunToken).ConfigureAwait(false)).ConfigureAwait(false); } finally { @@ -152,6 +169,14 @@ internal async Task RunTestsAsync(IEnumerable? tests, IRunContext? run } } + private static UnitTestElement ToUnitTestElement(TestCase testCase) + { + UnitTestElement testElement = testCase.ToUnitTestElementWithUpdatedSource(testCase.Source); + testElement.ExecutionContextProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); + testElement.HostRecordingHandle = testCase; + return testElement; + } + internal async Task RunTestsAsync(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle, IConfiguration? configuration, bool isMTP) { if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled) @@ -183,13 +208,22 @@ internal async Task RunTestsAsync(IEnumerable? sources, IRunContext? run try { TestSourceHandler testSourceHandler = new(); - if (!MSTestDiscovererHelpers.InitializeDiscovery(sources, runContext, frameworkHandle.ToAdapterMessageLogger(), configuration, testSourceHandler)) + if (!MSTestDiscovererHelpers.InitializeDiscovery(sources, runContext?.RunSettings?.SettingsXml, frameworkHandle.ToAdapterMessageLogger(), configuration, testSourceHandler)) { return; } sources = testSourceHandler.GetTestSources(sources); - await RunTestsFromRightContextAsync(frameworkHandle, async testRunToken => await TestExecutionManager.RunTestsAsync(sources, runContext, frameworkHandle, testSourceHandler, isMTP, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); + + // Translate the VSTest recorder into the platform-agnostic result recorder at this boundary; the + // execution engine reports results through this neutral recorder and never constructs VSTest results. + ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings); + + // Extract the neutral run inputs (test-run directory + run settings XML) from the host run context at + // 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.ToAdapterMessageLogger(), testResultRecorder, new TestElementFilterProvider(runContext), testSourceHandler, isMTP, testRunToken).ConfigureAwait(false)).ConfigureAwait(false); } finally { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Deployment/DeploymentContext.cs b/src/Adapter/MSTestAdapter.PlatformServices/Deployment/DeploymentContext.cs new file mode 100644 index 0000000000..e6c35de10c --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Deployment/DeploymentContext.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; + +/// +/// Platform-agnostic inputs the execution pipeline needs from the running test host: the test-run/results +/// directory and the run settings XML. This lets the platform services execution and deployment layers run +/// without taking a dependency on a specific test platform's run-context object model (for example the VSTest +/// IRunContext / IRunSettings types). It is populated at the adapter boundary. +/// +internal sealed class DeploymentContext +{ + public DeploymentContext(string? testRunDirectory, string? runSettingsXml) + { + TestRunDirectory = testRunDirectory; + RunSettingsXml = runSettingsXml; + } + + /// + /// Gets the host-provided test-run directory (the root under which results/deployment directories are + /// created), or to fall back to a temp directory. + /// + public string? TestRunDirectory { get; } + + /// + /// Gets the run settings XML supplied by the host, or when none was provided. + /// + public string? RunSettingsXml { get; } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.cs index 16e83134c8..454373159d 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumeratorWrapper.cs @@ -4,7 +4,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; @@ -23,7 +22,7 @@ internal sealed class AssemblyEnumeratorWrapper /// Gets test elements from an assembly. /// /// A collection of test elements. - internal static ICollection? GetTests(string? assemblyFileName, IRunSettings? runSettings, ITestSourceHandler testSourceHandler, bool isMTP, out List warnings) + internal static ICollection? GetTests(string? assemblyFileName, string? settingsXml, ITestSourceHandler testSourceHandler, bool isMTP, out List warnings) { warnings = []; @@ -48,7 +47,7 @@ internal sealed class AssemblyEnumeratorWrapper try { // Load the assembly in isolation if required. - AssemblyEnumerationResult result = GetTestsInIsolation(fullFilePath, runSettings, isMTP); + AssemblyEnumerationResult result = GetTestsInIsolation(fullFilePath, settingsXml, isMTP); warnings.AddRange(result.Warnings); return result.TestElements; } @@ -86,9 +85,9 @@ internal sealed class AssemblyEnumeratorWrapper } } - private static AssemblyEnumerationResult GetTestsInIsolation(string fullFilePath, IRunSettings? runSettings, bool isMTP) + private static AssemblyEnumerationResult GetTestsInIsolation(string fullFilePath, string? settingsXml, bool isMTP) { - using ITestSourceHost isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(fullFilePath, runSettings); + using ITestSourceHost isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(fullFilePath, settingsXml); // Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain var assemblyEnumerator = (AssemblyEnumerator)isolationHost.CreateInstanceForType(typeof(AssemblyEnumerator), [MSTestSettings.CurrentSettings])!; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/UnitTestDiscoverer.cs b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/UnitTestDiscoverer.cs index 05ce24f292..1ae8056ed7 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Discovery/UnitTestDiscoverer.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Discovery/UnitTestDiscoverer.cs @@ -4,7 +4,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; @@ -12,13 +11,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; [SuppressMessage("Performance", "CA1852: Seal internal types", Justification = "Overrides required for testability")] internal class UnitTestDiscoverer { - private readonly TestMethodFilter _testMethodFilter; - - internal UnitTestDiscoverer(ITestSourceHandler testSourceHandler) - { - _testMethodFilter = new TestMethodFilter(); - _testSource = testSourceHandler; - } + internal UnitTestDiscoverer(ITestSourceHandler testSourceHandler) => _testSource = testSourceHandler; /// /// Discovers the tests available from the provided sources. @@ -26,18 +19,20 @@ internal UnitTestDiscoverer(ITestSourceHandler testSourceHandler) /// The sources. /// The logger. /// The discovery Sink. - /// The discovery context. + /// The run settings XML, or when none was provided. + /// Provider for the test filter, or for no filter. /// Flag set to true when the platform running discovery is MTP. internal void DiscoverTests( IEnumerable sources, IAdapterMessageLogger logger, IUnitTestElementSink discoverySink, - IDiscoveryContext discoveryContext, + string? settingsXml, + ITestElementFilterProvider? filterProvider, bool isMTP) { foreach (string source in sources) { - DiscoverTestsInSource(source, logger, discoverySink, discoveryContext, isMTP); + DiscoverTestsInSource(source, logger, discoverySink, settingsXml, filterProvider, isMTP); } } @@ -47,16 +42,18 @@ internal void DiscoverTests( /// The source. /// The logger. /// The discovery Sink. - /// The discovery context. + /// The run settings XML, or when none was provided. + /// Provider for the test filter, or for no filter. /// Flag set to true when the platform running discovery is MTP. internal virtual void DiscoverTestsInSource( string source, IAdapterMessageLogger logger, IUnitTestElementSink discoverySink, - IDiscoveryContext? discoveryContext, + string? settingsXml, + ITestElementFilterProvider? filterProvider, bool isMTP) { - ICollection? testElements = AssemblyEnumeratorWrapper.GetTests(source, discoveryContext?.RunSettings, _testSource, isMTP, out List warnings); + ICollection? testElements = AssemblyEnumeratorWrapper.GetTests(source, settingsXml, _testSource, isMTP, out List warnings); if (MSTestSettings.CurrentSettings.TreatDiscoveryWarningsAsErrors) { @@ -103,15 +100,16 @@ internal virtual void DiscoverTestsInSource( source); } - SendTestCases(testElements, discoverySink, discoveryContext, logger); + SendTestCases(testElements, discoverySink, filterProvider, logger); } private readonly ITestSourceHandler _testSource; - internal void SendTestCases(IEnumerable testElements, IUnitTestElementSink discoverySink, IDiscoveryContext? discoveryContext, IAdapterMessageLogger logger) + internal static void SendTestCases(IEnumerable testElements, IUnitTestElementSink discoverySink, ITestElementFilterProvider? filterProvider, IAdapterMessageLogger logger) { // Get filter and skip discovery in case filter expression has parsing error. - ITestElementFilter? filter = _testMethodFilter.GetTestElementFilter(discoveryContext, logger, out bool filterHasError); + bool filterHasError = false; + ITestElementFilter? filter = filterProvider?.GetTestElementFilter(logger, out filterHasError); if (filterHasError) { return; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs index a41268c299..16081a0f4b 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using AdapterApplicationStateGuard = Microsoft.VisualStudio.TestPlatform.MSTestAdapter.ApplicationStateGuard; @@ -53,7 +53,7 @@ public void MarkClassComplete(string fullClassName) } } - internal static void ForceCleanup(TypeCache typeCache, IDictionary sourceLevelParameters, IMessageLogger logger) + internal static void ForceCleanup(TypeCache typeCache, IDictionary sourceLevelParameters, IAdapterMessageLogger logger) { IEnumerable classInfoCache = typeCache.ClassInfoListWithExecutableCleanupMethods; foreach (TestClassInfo classInfo in classInfoCache) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TcmTestPropertiesProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TcmTestPropertiesProvider.cs index 92d8534c6b..30c8af1f17 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TcmTestPropertiesProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TcmTestPropertiesProvider.cs @@ -8,16 +8,24 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; /// -/// Reads and parses the TcmTestProperties in order to populate them in TestRunParameters. +/// Reads and parses the test-case-management (TCM) properties supplied by a host on a test case, in order to +/// surface them to the running test through TestContext. /// +/// +/// This is a translation point between the VSTest test-case object model and the neutral execution context bag +/// consumed by the platform services engine. It runs at the adapter boundary (when a host hands the adapter +/// concrete test cases to run) and produces a string-keyed dictionary so the engine never depends on the VSTest +/// TestProperty object model. It is expected to move entirely into the adapter layer once the platform +/// services no longer reference the VSTest object model. +/// internal static class TcmTestPropertiesProvider { /// - /// Gets tcm properties from test case. + /// Gets the host execution context properties from a test case, keyed by the host property identifier. /// /// Test case. - /// Tcm properties. - public static IDictionary? GetTcmProperties(TestPlatformObjectModel.TestCase? testCase) + /// The properties, or when the test case is null or carries no test case id. + public static IReadOnlyDictionary? GetTcmProperties(TestPlatformObjectModel.TestCase? testCase) { // Return empty properties when testCase is null or when test case id is zero. if (testCase == null || @@ -26,26 +34,26 @@ internal static class TcmTestPropertiesProvider return null; } - var tcmProperties = new Dictionary(capacity: 15) + var tcmProperties = new Dictionary(capacity: 15) { // Step 1: Add common properties. - [EngineConstants.TestRunIdProperty] = testCase.GetPropertyValue(EngineConstants.TestRunIdProperty, default), - [EngineConstants.TestPlanIdProperty] = testCase.GetPropertyValue(EngineConstants.TestPlanIdProperty, default), - [EngineConstants.BuildConfigurationIdProperty] = testCase.GetPropertyValue(EngineConstants.BuildConfigurationIdProperty, default), - [EngineConstants.BuildDirectoryProperty] = testCase.GetPropertyValue(EngineConstants.BuildDirectoryProperty, default), - [EngineConstants.BuildFlavorProperty] = testCase.GetPropertyValue(EngineConstants.BuildFlavorProperty, default), - [EngineConstants.BuildNumberProperty] = testCase.GetPropertyValue(EngineConstants.BuildNumberProperty, default), - [EngineConstants.BuildPlatformProperty] = testCase.GetPropertyValue(EngineConstants.BuildPlatformProperty, default), - [EngineConstants.BuildUriProperty] = testCase.GetPropertyValue(EngineConstants.BuildUriProperty, default), - [EngineConstants.TfsServerCollectionUrlProperty] = testCase.GetPropertyValue(EngineConstants.TfsServerCollectionUrlProperty, default), - [EngineConstants.TfsTeamProjectProperty] = testCase.GetPropertyValue(EngineConstants.TfsTeamProjectProperty, default), - [EngineConstants.IsInLabEnvironmentProperty] = testCase.GetPropertyValue(EngineConstants.IsInLabEnvironmentProperty, default), + [EngineConstants.TestRunIdProperty.Id] = testCase.GetPropertyValue(EngineConstants.TestRunIdProperty, default), + [EngineConstants.TestPlanIdProperty.Id] = testCase.GetPropertyValue(EngineConstants.TestPlanIdProperty, default), + [EngineConstants.BuildConfigurationIdProperty.Id] = testCase.GetPropertyValue(EngineConstants.BuildConfigurationIdProperty, default), + [EngineConstants.BuildDirectoryProperty.Id] = testCase.GetPropertyValue(EngineConstants.BuildDirectoryProperty, default), + [EngineConstants.BuildFlavorProperty.Id] = testCase.GetPropertyValue(EngineConstants.BuildFlavorProperty, default), + [EngineConstants.BuildNumberProperty.Id] = testCase.GetPropertyValue(EngineConstants.BuildNumberProperty, default), + [EngineConstants.BuildPlatformProperty.Id] = testCase.GetPropertyValue(EngineConstants.BuildPlatformProperty, default), + [EngineConstants.BuildUriProperty.Id] = testCase.GetPropertyValue(EngineConstants.BuildUriProperty, default), + [EngineConstants.TfsServerCollectionUrlProperty.Id] = testCase.GetPropertyValue(EngineConstants.TfsServerCollectionUrlProperty, default), + [EngineConstants.TfsTeamProjectProperty.Id] = testCase.GetPropertyValue(EngineConstants.TfsTeamProjectProperty, default), + [EngineConstants.IsInLabEnvironmentProperty.Id] = testCase.GetPropertyValue(EngineConstants.IsInLabEnvironmentProperty, default), // Step 2: Add test case specific properties. - [EngineConstants.TestCaseIdProperty] = testCase.GetPropertyValue(EngineConstants.TestCaseIdProperty, default), - [EngineConstants.TestConfigurationIdProperty] = testCase.GetPropertyValue(EngineConstants.TestConfigurationIdProperty, default), - [EngineConstants.TestConfigurationNameProperty] = testCase.GetPropertyValue(EngineConstants.TestConfigurationNameProperty, default), - [EngineConstants.TestPointIdProperty] = testCase.GetPropertyValue(EngineConstants.TestPointIdProperty, default), + [EngineConstants.TestCaseIdProperty.Id] = testCase.GetPropertyValue(EngineConstants.TestCaseIdProperty, default), + [EngineConstants.TestConfigurationIdProperty.Id] = testCase.GetPropertyValue(EngineConstants.TestConfigurationIdProperty, default), + [EngineConstants.TestConfigurationNameProperty.Id] = testCase.GetPropertyValue(EngineConstants.TestConfigurationNameProperty, default), + [EngineConstants.TestPointIdProperty.Id] = testCase.GetPropertyValue(EngineConstants.TestPointIdProperty, default), }; return tcmProperties; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestCaseDiscoverySink.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestCaseDiscoverySink.cs index 47d5156839..d76e64a597 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestCaseDiscoverySink.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestCaseDiscoverySink.cs @@ -3,30 +3,27 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; /// -/// The test case discovery sink used internally by execution to collect the discovered tests. +/// The discovery sink used internally by execution to collect the tests discovered from a source. /// /// -/// It implements the platform-agnostic but still materializes a VSTest -/// for every discovered element, because the execution pipeline currently consumes -/// instances. That materialization is expected to disappear once execution flows the -/// neutral model end-to-end. +/// It collects the neutral model directly, so the execution pipeline can flow +/// discovered tests without round-tripping them through a VSTest TestCase. /// internal sealed class TestCaseDiscoverySink : IUnitTestElementSink { /// - /// Gets the tests. + /// Gets the discovered tests. /// - public ICollection Tests { get; } = []; + public ICollection TestElements { get; } = []; /// - /// Collects the discovered test, materializing it as a VSTest . + /// Collects the discovered test element. /// /// The discovered test element. public void SendTestElement(UnitTestElement testElement) - => Tests.Add(testElement.ToTestCase()); + => TestElements.Add(testElement); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs index 15c789c8e9..9f7b4ffdd3 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Parallelization.cs @@ -1,13 +1,11 @@ // 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.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; 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.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; using ExecutionScope = Microsoft.VisualStudio.TestTools.UnitTesting.ExecutionScope; @@ -20,16 +18,21 @@ internal partial class TestExecutionManager /// Execute the parameter tests. /// /// Tests to execute. - /// The run context. - /// Handle to record test start/end/results. + /// Host-provided test-run directory and run settings XML. + /// 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, IRunContext? runContext, IFrameworkHandle frameworkHandle, bool isDeploymentDone) + internal virtual async Task ExecuteTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone) { - InitializeRandomTestOrder(frameworkHandle); + _testResultRecorder = testResultRecorder; + _testElementFilterProvider = filterProvider; + + InitializeRandomTestOrder(messageLogger); var testsBySource = (from test in tests - group test by test.Source into testGroup - select new { Source = testGroup.Key, Tests = (IEnumerable)testGroup }).ToArray(); + group test by test.TestMethod.AssemblyName into testGroup + select new { Source = testGroup.Key, Tests = (IEnumerable)testGroup }).ToArray(); if (_testOrderRandom is { } random) { @@ -39,7 +42,7 @@ group test by test.Source into testGroup foreach (var group in testsBySource) { _testRunCancellationToken?.ThrowIfCancellationRequested(); - await ExecuteTestsInSourceAsync(group.Tests, runContext, frameworkHandle, group.Source, isDeploymentDone).ConfigureAwait(false); + await ExecuteTestsInSourceAsync(group.Tests, deploymentContext, messageLogger, group.Source, isDeploymentDone).ConfigureAwait(false); } } @@ -47,11 +50,11 @@ group test by test.Source into testGroup /// Execute the parameter tests present in parameter source. /// /// Tests to execute. - /// The run context. - /// Handle to record test start/end/results. + /// Host-provided test-run directory and run settings XML. + /// 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, IRunContext? runContext, 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"); @@ -62,9 +65,9 @@ private async Task ExecuteTestsInSourceAsync(IEnumerable tests, IRunCo } #endif - IAdapterMessageLogger adapterMessageLogger = frameworkHandle.ToAdapterMessageLogger(); + IAdapterMessageLogger adapterMessageLogger = messageLogger; - using ITestSourceHost isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(source, runContext?.RunSettings); + using ITestSourceHost isolationHost = PlatformServiceProvider.Instance.CreateTestSourceHost(source, deploymentContext.RunSettingsXml); bool usesAppDomains = isolationHost is TestSourceHost { UsesAppDomain: true }; if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled) @@ -73,7 +76,8 @@ private async Task ExecuteTestsInSourceAsync(IEnumerable tests, IRunCo } // Default test set is filtered tests based on user provided filter criteria - ITestElementFilter? filter = _testMethodFilter.GetTestElementFilter(runContext, adapterMessageLogger, out bool filterHasError); + bool filterHasError = false; + ITestElementFilter? filter = _testElementFilterProvider?.GetTestElementFilter(adapterMessageLogger, out filterHasError); if (filterHasError) { // Bail out without processing everything else below. @@ -113,19 +117,13 @@ private async Task ExecuteTestsInSourceAsync(IEnumerable tests, IRunCo int parallelWorkers = sourceSettings.Workers; ExecutionScope parallelScope = sourceSettings.Scope; - // Convert each test to its UnitTestElement exactly once and carry the pair through filtering and - // shuffling, so the surviving tests aren't converted a second time when building unitTestElements below. - (TestCase TestCase, UnitTestElement Element)[] testsToRunPairs = - [.. tests - .Select(test => (TestCase: test, Element: test.ToUnitTestElementWithUpdatedSource(source))) - .Where(pair => MatchTestFilter(filter, pair.Element))]; + UnitTestElement[] testsToRun = [.. tests.Where(t => MatchTestFilter(filter, t, source))]; if (_testOrderRandom is { } sourceRandom) { - Shuffle(sourceRandom, testsToRunPairs); + Shuffle(sourceRandom, testsToRun); } - TestCase[] testsToRun = [.. testsToRunPairs.Select(pair => pair.TestCase)]; - UnitTestElement[] unitTestElements = [.. testsToRunPairs.Select(pair => pair.Element)]; + UnitTestElement[] unitTestElements = [.. testsToRun.Select(e => e.WithUpdatedSource(source))]; // Create an instance of a type defined in adapter so that adapter gets loaded in the child app domain var testRunner = (UnitTestRunner)isolationHost.CreateInstanceForType( typeof(UnitTestRunner), @@ -157,9 +155,9 @@ [.. tests // Parallel and not parallel sets. // Single-pass partition: enumerate testsToRun once via GroupBy (lazy GroupBy evaluated twice // — once per FirstOrDefault — caused 2 full passes over testsToRun). - IEnumerable? parallelizableTestSet = null; - IEnumerable? nonParallelizableTestSet = null; - foreach (IGrouping group in testsToRun.GroupBy(t => t.GetPropertyValue(EngineConstants.DoNotParallelizeProperty, false))) + IEnumerable? parallelizableTestSet = null; + IEnumerable? nonParallelizableTestSet = null; + foreach (IGrouping group in testsToRun.GroupBy(t => t.DoNotParallelize)) { if (group.Key) { @@ -173,7 +171,7 @@ [.. tests if (parallelizableTestSet != null) { - ConcurrentQueue>? queue = null; + ConcurrentQueue>? queue = null; // Chunk the sets into further groups based on parallel level switch (parallelScope) @@ -181,13 +179,13 @@ [.. tests case ExecutionScope.MethodLevel: if (_testOrderRandom is { } methodRandom) { - IEnumerable[] methodChunks = [.. parallelizableTestSet.Select(t => (IEnumerable)[t])]; + IEnumerable[] methodChunks = [.. parallelizableTestSet.Select(t => (IEnumerable)[t])]; Shuffle(methodRandom, methodChunks); - queue = new ConcurrentQueue>(methodChunks); + queue = new ConcurrentQueue>(methodChunks); } else { - queue = new ConcurrentQueue>(parallelizableTestSet.Select(t => new[] { t })); + queue = new ConcurrentQueue>(parallelizableTestSet.Select(t => new[] { t })); } break; @@ -195,18 +193,18 @@ [.. tests case ExecutionScope.ClassLevel: if (_testOrderRandom is { } classRandom) { - IEnumerable[] classChunks = + IEnumerable[] classChunks = [ .. parallelizableTestSet - .GroupBy(t => t.GetPropertyValue(EngineConstants.TestClassNameProperty) as string) - .Select(g => (IEnumerable)g.ToArray()), + .GroupBy(t => t.TestMethod.FullClassName) + .Select(g => (IEnumerable)g.ToArray()), ]; Shuffle(classRandom, classChunks); - queue = new ConcurrentQueue>(classChunks); + queue = new ConcurrentQueue>(classChunks); } else { - queue = new ConcurrentQueue>(parallelizableTestSet.GroupBy(t => t.GetPropertyValue(EngineConstants.TestClassNameProperty) as string)); + queue = new ConcurrentQueue>(parallelizableTestSet.GroupBy(t => t.TestMethod.FullClassName)); } break; @@ -226,9 +224,9 @@ .. parallelizableTestSet { _testRunCancellationToken?.ThrowIfCancellationRequested(); - if (queue.TryDequeue(out IEnumerable? testSet)) + if (queue.TryDequeue(out IEnumerable? testSet)) { - await ExecuteTestsWithTestRunnerAsync(testSet, frameworkHandle, source, sourceLevelParameters, testRunner, usesAppDomains).ConfigureAwait(false); + await ExecuteTestsWithTestRunnerAsync(testSet, adapterMessageLogger, source, sourceLevelParameters, testRunner, usesAppDomains).ConfigureAwait(false); } } } @@ -263,17 +261,17 @@ .. parallelizableTestSet // Queue the non parallel set if (nonParallelizableTestSet != null) { - await ExecuteTestsWithTestRunnerAsync(nonParallelizableTestSet, frameworkHandle, source, sourceLevelParameters, testRunner, usesAppDomains).ConfigureAwait(false); + await ExecuteTestsWithTestRunnerAsync(nonParallelizableTestSet, adapterMessageLogger, source, sourceLevelParameters, testRunner, usesAppDomains).ConfigureAwait(false); } } else { - await ExecuteTestsWithTestRunnerAsync(testsToRun, frameworkHandle, source, sourceLevelParameters, testRunner, usesAppDomains).ConfigureAwait(false); + await ExecuteTestsWithTestRunnerAsync(testsToRun, adapterMessageLogger, source, sourceLevelParameters, testRunner, usesAppDomains).ConfigureAwait(false); } if (PlatformServiceProvider.Instance.IsGracefulStopRequested) { - testRunner.ForceCleanup(sourceLevelParameters!, new RemotingMessageLogger(frameworkHandle)); + testRunner.ForceCleanup(sourceLevelParameters!, new RemotingMessageLogger(adapterMessageLogger)); } if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Runner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Runner.cs index de21ede1f0..67de43cfe2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Runner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Runner.cs @@ -1,20 +1,15 @@ // 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.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; internal partial class TestExecutionManager { internal void SendTestResults( - TestCase test, + UnitTestElement test, TestTools.UnitTesting.TestResult[] unitTestResults, DateTimeOffset startTime, DateTimeOffset endTime, @@ -41,35 +36,40 @@ internal void SendTestResults( } } - // Takes the already-converted UnitTestElement (rather than reconstructing one from the TestCase) so the - // caller can convert each test exactly once and reuse that element both for filtering and downstream. - private static bool MatchTestFilter(ITestElementFilter? filter, UnitTestElement testElement) - // Keep the test when there is no filter or when the element satisfies the filter criteria. - => filter is null || filter.Matches(testElement); + private static bool MatchTestFilter(ITestElementFilter? filter, UnitTestElement test, string source) + { + if (filter is not null + && !filter.Matches(test.WithUpdatedSource(source))) + { + // Skip test if not fitting filter criteria. + return false; + } + + return true; + } private async Task ExecuteTestsWithTestRunnerAsync( - IEnumerable tests, - ITestExecutionRecorder testExecutionRecorder, + IEnumerable tests, + IAdapterMessageLogger adapterMessageLogger, string source, IDictionary sourceLevelParameters, UnitTestRunner testRunner, bool usesAppDomains) { - IEnumerable orderedTests = MSTestSettings.CurrentSettings.OrderTestsByNameInClass && !MSTestSettings.CurrentSettings.RandomizeTestOrder - ? tests.OrderBy(t => t.GetManagedType()).ThenBy(t => t.GetManagedMethod()) + // Ordering keys mirror the historical VSTest ManagedType/ManagedMethod test-case properties, which are + // only populated when the test method carries managed method metadata (see UnitTestElement.ToTestCase). + IEnumerable orderedTests = MSTestSettings.CurrentSettings.OrderTestsByNameInClass && !MSTestSettings.CurrentSettings.RandomizeTestOrder + ? tests.OrderBy(t => t.TestMethod.HasManagedMethodAndTypeProperties ? t.TestMethod.ManagedTypeName : null) + .ThenBy(t => t.TestMethod.HasManagedMethodAndTypeProperties ? t.TestMethod.ManagedMethodName : null) : tests; - // If testRunner is in a different AppDomain, we cannot pass the testExecutionRecorder directly. + // If testRunner is in a different AppDomain, we cannot pass the message logger directly. // Instead, we pass a proxy (remoting object) that is marshallable by ref. - IMessageLogger remotingMessageLogger = usesAppDomains - ? new RemotingMessageLogger(testExecutionRecorder) - : testExecutionRecorder; - - // Translate the VSTest recorder into the platform-agnostic result recorder a single time for this - // test set. This is the boundary at which VSTest result construction is applied. - ITestResultRecorder testResultRecorder = testExecutionRecorder.ToTestResultRecorder(_environment.MachineName, MSTestSettings.CurrentSettings); + IAdapterMessageLogger remotingMessageLogger = usesAppDomains + ? new RemotingMessageLogger(adapterMessageLogger) + : adapterMessageLogger; - foreach (TestCase currentTest in orderedTests) + foreach (UnitTestElement currentTest in orderedTests) { _testRunCancellationToken?.ThrowIfCancellationRequested(); if (PlatformServiceProvider.Instance.IsGracefulStopRequested) @@ -77,9 +77,11 @@ private async Task ExecuteTestsWithTestRunnerAsync( break; } - UnitTestElement unitTestElement = currentTest.ToUnitTestElementWithUpdatedSource(source); + UnitTestElement unitTestElement = currentTest.WithUpdatedSource(source); - testResultRecorder.RecordStart(currentTest); + // Report through the neutral recorder using the element itself; the adapter-side recorder resolves + // the host test case (preserving host-injected TCM / data-collector properties) with full fidelity. + _testResultRecorder.RecordStart(currentTest); DateTimeOffset startTime = DateTimeOffset.Now; @@ -89,7 +91,7 @@ private async Task ExecuteTestsWithTestRunnerAsync( } // Run single test passing test context properties to it. - IDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(currentTest); + IReadOnlyDictionary? tcmProperties = currentTest.ExecutionContextProperties; Dictionary testContextProperties = GetTestContextProperties(tcmProperties, sourceLevelParameters, unitTestElement); TestTools.UnitTesting.TestResult[] unitTestResult; @@ -117,7 +119,7 @@ private async Task ExecuteTestsWithTestRunnerAsync( DateTimeOffset endTime = DateTimeOffset.Now; - SendTestResults(currentTest, unitTestResult, startTime, endTime, testResultRecorder); + SendTestResults(currentTest, unitTestResult, startTime, endTime, _testResultRecorder); } } } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.TestContext.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.TestContext.cs index 7db20bc62a..e465af8e72 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.TestContext.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.TestContext.cs @@ -3,9 +3,7 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -20,7 +18,7 @@ internal partial class TestExecutionManager /// The unit test element to get properties from. /// Test context properties. private static Dictionary GetTestContextProperties( - IDictionary? tcmProperties, + IReadOnlyDictionary? tcmProperties, IDictionary sourceLevelParameters, UnitTestElement unitTestElement) { @@ -38,9 +36,9 @@ internal partial class TestExecutionManager // Add tcm properties. if (tcmProperties is not null) { - foreach (KeyValuePair kvp in tcmProperties) + foreach (KeyValuePair kvp in tcmProperties) { - testContextProperties[kvp.Key.Id] = kvp.Value; + testContextProperties[kvp.Key] = kvp.Value; } } @@ -52,7 +50,7 @@ internal partial class TestExecutionManager if (unitTestElement.Traits is { Length: > 0 }) { - foreach (Trait trait in unitTestElement.Traits) + foreach (TestTrait trait in unitTestElement.Traits) { ValidateAndAssignTestProperty(testContextProperties, trait.Name, trait.Value); } @@ -99,16 +97,16 @@ private static void ValidateAndAssignTestProperty( } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle errors in user specified run parameters")] - private void CacheSessionParameters(IRunContext? runContext, ITestExecutionRecorder testExecutionRecorder) + private void CacheSessionParameters(string? settingsXml, IAdapterMessageLogger messageLogger) { - if (StringEx.IsNullOrEmpty(runContext?.RunSettings?.SettingsXml)) + if (StringEx.IsNullOrEmpty(settingsXml)) { return; } try { - Dictionary? testRunParameters = RunSettingsUtilities.GetTestRunParameters(runContext.RunSettings.SettingsXml); + Dictionary? testRunParameters = RunSettingsUtilities.GetTestRunParameters(settingsXml); if (testRunParameters != null) { // Clear sessionParameters to prevent key collisions of test run parameters in case @@ -122,7 +120,7 @@ private void CacheSessionParameters(IRunContext? runContext, ITestExecutionRecor } catch (Exception ex) { - testExecutionRecorder.SendMessage(TestMessageLevel.Error, ex.Message); + messageLogger.SendMessage(MessageLevel.Error, ex.Message); } } } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Utilities.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Utilities.cs index c21f1add17..d23abdff7b 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Utilities.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.Utilities.cs @@ -1,28 +1,35 @@ // 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.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; internal partial class TestExecutionManager { + /// + /// A message logger that can be marshaled into the isolation (child app-domain) host so log messages + /// raised while running a test cross back to the parent-domain . + /// Marshaling is by reference (the wrapped logger stays in the parent domain), so no VSTest object-model + /// type is required in the child domain. + /// private sealed class RemotingMessageLogger : #if NETFRAMEWORK MarshalByRefObject, #endif - IMessageLogger + IAdapterMessageLogger { - private readonly IMessageLogger _realMessageLogger; + private readonly IAdapterMessageLogger _realMessageLogger; - public RemotingMessageLogger(IMessageLogger messageLogger) + public RemotingMessageLogger(IAdapterMessageLogger messageLogger) => _realMessageLogger = messageLogger; - public void SendMessage(TestMessageLevel testMessageLevel, string message) - => _realMessageLogger.SendMessage(testMessageLevel, message); + public void SendMessage(MessageLevel level, string message) + => _realMessageLogger.SendMessage(level, message); } - private void InitializeRandomTestOrder(IMessageLogger frameworkHandle) + private void InitializeRandomTestOrder(IAdapterMessageLogger messageLogger) { if (!MSTestSettings.CurrentSettings.RandomizeTestOrder) { @@ -36,8 +43,8 @@ private void InitializeRandomTestOrder(IMessageLogger frameworkHandle) int seed = MSTestSettings.CurrentSettings.RandomTestOrderSeed ?? Guid.NewGuid().GetHashCode(); _testOrderRandom = new Random(seed); - frameworkHandle.SendMessage( - TestMessageLevel.Informational, + messageLogger.SendMessage( + MessageLevel.Informational, string.Format(CultureInfo.CurrentCulture, Resource.RandomTestOrderBanner, seed)); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs index 8db6351aea..d8858cdaea 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs @@ -1,11 +1,10 @@ // 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.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +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; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -21,7 +20,6 @@ internal partial class TestExecutionManager /// Dictionary for test run parameters. /// private readonly IDictionary _sessionParameters; - private readonly IEnvironment _environment; private readonly Func, Task> _taskFactory; /// @@ -35,19 +33,23 @@ internal partial class TestExecutionManager /// private Random? _testOrderRandom; + /// + /// Recorder used to report test start/end/results back to the host. Supplied at the adapter boundary and set + /// once per run in before any source is processed. + /// + private ITestResultRecorder _testResultRecorder = null!; + /// /// Initializes a new instance of the class. /// public TestExecutionManager() - : this(EnvironmentWrapper.Instance) + : this(null) { } - internal TestExecutionManager(IEnvironment environment, Func, Task>? taskFactory = null) + internal TestExecutionManager(Func, Task>? taskFactory = null) { - _testMethodFilter = new TestMethodFilter(); _sessionParameters = new Dictionary(); - _environment = environment; _taskFactory = taskFactory ?? DefaultFactoryAsync; } @@ -67,9 +69,11 @@ private static Task DefaultFactoryAsync(Func taskGetter) } /// - /// Gets or sets method filter for filtering tests. + /// Provider for the test filter, supplied at the adapter boundary and set once per run in + /// so the filter is built at the same point (and with the same per-source + /// timing) as before. /// - private readonly TestMethodFilter _testMethodFilter; + private ITestElementFilterProvider? _testElementFilterProvider; #if !WINDOWS_UWP && !WIN_UI /// @@ -82,30 +86,32 @@ private static Task DefaultFactoryAsync(Func taskGetter) /// Runs the tests. /// /// Tests to be run. - /// Context to use when executing the tests. - /// Handle to the framework to record results and to do framework operations. + /// Host-provided test-run directory and run settings XML. + /// 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, IRunContext? runContext, IFrameworkHandle frameworkHandle, 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(runContext != null, "runContext"); - DebugEx.Assert(frameworkHandle != null, "frameworkHandle"); + DebugEx.Assert(deploymentContext != null, "deploymentContext"); + DebugEx.Assert(messageLogger != null, "messageLogger"); DebugEx.Assert(runCancellationToken != null, "runCancellationToken"); _testRunCancellationToken = runCancellationToken; PlatformServiceProvider.Instance.TestRunCancellationToken = _testRunCancellationToken; #if !WINDOWS_UWP && !WIN_UI - bool isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, 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(runContext, frameworkHandle); + CacheSessionParameters(deploymentContext.RunSettingsXml, messageLogger); // Execute the tests - await ExecuteTestsAsync(tests, runContext, frameworkHandle, isDeploymentDone).ConfigureAwait(false); + await ExecuteTestsAsync(tests, deploymentContext, messageLogger, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false); #if !WINDOWS_UWP && !WIN_UI if (!_hasAnyTestFailed) @@ -115,16 +121,16 @@ internal async Task RunTestsAsync(IEnumerable tests, IRunContext? runC #endif } - internal async Task RunTestsAsync(IEnumerable sources, IRunContext? runContext, IFrameworkHandle frameworkHandle, 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; var discoverySink = new TestCaseDiscoverySink(); - var tests = new List(); + var tests = new List(); - IAdapterMessageLogger logger = frameworkHandle.ToAdapterMessageLogger(); + IAdapterMessageLogger logger = messageLogger; // deploy everything first. foreach (string source in sources) @@ -132,24 +138,24 @@ internal async Task RunTestsAsync(IEnumerable sources, IRunContext? runC _testRunCancellationToken?.ThrowIfCancellationRequested(); // discover the tests - GetUnitTestDiscoverer(testSourceHandler).DiscoverTestsInSource(source, logger, discoverySink, runContext, isMTP); - tests.AddRange(discoverySink.Tests); + GetUnitTestDiscoverer(testSourceHandler).DiscoverTestsInSource(source, logger, discoverySink, deploymentContext.RunSettingsXml, filterProvider, isMTP); + tests.AddRange(discoverySink.TestElements); // Clear discoverSinksTests so that it just stores test for one source at one point of time - discoverySink.Tests.Clear(); + discoverySink.TestElements.Clear(); } #if !WINDOWS_UWP && !WIN_UI - bool isDeploymentDone = PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, runContext, 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(runContext, frameworkHandle); + CacheSessionParameters(deploymentContext.RunSettingsXml, messageLogger); // Run tests. - await ExecuteTestsAsync(tests, runContext, frameworkHandle, isDeploymentDone).ConfigureAwait(false); + await ExecuteTestsAsync(tests, deploymentContext, messageLogger, testResultRecorder, filterProvider, isDeploymentDone).ConfigureAwait(false); #if !WINDOWS_UWP && !WIN_UI if (!_hasAnyTestFailed) @@ -159,5 +165,14 @@ internal async Task RunTestsAsync(IEnumerable sources, IRunContext? runC #endif } +#if !WINDOWS_UWP && !WIN_UI + /// + /// 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, IAdapterMessageLogger messageLogger) + => PlatformServiceProvider.Instance.TestDeployment.Deploy(tests, deploymentContext, messageLogger); +#endif + internal virtual UnitTestDiscoverer GetUnitTestDiscoverer(ITestSourceHandler testSourceHandler) => new(testSourceHandler); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestRunInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestRunInfo.cs index 5999529378..90e7c4698b 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestRunInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestRunInfo.cs @@ -2,7 +2,6 @@ // 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.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -39,7 +38,7 @@ public static TestRunInfo CreateFrom(IReadOnlyList testsToRun) private static PlannedTest ToPlannedTest(UnitTestElement element) { - Trait[]? traits = element.Traits; + TestTrait[]? traits = element.Traits; KeyValuePair[] testProperties; if (traits is { Length: > 0 }) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.RunSingleTest.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.RunSingleTest.cs index 8537205e91..396541bad2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.RunSingleTest.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.RunSingleTest.cs @@ -5,7 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; @@ -14,7 +13,7 @@ internal sealed partial class UnitTestRunner { // Task cannot cross app domains. // For now, TestExecutionManager will call this sync method which is hacky. - internal TestResult[] RunSingleTest(UnitTestElement unitTestElement, IDictionary testContextProperties, IMessageLogger messageLogger) + internal TestResult[] RunSingleTest(UnitTestElement unitTestElement, IDictionary testContextProperties, IAdapterMessageLogger messageLogger) => RunSingleTestAsync(unitTestElement, testContextProperties, messageLogger).GetAwaiter().GetResult(); /// @@ -24,7 +23,7 @@ internal TestResult[] RunSingleTest(UnitTestElement unitTestElement, IDictionary /// The test context properties. /// The message logger. /// The . - internal async Task RunSingleTestAsync(UnitTestElement unitTestElement, IDictionary testContextProperties, IMessageLogger messageLogger) + internal async Task RunSingleTestAsync(UnitTestElement unitTestElement, IDictionary testContextProperties, IAdapterMessageLogger messageLogger) { if (unitTestElement is null) { @@ -288,5 +287,5 @@ private static bool IsTestMethodRunnable( return true; } - internal void ForceCleanup(IDictionary sourceLevelParameters, IMessageLogger logger) => ClassCleanupManager.ForceCleanup(_typeCache, sourceLevelParameters, logger); + internal void ForceCleanup(IDictionary sourceLevelParameters, IAdapterMessageLogger logger) => ClassCleanupManager.ForceCleanup(_typeCache, sourceLevelParameters, logger); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.TestFilter.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.TestFilter.cs index 8b0ba506c4..89f2a214eb 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.TestFilter.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.TestFilter.cs @@ -6,7 +6,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; // The programmatic test-filter types (ITestFilter, TestFilterContext, TestFilterResult, @@ -150,7 +149,7 @@ private static TestFilterContext CreateFilterContext(UnitTestElement element) private async Task FinishFilteredOutTestAsync( TestMethod testMethod, IDictionary testContextProperties, - IMessageLogger messageLogger, + IAdapterMessageLogger messageLogger, TestResult[] filterResult, ITestContext testContextForTestExecution) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestCaseExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestCaseExtensions.cs index 4c69d171ee..9d2636d2d2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestCaseExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestCaseExtensions.cs @@ -118,7 +118,7 @@ internal static UnitTestElement ToUnitTestElementWithUpdatedSource(this TestCase if (testCase.Traits.Any()) { - testElement.Traits = [.. testCase.Traits]; + testElement.Traits = [.. testCase.Traits.Select(t => new TestTrait(t.Name, t.Value))]; } string[]? workItemIds = testCase.GetPropertyValue(EngineConstants.WorkItemIdsProperty, null); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/MSTestDiscovererHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/MSTestDiscovererHelpers.cs index 76ea3316ec..20b5ef982d 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/MSTestDiscovererHelpers.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/MSTestDiscovererHelpers.cs @@ -3,7 +3,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; @@ -20,7 +19,7 @@ internal static class MSTestDiscovererHelpers internal static bool AreValidSources(IEnumerable sources, ITestSourceHandler testSourceHandler) => sources.Any(source => testSourceHandler.ValidSourceExtensions.Any(extension => string.Equals(Path.GetExtension(source), extension, StringComparison.OrdinalIgnoreCase))); - internal static bool InitializeDiscovery(IEnumerable sources, IDiscoveryContext? discoveryContext, IAdapterMessageLogger messageLogger, IConfiguration? configuration, ITestSourceHandler testSourceHandler) + internal static bool InitializeDiscovery(IEnumerable sources, string? settingsXml, IAdapterMessageLogger messageLogger, IConfiguration? configuration, ITestSourceHandler testSourceHandler) { if (!AreValidSources(sources, testSourceHandler)) { @@ -30,7 +29,7 @@ internal static bool InitializeDiscovery(IEnumerable sources, IDiscovery // Populate the runsettings. try { - MSTestSettings.PopulateSettings(discoveryContext, messageLogger, configuration); + MSTestSettings.PopulateSettings(settingsXml, messageLogger, configuration); return true; } catch (AdapterSettingsException ex) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs index cd6c40f80c..518667da1d 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs @@ -5,9 +5,9 @@ using System.Security; #endif +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; @@ -198,7 +198,7 @@ internal static bool IsDoNotParallelizeSet(Assembly assembly) /// /// The member to inspect. /// List of traits. - internal static Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider) + internal static TestTrait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider) => PlatformServiceProvider.Instance.ReflectionOperations.GetTestPropertiesAsTraits(testPropertyProvider); /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs index 721f5afd7a..b85063c118 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectionHelper.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; @@ -62,7 +62,7 @@ public static string[] GetTestCategories(this IReflectionOperations reflectionOp /// The reflection operations to use. /// The member to inspect. /// List of traits. - public static Trait[] GetTestPropertiesAsTraits(this IReflectionOperations reflectionOperations, MethodInfo testPropertyProvider) + public static TestTrait[] GetTestPropertiesAsTraits(this IReflectionOperations reflectionOperations, MethodInfo testPropertyProvider) { Attribute[] attributesFromMethod = reflectionOperations.GetCustomAttributesCached(testPropertyProvider); Attribute[] attributesFromClass = testPropertyProvider.ReflectedType is { } testClass ? reflectionOperations.GetCustomAttributesCached(testClass) : []; @@ -89,13 +89,13 @@ public static Trait[] GetTestPropertiesAsTraits(this IReflectionOperations refle return []; } - var traits = new Trait[countTestPropertyAttribute]; + var traits = new TestTrait[countTestPropertyAttribute]; int index = 0; foreach (Attribute attribute in attributesFromMethod) { if (attribute is TestPropertyAttribute testProperty) { - traits[index++] = new Trait(testProperty.Name, testProperty.Value); + traits[index++] = new TestTrait(testProperty.Name, testProperty.Value); } } @@ -103,7 +103,7 @@ public static Trait[] GetTestPropertiesAsTraits(this IReflectionOperations refle { if (attribute is TestPropertyAttribute testProperty) { - traits[index++] = new Trait(testProperty.Name, testProperty.Value); + traits[index++] = new TestTrait(testProperty.Name, testProperty.Value); } } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/IPlatformServiceProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/IPlatformServiceProvider.cs index 25761fb0a8..8537dd59e7 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/IPlatformServiceProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/IPlatformServiceProvider.cs @@ -3,7 +3,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using UTF = Microsoft.VisualStudio.TestTools.UnitTesting; @@ -67,7 +66,7 @@ internal interface IPlatformServiceProvider /// /// The source. /// - /// + /// /// The run Settings for the session. /// /// @@ -75,7 +74,7 @@ internal interface IPlatformServiceProvider /// ITestSourceHost CreateTestSourceHost( string source, - TestPlatform.ObjectModel.Adapter.IRunSettings? runSettings); + string? settingsXml); /// /// Gets the TestContext object for a platform. @@ -97,5 +96,5 @@ ITestSourceHost CreateTestSourceHost( /// /// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant. /// - ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IMessageLogger messageLogger, UTF.UnitTestOutcome outcome); + ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IAdapterMessageLogger messageLogger, UTF.UnitTestOutcome outcome); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestDeployment.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestDeployment.cs index 1aabea8f1c..a3215764fb 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestDeployment.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestDeployment.cs @@ -3,8 +3,8 @@ #if !WINDOWS_UWP && !WIN_UI -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; @@ -14,13 +14,13 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Int internal interface ITestDeployment { /// - /// Deploy deployment items for the specified test cases. + /// Deploy deployment items for the specified tests. /// - /// The test cases. - /// The run context. - /// The framework handle. + /// The tests. + /// The host deployment inputs (test-run directory, run settings XML). + /// The logger used to surface deployment warnings. /// True if deployment is done. - bool Deploy(IEnumerable testCases, IRunContext? runContext, IFrameworkHandle frameworkHandle); + bool Deploy(IEnumerable testElements, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger); /// /// Gets the set of deployment items on a method and its corresponding class. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestElementFilterProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestElementFilterProvider.cs new file mode 100644 index 0000000000..202b395383 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestElementFilterProvider.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; + +/// +/// Platform-agnostic factory that produces the in effect for the current +/// run or discovery. +/// +/// +/// This abstraction lets the platform services discovery and execution pipelines obtain the active filter +/// without taking a dependency on a specific test platform's filter object model (for example the VSTest +/// ITestCaseFilterExpression parsed from an IRunContext / IDiscoveryContext). The concrete +/// provider is created at the adapter boundary, closing over the host filter context; the engine invokes it at +/// the exact point it needs the filter so parse-error reporting keeps the same timing and per-source semantics. +/// +internal interface ITestElementFilterProvider +{ + /// + /// Builds the for the current run/discovery. + /// + /// Logger used to report a filter parsing error. + /// + /// Set to when the filter is unsupported or failed to parse; in that case the caller + /// should report no tests for the affected source. + /// + /// + /// The filter to apply, or when no filter applies (every element is included). + /// + ITestElementFilter? GetTestElementFilter(IAdapterMessageLogger logger, out bool filterHasError); +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestResultRecorder.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestResultRecorder.cs index d944cd33ff..7887c7df51 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestResultRecorder.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestResultRecorder.cs @@ -1,7 +1,7 @@ // 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.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using FrameworkTestResult = Microsoft.VisualStudio.TestTools.UnitTesting.TestResult; @@ -12,34 +12,34 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Int /// test host is running the tests. /// /// -/// This abstraction lets the platform services layer report test start/end and results without taking a -/// dependency on a specific test platform's result object model (for example the VSTest TestResult, -/// TestOutcome and attachment types). The concrete recorder is provided at the platform boundary by a -/// wrapper over the host's result recorder (currently TestResultRecorderExtensions, which wraps the -/// VSTest ITestExecutionRecorder), and is expected to move fully out of the platform services layer in -/// a later phase. +/// This abstraction lets the platform services execution engine report test start/end and results using only +/// the neutral and framework models, without +/// taking a dependency on a specific test platform's result object model (for example the VSTest +/// TestResult, TestOutcome and attachment types). The concrete recorder — which builds the host +/// result representation — is provided at the adapter boundary (see the VSTest implementation in +/// TestResultRecorderExtensions in the adapter layer). /// internal interface ITestResultRecorder { /// - /// Signals that execution of the given test case has started. + /// Signals that execution of the given test has started. /// - /// The test case whose execution is starting. - void RecordStart(TestCase testCase); + /// The test whose execution is starting. + void RecordStart(UnitTestElement testElement); /// - /// Signals that execution of the given test case ended without producing any result. + /// Signals that execution of the given test ended without producing any result. /// - /// The test case whose execution ended. - void RecordEmptyResult(TestCase testCase); + /// The test whose execution ended. + void RecordEmptyResult(UnitTestElement testElement); /// - /// Reports a single framework for the given test case to the test host. + /// Reports a single framework for the given test to the test host. /// - /// The test case the result belongs to. + /// The test the result belongs to. /// The framework result produced by executing the test. /// The time at which the test started executing. /// The time at which the test finished executing. /// if the result was reported as a failure; otherwise, . - bool RecordResult(TestCase testCase, FrameworkTestResult unitTestResult, DateTimeOffset startTime, DateTimeOffset endTime); + bool RecordResult(UnitTestElement testElement, FrameworkTestResult unitTestResult, DateTimeOffset startTime, DateTimeOffset endTime); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.Configuration.cs b/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.Configuration.cs index 724ab28194..ee399d9ab5 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.Configuration.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.Configuration.cs @@ -10,7 +10,6 @@ #endif using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using MessageLevel = Microsoft.VisualStudio.TestTools.UnitTesting.MessageLevel; using StringEx = Microsoft.VisualStudio.TestTools.UnitTesting.StringEx; @@ -20,34 +19,34 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; internal sealed partial class MSTestSettings { /// - /// Populate adapter settings from the context. + /// Populate adapter settings from the run settings XML. /// - /// The discovery context. + /// The run settings XML, or when none was provided. /// The logger for messages. /// The configuration. - internal static void PopulateSettings(IDiscoveryContext? context, IAdapterMessageLogger? logger, IConfiguration? configuration) + internal static void PopulateSettings(string? settingsXml, IAdapterMessageLogger? logger, IConfiguration? configuration) { #if !WINDOWS_UWP if (configuration?["mstest"] is not null - && context?.RunSettings is not null - && RunSettingsFileHasMSTestSettings(context.RunSettings.SettingsXml)) + && settingsXml is not null + && RunSettingsFileHasMSTestSettings(settingsXml)) { throw new InvalidOperationException(Resource.DuplicateConfigurationError); } #endif var settings = new MSTestSettings(); - var runConfigurationSettings = RunConfigurationSettings.GetSettings(context?.RunSettings?.SettingsXml); + var runConfigurationSettings = RunConfigurationSettings.GetSettings(settingsXml); #if !WINDOWS_UWP - if (!StringEx.IsNullOrEmpty(context?.RunSettings?.SettingsXml) && configuration?["mstest"] is null) + if (!StringEx.IsNullOrEmpty(settingsXml) && configuration?["mstest"] is null) #else - if (!StringEx.IsNullOrEmpty(context?.RunSettings?.SettingsXml)) + if (!StringEx.IsNullOrEmpty(settingsXml)) #endif { - MSTestSettings? aliasSettings = GetSettings(context.RunSettings.SettingsXml, SettingsNameAlias, logger); - settings = aliasSettings ?? GetSettings(context.RunSettings.SettingsXml, SettingsName, logger) ?? new MSTestSettings(); - SetGlobalSettings(context.RunSettings.SettingsXml, settings, logger); + MSTestSettings? aliasSettings = GetSettings(settingsXml, SettingsNameAlias, logger); + settings = aliasSettings ?? GetSettings(settingsXml, SettingsName, logger) ?? new MSTestSettings(); + SetGlobalSettings(settingsXml, settings, logger); } #if !WINDOWS_UWP else if (configuration?["mstest"] is not null) @@ -71,7 +70,7 @@ internal static void PopulateSettings(IDiscoveryContext? context, IAdapterMessag { telemetry.ConfigurationSource = configuration?["mstest"] is not null ? "testconfig.json" - : !StringEx.IsNullOrEmpty(context?.RunSettings?.SettingsXml) + : !StringEx.IsNullOrEmpty(settingsXml) ? "runsettings" : "none"; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestMethod.cs b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestMethod.cs index c2077d253e..22443ffaec 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestMethod.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestMethod.cs @@ -135,6 +135,11 @@ private static string GetManagedTypeName(string fullClassName) internal TestMethod Clone() => (TestMethod)MemberwiseClone(); + // NOTE: This method has a latent bug (it assigns AssemblyName/MethodInfo on `this` instead of on the + // returned clone), tracked by https://github.com/microsoft/testfx/issues/9573. It is intentionally left + // untouched here so the existing ToTestCase / test-case filter bridge callers keep their exact current + // behavior; the execution engine uses the correct CloneWithSource below instead. The two are unified once + // #9573 is addressed. internal TestMethod CloneWithUpdatedSource(string source) { var clone = (TestMethod)MemberwiseClone(); @@ -142,4 +147,15 @@ internal TestMethod CloneWithUpdatedSource(string source) clone.MethodInfo = null; return clone; } + + // Correct counterpart of CloneWithUpdatedSource: assigns the updated source and clears the cached + // MethodInfo on the returned clone (leaving `this` untouched). Used by the execution engine's source + // resolution. See https://github.com/microsoft/testfx/issues/9573. + internal TestMethod CloneWithSource(string source) + { + var clone = (TestMethod)MemberwiseClone(); + clone.AssemblyName = source; + clone.MethodInfo = null; + return clone; + } } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestTrait.cs b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestTrait.cs new file mode 100644 index 0000000000..44c4799091 --- /dev/null +++ b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestTrait.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; + +/// +/// A platform-agnostic name/value trait associated with a test (for example the values produced by +/// [TestProperty] attributes). This mirrors the shape historically carried by the test platform's +/// trait type but does not depend on any specific test platform's object model, so the platform services +/// layer can describe test traits without referencing that object model. +/// +#if NETFRAMEWORK +// A (which carries these) is serialized across app domains on .NET Framework. +[Serializable] +#endif +internal readonly struct TestTrait +{ + public TestTrait(string name, string value) + { + Name = name; + Value = value; + } + + /// + /// Gets the trait name. + /// + public string Name { get; } + + /// + /// Gets the trait value. + /// + public string Value { get; } +} diff --git a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs index fb20da9858..56c2fb4710 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs @@ -53,7 +53,7 @@ public UnitTestElement(TestMethod testMethod) /// /// Gets or sets the traits for test method. /// - public Trait[]? Traits { get; set; } + public TestTrait[]? Traits { get; set; } /// /// Gets or sets the priority of the test method, if any. @@ -81,6 +81,51 @@ public UnitTestElement(TestMethod testMethod) /// internal string[]? WorkItemIds { get; set; } + /// + /// Gets or sets host-provided execution context properties (for example the values historically read + /// from a test-case-management host: run id, plan id, build configuration, test point id, ...) that are + /// surfaced to the running test through TestContext. Keyed by the host property identifier so the + /// platform services layer does not depend on a specific test platform's property object model. This is + /// only populated when a host supplies such values (for internally discovered tests it stays null). + /// +#if NETFRAMEWORK + // Consumed on the source-processing side before execution; the isolation host copy never reads it. + [field: NonSerialized] +#endif + internal IReadOnlyDictionary? ExecutionContextProperties { get; set; } + + /// + /// Gets or sets the host's test case for this test, used to report its lifecycle and results back to the + /// host with full fidelity (preserving any host-injected data — such as test-case-management or + /// data-collector properties — that the neutral model does not otherwise carry) and to describe it to the + /// (still VSTest-based) deployment service. For tests handed to the adapter by a host it is that original + /// test case; for tests discovered internally by the platform services it is until + /// materialized on demand by . The execution engine treats it as an + /// opaque handle — it reads nothing VSTest-specific off it and only threads it into the result recorder and + /// the deployment boundary. + /// +#if NETFRAMEWORK + // Result recording and deployment happen on the source-processing side; the isolation host copy never reads it. + [field: NonSerialized] +#endif + internal object? HostRecordingHandle { get; set; } + + /// + /// Returns the host test case for this test, reusing the host-provided one when present and otherwise + /// materializing a single VSTest test case on demand and caching it (so deployment, test-start and every + /// reported result share one instance, matching the historical "one test case per discovered test"). + /// + internal TestCase GetOrCreateHostTestCase() + { + if (HostRecordingHandle is not TestCase testCase) + { + testCase = ToTestCase(); + HostRecordingHandle = testCase; + } + + return testCase; + } + internal UnitTestElement Clone() { var clone = (UnitTestElement)MemberwiseClone(); @@ -88,6 +133,11 @@ internal UnitTestElement Clone() return clone; } + // Legacy source-updating clone used only by the ToTestCase / test-case filter bridge + // (TestCaseExtensions.ToUnitTestElementWithUpdatedSource). It delegates to the buggy + // TestMethod.CloneWithUpdatedSource and is retained to preserve that path's exact current behavior; the + // execution engine uses WithUpdatedSource / CloneWithSource instead. Tracked by + // https://github.com/microsoft/testfx/issues/9573. internal UnitTestElement CloneWithUpdatedSource(string source) { var clone = (UnitTestElement)MemberwiseClone(); @@ -95,6 +145,27 @@ internal UnitTestElement CloneWithUpdatedSource(string source) return clone; } + // Correct source-updating clone: the returned clone (only) targets the new source. Used by the execution + // engine via WithUpdatedSource. See https://github.com/microsoft/testfx/issues/9573. + internal UnitTestElement CloneWithSource(string source) + { + var clone = (UnitTestElement)MemberwiseClone(); + clone.TestMethod = TestMethod.CloneWithSource(source); + return clone; + } + + /// + /// Returns this element when it already targets , otherwise a clone whose test + /// method points at . This mirrors the source resolution the adapter previously + /// performed while converting a host test case, so a deployed source (relocated to the deployment + /// directory) is honored without reloading the assembly from its original location (see + /// https://github.com/microsoft/testfx/issues/6713). + /// + /// The (possibly deployment-relocated) source of the test. + /// An element whose is . + internal UnitTestElement WithUpdatedSource(string source) + => TestMethod.AssemblyName == source ? this : CloneWithSource(source); + /// /// Convert the UnitTestElement instance to an Object Model testCase instance. /// @@ -146,7 +217,10 @@ internal TestCase ToTestCase() if (Traits is { Length: > 0 }) { - testCase.Traits.AddRange(Traits); + foreach (TestTrait trait in Traits) + { + testCase.Traits.Add(new Trait(trait.Name, trait.Value)); + } } if (WorkItemIds != null) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/PlatformServiceProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/PlatformServiceProvider.cs index 81c975c823..aa460a8231 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/PlatformServiceProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/PlatformServiceProvider.cs @@ -4,7 +4,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using UTF = Microsoft.VisualStudio.TestTools.UnitTesting; @@ -130,7 +129,7 @@ internal static IPlatformServiceProvider Instance /// /// The source. /// - /// + /// /// The run Settings for the session. /// /// @@ -138,9 +137,9 @@ internal static IPlatformServiceProvider Instance /// public ITestSourceHost CreateTestSourceHost( string source, - TestPlatform.ObjectModel.Adapter.IRunSettings? runSettings) + string? settingsXml) { - var testSourceHost = new TestSourceHost(source, runSettings); + var testSourceHost = new TestSourceHost(source, settingsXml); testSourceHost.SetupHost(); return testSourceHost; @@ -166,7 +165,7 @@ public ITestSourceHost CreateTestSourceHost( /// /// This was required for compatibility reasons since the TestContext object that the V1 adapter had for desktop is not .Net Core compliant. /// - public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IMessageLogger messageLogger, UTF.UnitTestOutcome outcome) + public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IAdapterMessageLogger messageLogger, UTF.UnitTestOutcome outcome) { var testContextImplementation = new TestContextImplementation(testMethod, testClassFullName, properties, messageLogger, TestRunCancellationToken); testContextImplementation.SetOutcome(outcome); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs index 5efc98a041..9cfb5f78d4 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs @@ -10,7 +10,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using ITestMethod = Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ObjectModel.ITestMethod; @@ -71,7 +70,7 @@ public override string ToString() #else private readonly object _propertiesLock = new(); #endif - private readonly IMessageLogger? _messageLogger; + private readonly IAdapterMessageLogger? _messageLogger; private readonly TestRunCancellationToken? _testRunCancellationToken; private CancellationTokenRegistration? _cancellationTokenRegistration; @@ -113,7 +112,7 @@ public override string ToString() /// Properties/configuration passed in. /// The message logger to use. /// The global test run cancellation token. - internal TestContextImplementation(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IMessageLogger? messageLogger, TestRunCancellationToken? testRunCancellationToken) + internal TestContextImplementation(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IAdapterMessageLogger? messageLogger, TestRunCancellationToken? testRunCancellationToken) { // testMethod can be null when running ForceCleanup (done when reaching --maximum-failed-tests. DebugEx.Assert(properties != null, "properties is not null"); @@ -428,7 +427,7 @@ public void SetDisplayName(string? displayName) /// public override void DisplayMessage(MessageLevel messageLevel, string message) - => _messageLogger?.SendMessage(messageLevel.ToTestMessageLevel(), message); + => _messageLogger?.SendMessage(messageLevel, message); #endregion /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs index 11a4cd49cb..5c17c97d43 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDeployment.cs @@ -4,12 +4,14 @@ #if !WINDOWS_UWP && !WIN_UI using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; #if NETFRAMEWORK +// SuspendCodeCoverage (used below to pause dynamic code coverage while deployment copies files) is a VSTest +// object-model type. Its neutralization is deferred to a later platform-services decoupling step; the rest of +// the deployment input is already platform-agnostic. using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; #endif using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -104,13 +106,13 @@ public void Cleanup() /// /// Deploy files related to the list of tests specified. /// - /// The tests. - /// The run context. - /// The framework handle. + /// The tests. + /// The host deployment inputs. + /// The logger used to surface deployment warnings. /// Return true if deployment is done. - public bool Deploy(IEnumerable testCases, IRunContext? runContext, IFrameworkHandle frameworkHandle) + public bool Deploy(IEnumerable testElements, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger) { - DebugEx.Assert(testCases != null, "tests"); + DebugEx.Assert(testElements != null, "tests"); // Reset runDirectories before doing deployment, so that older values of runDirectories is not picked // even if test host is kept alive. @@ -118,7 +120,7 @@ public bool Deploy(IEnumerable testCases, IRunContext? runContext, IFr _adapterSettings = MSTestSettingsProvider.Settings; bool canDeploy = CanDeploy(); - bool hasDeploymentItems = testCases.Any(DeploymentItemUtility.HasDeploymentItems); + bool hasDeploymentItems = testElements.Any(DeploymentItemUtility.HasDeploymentItems); // deployment directories should not be created in this case,simply return if (!canDeploy && hasDeploymentItems) @@ -127,8 +129,8 @@ public bool Deploy(IEnumerable testCases, IRunContext? runContext, IFr } #if NETFRAMEWORK - string? firstTestSource = testCases.FirstOrDefault()?.Source; - RunDirectories = _deploymentUtility.CreateDeploymentDirectories(runContext, firstTestSource); + string? firstTestSource = testElements.FirstOrDefault()?.TestMethod.AssemblyName; + RunDirectories = _deploymentUtility.CreateDeploymentDirectories(deploymentContext, firstTestSource); // Deployment directories are created but deployment will not happen. // This is added just to keep consistency with MSTest v1 behavior. @@ -143,8 +145,8 @@ public bool Deploy(IEnumerable testCases, IRunContext? runContext, IFr return false; } - string? firstTestSource = testCases.FirstOrDefault()?.Source; - RunDirectories = _deploymentUtility.CreateDeploymentDirectories(runContext, firstTestSource); + string? firstTestSource = testElements.FirstOrDefault()?.TestMethod.AssemblyName; + RunDirectories = _deploymentUtility.CreateDeploymentDirectories(deploymentContext, firstTestSource); #endif // Object model currently does not have support for SuspendCodeCoverage. We can remove this once support is added @@ -153,15 +155,15 @@ public bool Deploy(IEnumerable testCases, IRunContext? runContext, IFr #endif { // Group the tests by source - var testsBySource = from test in testCases - group test by test.Source into testGroup + var testsBySource = from test in testElements + group test by test.TestMethod.AssemblyName into testGroup select new { Source = testGroup.Key, Tests = testGroup }; TestRunDirectories runDirectories = RunDirectories; foreach (var group in testsBySource) { // do the deployment - _deploymentUtility.Deploy(@group.Tests, @group.Source, runContext, frameworkHandle, RunDirectories); + _deploymentUtility.Deploy(@group.Tests, @group.Source, deploymentContext, messageLogger, RunDirectories); } // Update the runDirectories diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs index 7ff1be7380..d2e5960d17 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs @@ -11,7 +11,6 @@ #if NETFRAMEWORK using Microsoft.VisualStudio.TestPlatform.ObjectModel; #endif -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; #if NETFRAMEWORK || (NET && !WINDOWS_UWP) using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; #endif @@ -66,10 +65,10 @@ internal class TestSourceHost : ITestSourceHost /// Initializes a new instance of the class. /// /// The source file name. - /// The run-settings provided for this session. - public TestSourceHost(string sourceFileName, IRunSettings? runSettings) + /// The run-settings XML provided for this session. + public TestSourceHost(string sourceFileName, string? settingsXml) #if NETFRAMEWORK - : this(sourceFileName, runSettings, new AppDomainWrapper()) + : this(sourceFileName, settingsXml, new AppDomainWrapper()) #endif { #if !WINDOWS_UWP && !NETFRAMEWORK @@ -81,7 +80,7 @@ public TestSourceHost(string sourceFileName, IRunSettings? runSettings) } #if NETFRAMEWORK - internal TestSourceHost(string sourceFileName, IRunSettings? runSettings, IAppDomain appDomain) + internal TestSourceHost(string sourceFileName, string? settingsXml, IAppDomain appDomain) { _sourceFileName = sourceFileName; _appDomain = appDomain; @@ -90,7 +89,7 @@ internal TestSourceHost(string sourceFileName, IRunSettings? runSettings, IAppDo SetContext(sourceFileName); // Set isAppDomainCreationDisabled flag - _isAppDomainCreationDisabled = runSettings != null && MSTestAdapterSettings.IsAppDomainCreationDisabled(runSettings.SettingsXml); + _isAppDomainCreationDisabled = settingsXml is not null && MSTestAdapterSettings.IsAppDomainCreationDisabled(settingsXml); } /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs index 50546a66e2..c371e13baa 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs @@ -3,9 +3,9 @@ #if !WINDOWS_UWP && !WIN_UI +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; @@ -105,21 +105,17 @@ internal static bool IsValidDeploymentItem([NotNullWhen(true)] string? sourcePat /// /// Returns whether there are any deployment items defined on the test. /// - /// The test Case. + /// The test. /// True if has deployment items. - internal static bool HasDeploymentItems(TestCase testCase) - { - KeyValuePair[]? deploymentItems = GetDeploymentItems(testCase); - - return deploymentItems is { Length: > 0 }; - } + internal static bool HasDeploymentItems(UnitTestElement testElement) + => testElement.DeploymentItems is { Length: > 0 }; - internal static IList GetDeploymentItems(IEnumerable tests) + internal static IList GetDeploymentItems(IEnumerable tests) { List allDeploymentItems = []; - foreach (TestCase test in tests) + foreach (UnitTestElement test in tests) { - KeyValuePair[]? items = GetDeploymentItems(test); + KeyValuePair[]? items = test.DeploymentItems; if (items == null || items.Length == 0) { continue; @@ -220,14 +216,6 @@ private static List GetDeploymentItems(IEnumerable - /// Returns the deployment items defined on the test. - /// - /// The test Case. - /// The . - private static KeyValuePair[]? GetDeploymentItems(TestCase testCase) => testCase.GetPropertyValue(EngineConstants.DeploymentItemsProperty) as - KeyValuePair[]; - private static KeyValuePair[]? ToKeyValuePairs(IEnumerable deploymentItemList) { if (!deploymentItemList.Any()) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs index be6335ba69..47713d16af 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs @@ -4,13 +4,11 @@ #if !WINDOWS_UWP && !WIN_UI using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; @@ -43,31 +41,31 @@ public DeploymentUtilityBase(DeploymentItemUtility deploymentItemUtility, Assemb protected AssemblyUtility AssemblyUtility { get; set; } - public bool Deploy(IEnumerable tests, string source, IRunContext? runContext, ITestExecutionRecorder testExecutionRecorder, TestRunDirectories runDirectories) + public bool Deploy(IEnumerable tests, string source, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, TestRunDirectories runDirectories) { IList deploymentItems = DeploymentItemUtility.GetDeploymentItems(tests); // we just deploy source if there are no deployment items for current source but there are deployment items for other sources - return Deploy(source, runContext, testExecutionRecorder, deploymentItems, runDirectories); + return Deploy(source, deploymentContext, messageLogger, deploymentItems, runDirectories); } /// /// Create deployment directories. /// - /// The run context. + /// The host deployment inputs. /// /// The path to the test assembly of the first test case. In most cases, all /// test cases belong to the same assembly, but not guaranteed. We are using the path from /// the first test case as a "best effort" implementation. DeploymentItem isn't correctly designed and should be deprecated in future. /// /// TestRunDirectories instance. - public TestRunDirectories CreateDeploymentDirectories(IRunContext? runContext, string? firstTestSource) + public TestRunDirectories CreateDeploymentDirectories(DeploymentContext deploymentContext, string? firstTestSource) { - string resultsDirectory = GetTestResultsDirectory(runContext); + string resultsDirectory = GetTestResultsDirectory(deploymentContext); string rootDeploymentDirectory = GetRootDeploymentDirectory(resultsDirectory); #if NETFRAMEWORK - bool isAppDomainCreationDisabled = runContext?.RunSettings != null && MSTestAdapterSettings.IsAppDomainCreationDisabled(runContext.RunSettings.SettingsXml); + bool isAppDomainCreationDisabled = deploymentContext.RunSettingsXml is not null && MSTestAdapterSettings.IsAppDomainCreationDisabled(deploymentContext.RunSettingsXml); #else // AppDomains are only supported in .NET Framework. const bool isAppDomainCreationDisabled = true; @@ -94,10 +92,10 @@ public TestRunDirectories CreateDeploymentDirectories(IRunContext? runContext, s /// /// Get the parent test results directory where deployment will be done. /// - /// The run context. + /// The host deployment inputs. /// The test results directory. - public static string GetTestResultsDirectory(IRunContext? runContext) => !StringEx.IsNullOrEmpty(runContext?.TestRunDirectory) - ? runContext.TestRunDirectory + public static string GetTestResultsDirectory(DeploymentContext deploymentContext) => !StringEx.IsNullOrEmpty(deploymentContext.TestRunDirectory) + ? deploymentContext.TestRunDirectory : Path.GetFullPath(Path.Combine(Path.GetTempPath(), TestRunDirectories.DefaultDeploymentRootDirectory)); /// @@ -397,20 +395,20 @@ protected static bool IsOutputDirectoryValid(DeploymentItem deploymentItem, stri /// /// Log the parameter warnings on the parameter logger. /// - /// Execution recorder. + /// Message logger. /// Warnings. - private static void LogWarnings(ITestExecutionRecorder testExecutionRecorder, IEnumerable warnings) + private static void LogWarnings(IAdapterMessageLogger messageLogger, IEnumerable warnings) { - DebugEx.Assert(testExecutionRecorder != null, "Logger should not be null"); + DebugEx.Assert(messageLogger != null, "Logger should not be null"); // log the warnings foreach (string warning in warnings) { - testExecutionRecorder.SendMessage(TestMessageLevel.Warning, warning); + messageLogger.SendMessage(MessageLevel.Warning, warning); } } - private bool Deploy(string source, IRunContext? runContext, ITestExecutionRecorder testExecutionRecorder, IList deploymentItems, TestRunDirectories runDirectories) + private bool Deploy(string source, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, IList deploymentItems, TestRunDirectories runDirectories) { if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled) { @@ -427,10 +425,10 @@ private bool Deploy(string source, IRunContext? runContext, ITestExecutionRecord PlatformServiceProvider.Instance.AdapterTraceLogger.Info("MSTestExecutor: Using deployment directory {0} for source {1}.", runDirectories.OutDirectory, source); } - IEnumerable warnings = Deploy([.. deploymentItems], source, runDirectories.OutDirectory, GetTestResultsDirectory(runContext)); + IEnumerable warnings = Deploy([.. deploymentItems], source, runDirectories.OutDirectory, GetTestResultsDirectory(deploymentContext)); // Log warnings - LogWarnings(testExecutionRecorder, warnings); + LogWarnings(messageLogger, warnings); return deploymentItems is { Count: > 0 }; } diff --git a/src/TestFramework/TestFramework/TestFramework.csproj b/src/TestFramework/TestFramework/TestFramework.csproj index f7e4b961f1..759d85cc85 100644 --- a/src/TestFramework/TestFramework/TestFramework.csproj +++ b/src/TestFramework/TestFramework/TestFramework.csproj @@ -58,6 +58,7 @@ + diff --git a/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs b/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs index 55d5b3adc5..40326955f6 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs +++ b/test/IntegrationTests/MSTest.IntegrationTests/Utilities/CLITestBase.discovery.cs @@ -7,11 +7,15 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using ITestResultRecorder = Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ITestResultRecorder; using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; namespace Microsoft.MSTestV2.CLIAutomation; @@ -27,7 +31,7 @@ internal static ImmutableArray DiscoverTests(string assemblyPath, stri string runSettingsXml = GetRunSettingsXml(string.Empty); var context = new InternalDiscoveryContext(runSettingsXml, testCaseFilter); - unitTestDiscoverer.DiscoverTestsInSource(assemblyPath, logger.ToAdapterMessageLogger(), sink.ToUnitTestElementSink(), context, false); + unitTestDiscoverer.DiscoverTestsInSource(assemblyPath, logger.ToAdapterMessageLogger(), sink.ToUnitTestElementSink(), runSettingsXml, new TestElementFilterProvider(context), false); return sink.DiscoveredTests; } @@ -37,7 +41,8 @@ internal static async Task> RunTestsAsync(IEnumerable var testExecutionManager = new TestExecutionManager(); var frameworkHandle = new InternalFrameworkHandle(); - await testExecutionManager.ExecuteTestsAsync(testCases, null, frameworkHandle, false); + ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings); + await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), new DeploymentContext(null, null), frameworkHandle.ToAdapterMessageLogger(), testResultRecorder, filterProvider: null, false); return frameworkHandle.GetFlattenedTestResults(); } @@ -49,10 +54,23 @@ internal static async Task> RunTestsAsync(IEnumerable string runSettingsXml = GetRunSettingsXml(string.Empty); var runContext = new InternalRunContext(runSettingsXml, testCaseFilter); - await testExecutionManager.ExecuteTestsAsync(testCases, runContext, frameworkHandle, false); + ITestResultRecorder testResultRecorder = frameworkHandle.ToTestResultRecorder(Environment.MachineName, MSTestSettings.CurrentSettings); + await testExecutionManager.ExecuteTestsAsync(ToUnitTestElements(testCases), new DeploymentContext(runContext.TestRunDirectory, runContext.RunSettings?.SettingsXml), frameworkHandle.ToAdapterMessageLogger(), testResultRecorder, new TestElementFilterProvider(runContext), false); return frameworkHandle.GetFlattenedTestResults(); } + // Mirrors the adapter's execution boundary (see MSTestExecutor): each host test case becomes a neutral + // UnitTestElement carrying its execution-context (TCM) properties and the originating test case as an + // opaque recording handle, so results are recorded against the exact same TestCase instances. + private static IEnumerable ToUnitTestElements(IEnumerable testCases) + => testCases.Select(static testCase => + { + UnitTestElement element = testCase.ToUnitTestElementWithUpdatedSource(testCase.Source); + element.ExecutionContextProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); + element.HostRecordingHandle = testCase; + return element; + }); + #region Helper classes private class InternalLogger : IMessageLogger { diff --git a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/DesktopTestSourceHostTests.cs b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/DesktopTestSourceHostTests.cs index f052814199..aafd28b3a1 100644 --- a/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/DesktopTestSourceHostTests.cs +++ b/test/IntegrationTests/PlatformServices.Desktop.IntegrationTests/DesktopTestSourceHostTests.cs @@ -5,11 +5,8 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; -using Moq; - using TestFramework.ForTestingMSTest; namespace PlatformServices.Desktop.ComponentTests; @@ -37,9 +34,10 @@ public void ParentDomainShouldHonorSearchDirectoriesSpecifiedInRunsettings() """; + LoadMSTestSettings(runSettingsXml); _testSourceHost = new TestSourceHost( GetTestAssemblyPath("DesktopTestProjectx86Debug"), - GetMockedIRunSettings(runSettingsXml).Object); + runSettingsXml); _testSourceHost.SetupHost(); // Loading SampleProjectForAssemblyResolution.dll should not throw. @@ -67,9 +65,10 @@ public void ChildDomainResolutionPathsShouldHaveSearchDirectoriesSpecifiedInRuns """; + LoadMSTestSettings(runSettingsXml); _testSourceHost = new TestSourceHost( GetTestAssemblyPath("DesktopTestProjectx86Debug"), - GetMockedIRunSettings(runSettingsXml).Object); + runSettingsXml); _testSourceHost.SetupHost(); var asm = Assembly.LoadFrom(sampleProjectPath); @@ -125,17 +124,12 @@ private static string GetTestAssemblyPath(string assetName) return testAssetPath; } - private static Mock GetMockedIRunSettings(string runSettingsXml) + private static void LoadMSTestSettings(string runSettingsXml) { - var mockRunSettings = new Mock(); - mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - StringReader stringReader = new(runSettingsXml); var reader = XmlReader.Create(stringReader, XmlRunSettingsUtilities.ReaderSettings); MSTestSettingsProvider mstestSettingsProvider = new(); reader.ReadToFollowing("MSTestV2"); mstestSettingsProvider.Load(reader); - - return mockRunSettings; } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs index ed5bdcead3..d7d1d1d524 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.cs @@ -121,7 +121,7 @@ public void GetTestsShouldReturnBaseTestMethodsFromAnotherAssemblyByDefault() mockRunContext.Setup(dc => dc.RunSettings).Returns(mockRunSettings.Object); mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(mockRunContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(mockRunContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyDerivedTestClass), Assembly.GetExecutingAssembly().FullName!); @@ -151,7 +151,7 @@ public void GetTestsShouldReturnBaseTestMethodsFromAnotherAssemblyByConfiguratio mockRunContext.Setup(dc => dc.RunSettings).Returns(mockRunSettings.Object); mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(mockRunContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(mockRunContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); SetupTestClassAndTestMethods(isValidTestClass: true, isValidTestMethod: true); TypeEnumerator typeEnumerator = GetTypeEnumeratorInstance(typeof(DummyDerivedTestClass), Assembly.GetExecutingAssembly().FullName!); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/UnitTestDiscovererTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/UnitTestDiscovererTests.cs index b15605e885..62e93422f8 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/UnitTestDiscovererTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/UnitTestDiscovererTests.cs @@ -80,7 +80,7 @@ public void DiscoverTestsShouldThrowOnFileNotFound() .Returns(false); } - Action act = () => _unitTestDiscoverer.DiscoverTests(sources, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, false); + Action act = () => _unitTestDiscoverer.DiscoverTests(sources, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object.RunSettings?.SettingsXml, new TestElementFilterProvider(_mockDiscoveryContext.Object), false); act.Should().Throw() .WithMessage(string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_FileDoesNotExist, sources[0])); } @@ -93,7 +93,7 @@ public void DiscoverTestsInSourceShouldThrowOnFileNotFound() _testablePlatformServiceProvider.MockFileOperations.Setup(fo => fo.DoesFileExist(Source)) .Returns(false); - Action act = () => _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, false); + Action act = () => _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object.RunSettings?.SettingsXml, new TestElementFilterProvider(_mockDiscoveryContext.Object), false); act.Should().Throw() .WithMessage(string.Format(CultureInfo.CurrentCulture, Resource.TestAssembly_FileDoesNotExist, Source)); } @@ -122,10 +122,10 @@ public void DiscoverTestsInSourceShouldSendBackTestCasesDiscovered() """); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); // Act - _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, false); + _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object.RunSettings?.SettingsXml, new TestElementFilterProvider(_mockDiscoveryContext.Object), false); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.IsAny()), Times.AtLeastOnce); @@ -157,10 +157,10 @@ public void DiscoverTestsInSourceShouldThrowWhenTreatDiscoveryWarningsAsErrorsIs """; _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(settingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); // Act - Action action = () => _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, false); + Action action = () => _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object.RunSettings?.SettingsXml, new TestElementFilterProvider(_mockDiscoveryContext.Object), false); // Assert action.Should().Throw() @@ -195,11 +195,11 @@ public void DiscoverTestsInSourceShouldNotThrowWhenTreatDiscoveryWarningsAsError """); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); // Act & Assert // Should not throw an exception - _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, false); + _unitTestDiscoverer.DiscoverTestsInSource(Source, _mockMessageLogger.Object.ToAdapterMessageLogger(), _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object.RunSettings?.SettingsXml, new TestElementFilterProvider(_mockDiscoveryContext.Object), false); // Verify warning message was sent to logger (not error) _mockMessageLogger.Verify(lm => lm.SendMessage(TestMessageLevel.Warning, It.IsAny()), Times.AtLeastOnce); @@ -209,7 +209,7 @@ public void DiscoverTestsInSourceShouldNotThrowWhenTreatDiscoveryWarningsAsError public void SendTestCasesShouldNotSendAnyTestCasesIfThereAreNoTestElements() { // There is a null check for testElements in the code flow before this function call. So not adding a unit test for that. - _unitTestDiscoverer.SendTestCases(new List { }, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger()); + UnitTestDiscoverer.SendTestCases(new List { }, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), new TestElementFilterProvider(_mockDiscoveryContext.Object), _mockMessageLogger.Object.ToAdapterMessageLogger()); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.IsAny()), Times.Never); @@ -221,7 +221,7 @@ public void SendTestCasesShouldSendAllTestCaseData() var test2 = new UnitTestElement(CreateTestMethod("M2", "C", "A", displayName: null)); var testElements = new List { test1, test2 }; - _unitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), _mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger()); + UnitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), new TestElementFilterProvider(_mockDiscoveryContext.Object), _mockMessageLogger.Object.ToAdapterMessageLogger()); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.Is(tc => tc.FullyQualifiedName == "C.M1")), Times.Once); @@ -240,7 +240,7 @@ public void SendTestCasesShouldSendFilteredTestCasesIfValidFilterExpression() var testElements = new List { test1, test2 }; // Action - _unitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), discoveryContext, _mockMessageLogger.Object.ToAdapterMessageLogger()); + UnitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), new TestElementFilterProvider(discoveryContext), _mockMessageLogger.Object.ToAdapterMessageLogger()); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.Is(tc => tc.FullyQualifiedName == "C.M1")), Times.Once); @@ -259,7 +259,7 @@ public void SendTestCasesShouldSendAllTestCasesIfNullFilterExpression() var testElements = new List { test1, test2 }; // Action - _unitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), discoveryContext, _mockMessageLogger.Object.ToAdapterMessageLogger()); + UnitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), new TestElementFilterProvider(discoveryContext), _mockMessageLogger.Object.ToAdapterMessageLogger()); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.Is(tc => tc.FullyQualifiedName == "C.M1")), Times.Once); @@ -278,7 +278,7 @@ public void SendTestCasesShouldSendAllTestCasesIfGetTestCaseFilterNotPresent() var testElements = new List { test1, test2 }; // Action - _unitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), discoveryContext, _mockMessageLogger.Object.ToAdapterMessageLogger()); + UnitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), new TestElementFilterProvider(discoveryContext), _mockMessageLogger.Object.ToAdapterMessageLogger()); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.Is(tc => tc.FullyQualifiedName == "C.M1")), Times.Once); @@ -297,7 +297,7 @@ public void SendTestCasesShouldNotSendAnyTestCasesIfFilterError() var testElements = new List { test1, test2 }; // Action - _unitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), discoveryContext, _mockMessageLogger.Object.ToAdapterMessageLogger()); + UnitTestDiscoverer.SendTestCases(testElements, _mockTestCaseDiscoverySink.Object.ToUnitTestElementSink(), new TestElementFilterProvider(discoveryContext), _mockMessageLogger.Object.ToAdapterMessageLogger()); // Assert. _mockTestCaseDiscoverySink.Verify(ds => ds.SendTestCase(It.Is(tc => tc.FullyQualifiedName == "C.M1")), Times.Never); @@ -327,7 +327,8 @@ internal override void DiscoverTestsInSource( string source, IAdapterMessageLogger logger, IUnitTestElementSink discoverySink, - IDiscoveryContext? discoveryContext, + string? settingsXml, + ITestElementFilterProvider? filterProvider, bool isMTP) { var testElement1 = new UnitTestElement(new TestMethod("A", "C", source, displayName: null)); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TcmTestPropertiesProviderTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TcmTestPropertiesProviderTests.cs index 64e70e9b80..69fe685e75 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TcmTestPropertiesProviderTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TcmTestPropertiesProviderTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using AwesomeAssertions; @@ -34,7 +34,7 @@ public class TcmTestPropertiesProviderTests : TestContainer public void GetTcmPropertiesShouldReturnEmptyDictionaryIfTestCaseIsNull() { - IDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(null); + IReadOnlyDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(null); tcmProperties.Should().BeNull(); } @@ -61,7 +61,7 @@ public void GetTcmPropertiesShouldReturnEmptyDictionaryIfTestCaseIdIsZero() ]; SetTestCaseProperties(testCase, propertiesValue); - IDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); + IReadOnlyDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); tcmProperties.Should().BeNull(); } @@ -88,7 +88,7 @@ public void GetTcmPropertiesShouldGetAllPropertiesFromTestCase() ]; SetTestCaseProperties(testCase, propertiesValue); - IDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); + IReadOnlyDictionary? tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); VerifyTcmProperties(tcmProperties, testCase); } @@ -116,7 +116,7 @@ public void GetTcmPropertiesShouldCopyMultiplePropertiesCorrectlyFromTestCase() 345 ]; SetTestCaseProperties(testCase1, propertiesValue1); - IDictionary? tcmProperties1 = TcmTestPropertiesProvider.GetTcmProperties(testCase1); + IReadOnlyDictionary? tcmProperties1 = TcmTestPropertiesProvider.GetTcmProperties(testCase1); VerifyTcmProperties(tcmProperties1, testCase1); // Verify 2nd call. @@ -140,7 +140,7 @@ public void GetTcmPropertiesShouldCopyMultiplePropertiesCorrectlyFromTestCase() 346 ]; SetTestCaseProperties(testCase2, propertiesValue2); - IDictionary? tcmProperties2 = TcmTestPropertiesProvider.GetTcmProperties(testCase2); + IReadOnlyDictionary? tcmProperties2 = TcmTestPropertiesProvider.GetTcmProperties(testCase2); VerifyTcmProperties(tcmProperties2, testCase2); } @@ -167,7 +167,7 @@ public void GetTcmPropertiesShouldHandleDuplicateTestsProperlyFromTestCase() 345 ]; SetTestCaseProperties(testCase1, propertiesValue1); - IDictionary? tcmProperties1 = TcmTestPropertiesProvider.GetTcmProperties(testCase1); + IReadOnlyDictionary? tcmProperties1 = TcmTestPropertiesProvider.GetTcmProperties(testCase1); VerifyTcmProperties(tcmProperties1, testCase1); // Verify 2nd call. @@ -191,7 +191,7 @@ public void GetTcmPropertiesShouldHandleDuplicateTestsProperlyFromTestCase() 346 ]; SetTestCaseProperties(testCase2, propertiesValue2); - IDictionary? tcmProperties2 = TcmTestPropertiesProvider.GetTcmProperties(testCase2); + IReadOnlyDictionary? tcmProperties2 = TcmTestPropertiesProvider.GetTcmProperties(testCase2); VerifyTcmProperties(tcmProperties2, testCase2); // Verify 3rd call. @@ -215,7 +215,7 @@ public void GetTcmPropertiesShouldHandleDuplicateTestsProperlyFromTestCase() 347 ]; SetTestCaseProperties(testCase3, propertiesValue3); - IDictionary? tcmProperties3 = TcmTestPropertiesProvider.GetTcmProperties(testCase3); + IReadOnlyDictionary? tcmProperties3 = TcmTestPropertiesProvider.GetTcmProperties(testCase3); VerifyTcmProperties(tcmProperties3, testCase3); } @@ -232,12 +232,12 @@ private void SetTestCaseProperties(TestCase testCase, object[] propertiesValue) } } - private void VerifyTcmProperties(IDictionary? tcmProperties, TestCase testCase) + private void VerifyTcmProperties(IReadOnlyDictionary? tcmProperties, TestCase testCase) { tcmProperties.Should().NotBeNull(); foreach (TestProperty property in _tcmKnownProperties) { - testCase.GetPropertyValue(property)!.Equals(tcmProperties[property]).Should().BeTrue(); + testCase.GetPropertyValue(property)!.Equals(tcmProperties[property.Id]).Should().BeTrue(); } } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestCaseDiscoverySinkTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestCaseDiscoverySinkTests.cs index a675af493d..07f916bd40 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestCaseDiscoverySinkTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestCaseDiscoverySinkTests.cs @@ -18,22 +18,22 @@ public class TestCaseDiscoverySinkTests : TestContainer public void TestCaseDiscoverySinkConstructorShouldInitializeTests() { - _testCaseDiscoverySink.Tests.Should().NotBeNull(); - _testCaseDiscoverySink.Tests.Count.Should().Be(0); + _testCaseDiscoverySink.TestElements.Should().NotBeNull(); + _testCaseDiscoverySink.TestElements.Count.Should().Be(0); } - public void SendTestElementShouldAddTheMaterializedTestCaseToTests() + public void SendTestElementShouldAddTheTestElement() { var testElement = new UnitTestElement(new TestMethod("M", "C", "A", displayName: null)); _testCaseDiscoverySink.SendTestElement(testElement); - _testCaseDiscoverySink.Tests.Should().NotBeNull(); - _testCaseDiscoverySink.Tests.Count.Should().Be(1); - _testCaseDiscoverySink.Tests.ToArray()[0].FullyQualifiedName.Should().Be("C.M"); + _testCaseDiscoverySink.TestElements.Should().NotBeNull(); + _testCaseDiscoverySink.TestElements.Count.Should().Be(1); + _testCaseDiscoverySink.TestElements.ToArray()[0].Should().BeSameAs(testElement); } - public void SendTestElementShouldAddEachTestCaseInOrder() + public void SendTestElementShouldAddEachTestElementInOrder() { var testElement1 = new UnitTestElement(new TestMethod("M1", "C", "A", displayName: null)); var testElement2 = new UnitTestElement(new TestMethod("M2", "C", "A", displayName: null)); @@ -41,8 +41,8 @@ public void SendTestElementShouldAddEachTestCaseInOrder() _testCaseDiscoverySink.SendTestElement(testElement1); _testCaseDiscoverySink.SendTestElement(testElement2); - _testCaseDiscoverySink.Tests.Count.Should().Be(2); - _testCaseDiscoverySink.Tests.ToArray()[0].FullyQualifiedName.Should().Be("C.M1"); - _testCaseDiscoverySink.Tests.ToArray()[1].FullyQualifiedName.Should().Be("C.M2"); + _testCaseDiscoverySink.TestElements.Count.Should().Be(2); + _testCaseDiscoverySink.TestElements.ToArray()[0].Should().BeSameAs(testElement1); + _testCaseDiscoverySink.TestElements.ToArray()[1].Should().BeSameAs(testElement2); } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs index 2a18bb5757..ed6a3a1e2c 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs @@ -5,8 +5,10 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions; 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.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.TestableImplementations; @@ -54,6 +56,20 @@ public class TestExecutionManagerTests : TestContainer private List _callers = []; private int _enqueuedParallelTestsCount; + /// + /// Builds the VSTest-backed result recorder the execution engine now receives at its boundary, recording into + /// exactly as the adapter does in production. + /// + private ITestResultRecorder TestResultRecorder + => _frameworkHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings); + + /// + /// Builds the neutral run inputs (test-run directory + run settings XML) the execution engine now receives at + /// its boundary, extracted from exactly as the adapter does in production. + /// + private DeploymentContext CurrentDeploymentContext + => new(_runContext.TestRunDirectory, _runContext.RunSettings?.SettingsXml); + public TestExecutionManagerTests() { _runContext = new TestableRunContextTestExecutionTests(() => new TestableTestCaseFilterExpression(_ => true)); @@ -63,7 +79,6 @@ public TestExecutionManagerTests() _mockTestSourceHandler = new Mock(); _testExecutionManager = new TestExecutionManager( - EnvironmentWrapper.Instance, task => { _enqueuedParallelTestsCount++; @@ -92,7 +107,7 @@ public async Task RunTestsForTestWithFilterErrorShouldSendZeroResults() // Causing the FilterExpressionError _runContext = new TestableRunContextTestExecutionTests(() => throw new TestPlatformFormatException()); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, _cancellationToken); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); // No Results _frameworkHandle.TestCaseStartList.Count.Should().Be(0); @@ -108,7 +123,7 @@ public async Task RunTestsForTestWithFilterShouldSendResultsForFilteredTests() _runContext = new TestableRunContextTestExecutionTests(() => new TestableTestCaseFilterExpression(p => p.DisplayName == "PassingTest")); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, _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"]; @@ -125,7 +140,7 @@ public void SendTestResults_WhenUnitTestResultsIsEmpty_RecordsEndWithoutResult() TestCase testCase = GetTestCase(typeof(DummyTestClass), "PassingTest"); Microsoft.VisualStudio.TestTools.UnitTesting.TestResult[] unitTestResults = []; - _testExecutionManager.SendTestResults(testCase, unitTestResults, DateTimeOffset.Now, DateTimeOffset.Now, _frameworkHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings)); + _testExecutionManager.SendTestResults(ToUnitTestElement(testCase), unitTestResults, DateTimeOffset.Now, DateTimeOffset.Now, _frameworkHandle.ToTestResultRecorder(EnvironmentWrapper.Instance.MachineName, MSTestSettings.CurrentSettings)); _frameworkHandle.TestCaseEndList.Should().Equal("PassingTest:None"); _frameworkHandle.ResultsList.Should().BeEmpty(); @@ -136,7 +151,7 @@ public async Task RunTestsForIgnoredTestShouldSendResultsMarkingIgnoredTestsAsSk TestCase testCase = GetTestCase(typeof(DummyTestClass), "IgnoredTest"); TestCase[] tests = [testCase]; - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, _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"); @@ -149,7 +164,7 @@ public async Task RunTestsForASingleTestShouldSendSingleResult() TestCase[] tests = [testCase]; - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); List expectedTestCaseStartList = ["PassingTest"]; List expectedTestCaseEndList = ["PassingTest:Passed"]; @@ -166,7 +181,7 @@ public async Task RunTestsForMultipleTestShouldSendMultipleResults() TestCase failingTestCase = GetTestCase(typeof(DummyTestClass), "FailingTest"); TestCase[] tests = [testCase, failingTestCase]; - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, _cancellationToken); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); List expectedTestCaseStartList = ["PassingTest", "FailingTest"]; List expectedTestCaseEndList = ["PassingTest:Passed", "FailingTest:Failed"]; @@ -186,7 +201,7 @@ public async Task RunTestsForCancellationTokenCanceledSetToTrueShouldSendZeroRes // Cancel the test run _cancellationToken.Cancel(); - Func func = () => _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, _cancellationToken); + Func func = () => _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _cancellationToken); await func.Should().ThrowAsync(); // No Results @@ -204,13 +219,13 @@ public async Task RunTestsForTestShouldDeployBeforeExecution() // Setup mocks. TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); testablePlatformService.MockTestDeployment.Setup( - td => td.Deploy(tests, _runContext, _frameworkHandle)).Callback(() => SetCaller("Deploy")); + td => td.Deploy(It.IsAny>(), It.IsAny(), It.IsAny())).Callback(() => SetCaller("Deploy")); await _testExecutionManager.RunTestsAsync( - tests, - _runContext, - _frameworkHandle, - new TestRunCancellationToken()); + ToUnitTestElements(tests), + CurrentDeploymentContext, + _frameworkHandle.ToAdapterMessageLogger(), + TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _callers[0].Should().Be("Deploy", "Deploy should be called before execution."); _callers[1].Should().Be("LoadAssembly", "Deploy should be called before execution."); @@ -230,7 +245,7 @@ public async Task RunTestsForTestShouldCleanupAfterExecution() td => td.Cleanup()).Callback(() => SetCaller("Cleanup")); #endif - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, 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."); @@ -247,7 +262,7 @@ public async Task RunTestsForTestShouldNotCleanupOnTestFailure() TestCase[] tests = [testCase, failingTestCase]; TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); testablePlatformService.MockTestDeployment.Verify(td => td.Cleanup(), Times.Never); } @@ -262,11 +277,11 @@ public async Task RunTestsForTestShouldLoadSourceFromDeploymentDirectoryIfDeploy // Setup mocks. testablePlatformService.MockTestDeployment.Setup( - td => td.Deploy(tests, _runContext, _frameworkHandle)).Returns(true); + td => td.Deploy(It.IsAny>(), It.IsAny(), It.IsAny())).Returns(true); testablePlatformService.MockTestDeployment.Setup(td => td.GetDeploymentDirectory()) .Returns(@"C:\temp"); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, 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"))), @@ -292,7 +307,7 @@ public async Task RunTestsForTestShouldPassInTestRunParametersInformationAsPrope """); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, 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(); @@ -314,7 +329,7 @@ public async Task RunTestsForTestShouldPassInTcmPropertiesAsPropertiesToTheTest( """); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); VerifyTcmProperties(DummyTestClass.TestContextProperties, testCase); } @@ -327,7 +342,7 @@ public async Task RunTestsForTestShouldPassInDeploymentInformationAsPropertiesTo // Setup mocks. TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, 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); } @@ -351,7 +366,7 @@ public async Task RunTestsShouldClearSessionParametersAcrossRuns() """); // Trigger First Run - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, 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( @@ -368,7 +383,7 @@ public async Task RunTestsShouldClearSessionParametersAcrossRuns() """); // Trigger another Run - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); "http://updatedLocalHost".Equals(DummyTestClass.TestContextProperties!["webAppUrl"]).Should().BeTrue(); } @@ -382,7 +397,7 @@ private async Task RunTestsForSourceShouldRunTestsInASource() { var sources = new List { Assembly.GetExecutingAssembly().Location }; - await _testExecutionManager.RunTestsAsync(sources, _runContext, _frameworkHandle, _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(); @@ -404,7 +419,7 @@ private async Task RunTestsForSourceShouldPassInTestRunParametersInformationAsPr """); - await _testExecutionManager.RunTestsAsync(sources, _runContext, _frameworkHandle, _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(); @@ -415,7 +430,7 @@ private async Task RunTestsForSourceShouldPassInDeploymentInformationAsPropertie { var sources = new List { Assembly.GetExecutingAssembly().Location }; - await _testExecutionManager.RunTestsAsync(sources, _runContext, _frameworkHandle, _mockTestSourceHandler.Object, false, _cancellationToken); + await _testExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); DummyTestClass.TestContextProperties.Should().NotBeNull(); } @@ -426,10 +441,10 @@ public async Task RunTestsForMultipleSourcesShouldRunEachTestJustOnce() var sources = new List { Assembly.GetExecutingAssembly().Location, Assembly.GetExecutingAssembly().Location }; TestableTestExecutionManager testableTestExecutionManager = new() { - ExecuteTestsWrapper = (tests, runContext, frameworkHandle, isDeploymentDone) => testsCount += tests.Count(), + ExecuteTestsWrapper = (tests, runContext, frameworkHandle, testResultRecorder, isDeploymentDone) => testsCount += tests.Count(), }; - await testableTestExecutionManager.RunTestsAsync(sources, _runContext, _frameworkHandle, _mockTestSourceHandler.Object, false, _cancellationToken); + await testableTestExecutionManager.RunTestsAsync(sources, CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), _mockTestSourceHandler.Object, false, _cancellationToken); testsCount.Should().Be(4); } @@ -461,8 +476,8 @@ public async Task RunTestsForTestShouldRunTestsInParallelWhenEnabledInRunsetting try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + 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); @@ -498,8 +513,8 @@ public async Task RunTestsForTestShouldRunTestsByMethodLevelWhenSpecified() try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _enqueuedParallelTestsCount.Should().Be(2); @@ -535,8 +550,8 @@ public async Task RunTestsForTestShouldRunTestsWithSpecifiedNumberOfWorkers() try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + 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); @@ -570,7 +585,7 @@ public async Task RunTestsForTestShouldNotRunTestsInParallelWhenDisabledFromRuns try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); testablePlatformService.SetupMockReflectionOperations(); @@ -599,7 +614,7 @@ public async Task RunTestsForTestShouldNotRunTestsInParallelWhenDisabledFromRuns testablePlatformService.MockReflectionOperations.Setup(fo => fo.GetRuntimeMethods(It.IsAny())) .Returns((Type t) => originalReflectionOperation.GetRuntimeMethods(t)); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassForParallelize.ThreadIds.Count.Should().Be(1); } @@ -629,7 +644,7 @@ public async Task RunTestsForTestShouldNotRunTestsInParallelWhenDisabledFromSour try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); testablePlatformService.SetupMockReflectionOperations(); @@ -658,7 +673,7 @@ public async Task RunTestsForTestShouldNotRunTestsInParallelWhenDisabledFromSour testablePlatformService.MockReflectionOperations.Setup(fo => fo.GetRuntimeMethods(It.IsAny())) .Returns((Type t) => originalReflectionOperation.GetRuntimeMethods(t)); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); DummyTestClassForParallelize.ThreadIds.Count.Should().Be(1); } @@ -696,8 +711,8 @@ public async Task RunTestsForTestShouldRunNonParallelizableTestsSeparately() try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + 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); @@ -730,7 +745,7 @@ public async Task RunTestsForTestShouldPreferParallelSettingsFromRunSettingsOver try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); TestablePlatformServiceProvider testablePlatformService = SetupTestablePlatformService(); testablePlatformService.SetupMockReflectionOperations(); @@ -763,7 +778,7 @@ public async Task RunTestsForTestShouldPreferParallelSettingsFromRunSettingsOver testablePlatformService.MockReflectionOperations.Setup(fo => fo.GetRuntimeMethods(It.IsAny())) .Returns((Type t) => originalReflectionOperation.GetRuntimeMethods(t)); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _enqueuedParallelTestsCount.Should().Be(2); @@ -802,8 +817,8 @@ private async Task RunTestsForTestShouldRunTestsInTheParentDomainsApartmentState try { - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + 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()); @@ -842,15 +857,15 @@ static TestCase[] BuildTests() => """); - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); var firstHandle = new TestableFrameworkHandle(); - var firstManager = new TestExecutionManager(EnvironmentWrapper.Instance, task => task()); - await firstManager.RunTestsAsync(BuildTests(), _runContext, firstHandle, new TestRunCancellationToken()); + var firstManager = new TestExecutionManager(task => task()); + 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(EnvironmentWrapper.Instance, task => task()); - await secondManager.RunTestsAsync(BuildTests(), _runContext, secondHandle, new TestRunCancellationToken()); + var secondManager = new TestExecutionManager(task => task()); + 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); @@ -885,9 +900,9 @@ public async Task RunTestsWithoutRandomizeTestOrderShouldPreserveInputOrder() """); - MSTestSettings.PopulateSettings(_runContext, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_runContext.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); - await _testExecutionManager.RunTestsAsync(tests, _runContext, _frameworkHandle, new TestRunCancellationToken()); + await _testExecutionManager.RunTestsAsync(ToUnitTestElements(tests), CurrentDeploymentContext, _frameworkHandle.ToAdapterMessageLogger(), TestResultRecorder, new TestElementFilterProvider(_runContext), new TestRunCancellationToken()); _frameworkHandle.TestCaseStartList.Should().Equal("TestA", "TestB", "TestC"); } @@ -904,6 +919,21 @@ private static TestCase GetTestCase(Type typeOfClass, string testName) return element.ToTestCase(); } + // Mirrors the conversion the adapter performs at the execution boundary (see MSTestExecutor): each host + // test case becomes a neutral UnitTestElement carrying the host execution-context (TCM) properties and the + // originating test case as an opaque recording handle, so recorded results are reported against the exact + // same TestCase instances the tests build (keeping the framework-handle Verify assertions meaningful). + private static UnitTestElement[] ToUnitTestElements(params TestCase[] tests) + => [.. tests.Select(ToUnitTestElement)]; + + private static UnitTestElement ToUnitTestElement(TestCase testCase) + { + UnitTestElement element = testCase.ToUnitTestElementWithUpdatedSource(testCase.Source); + element.ExecutionContextProperties = TcmTestPropertiesProvider.GetTcmProperties(testCase); + element.HostRecordingHandle = testCase; + return element; + } + private TestablePlatformServiceProvider SetupTestablePlatformService() { var testablePlatformService = new TestablePlatformServiceProvider(); @@ -1248,11 +1278,11 @@ internal sealed class TestableTestCaseFilterExpression : ITestCaseFilterExpressi internal class TestableTestExecutionManager : TestExecutionManager { - internal Action, IRunContext?, IFrameworkHandle, bool> ExecuteTestsWrapper { get; set; } = null!; + internal Action, DeploymentContext, IAdapterMessageLogger, ITestResultRecorder, bool> ExecuteTestsWrapper { get; set; } = null!; - internal override Task ExecuteTestsAsync(IEnumerable tests, IRunContext? runContext, IFrameworkHandle frameworkHandle, bool isDeploymentDone) + internal override Task ExecuteTestsAsync(IEnumerable tests, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger, ITestResultRecorder testResultRecorder, ITestElementFilterProvider? filterProvider, bool isDeploymentDone) { - ExecuteTestsWrapper?.Invoke(tests, runContext, frameworkHandle, isDeploymentDone); + ExecuteTestsWrapper?.Invoke(tests, deploymentContext, messageLogger, testResultRecorder, isDeploymentDone); return Task.CompletedTask; } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs index e5cb2d9162..3395bd01e3 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestPropertyAttributeTests.cs @@ -39,7 +39,7 @@ protected override void Dispose(bool disposing) public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; + TestTrait[] traits = [.. ReflectHelper.GetTestPropertiesAsTraits(typeof(DummyTestClassBase).GetMethod(nameof(DummyTestClassBase.VirtualTestMethodInBaseAndDerived))!)]; traits.Length.Should().Be(3); traits[0].Name.Should().Be("TestMethodKeyFromBase"); traits[0].Value.Should().Be("TestMethodValueFromBase"); @@ -51,7 +51,7 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassCorrectly() public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClassesAndOverriddenMethodsCorrectly_OverriddenIsTestMethod() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; + TestTrait[] traits = [.. ReflectHelper.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInBaseAndDerived))!)]; traits.Length.Should().Be(6); traits[0].Name.Should().Be("DerivedMethod1Key"); traits[0].Value.Should().Be("DerivedMethod1Value"); @@ -69,7 +69,7 @@ public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClasse public void GetTestMethodInfoShouldAddPropertiesFromContainingClassAndBaseClassesAndOverriddenMethodsCorrectly_OverriddenIsNotTestMethod() { - TestPlatform.ObjectModel.Trait[] traits = [.. ReflectHelper.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; + TestTrait[] traits = [.. ReflectHelper.GetTestPropertiesAsTraits(typeof(DummyTestClassDerived).GetMethod(nameof(DummyTestClassDerived.VirtualTestMethodInDerivedButNotTestMethodInBase))!)]; traits.Length.Should().Be(6); traits[0].Name.Should().Be("DerivedMethod2Key"); traits[0].Value.Should().Be("DerivedMethod2Value"); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestRunInfoTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestRunInfoTests.cs index e787279850..2dbfe026bf 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestRunInfoTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestRunInfoTests.cs @@ -54,7 +54,7 @@ public void CreateFromPopulatesCategoriesAndProperties() var element = new UnitTestElement(testMethod) { TestCategory = ["Smoke", "Compatibility"], - Traits = [new Trait("Owner", "alice"), new Trait("Owner", "bob"), new Trait("Priority", "1")], + Traits = [new TestTrait("Owner", "alice"), new TestTrait("Owner", "bob"), new TestTrait("Priority", "1")], }; var info = TestRunInfo.CreateFrom([element]); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj index f2fa7a0cdd..b265512c2a 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj @@ -25,6 +25,7 @@ + diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestSettingsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestSettingsTests.cs index a38cfe178b..fb598c3b15 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestSettingsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestSettingsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using AwesomeAssertions; @@ -376,7 +376,7 @@ public void PopulateSettingsShouldWarnWhenRandomizeTestOrderAndOrderTestsByNameI _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings.CurrentSettings.RandomizeTestOrder.Should().BeTrue(); MSTestSettings.CurrentSettings.OrderTestsByNameInClass.Should().BeTrue(); @@ -400,7 +400,7 @@ public void PopulateSettingsShouldNotWarnWhenOnlyRandomizeTestOrderIsEnabled() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings.CurrentSettings.RandomizeTestOrder.Should().BeTrue(); MSTestSettings.CurrentSettings.OrderTestsByNameInClass.Should().BeFalse(); @@ -656,7 +656,7 @@ public void DisableParallelizationShouldBeFalseByDefault() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings.CurrentSettings.DisableParallelization.Should().BeFalse(); _mockMessageLogger.Verify(lm => lm.SendMessage(TestMessageLevel.Warning, It.IsAny()), Times.Never); @@ -675,7 +675,7 @@ public void DisableParallelizationShouldBeConsumedFromRunSettingsWhenSpecified() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings.CurrentSettings.DisableParallelization.Should().BeTrue(); } @@ -693,7 +693,7 @@ public void DisableParallelization_WithInvalidValue_GettingAWarning() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); _mockMessageLogger.Verify(lm => lm.SendMessage(TestMessageLevel.Warning, "Invalid value '3' for runsettings entry 'DisableParallelization', setting will be ignored."), Times.Once); } @@ -981,7 +981,7 @@ public void CurrentSettingShouldReturnCachedLoadedSettings() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; MSTestSettings adapterSettings2 = MSTestSettings.CurrentSettings; @@ -1033,7 +1033,7 @@ public void PopulateSettingsShouldInitializeDefaultAdapterSettingsWhenDiscoveryC public void PopulateSettingsShouldInitializeDefaultSettingsWhenRunSettingsIsNull() { - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; adapterSettings.CaptureDebugTraces.Should().BeTrue(); @@ -1045,7 +1045,7 @@ public void PopulateSettingsShouldInitializeDefaultSettingsWhenRunSettingsIsNull public void PopulateSettingsShouldInitializeDefaultSettingsWhenRunSettingsXmlIsEmpty() { _mockDiscoveryContext.Setup(md => md.RunSettings!.SettingsXml).Returns(string.Empty); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; adapterSettings.CaptureDebugTraces.Should().BeTrue(); @@ -1067,7 +1067,7 @@ public void PopulateSettingsShouldInitializeSettingsToDefaultIfNotSpecified() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; @@ -1092,7 +1092,7 @@ public void PopulateSettingsShouldInitializeSettingsFromMSTestSection() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; @@ -1118,7 +1118,7 @@ public void PopulateSettingsShouldInitializeSettingsFromMSTestV2Section() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; @@ -1147,7 +1147,7 @@ public void PopulateSettingsShouldInitializeSettingsFromMSTestV2OverMSTestV1Sect _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); MSTestSettings adapterSettings = MSTestSettings.CurrentSettings; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs index f8cd836e35..45ce00f498 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs @@ -33,6 +33,44 @@ public void UnitTestElementConstructorShouldThrowIfTestMethodIsNull() #endregion + #region Source resolution / host test case tests + + public void WithUpdatedSourceShouldReturnSameInstanceWhenSourceUnchanged() + => _unitTestElement.WithUpdatedSource("A").Should().BeSameAs(_unitTestElement); + + public void WithUpdatedSourceShouldReturnCloneWithNewSourceAndLeaveOriginalUnchanged() + { + UnitTestElement clone = _unitTestElement.WithUpdatedSource("B"); + + clone.Should().NotBeSameAs(_unitTestElement); + clone.TestMethod.AssemblyName.Should().Be("B"); + // The original element (and its test method) must not be mutated by the clone. + _unitTestElement.TestMethod.AssemblyName.Should().Be("A"); + clone.TestMethod.Should().NotBeSameAs(_testMethod); + } + + public void GetOrCreateHostTestCaseShouldReturnHostHandleWhenPresent() + { + var hostTestCase = new TestCase("C.M", EngineConstants.ExecutorUri, "A"); + _unitTestElement.HostRecordingHandle = hostTestCase; + + _unitTestElement.GetOrCreateHostTestCase().Should().BeSameAs(hostTestCase); + } + + public void GetOrCreateHostTestCaseShouldMaterializeAndCacheWhenNoHandle() + { + _unitTestElement.HostRecordingHandle.Should().BeNull(); + + TestCase materialized = _unitTestElement.GetOrCreateHostTestCase(); + + materialized.Should().NotBeNull(); + // Subsequent calls (deployment, test-start, each reported result) reuse the same instance. + _unitTestElement.GetOrCreateHostTestCase().Should().BeSameAs(materialized); + _unitTestElement.HostRecordingHandle.Should().BeSameAs(materialized); + } + + #endregion + #region ToTestCase tests public void ToTestCaseShouldSetFullyQualifiedName() @@ -126,7 +164,7 @@ public void ToTestCaseShouldSetTraitsIfPresent() testCase.Traits.Count().Should().Be(0); #pragma warning restore CA1827 // Do not use Count() or LongCount() when Any() can be used - var trait = new Trait("trait", "value"); + var trait = new TestTrait("trait", "value"); _unitTestElement.Traits = [trait]; testCase = _unitTestElement.ToTestCase(); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/RunConfigurationSettingsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/RunConfigurationSettingsTests.cs index 15e1c5c839..94e060c056 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/RunConfigurationSettingsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/RunConfigurationSettingsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using AwesomeAssertions; @@ -69,7 +69,7 @@ public void PopulateSettingsShouldInitializeDefaultConfigurationSettingsWhenDisc public void PopulateSettingsShouldInitializeDefaultSettingsWhenRunSettingsIsNull() { - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); RunConfigurationSettings settings = MSTestSettings.RunConfigurationSettings; settings.ExecutionApartmentState.Should().BeNull(); @@ -78,7 +78,7 @@ public void PopulateSettingsShouldInitializeDefaultSettingsWhenRunSettingsIsNull public void PopulateSettingsShouldInitializeDefaultSettingsWhenRunSettingsXmlIsEmpty() { _mockDiscoveryContext.Setup(md => md.RunSettings!.SettingsXml).Returns(string.Empty); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); RunConfigurationSettings settings = MSTestSettings.RunConfigurationSettings; settings.ExecutionApartmentState.Should().BeNull(); @@ -97,7 +97,7 @@ public void PopulateSettingsShouldInitializeSettingsToDefaultIfNotSpecified() _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); RunConfigurationSettings settings = MSTestSettings.RunConfigurationSettings; settings.Should().NotBeNull(); @@ -120,7 +120,7 @@ public void PopulateSettingsShouldInitializeSettingsFromRunConfigurationSection( _mockDiscoveryContext.Setup(dc => dc.RunSettings).Returns(_mockRunSettings.Object); _mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); + MSTestSettings.PopulateSettings(_mockDiscoveryContext.Object.RunSettings?.SettingsXml, _mockMessageLogger.Object.ToAdapterMessageLogger(), null); RunConfigurationSettings settings = MSTestSettings.RunConfigurationSettings; settings.Should().NotBeNull(); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs index ad2b3917b3..b154dfc5c1 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestDeploymentTests.cs @@ -4,12 +4,11 @@ #if NETFRAMEWORK using AwesomeAssertions; +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.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Moq; @@ -45,15 +44,12 @@ public DesktopTestDeploymentTests() public void DeployShouldDeployFilesInASourceAndReturnTrue() { - TestCase testCase = GetTestCase(Assembly.GetExecutingAssembly().Location); + UnitTestElement testCase = GetTestCase(Assembly.GetExecutingAssembly().Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); string? warning; string sourceFile = Assembly.GetExecutingAssembly().GetName().Name + ".exe"; @@ -68,17 +64,14 @@ public void DeployShouldDeployFilesInASourceAndReturnTrue() public void DeployShouldDeployFilesInMultipleSourcesAndReturnTrue() { - TestCase testCase1 = GetTestCase(Assembly.GetExecutingAssembly().Location); + UnitTestElement testCase1 = GetTestCase(Assembly.GetExecutingAssembly().Location); string sourceFile2 = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "a.dll"); - TestCase testCase2 = GetTestCase(sourceFile2); + UnitTestElement testCase2 = GetTestCase(sourceFile2); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase1, testCase2 }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase1, testCase2 }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); string? warning; string sourceFile1 = Assembly.GetExecutingAssembly().GetName().Name + ".exe"; @@ -100,15 +93,12 @@ public void DeployShouldDeployFilesInMultipleSourcesAndReturnTrue() public void DeployShouldCreateDeploymentDirectories() { - TestCase testCase = GetTestCase(typeof(DesktopTestDeploymentTests).Assembly.Location); + UnitTestElement testCase = GetTestCase(typeof(DesktopTestDeploymentTests).Assembly.Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); // matched twice because root deployment and out directory are same in net core _mockFileUtility.Verify(fu => fu.CreateDirectoryIfNotExists(testRunDirectories.RootDeploymentDirectory), Times.Once); @@ -118,16 +108,16 @@ public void DeployShouldCreateDeploymentDirectories() #region private methods - private TestCase GetTestCase(string source) + private static UnitTestElement GetTestCase(string source) { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), source); + var testCase = new UnitTestElement(new TestMethod("M", "C", source, displayName: null)); KeyValuePair[] kvpArray = [ new KeyValuePair( DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory) ]; - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); + testCase.DeploymentItems = kvpArray; return testCase; } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceHostTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceHostTests.cs index c30b8edf73..e40ca50f4d 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceHostTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/DesktopTestSourceHostTests.cs @@ -8,7 +8,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; using Moq; @@ -135,10 +134,7 @@ public void SetupHostShouldHaveParentDomainsAppBaseSetToTestSourceLocation() """; string location = typeof(TestSourceHost).Assembly.Location; - var mockRunSettings = new Mock(); - mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - - TestSourceHost sourceHost = new(location, mockRunSettings.Object); + TestSourceHost sourceHost = new(location, runSettingsXml); try { @@ -189,10 +185,7 @@ public void NoAppDomainShouldGetCreatedWhenDisableAppDomainIsSetToTrue() "; string location = typeof(TestSourceHost).Assembly.Location; - var mockRunSettings = new Mock(); - mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - - Mock testSourceHost = new(location, mockRunSettings.Object) { CallBase = true }; + Mock testSourceHost = new(location, runSettingsXml) { CallBase = true }; try { @@ -220,10 +213,7 @@ public void AppDomainShouldGetCreatedWhenDisableAppDomainIsSetToFalse() """; string location = typeof(TestSourceHost).Assembly.Location; - var mockRunSettings = new Mock(); - mockRunSettings.Setup(rs => rs.SettingsXml).Returns(runSettingsXml); - - Mock testSourceHost = new(location, mockRunSettings.Object) { CallBase = true }; + Mock testSourceHost = new(location, runSettingsXml) { CallBase = true }; try { diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs index 568ee31d94..f87ab95723 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/ReflectionOperationsTests.cs @@ -4,6 +4,7 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; +using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; @@ -237,9 +238,9 @@ public void GetTestPropertiesAsTraitsShouldIncludeTestPropertiesAtMethodLevel() { _attributeMockingHelper.AddCustomAttribute(typeof(TestPropertyAttribute), [new TestPropertyAttribute("MethodKey", "MethodValue")], MemberTypes.Method); - Trait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); + TestTrait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); - actual.Should().BeEquivalentTo(new[] { new Trait("MethodKey", "MethodValue") }, options => options.WithStrictOrdering()); + actual.Should().BeEquivalentTo(new[] { new TestTrait("MethodKey", "MethodValue") }, options => options.WithStrictOrdering()); } /// @@ -249,9 +250,9 @@ public void GetTestPropertiesAsTraitsShouldIncludeTestPropertiesAtClassLevel() { _attributeMockingHelper.AddCustomAttribute(typeof(TestPropertyAttribute), [new TestPropertyAttribute("ClassKey", "ClassValue")], MemberTypes.TypeInfo); - Trait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); + TestTrait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); - actual.Should().BeEquivalentTo(new[] { new Trait("ClassKey", "ClassValue") }, options => options.WithStrictOrdering()); + actual.Should().BeEquivalentTo(new[] { new TestTrait("ClassKey", "ClassValue") }, options => options.WithStrictOrdering()); } /// @@ -262,9 +263,9 @@ public void GetTestPropertiesAsTraitsShouldIncludeTestPropertiesAtAllLevels() _attributeMockingHelper.AddCustomAttribute(typeof(TestPropertyAttribute), [new TestPropertyAttribute("MethodKey", "MethodValue")], MemberTypes.Method); _attributeMockingHelper.AddCustomAttribute(typeof(TestPropertyAttribute), [new TestPropertyAttribute("ClassKey", "ClassValue")], MemberTypes.TypeInfo); - Trait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); + TestTrait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); - actual.Should().BeEquivalentTo(new[] { new Trait("MethodKey", "MethodValue"), new Trait("ClassKey", "ClassValue") }, options => options.WithStrictOrdering()); + actual.Should().BeEquivalentTo(new[] { new TestTrait("MethodKey", "MethodValue"), new TestTrait("ClassKey", "ClassValue") }, options => options.WithStrictOrdering()); } /// @@ -274,9 +275,9 @@ public void GetTestPropertiesAsTraitsShouldIncludeMultipleTestPropertiesAtMethod { _attributeMockingHelper.AddCustomAttribute(typeof(TestPropertyAttribute), [new TestPropertyAttribute("Key1", "Value1"), new TestPropertyAttribute("Key2", "Value2")], MemberTypes.Method); - Trait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); + TestTrait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); - actual.Should().BeEquivalentTo(new[] { new Trait("Key1", "Value1"), new Trait("Key2", "Value2") }, options => options.WithStrictOrdering()); + actual.Should().BeEquivalentTo(new[] { new TestTrait("Key1", "Value1"), new TestTrait("Key2", "Value2") }, options => options.WithStrictOrdering()); } /// @@ -286,7 +287,7 @@ public void GetTestPropertiesAsTraitsShouldReturnEmptyWhenNoTestProperties() { _attributeMockingHelper.AddCustomAttribute(typeof(TestCategoryBaseAttribute), [new TestCategoryAttribute("SomeCategory")], MemberTypes.Method); - Trait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); + TestTrait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(_method.Object); actual.Should().BeEmpty(); } @@ -577,7 +578,7 @@ public void GetTestPropertiesAsTraitsShouldNotIncludeClassLevelAttributesWhenRef [new TestPropertyAttribute("ClassKey", "ClassValue")], MemberTypes.TypeInfo); - Trait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(methodWithNullReflectedType.Object); + TestTrait[] actual = _reflectionOperations.GetTestPropertiesAsTraits(methodWithNullReflectedType.Object); actual.Should().BeEmpty(); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestContextImplementationTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestContextImplementationTests.cs index 6062851605..3a6bf32c98 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestContextImplementationTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestContextImplementationTests.cs @@ -9,8 +9,8 @@ using AwesomeAssertions; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; +using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Moq; @@ -28,7 +28,7 @@ public class TestContextImplementationTests : TestContainer private TestContextImplementation _testContextImplementation = null!; - private TestContextImplementation CreateTestContextImplementation(IMessageLogger? messageLogger = null) + private TestContextImplementation CreateTestContextImplementation(IAdapterMessageLogger? messageLogger = null) => new(_testMethod.Object, null, _properties, messageLogger, null); public void TestContextConstructorShouldInitializeProperties() @@ -346,19 +346,19 @@ public void GetResultFilesShouldReturnListOfAddedResultFiles() public void DisplayMessageShouldForwardToIMessageLogger() { - var messageLoggerMock = new Mock(MockBehavior.Strict); + var messageLoggerMock = new Mock(MockBehavior.Strict); messageLoggerMock - .Setup(l => l.SendMessage(It.IsAny(), It.IsAny())); + .Setup(l => l.SendMessage(It.IsAny(), It.IsAny())); _testContextImplementation = CreateTestContextImplementation(messageLoggerMock.Object); _testContextImplementation.DisplayMessage(MessageLevel.Informational, "InfoMessage"); _testContextImplementation.DisplayMessage(MessageLevel.Warning, "WarningMessage"); _testContextImplementation.DisplayMessage(MessageLevel.Error, "ErrorMessage"); - messageLoggerMock.Verify(x => x.SendMessage(TestMessageLevel.Informational, "InfoMessage"), Times.Once); - messageLoggerMock.Verify(x => x.SendMessage(TestMessageLevel.Warning, "WarningMessage"), Times.Once); - messageLoggerMock.Verify(x => x.SendMessage(TestMessageLevel.Error, "ErrorMessage"), Times.Once); + messageLoggerMock.Verify(x => x.SendMessage(MessageLevel.Informational, "InfoMessage"), Times.Once); + messageLoggerMock.Verify(x => x.SendMessage(MessageLevel.Warning, "WarningMessage"), Times.Once); + messageLoggerMock.Verify(x => x.SendMessage(MessageLevel.Error, "ErrorMessage"), Times.Once); } public void GetAndClearOutput_ShouldReturnContentThenClearBuffer() @@ -399,7 +399,7 @@ public void GetAndClearTrace_ShouldReturnContentThenClearBuffer() public void WritesFromBackgroundThreadShouldNotThrow() { - TestContextImplementation testContextImplementation = CreateTestContextImplementation(new Mock().Object); + TestContextImplementation testContextImplementation = CreateTestContextImplementation(new Mock().Object); var t = new Thread(() => { for (int i = 0; i < 100; i++) @@ -704,12 +704,12 @@ public void CloneForDataDrivenIterationShouldCopyTestRunCount() public void CloneForDataDrivenIterationShouldShareMessageLogger() { - var messageLoggerMock = new Mock(); + var messageLoggerMock = new Mock(); _testContextImplementation = CreateTestContextImplementation(messageLoggerMock.Object); TestContextImplementation clone = _testContextImplementation.CloneForDataDrivenIteration(); clone.DisplayMessage(MessageLevel.Informational, "from-clone"); - messageLoggerMock.Verify(x => x.SendMessage(TestMessageLevel.Informational, "from-clone"), Times.Once); + messageLoggerMock.Verify(x => x.SendMessage(MessageLevel.Informational, "from-clone"), Times.Once); } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs index c88f094732..97f30a2bd2 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestDeploymentTests.cs @@ -4,12 +4,11 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; +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.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; using Moq; @@ -111,15 +110,12 @@ public void CleanupShouldNotDeleteDirectoriesIfRunSettingsSpecifiesSo() MSTestSettingsProvider mstestSettingsProvider = new(); mstestSettingsProvider.Load(reader); - TestCase testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); + UnitTestElement testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); testDeployment.Cleanup(); @@ -128,15 +124,12 @@ public void CleanupShouldNotDeleteDirectoriesIfRunSettingsSpecifiesSo() public void CleanupShouldDeleteRootDeploymentDirectory() { - TestCase testCase = GetTestCase(typeof(DeploymentUtilityTests).Assembly.Location); + UnitTestElement testCase = GetTestCase(typeof(DeploymentUtilityTests).Assembly.Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); // Act. testDeployment.Cleanup(); @@ -152,15 +145,12 @@ public void CleanupShouldDeleteRootDeploymentDirectory() public void GetDeploymentDirectoryShouldReturnDeploymentOutputDirectory() { - TestCase testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); + UnitTestElement testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); // Act. testDeployment.GetDeploymentDirectory().Should().Be(testRunDirectories.OutDirectory); @@ -172,14 +162,14 @@ public void GetDeploymentDirectoryShouldReturnDeploymentOutputDirectory() public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseButHasDeploymentItems() { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); + UnitTestElement testCase = CreateTestElement("path/to/asm.dll"); KeyValuePair[] kvpArray = [ new KeyValuePair( DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory) ]; - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); + testCase.DeploymentItems = kvpArray; var testDeployment = new TestDeployment( new DeploymentItemUtility(_mockReflectionOperations.Object), @@ -194,7 +184,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseButHasDeployme mstestSettingsProvider.Load(reader); // Deployment should not happen - testDeployment.Deploy(new List { testCase }, null, null!).Should().BeFalse(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(null, null), new Mock().Object).Should().BeFalse(); // Deployment directories should not be created testDeployment.GetDeploymentDirectory().Should().BeNull(); @@ -202,8 +192,8 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseButHasDeployme public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseAndHasNoDeploymentItems() { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); + UnitTestElement testCase = CreateTestElement("path/to/asm.dll"); + testCase.DeploymentItems = null; var testDeployment = new TestDeployment( new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), @@ -217,7 +207,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseAndHasNoDeploy mstestSettingsProvider.Load(reader); // Deployment should not happen - testDeployment.Deploy(new List { testCase }, null, null!).Should().BeFalse(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(null, null), new Mock().Object).Should().BeFalse(); // Deployment directories should get created on .NET Framework for compat, but not on .NET Core #if NETFRAMEWORK @@ -229,8 +219,8 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToFalseAndHasNoDeploy public void DeployShouldReturnFalseWhenDeploymentEnabledSetToTrueButHasNoDeploymentItems() { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), "path/to/asm.dll"); - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); + UnitTestElement testCase = CreateTestElement("path/to/asm.dll"); + testCase.DeploymentItems = null; var testDeployment = new TestDeployment( new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), @@ -244,7 +234,7 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToTrueButHasNoDeploym mstestSettingsProvider.Load(reader); // Deployment should not happen - testDeployment.Deploy(new List { testCase }, null, null!).Should().BeFalse(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(null, null), new Mock().Object).Should().BeFalse(); // Deployment directories should get created on .NET Framework for compat, but not on .NET Core #if NETFRAMEWORK @@ -257,14 +247,14 @@ public void DeployShouldReturnFalseWhenDeploymentEnabledSetToTrueButHasNoDeploym // TODO: This test has to have mocks (tracked by https://github.com/microsoft/testfx/issues/8086). It actually deploys stuff and we cannot assume that all the dependencies get copied over to bin\debug. internal void DeployShouldReturnTrueWhenDeploymentEnabledSetToTrueAndHasDeploymentItems() { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), typeof(TestDeploymentTests).Assembly.Location); + UnitTestElement testCase = CreateTestElement(typeof(TestDeploymentTests).Assembly.Location); KeyValuePair[] kvpArray = [ new KeyValuePair( DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory) ]; - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); + testCase.DeploymentItems = kvpArray; var testDeployment = new TestDeployment( new DeploymentItemUtility(_mockReflectionOperations.Object), new DeploymentUtility(), @@ -278,7 +268,7 @@ internal void DeployShouldReturnTrueWhenDeploymentEnabledSetToTrueAndHasDeployme mstestSettingsProvider.Load(reader); // Deployment should happen - testDeployment.Deploy(new List { testCase }, null, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(null, null), new Mock().Object).Should().BeTrue(); // Deployment directories should get created testDeployment.GetDeploymentDirectory().Should().NotBeNull(); @@ -309,15 +299,12 @@ public void GetDeploymentInformationShouldReturnAppBaseDirectoryIfRunDirectoryIs public void GetDeploymentInformationShouldReturnRunDirectoryInformationIfSourceIsNull() { // Arrange. - TestCase testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); + UnitTestElement testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); // Act. IDictionary properties = TestDeployment.GetDeploymentInformation(null); @@ -339,15 +326,12 @@ public void GetDeploymentInformationShouldReturnRunDirectoryInformationIfSourceI public void GetDeploymentInformationShouldReturnRunDirectoryInformationIfSourceIsNotNull() { // Arrange. - TestCase testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); + UnitTestElement testCase = GetTestCase(typeof(TestDeploymentTests).Assembly.Location); // Setup mocks. TestDeployment testDeployment = CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories); - var mockRunContext = new Mock(); - mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(testRunDirectories.RootDeploymentDirectory); - - testDeployment.Deploy(new List { testCase }, mockRunContext.Object, new Mock().Object).Should().BeTrue(); + testDeployment.Deploy(new List { testCase }, new DeploymentContext(testRunDirectories.RootDeploymentDirectory, null), new Mock().Object).Should().BeTrue(); // Act. IDictionary properties = TestDeployment.GetDeploymentInformation(typeof(TestDeploymentTests).Assembly.Location); @@ -383,20 +367,23 @@ private void SetupDeploymentItems(ICustomAttributeProvider attributeProvider, Ke ru => ru.GetAttributes(attributeProvider)).Returns(deploymentItemAttributes); } - private static TestCase GetTestCase(string source) + private static UnitTestElement GetTestCase(string source) { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), source); + UnitTestElement testCase = CreateTestElement(source); KeyValuePair[] kvpArray = [ new KeyValuePair( DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory) ]; - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); + testCase.DeploymentItems = kvpArray; return testCase; } + private static UnitTestElement CreateTestElement(string source) + => new(new TestMethod("M", "C", source, displayName: null)); + private TestDeployment CreateAndSetupDeploymentRelatedUtilities(out TestRunDirectories testRunDirectories) { string currentExecutingFolder = Path.GetDirectoryName(typeof(TestDeploymentTests).Assembly.Location)!; diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs index 82126a12c3..baf709a8e7 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/TestableImplementations/TestablePlatformServiceProvider.cs @@ -4,7 +4,6 @@ using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Moq; @@ -41,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; @@ -69,7 +73,7 @@ public IReflectionOperations ReflectionOperations public int GetTestContextCallCount { get; private set; } - public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IMessageLogger messageLogger, UnitTestOutcome outcome) + public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFullName, IDictionary properties, IAdapterMessageLogger messageLogger, UnitTestOutcome outcome) { GetTestContextCallCount++; var testContextImpl = new TestContextImplementation(testMethod, testClassFullName, properties, messageLogger, testRunCancellationToken: null); @@ -77,7 +81,7 @@ public ITestContext GetTestContext(ITestMethod? testMethod, string? testClassFul return testContextImpl; } - public ITestSourceHost CreateTestSourceHost(string source, TestPlatform.ObjectModel.Adapter.IRunSettings? runSettings) => MockTestSourceHost.Object; + public ITestSourceHost CreateTestSourceHost(string source, string? settingsXml) => MockTestSourceHost.Object; public void SetupMockReflectionOperations() { diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs index c1a11d0c2b..10086d9e2d 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentItemUtilityTests.cs @@ -4,12 +4,12 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; +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.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Moq; @@ -19,13 +19,6 @@ namespace MSTestAdapter.PlatformServices.UnitTests.Utilities; public class DeploymentItemUtilityTests : TestContainer { - internal static readonly TestProperty DeploymentItemsProperty = TestProperty.Register( - "MSTestDiscoverer.DeploymentItems", - "DeploymentItems", - typeof(KeyValuePair[]), - TestPropertyAttributes.Hidden, - typeof(TestCase)); - private readonly Mock _mockReflectionOperations; private readonly DeploymentItemUtility _deploymentItemUtility; private readonly ICollection _warnings; @@ -391,22 +384,24 @@ public void IsValidDeploymentItemShouldReturnTrueForAValidDeploymentItem() public void HasDeployItemsShouldReturnFalseForNoDeploymentItems() { - TestCase testCase = new("A.C.M", new Uri("executor://testExecutor"), "A"); - testCase.SetPropertyValue(DeploymentItemsProperty, null); + var testCase = new UnitTestElement(new TestMethod("M", "C", "A", displayName: null)) + { + DeploymentItems = null, + }; DeploymentItemUtility.HasDeploymentItems(testCase).Should().BeFalse(); } public void HasDeployItemsShouldReturnTrueWhenDeploymentItemsArePresent() { - TestCase testCase = new("A.C.M", new Uri("executor://testExecutor"), "A"); + var testCase = new UnitTestElement(new TestMethod("M", "C", "A", displayName: null)); KeyValuePair[] kvpArray = [ new KeyValuePair( _defaultDeploymentItemPath, _defaultDeploymentItemOutputDirectory) ]; - testCase.SetPropertyValue(DeploymentItemsProperty, kvpArray); + testCase.DeploymentItems = kvpArray; DeploymentItemUtility.HasDeploymentItems(testCase).Should().BeTrue(); } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs index c66fcba062..4f7d0d9e59 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Utilities/DeploymentUtilityTests.cs @@ -4,14 +4,12 @@ #if !WINDOWS_UWP && !WIN_UI using AwesomeAssertions; +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.Interface; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Resources; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Moq; @@ -29,8 +27,7 @@ public class DeploymentUtilityTests : TestContainer private readonly Mock _mockReflectionOperations; private readonly Mock _mockFileUtility; private readonly Mock _mockAssemblyUtility; - private readonly Mock _mockRunContext; - private readonly Mock _mockTestExecutionRecorder; + private readonly Mock _mockMessageLogger; private readonly DeploymentUtility _deploymentUtility; @@ -52,16 +49,15 @@ public DeploymentUtilityTests() _mockAssemblyUtility.Object, _mockFileUtility.Object); - _mockRunContext = new Mock(); - _mockTestExecutionRecorder = new Mock(); + _mockMessageLogger = new Mock(); } #region Deploy tests public void DeployShouldReturnFalseWhenNoDeploymentItemsOnTestCase() { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), Path.Combine(RootDeploymentDirectory, "asm.dll")); - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, null); + UnitTestElement testCase = CreateTestElement(Path.Combine(RootDeploymentDirectory, "asm.dll")); + testCase.DeploymentItems = null; var testRunDirectories = new TestRunDirectories(RootDeploymentDirectory, Path.Combine(RootDeploymentDirectory, "asm.dll"), isAppDomainCreationDisabled: true); _mockFileUtility.Setup(fu => fu.DoesDirectoryExist(It.Is(s => !s.EndsWith(".dll") && !s.EndsWith(".exe") && !s.EndsWith(".config")))) @@ -77,17 +73,17 @@ public void DeployShouldReturnFalseWhenNoDeploymentItemsOnTestCase() #endif _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeFalse(); } #if NETFRAMEWORK public void DeployShouldDeploySourceAndItsConfigFile() { - TestCase testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); // Setup mocks. _mockFileUtility.Setup(fu => fu.DoesDirectoryExist(It.Is(s => !s.EndsWith(".dll") && !s.EndsWith(".exe") && !s.EndsWith(".config")))) @@ -102,10 +98,10 @@ public void DeployShouldDeploySourceAndItsConfigFile() // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -132,7 +128,7 @@ public void DeployShouldDeployDependentFiles() { string dependencyFile = "C:\\temp\\dependency.dll"; - TestCase testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); // Setup mocks. _mockFileUtility.Setup(fu => fu.DoesDirectoryExist(It.Is(s => !s.EndsWith(".dll") && !s.EndsWith(".exe") && !s.EndsWith(".config")))) @@ -147,10 +143,10 @@ public void DeployShouldDeployDependentFiles() // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -167,7 +163,7 @@ public void DeployShouldDeployDependentFiles() public void DeployShouldDeploySatelliteAssemblies() { - TestCase testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); string assemblyFullPath = Assembly.GetExecutingAssembly().Location; string satelliteFullPath = Path.Combine(Path.GetDirectoryName(assemblyFullPath), "de", "satellite.dll"); @@ -184,10 +180,10 @@ public void DeployShouldDeploySatelliteAssemblies() // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -208,7 +204,7 @@ public void DeployShouldNotDeployIfOutputDirectoryIsInvalid() string deploymentItemPath = "C:\\temp\\sample.dll"; string deploymentItemOutputDirectory = "..\\..\\out"; - TestCase testCase = GetTestCaseAndTestRunDirectories(deploymentItemPath, deploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(deploymentItemPath, deploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); // Setup mocks. _mockFileUtility.Setup(fu => fu.DoesDirectoryExist(It.Is(s => !s.EndsWith(".dll") && !s.EndsWith(".exe") && !s.EndsWith(".config")))) @@ -225,10 +221,10 @@ public void DeployShouldNotDeployIfOutputDirectoryIsInvalid() // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -243,10 +239,10 @@ public void DeployShouldNotDeployIfOutputDirectoryIsInvalid() Times.Never); // Verify the warning. - _mockTestExecutionRecorder.Verify( + _mockMessageLogger.Verify( ter => ter.SendMessage( - TestMessageLevel.Warning, + MessageLevel.Warning, string.Format( CultureInfo.InvariantCulture, Resource.DeploymentErrorBadDeploymentItem, @@ -257,7 +253,7 @@ public void DeployShouldNotDeployIfOutputDirectoryIsInvalid() public void DeployShouldDeployContentsOfADirectoryIfSpecified() { - TestCase testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); string content1 = Path.Combine(DefaultDeploymentItemPath, "directoryContents.dll"); var directoryContentFiles = new List { content1 }; @@ -280,10 +276,10 @@ public void DeployShouldDeployContentsOfADirectoryIfSpecified() // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -301,7 +297,7 @@ public void DeployShouldDeployContentsOfADirectoryIfSpecified() #if NETFRAMEWORK public void DeployShouldDeployPdbWithSourceIfPdbFileIsPresentInSourceDirectory() { - TestCase testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); // Setup mocks. _mockFileUtility.Setup(fu => fu.DoesDirectoryExist(It.Is(s => !s.EndsWith(".dll") && !s.EndsWith(".exe") && !s.EndsWith(".config")))) @@ -324,10 +320,10 @@ public void DeployShouldDeployPdbWithSourceIfPdbFileIsPresentInSourceDirectory() // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -356,7 +352,7 @@ public void DeployShouldNotDeployPdbFileOfAssemblyIfPdbFileIsNotPresentInAssembl // Path for pdb file of dependent assembly if pdb file is present. string pdbFile = Path.ChangeExtension(dependencyFile, "pdb"); - TestCase testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); + UnitTestElement testCase = GetTestCaseAndTestRunDirectories(DefaultDeploymentItemPath, DefaultDeploymentItemOutputDirectory, out TestRunDirectories testRunDirectories); // Setup mocks. _mockFileUtility.Setup(fu => fu.DoesDirectoryExist(It.Is(s => !s.EndsWith(".dll") && !s.EndsWith(".exe") && !s.EndsWith(".config")))) @@ -379,10 +375,10 @@ public void DeployShouldNotDeployPdbFileOfAssemblyIfPdbFileIsNotPresentInAssembl // Act. _deploymentUtility.Deploy( - new List { testCase }, - testCase.Source, - _mockRunContext.Object, - _mockTestExecutionRecorder.Object, + new List { testCase }, + testCase.TestMethod.AssemblyName, + new DeploymentContext(null, null), + _mockMessageLogger.Object, testRunDirectories).Should().BeTrue(); // Assert. @@ -411,12 +407,11 @@ public void DeployShouldNotDeployPdbFileOfAssemblyIfPdbFileIsNotPresentInAssembl public void CreateDeploymentDirectoriesShouldCreateDeploymentDirectoryFromRunContext() { // Setup mocks - _mockRunContext.Setup(rc => rc.TestRunDirectory).Returns(TestRunDirectory); _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(TestRunDirectory, It.IsAny())) .Returns(RootDeploymentDirectory); // Act. - _deploymentUtility.CreateDeploymentDirectories(_mockRunContext.Object, null); + _deploymentUtility.CreateDeploymentDirectories(new DeploymentContext(TestRunDirectory, null), null); // Assert. _mockFileUtility.Verify(fu => fu.CreateDirectoryIfNotExists(RootDeploymentDirectory), Times.Once); @@ -428,12 +423,11 @@ public void CreateDeploymentDirectoriesShouldCreateDeploymentDirectoryFromRunCon public void CreateDeploymentDirectoriesShouldCreateDefaultDeploymentDirectoryIfTestRunDirectoryIsNull() { // Setup mocks - _mockRunContext.Setup(rc => rc.TestRunDirectory).Returns((string)null!); _mockFileUtility.Setup(fu => fu.GetNextIterationDirectoryName(It.Is(s => s.Contains(TestRunDirectories.DefaultDeploymentRootDirectory)), It.IsAny())) .Returns(RootDeploymentDirectory); // Act. - _deploymentUtility.CreateDeploymentDirectories(_mockRunContext.Object, null); + _deploymentUtility.CreateDeploymentDirectories(new DeploymentContext(null, null), null); // Assert. _mockFileUtility.Verify(fu => fu.CreateDirectoryIfNotExists(RootDeploymentDirectory), Times.Once); @@ -449,7 +443,7 @@ public void CreateDeploymentDirectoriesShouldCreateDefaultDeploymentDirectoryIfR .Returns(RootDeploymentDirectory); // Act. - _deploymentUtility.CreateDeploymentDirectories(null, null); + _deploymentUtility.CreateDeploymentDirectories(new DeploymentContext(null, null), null); // Assert. _mockFileUtility.Verify(fu => fu.CreateDirectoryIfNotExists(RootDeploymentDirectory), Times.Once); @@ -462,16 +456,16 @@ public void CreateDeploymentDirectoriesShouldCreateDefaultDeploymentDirectoryIfR #region private methods - private static TestCase GetTestCaseAndTestRunDirectories(string deploymentItemPath, string defaultDeploymentItemOutputDirectoryOut, out TestRunDirectories testRunDirectories) + private static UnitTestElement GetTestCaseAndTestRunDirectories(string deploymentItemPath, string defaultDeploymentItemOutputDirectoryOut, out TestRunDirectories testRunDirectories) { - var testCase = new TestCase("A.C.M", new Uri("executor://testExecutor"), Assembly.GetExecutingAssembly().Location); + UnitTestElement testCase = CreateTestElement(Assembly.GetExecutingAssembly().Location); KeyValuePair[] kvpArray = [ new KeyValuePair( deploymentItemPath, defaultDeploymentItemOutputDirectoryOut) ]; - testCase.SetPropertyValue(DeploymentItemUtilityTests.DeploymentItemsProperty, kvpArray); + testCase.DeploymentItems = kvpArray; string currentExecutingFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; const bool isAppDomainCreationDisabled = @@ -486,6 +480,9 @@ private static TestCase GetTestCaseAndTestRunDirectories(string deploymentItemPa return testCase; } + private static UnitTestElement CreateTestElement(string source) + => new(new TestMethod("M", "C", source, displayName: null)); + #endregion } #endif