diff --git a/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/TestApplicationBuilderExtensions.cs b/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/TestApplicationBuilderExtensions.cs index 436ce80215..326c85f88f 100644 --- a/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/TestApplicationBuilderExtensions.cs +++ b/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/TestApplicationBuilderExtensions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #if !WINDOWS_UWP -using Microsoft.Testing.Extensions.VSTestBridge.Capabilities; +using Microsoft.Testing.Extensions.TrxReport.Abstractions; using Microsoft.Testing.Extensions.VSTestBridge.Helpers; using Microsoft.Testing.Platform.Builder; using Microsoft.Testing.Platform.Capabilities.TestFramework; @@ -17,6 +17,17 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting; [SuppressMessage("ApiDesign", "RS0030:Do not use banned APIs", Justification = "We can use MTP from this folder")] public static class TestApplicationBuilderExtensions { + // NOTE: We intentionally use this class and not VSTestBridgeExtensionBaseCapabilities because + // we don't want MSTest to use vstestProvider capability + private sealed class MSTestCapabilities : ITrxReportCapability + { + bool ITrxReportCapability.IsSupported { get; } = true; + + void ITrxReportCapability.Enable() + { + } + } + /// /// Register MSTest as the test framework and register the necessary services. /// @@ -34,7 +45,7 @@ public static void AddMSTest(this ITestApplicationBuilder testApplicationBuilder testApplicationBuilder.AddRunSettingsEnvironmentVariableProvider(extension); testApplicationBuilder.RegisterTestFramework( serviceProvider => new TestFrameworkCapabilities( - new VSTestBridgeExtensionBaseCapabilities(), + new MSTestCapabilities(), #pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. new MSTestBannerCapability(serviceProvider.GetRequiredService()), MSTestGracefulStopTestExecutionCapability.Instance), diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Capabilities/VSTestBridgeExtensionBaseCapabilities.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Capabilities/VSTestBridgeExtensionBaseCapabilities.cs index b9cb3dcdb4..5b2b690303 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Capabilities/VSTestBridgeExtensionBaseCapabilities.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Capabilities/VSTestBridgeExtensionBaseCapabilities.cs @@ -9,6 +9,10 @@ namespace Microsoft.Testing.Extensions.VSTestBridge.Capabilities; /// /// The VSTest bridged test framework capabilities. /// +// NOTE: MSTest no longer uses this, as we don't want to use the vstestProvider. +// Only NUnit and Expecto use this. +// https://github.com/nunit/nunit3-vs-adapter/blob/3d0f824243aaaeb85621d3c7dddc92e7a7c45097/src/NUnitTestAdapter/TestingPlatformAdapter/TestApplicationBuilderExtensions.cs#L20 +// https://github.com/YoloDev/YoloDev.Expecto.TestSdk/blob/0d1a3eadd65b605f61bb01d302f28382be76b8ac/src/YoloDev.Expecto.TestSdk/TestApplicationHelpers.fs#L16 public sealed class VSTestBridgeExtensionBaseCapabilities : ITrxReportCapability, IVSTestFlattenedTestNodesReportCapability, INamedFeatureCapability { private const string VSTestProviderSupport = "vstestProvider"; diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj index a3d08254e9..fd51a85275 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs index 2d992b1e77..f28f435f65 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs @@ -4,6 +4,8 @@ #pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. using Microsoft.Testing.Extensions.VSTestBridge.Helpers; +using Microsoft.Testing.Platform.Capabilities.TestFramework; +using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Extensions.Messages; using Microsoft.Testing.Platform.Logging; using Microsoft.Testing.Platform.Messages; @@ -30,15 +32,26 @@ internal sealed class FrameworkHandlerAdapter : IFrameworkHandle private readonly IMessageBus _messageBus; private readonly VSTestBridgedTestFrameworkBase _adapterExtensionBase; private readonly TestSessionContext _session; - private readonly IClientInfo _clientInfo; private readonly CancellationToken _cancellationToken; private readonly bool _isTrxEnabled; private readonly MessageLoggerAdapter _comboMessageLogger; private readonly string _testAssemblyPath; - - public FrameworkHandlerAdapter(VSTestBridgedTestFrameworkBase adapterExtensionBase, TestSessionContext session, IClientInfo clientInfo, string[] testAssemblyPaths, - ITestApplicationModuleInfo testApplicationModuleInfo, ILoggerFactory loggerFactory, IMessageBus messageBus, IOutputDevice outputDevice, - bool isTrxEnabled, CancellationToken cancellationToken, IFrameworkHandle? frameworkHandle = null) + private readonly INamedFeatureCapability? _namedFeatureCapability; + private readonly ICommandLineOptions _commandLineOptions; + + public FrameworkHandlerAdapter( + VSTestBridgedTestFrameworkBase adapterExtensionBase, + TestSessionContext session, + string[] testAssemblyPaths, + ITestApplicationModuleInfo testApplicationModuleInfo, + INamedFeatureCapability? namedFeatureCapability, + ICommandLineOptions commandLineOptions, + IMessageBus messageBus, + IOutputDevice outputDevice, + ILoggerFactory loggerFactory, + bool isTrxEnabled, + CancellationToken cancellationToken, + IFrameworkHandle? frameworkHandle = null) { if (testAssemblyPaths.Length == 0) { @@ -58,12 +71,13 @@ public FrameworkHandlerAdapter(VSTestBridgedTestFrameworkBase adapterExtensionBa _testAssemblyPath = testAssemblyPaths[0]; } + _namedFeatureCapability = namedFeatureCapability; + _commandLineOptions = commandLineOptions; _frameworkHandle = frameworkHandle; _logger = loggerFactory.CreateLogger(); _messageBus = messageBus; _adapterExtensionBase = adapterExtensionBase; _session = session; - _clientInfo = clientInfo; _cancellationToken = cancellationToken; _isTrxEnabled = isTrxEnabled; _comboMessageLogger = new MessageLoggerAdapter(loggerFactory, outputDevice, adapterExtensionBase, frameworkHandle); @@ -126,7 +140,7 @@ public void RecordResult(TestResult testResult) _frameworkHandle?.RecordResult(testResult); // Publish node state change to Microsoft Testing Platform - var testNode = testResult.ToTestNode(_isTrxEnabled, _clientInfo); + var testNode = testResult.ToTestNode(_isTrxEnabled, _namedFeatureCapability, _commandLineOptions); var testNodeChange = new TestNodeUpdateMessage(_session.SessionUid, testNode); _messageBus.PublishAsync(_adapterExtensionBase, testNodeChange).Await(); @@ -145,7 +159,7 @@ public void RecordStart(TestCase testCase) _frameworkHandle?.RecordStart(testCase); // Publish node state change to Microsoft Testing Platform - var testNode = testCase.ToTestNode(_isTrxEnabled, _clientInfo); + var testNode = testCase.ToTestNode(_isTrxEnabled, _namedFeatureCapability, _commandLineOptions); testNode.Properties.Add(InProgressTestNodeStateProperty.CachedInstance); var testNodeChange = new TestNodeUpdateMessage(_session.SessionUid, testNode); diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs index 46343a7174..046d03c7b9 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs @@ -5,9 +5,12 @@ using Microsoft.Testing.Extensions.TrxReport.Abstractions; using Microsoft.Testing.Platform; +using Microsoft.Testing.Platform.Capabilities.TestFramework; +using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Extensions.Messages; -using Microsoft.Testing.Platform.Services; -using Microsoft.Testing.Platform.TestHost; +using Microsoft.Testing.Platform.ServerMode; +using Microsoft.TestPlatform.AdapterUtilities; +using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel; @@ -21,10 +24,34 @@ internal static class ObjectModelConverters VSTestTestNodeProperties.OriginalExecutorUriPropertyName, VSTestTestNodeProperties.OriginalExecutorUriPropertyName, typeof(Uri), typeof(TestNode)); + private static readonly TestProperty ManagedTypeProperty = TestProperty.Register( + id: "TestCase.ManagedType", + label: "TestCase.ManagedType", + valueType: typeof(string), + owner: typeof(TestCase)); + + private static readonly TestProperty ManagedMethodProperty = TestProperty.Register( + id: "TestCase.ManagedMethod", + label: "TestCase.ManagedMethod", + valueType: typeof(string), + owner: typeof(TestCase)); + + private static readonly TestProperty TestCategoryProperty = TestProperty.Register( + id: "MSTestDiscoverer.TestCategory", + label: "TestCategory", + valueType: typeof(string[]), + owner: typeof(TestCase)); + + private static readonly TestProperty TraitsProperty = TestProperty.Register( + id: "TestObject.Traits", + label: "Traits", + valueType: typeof(KeyValuePair[]), + owner: typeof(TestObject)); + /// /// Converts a VSTest to a Microsoft Testing Platform . /// - public static TestNode ToTestNode(this TestCase testCase, bool isTrxEnabled, IClientInfo client, string? displayNameFromTestResult = null) + public static TestNode ToTestNode(this TestCase testCase, bool isTrxEnabled, INamedFeatureCapability? namedFeatureCapability, ICommandLineOptions commandLineOptions, string? displayNameFromTestResult = null) { string testNodeUid = testCase.Id.ToString(); @@ -34,7 +61,19 @@ public static TestNode ToTestNode(this TestCase testCase, bool isTrxEnabled, ICl DisplayName = displayNameFromTestResult ?? testCase.DisplayName ?? testCase.FullyQualifiedName, }; - CopyVSTestProperties(testCase.Properties, testNode, testCase, testCase.GetPropertyValue, isTrxEnabled, client); + // This will be false for Expecto and NUnit currently, as they don't provide ManagedType/ManagedMethod. + if (TryGetMethodIdentifierProperty(testCase, out TestMethodIdentifierProperty? methodIdentifierProperty)) + { + testNode.Properties.Add(methodIdentifierProperty); + } + + CopyCategoryAndTraits(testCase, testNode, isTrxEnabled); + + if (ShouldAddVSTestProviderProperties(namedFeatureCapability, commandLineOptions)) + { + CopyVSTestProviderProperties(testCase.Properties, testNode, testCase.GetPropertyValue); + } + if (testCase.CodeFilePath is not null) { testNode.Properties.Add(new TestFileLocationProperty(testCase.CodeFilePath, new(new(testCase.LineNumber, -1), new(testCase.LineNumber, -1)))); @@ -43,73 +82,60 @@ public static TestNode ToTestNode(this TestCase testCase, bool isTrxEnabled, ICl return testNode; } - private static void CopyVSTestProperties(IEnumerable testProperties, TestNode testNode, TestCase testCase, Func getPropertyValue, - bool isTrxEnabled, IClientInfo client) + private static void CopyCategoryAndTraits(TestObject testCaseOrResult, TestNode testNode, bool isTrxEnabled) { - foreach (TestProperty property in testProperties) + // TPv2 is doing some special handling for MSTest... we should probably do the same. + // See https://github.com/microsoft/vstest/blob/main/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs#L66-L70 + if (testCaseOrResult.GetPropertyValue(TestCategoryProperty, defaultValue: null) is string[] mstestCategories) { - testNode.Properties.Add(new VSTestProperty(property, testCase)); - if (isTrxEnabled) { - // TPv2 is doing some special handling for MSTest... we should probably do the same. - // See https://github.com/microsoft/vstest/blob/main/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs#L66-L70 - if (property.Id == "MSTestDiscoverer.TestCategory" - && getPropertyValue(property) is string[] mstestCategories) - { - testNode.Properties.Add(new TrxCategoriesProperty(mstestCategories)); - } + testNode.Properties.Add(new TrxCategoriesProperty(mstestCategories)); } - // Implement handling of specific vstest properties for VS/VS Code Test Explorer, - // see https://github.com/microsoft/testanywhere/blob/main/docs/design/proposed/IDE_Protocol_IDE_Integration.md#vstest-test-node - if (client.Id == WellKnownClients.VisualStudio) + foreach (string category in mstestCategories) { - if (property.Id == TestCaseProperties.Id.Id - && getPropertyValue(property) is Guid testCaseId) - { - testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.Id", testCaseId.ToString())); - } - else if (property.Id == TestCaseProperties.FullyQualifiedName.Id - && getPropertyValue(property) is string testCaseFqn) - { - testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.FullyQualifiedName", testCaseFqn)); - } - else if (property.Id == OriginalExecutorUriProperty.Id - && getPropertyValue(property) is Uri originalExecutorUri) - { - testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.original-executor-uri", originalExecutorUri.AbsoluteUri)); - } - - // The TP object holding the hierarchy property is defined on adapter utilities and we don't want to enforce that dependency - // so instead I use the string ID copied from TP. - else if (property.Id == "TestCase.Hierarchy" - && getPropertyValue(property) is string[] testCaseHierarchy - && testCaseHierarchy.Length == 4) - { - testNode.Properties.Add(new SerializableNamedArrayStringProperty("vstest.TestCase.Hierarchy", testCaseHierarchy)); - } + testNode.Properties.Add(new TestMetadataProperty(category, string.Empty)); } + } - // ID is defined on TraitCollection but is internal so again we copy the string here. - if (property.Id == "TestObject.Traits" - && getPropertyValue(property) is KeyValuePair[] traits && traits.Length > 0) + if (testCaseOrResult.GetPropertyValue[]>(TraitsProperty, defaultValue: null) is KeyValuePair[] traits && + traits.Length > 0) + { + foreach (KeyValuePair trait in traits) { - foreach (KeyValuePair trait in traits) - { - testNode.Properties.Add(new TestMetadataProperty(trait.Key, trait.Value)); - } + testNode.Properties.Add(new TestMetadataProperty(trait.Key, trait.Value)); } + } + } - // TPv2 is doing some special handling for MSTest... we should probably do the same. - // See https://github.com/microsoft/vstest/blob/main/src/Microsoft.TestPlatform.Extensions.TrxLogger/Utility/Converter.cs#L66-L70 - else if (property.Id == "MSTestDiscoverer.TestCategory" - && getPropertyValue(property) is string[] mstestCategories && mstestCategories.Length > 0) + private static void CopyVSTestProviderProperties(IEnumerable testProperties, TestNode testNode, Func getPropertyValue) + { + foreach (TestProperty property in testProperties) + { + // If vstestProvider is enabled (only known to be true for NUnit and Expecto so far), and we are running server mode in IDE (not dotnet test), + // we add these stuff. + // Once NUnit and Expecto allow us to move forward and remove vstestProvider, we can remove this logic and get rid of the whole vstestProvider capability. + if (property.Id == TestCaseProperties.Id.Id + && getPropertyValue(property) is Guid testCaseId) { - foreach (string category in mstestCategories) - { - testNode.Properties.Add(new TestMetadataProperty(category, string.Empty)); - } + testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.Id", testCaseId.ToString())); + } + else if (property.Id == TestCaseProperties.FullyQualifiedName.Id + && getPropertyValue(property) is string testCaseFqn) + { + testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.FullyQualifiedName", testCaseFqn)); + } + else if (property.Id == OriginalExecutorUriProperty.Id + && getPropertyValue(property) is Uri originalExecutorUri) + { + testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.original-executor-uri", originalExecutorUri.AbsoluteUri)); + } + else if (property.Id == HierarchyConstants.HierarchyPropertyId + && getPropertyValue(property) is string[] testCaseHierarchy + && testCaseHierarchy.Length == 4) + { + testNode.Properties.Add(new SerializableNamedArrayStringProperty("vstest.TestCase.Hierarchy", testCaseHierarchy)); } } } @@ -117,10 +143,21 @@ private static void CopyVSTestProperties(IEnumerable testPropertie /// /// Converts a VSTest to a Microsoft Testing Platform . /// - public static TestNode ToTestNode(this TestResult testResult, bool isTrxEnabled, IClientInfo client) + public static TestNode ToTestNode(this TestResult testResult, bool isTrxEnabled, INamedFeatureCapability? namedFeatureCapability, ICommandLineOptions commandLineOptions) { - var testNode = testResult.TestCase.ToTestNode(isTrxEnabled, client, testResult.DisplayName); - CopyVSTestProperties(testResult.Properties, testNode, testResult.TestCase, testResult.GetPropertyValue, isTrxEnabled, client); + var testNode = testResult.TestCase.ToTestNode(isTrxEnabled, namedFeatureCapability, commandLineOptions, testResult.DisplayName); + + CopyCategoryAndTraits(testResult, testNode, isTrxEnabled); + + bool addVSTestProviderProperties = ShouldAddVSTestProviderProperties(namedFeatureCapability, commandLineOptions); + if (addVSTestProviderProperties) + { + // TODO: This call might be unnecessary. + // All the relevant properties should be on TestCase, not TestResult. + // And properties on TestCase were already copied as part of ToTestNode call above. + CopyVSTestProviderProperties(testResult.Properties, testNode, testResult.GetPropertyValue); + } + testNode.AddOutcome(testResult); if (isTrxEnabled) @@ -157,14 +194,22 @@ public static TestNode ToTestNode(this TestResult testResult, bool isTrxEnabled, if (testResultMessage.Category == TestResultMessage.StandardErrorCategory) { string message = testResultMessage.Text ?? string.Empty; - testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.StandardError", message)); + if (addVSTestProviderProperties) + { + testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.StandardError", message)); + } + standardErrorMessages.Add(message); } if (testResultMessage.Category == TestResultMessage.StandardOutCategory) { string message = testResultMessage.Text ?? string.Empty; - testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.StandardOutput", message)); + if (addVSTestProviderProperties) + { + testNode.Properties.Add(new SerializableKeyValuePairStringProperty("vstest.TestCase.StandardOutput", message)); + } + standardOutputMessages.Add(message); } } @@ -238,6 +283,45 @@ internal static void FixUpTestCase(this TestCase testCase, string? testAssemblyP testCase.ExecutorUri = new(Constants.ExecutorUri); } + private static bool TryGetMethodIdentifierProperty(TestCase testCase, [NotNullWhen(true)] out TestMethodIdentifierProperty? methodIdentifierProperty) + { + string? managedType = testCase.GetPropertyValue(ManagedTypeProperty, defaultValue: null); + string? managedMethod = testCase.GetPropertyValue(ManagedMethodProperty, defaultValue: null); + // NOTE: ManagedMethod, in case of MSTest, will have the parameter types. + // So, we prefer using it to display the parameter types in Test Explorer. + if (RoslynString.IsNullOrEmpty(managedType) || RoslynString.IsNullOrEmpty(managedMethod)) + { + methodIdentifierProperty = null; + return false; + } + + methodIdentifierProperty = GetMethodIdentifierPropertyFromManagedTypeAndManagedMethod(managedType, managedMethod); + return true; + } + + private static TestMethodIdentifierProperty GetMethodIdentifierPropertyFromManagedTypeAndManagedMethod( + string managedType, + string managedMethod) + { + ManagedNameParser.ParseManagedMethodName(managedMethod, out string methodName, out int arity, out string[]? parameterTypes); + if (arity != 0) + { + methodName = $"{methodName}`{arity.ToString(CultureInfo.InvariantCulture)}"; + } + + parameterTypes ??= []; + + ManagedNameParser.ParseManagedTypeName(managedType, out string @namespace, out string typeName); + + // In the context of the VSTestBridge where we only have access to VSTest object model, we cannot determine ReturnTypeFullName. + // For now, we lose this bit of information. + // If really needed in the future, we can introduce a VSTest property to hold this info. + // But the eventual goal should be to stop using the VSTestBridge altogether. + // TODO: For AssemblyFullName, can we use Assembly.GetEntryAssembly().FullName? + // Or alternatively, does VSTest object model expose the assembly full name somewhere? + return new TestMethodIdentifierProperty(AssemblyFullName: string.Empty, @namespace, typeName, methodName, parameterTypes, ReturnTypeFullName: string.Empty); + } + private static bool TryParseFullyQualifiedType(string fullyQualifiedName, [NotNullWhen(true)] out string? fullyQualifiedType) { fullyQualifiedType = null; @@ -256,4 +340,9 @@ private static bool TryParseFullyQualifiedType(string fullyQualifiedName, [NotNu fullyQualifiedType = fullyQualifiedName[..lastDotIndexBeforeOpenBracket]; return true; } + + private static bool ShouldAddVSTestProviderProperties(INamedFeatureCapability? namedFeatureCapability, ICommandLineOptions commandLineOptions) + => namedFeatureCapability?.IsSupported(JsonRpcStrings.VSTestProviderSupport) == true && + commandLineOptions.IsOptionSet(PlatformCommandLineProvider.ServerOptionKey) && + !commandLineOptions.IsOptionSet(PlatformCommandLineProvider.DotNetTestPipeOptionKey); } diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/TestCaseDiscoverySinkAdapter.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/TestCaseDiscoverySinkAdapter.cs index cd720b8074..1abd875609 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/TestCaseDiscoverySinkAdapter.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/TestCaseDiscoverySinkAdapter.cs @@ -4,6 +4,8 @@ #pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. using Microsoft.Testing.Extensions.VSTestBridge.Helpers; +using Microsoft.Testing.Platform.Capabilities.TestFramework; +using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Extensions.Messages; using Microsoft.Testing.Platform.Logging; using Microsoft.Testing.Platform.Messages; @@ -25,19 +27,25 @@ internal sealed class TestCaseDiscoverySinkAdapter : ITestCaseDiscoverySink /// private readonly ITestCaseDiscoverySink? _testCaseDiscoverySink; private readonly ILogger _logger; + private readonly INamedFeatureCapability? _namedFeatureCapability; + private readonly ICommandLineOptions _commandLineOptions; private readonly IMessageBus _messageBus; private readonly bool _isTrxEnabled; - private readonly IClientInfo _clientInfo; private readonly VSTestBridgedTestFrameworkBase _adapterExtension; private readonly TestSessionContext _session; private readonly CancellationToken _cancellationToken; private readonly string? _testAssemblyPath; - public TestCaseDiscoverySinkAdapter(VSTestBridgedTestFrameworkBase adapterExtension, TestSessionContext session, string[] testAssemblyPaths, + public TestCaseDiscoverySinkAdapter( + VSTestBridgedTestFrameworkBase adapterExtension, + TestSessionContext session, + string[] testAssemblyPaths, ITestApplicationModuleInfo testApplicationModuleInfo, + INamedFeatureCapability? namedFeatureCapability, + ICommandLineOptions commandLineOptions, + IMessageBus messageBus, ILoggerFactory loggerFactory, - IMessageBus messageBus, bool isTrxEnabled, - IClientInfo clientInfo, + bool isTrxEnabled, CancellationToken cancellationToken, ITestCaseDiscoverySink? testCaseDiscoverySink = null) { @@ -61,9 +69,10 @@ public TestCaseDiscoverySinkAdapter(VSTestBridgedTestFrameworkBase adapterExtens _testCaseDiscoverySink = testCaseDiscoverySink; _logger = loggerFactory.CreateLogger(); + _namedFeatureCapability = namedFeatureCapability; + _commandLineOptions = commandLineOptions; _messageBus = messageBus; _isTrxEnabled = isTrxEnabled; - _clientInfo = clientInfo; _adapterExtension = adapterExtension; _session = session; _cancellationToken = cancellationToken; @@ -82,7 +91,7 @@ public void SendTestCase(TestCase discoveredTest) _testCaseDiscoverySink?.SendTestCase(discoveredTest); // Publish node state change to Microsoft Testing Platform - var testNode = discoveredTest.ToTestNode(_isTrxEnabled, _clientInfo); + var testNode = discoveredTest.ToTestNode(_isTrxEnabled, _namedFeatureCapability, _commandLineOptions); testNode.Properties.Add(DiscoveredTestNodeStateProperty.CachedInstance); var testNodeChange = new TestNodeUpdateMessage(_session.SessionUid, testNode); diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/VSTestProperty.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/VSTestProperty.cs deleted file mode 100644 index 5ee2545535..0000000000 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/VSTestProperty.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.Testing.Platform.Extensions.Messages; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; - -namespace Microsoft.Testing.Extensions.VSTestBridge.ObjectModel; - -internal sealed class VSTestProperty(TestProperty property, TestCase testCase) : IProperty -{ - public TestProperty Property { get; } = property; - - public TestCase TestCase { get; } = testCase; - - // We don't want to pay the allocation if we're not printing it for instance inside the logs. - // So we go to get the property and ToString() it only if needed. - public override string ToString() - { - object? value = TestCase.GetPropertyValue(Property); - return $"VSTestProperty [Id: {Property.Id}] [Description: {Property.Description}] [ValueType: {Property.ValueType}] [Value: {value ?? "null"}]"; - } -} diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestDiscoverTestExecutionRequestFactory.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestDiscoverTestExecutionRequestFactory.cs index b3076a43ae..ec247ba950 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestDiscoverTestExecutionRequestFactory.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestDiscoverTestExecutionRequestFactory.cs @@ -2,11 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.VSTestBridge.ObjectModel; +using Microsoft.Testing.Platform.Capabilities.TestFramework; using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Configurations; using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Logging; -using Microsoft.Testing.Platform.Messages; using Microsoft.Testing.Platform.OutputDevice; using Microsoft.Testing.Platform.Requests; using Microsoft.Testing.Platform.Services; @@ -60,9 +60,17 @@ public static VSTestDiscoverTestExecutionRequest CreateRequest( RunSettingsAdapter runSettings = new(commandLineOptions, fileSystem, configuration, clientInfo, loggerFactory, messageLogger); DiscoveryContextAdapter discoveryContext = new(commandLineOptions, runSettings, discoverTestExecutionRequest.Filter); - ITestApplicationModuleInfo testApplicationModuleInfo = serviceProvider.GetTestApplicationModuleInfo(); - IMessageBus messageBus = serviceProvider.GetRequiredService(); - TestCaseDiscoverySinkAdapter discoverySink = new(adapterExtension, discoverTestExecutionRequest.Session, testAssemblyPaths, testApplicationModuleInfo, loggerFactory, messageBus, adapterExtension.IsTrxEnabled, clientInfo, cancellationToken); + TestCaseDiscoverySinkAdapter discoverySink = new( + adapterExtension, + discoverTestExecutionRequest.Session, + testAssemblyPaths, + serviceProvider.GetTestApplicationModuleInfo(), + serviceProvider.GetTestFrameworkCapabilities().GetCapability(), + serviceProvider.GetCommandLineOptions(), + serviceProvider.GetMessageBus(), + loggerFactory, + adapterExtension.IsTrxEnabled, + cancellationToken); return new(discoverTestExecutionRequest.Session, discoverTestExecutionRequest.Filter, testAssemblyPaths, discoveryContext, messageLogger, discoverySink); } diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestRunTestExecutionRequestFactory.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestRunTestExecutionRequestFactory.cs index 67ef610b2a..71f00dca62 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestRunTestExecutionRequestFactory.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Requests/VSTestRunTestExecutionRequestFactory.cs @@ -2,12 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.VSTestBridge.ObjectModel; +using Microsoft.Testing.Platform.Capabilities.TestFramework; using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Configurations; using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Logging; -using Microsoft.Testing.Platform.Messages; -using Microsoft.Testing.Platform.OutputDevice; using Microsoft.Testing.Platform.Requests; using Microsoft.Testing.Platform.Services; @@ -55,11 +54,18 @@ public static VSTestRunTestExecutionRequest CreateRequest( IFileSystem fileSystem = serviceProvider.GetFileSystem(); IClientInfo clientInfo = serviceProvider.GetClientInfo(); - ITestApplicationModuleInfo testApplicationModuleInfo = serviceProvider.GetTestApplicationModuleInfo(); - IMessageBus messageBus = serviceProvider.GetRequiredService(); - IOutputDevice outputDevice = serviceProvider.GetOutputDevice(); - FrameworkHandlerAdapter frameworkHandlerAdapter = new(adapterExtension, runTestExecutionRequest.Session, clientInfo, testAssemblyPaths, - testApplicationModuleInfo, loggerFactory, messageBus, outputDevice, adapterExtension.IsTrxEnabled, cancellationToken); + FrameworkHandlerAdapter frameworkHandlerAdapter = new( + adapterExtension, + runTestExecutionRequest.Session, + testAssemblyPaths, + serviceProvider.GetTestApplicationModuleInfo(), + serviceProvider.GetTestFrameworkCapabilities().GetCapability(), + serviceProvider.GetCommandLineOptions(), + serviceProvider.GetMessageBus(), + serviceProvider.GetOutputDevice(), + loggerFactory, + adapterExtension.IsTrxEnabled, + cancellationToken); RunSettingsAdapter runSettings = new(commandLineOptions, fileSystem, configuration, clientInfo, loggerFactory, frameworkHandlerAdapter); RunContextAdapter runContext = new(commandLineOptions, runSettings, runTestExecutionRequest.Filter); diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs index 827cd6ad8a..4fe9357fec 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs @@ -12,7 +12,6 @@ using Microsoft.Testing.Platform.Extensions.TestFramework; using Microsoft.Testing.Platform.Logging; using Microsoft.Testing.Platform.Messages; -using Microsoft.Testing.Platform.OutputDevice; using Microsoft.Testing.Platform.Requests; using Microsoft.Testing.Platform.Services; @@ -131,30 +130,49 @@ protected abstract Task RunTestsAsync(VSTestRunTestExecutionRequest request, IMe private VSTestDiscoverTestExecutionRequest UpdateDiscoverRequest( VSTestDiscoverTestExecutionRequest discoverRequest, - IMessageBus messageBus, CancellationToken cancellationToken) + IMessageBus messageBus, + CancellationToken cancellationToken) { // Before passing down the request, we need to replace the discovery sink with a custom implementation calling // both the original (VSTest) sink and our own. - ITestApplicationModuleInfo testApplicationModuleInfo = ServiceProvider.GetTestApplicationModuleInfo(); ILoggerFactory loggerFactory = ServiceProvider.GetRequiredService(); - IClientInfo clientInfo = ServiceProvider.GetRequiredService(); - TestCaseDiscoverySinkAdapter testCaseDiscoverySinkAdapter = new(this, discoverRequest.Session, discoverRequest.AssemblyPaths, testApplicationModuleInfo, loggerFactory, messageBus, IsTrxEnabled, clientInfo, cancellationToken, discoverRequest.DiscoverySink); + TestCaseDiscoverySinkAdapter testCaseDiscoverySinkAdapter = new( + this, + discoverRequest.Session, + discoverRequest.AssemblyPaths, + ServiceProvider.GetTestApplicationModuleInfo(), + ServiceProvider.GetTestFrameworkCapabilities().GetCapability(), + ServiceProvider.GetCommandLineOptions(), + messageBus, + loggerFactory, + IsTrxEnabled, + cancellationToken, + discoverRequest.DiscoverySink); return new(discoverRequest.Session, discoverRequest.Filter, discoverRequest.AssemblyPaths, discoverRequest.DiscoveryContext, discoverRequest.MessageLogger, testCaseDiscoverySinkAdapter); } - private VSTestRunTestExecutionRequest UpdateRunRequest(VSTestRunTestExecutionRequest runRequest, IMessageBus messageBus, + private VSTestRunTestExecutionRequest UpdateRunRequest( + VSTestRunTestExecutionRequest runRequest, + IMessageBus messageBus, CancellationToken cancellationToken) { // Before passing down the request, we need to replace the framework handle with a custom implementation calling // both the original (VSTest) framework handle and our own. - ITestApplicationModuleInfo testApplicationModuleInfo = ServiceProvider.GetTestApplicationModuleInfo(); ILoggerFactory loggerFactory = ServiceProvider.GetRequiredService(); - IOutputDevice outputDevice = ServiceProvider.GetOutputDevice(); - IClientInfo clientInfo = ServiceProvider.GetClientInfo(); - FrameworkHandlerAdapter frameworkHandlerAdapter = new(this, runRequest.Session, clientInfo, runRequest.AssemblyPaths, testApplicationModuleInfo, - loggerFactory, messageBus, outputDevice, IsTrxEnabled, cancellationToken, runRequest.FrameworkHandle); + FrameworkHandlerAdapter frameworkHandlerAdapter = new( + this, + runRequest.Session, + runRequest.AssemblyPaths, + ServiceProvider.GetTestApplicationModuleInfo(), + ServiceProvider.GetTestFrameworkCapabilities().GetCapability(), + ServiceProvider.GetCommandLineOptions(), + messageBus, + ServiceProvider.GetOutputDevice(), + loggerFactory, + IsTrxEnabled, + cancellationToken, runRequest.FrameworkHandle); return new(runRequest.Session, runRequest.Filter, runRequest.AssemblyPaths, runRequest.RunContext, frameworkHandlerAdapter); diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs index 93a1c5177e..d89f32c8fa 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs @@ -314,7 +314,7 @@ public sealed record TestFileLocationProperty(string FilePath, LinePositionSpan /// /// Assembly full name. /// Namespace. -/// Type name. +/// Type name in metadata format, not including the namespace. Generics are represented by backtick followed by arity. Nested types are represented by +. /// Method name. /// Parameter type full name. /// Return type full name. diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs index adaad5806e..33fe75a9a8 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs @@ -163,8 +163,10 @@ public Json(Dictionary? serializers = null, Dictionary 0 ? $"{testMethodIdentifierProperty.MethodName}({string.Join(",", testMethodIdentifierProperty.ParameterTypeFullNames)})" : testMethodIdentifierProperty.MethodName)); diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs index e67983a7bd..36023ccfab 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs @@ -225,8 +225,10 @@ static SerializerUtilities() if (property is TestMethodIdentifierProperty testMethodIdentifierProperty) { - properties["location.namespace"] = testMethodIdentifierProperty.Namespace; - properties["location.type"] = testMethodIdentifierProperty.TypeName; + properties["location.type"] = RoslynString.IsNullOrEmpty(testMethodIdentifierProperty.Namespace) + ? testMethodIdentifierProperty.TypeName + : $"{testMethodIdentifierProperty.Namespace}.{testMethodIdentifierProperty.TypeName}"; + properties["location.method"] = testMethodIdentifierProperty.ParameterTypeFullNames.Length > 0 ? $"{testMethodIdentifierProperty.MethodName}({string.Join(",", testMethodIdentifierProperty.ParameterTypeFullNames)})" : testMethodIdentifierProperty.MethodName; diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerModeTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerModeTests.cs index 154dceb6df..b79c30d152 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerModeTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/ServerModeTests.cs @@ -23,7 +23,7 @@ public async Task DiscoverAndRun(string tfm) InitializeResponse initializeResponseArgs = await jsonClient.Initialize(); - Assert.IsTrue(initializeResponseArgs.Capabilities.Testing.VSTestProvider); + Assert.IsFalse(initializeResponseArgs.Capabilities.Testing.VSTestProvider); Assert.IsFalse(initializeResponseArgs.Capabilities.Testing.MultiRequestSupport); Assert.IsTrue(initializeResponseArgs.Capabilities.Testing.SupportsDiscovery); diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs index f2ae46978b..43ca08888b 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs @@ -5,9 +5,10 @@ using Microsoft.Testing.Extensions.TrxReport.Abstractions; using Microsoft.Testing.Extensions.VSTestBridge.ObjectModel; +using Microsoft.Testing.Platform.Capabilities.TestFramework; +using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Extensions.Messages; -using Microsoft.Testing.Platform.Services; -using Microsoft.Testing.Platform.TestHost; +using Microsoft.Testing.Platform.ServerMode; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; @@ -17,9 +18,6 @@ namespace Microsoft.Testing.Extensions.VSTestBridge.UnitTests.ObjectModel; [TestClass] public sealed class ObjectModelConvertersTests { - private static readonly IClientInfo TestClient = new ClientInfoService("UnitTest", string.Empty); - private static readonly IClientInfo VSTestClient = new ClientInfoService(WellKnownClients.VisualStudio, string.Empty); - [TestMethod] public void ToTestNode_WhenTestCaseHasDisplayName_TestNodeDisplayNameUsesIt() { @@ -27,7 +25,7 @@ public void ToTestNode_WhenTestCaseHasDisplayName_TestNodeDisplayNameUsesIt() { DisplayName = "MyDisplayName", }; - var testNode = testCase.ToTestNode(false, TestClient); + var testNode = testCase.ToTestNode(false, null, new ConsoleCommandLineOptions()); Assert.AreEqual("MyDisplayName", testNode.DisplayName); } @@ -36,7 +34,7 @@ public void ToTestNode_WhenTestCaseHasDisplayName_TestNodeDisplayNameUsesIt() public void ToTestNode_WhenTestCaseHasNoDisplayName_TestNodeDisplayNameUsesIt() { TestCase testCase = new("SomeFqn", new("executor://uri", UriKind.Absolute), "source.cs"); - var testNode = testCase.ToTestNode(false, TestClient); + var testNode = testCase.ToTestNode(false, null, new ConsoleCommandLineOptions()); Assert.AreEqual("SomeFqn", testNode.DisplayName); } @@ -48,7 +46,7 @@ public void ToTestNode_WhenTestResultHasCodeFilePath_SetsTestFileLocationPropert { CodeFilePath = "FilePath", }); - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); Assert.AreEqual("FilePath", testNode.Properties.Single().FilePath); } @@ -61,7 +59,7 @@ public void ToTestNode_WhenTestResultOutcomeIsFailed_TestNodePropertiesContainFa ErrorMessage = "SomeErrorMessage", ErrorStackTrace = "SomeStackTrace", }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); FailedTestNodeStateProperty[] failedTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, failedTestNodeStateProperties.Length); @@ -77,7 +75,7 @@ public void ToTestNode_WhenTestResultHasMSTestDiscovererTestCategoryTestProperty var testCategoryProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", "Label", typeof(string[]), TestPropertyAttributes.None, typeof(TestCase)); testResult.SetPropertyValue(testCategoryProperty, ["category1"]); - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); TestMetadataProperty[] testMetadatas = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, testMetadatas.Length); @@ -92,7 +90,7 @@ public void ToTestNode_WhenTestResultHasMSTestDiscovererTestCategoryTestProperty var testCategoryProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", "Label", typeof(string[]), TestPropertyAttributes.None, typeof(TestCase)); testResult.SetPropertyValue(testCategoryProperty, ["category1"]); - var testNode = testResult.ToTestNode(true, VSTestClient); + var testNode = testResult.ToTestNode(true, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); TrxCategoriesProperty[] trxCategoriesProperty = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, trxCategoriesProperty.Length); @@ -107,7 +105,7 @@ public void ToTestNode_WhenTestResultHasTestCaseHierarchyTestProperty_TestNodePr var testCaseHierarchy = TestProperty.Register("TestCase.Hierarchy", "Label", typeof(string[]), TestPropertyAttributes.None, typeof(TestCase)); testResult.SetPropertyValue(testCaseHierarchy, ["assembly", "class", "category", "test"]); - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); SerializableNamedArrayStringProperty[] trxCategoriesProperty = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, trxCategoriesProperty.Length); @@ -126,7 +124,7 @@ public void ToTestNode_WhenTestResultHasOriginalExecutorUriProperty_TestNodeProp testResult.SetPropertyValue(originalExecutorUriProperty, new Uri("https://vs.com/")); - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); SerializableKeyValuePairStringProperty[] serializableKeyValuePairStringProperty = testNode.Properties.OfType().ToArray(); Assert.AreEqual(3, serializableKeyValuePairStringProperty.Length); @@ -139,7 +137,7 @@ public void ToTestNode_WhenTestResultHasFullyQualifiedTypeAndTrxEnabled_TestNode { TestResult testResult = new(new TestCase("assembly.class.test", new("executor://uri", UriKind.Absolute), "source.cs")); - var testNode = testResult.ToTestNode(true, TestClient); + var testNode = testResult.ToTestNode(true, null, new ConsoleCommandLineOptions()); Assert.AreEqual(testNode.Properties.OfType()?.Length, 1); Assert.AreEqual("assembly.class", testNode.Properties.Single().FullyQualifiedTypeName); @@ -150,7 +148,7 @@ public void ToTestNode_WhenTestResultHasNoFullyQualifiedTypeAndTrxEnabled_Throws { TestResult testResult = new(new TestCase("test", new("executor://uri", UriKind.Absolute), "source.cs")); - string errorMessage = Assert.ThrowsException(() => testResult.ToTestNode(true, TestClient)).Message; + string errorMessage = Assert.ThrowsException(() => testResult.ToTestNode(true, null, new ConsoleCommandLineOptions())).Message; Assert.IsTrue(errorMessage.Contains("Unable to parse fully qualified type name from test case: ")); } @@ -168,7 +166,7 @@ public void ToTestNode_FromTestResult_TestNodePropertiesContainCorrectTimingProp EndTime = endTime, Duration = duration, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); var testResultTimingProperty = new TimingProperty(new(startTime, endTime, duration), []); Assert.AreEqual(testNode.Properties.OfType()[0], testResultTimingProperty); @@ -182,7 +180,7 @@ public void ToTestNode_WhenTestResultOutcomeIsNotFoundWithoutSetErrorMessage_Tes Outcome = TestOutcome.NotFound, ErrorStackTrace = "SomeStackTrace", }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); ErrorTestNodeStateProperty[] errorTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, errorTestNodeStateProperties.Length); @@ -198,7 +196,7 @@ public void ToTestNode_WhenTestResultOutcomeIsSkipped_TestNodePropertiesContainS { Outcome = TestOutcome.Skipped, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); SkippedTestNodeStateProperty[] skipTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, skipTestNodeStateProperties.Length); @@ -211,7 +209,7 @@ public void ToTestNode_WhenTestResultOutcomeIsNone_TestNodePropertiesContainSkip { Outcome = TestOutcome.None, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); SkippedTestNodeStateProperty[] skipTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, skipTestNodeStateProperties.Length); @@ -224,7 +222,7 @@ public void ToTestNode_WhenTestResultOutcomeIsPassed_TestNodePropertiesContainPa { Outcome = TestOutcome.Passed, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, null, new ConsoleCommandLineOptions()); PassedTestNodeStateProperty[] passedTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, passedTestNodeStateProperties.Length); @@ -238,7 +236,7 @@ public void ToTestNode_WhenTestResultHasUidAndDisplayNameWithWellKnownClient_Tes DisplayName = "TestName", }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); SerializableKeyValuePairStringProperty[] errorTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(2, errorTestNodeStateProperties.Length, "Expected 2 SerializableKeyValuePairStringProperty"); @@ -256,7 +254,7 @@ public void ToTestNode_WhenTestResultHasTraits_TestNodePropertiesContainIt() Traits = { new Trait("key", "value") }, }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); TestMetadataProperty[] testMetadatas = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, testMetadatas.Length); @@ -277,7 +275,7 @@ public void ToTestNode_WhenTestResultHasMultipleStandardOutputMessages_TestNodeP }, }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); StandardOutputProperty[] standardOutputProperties = testNode.Properties.OfType().ToArray(); Assert.IsTrue(standardOutputProperties.Length == 1); @@ -297,10 +295,29 @@ public void ToTestNode_WhenTestResultHasMultipleStandardErrorMessages_TestNodePr }, }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, new NamedFeatureCapabilityWithVSTestProvider(), new ServerModeCommandLineOptions()); StandardErrorProperty[] standardErrorProperties = testNode.Properties.OfType().ToArray(); Assert.IsTrue(standardErrorProperties.Length == 1); Assert.AreEqual($"message1{Environment.NewLine}message2", standardErrorProperties[0].StandardError); } + + private sealed class NamedFeatureCapabilityWithVSTestProvider : INamedFeatureCapability + { + public bool IsSupported(string featureName) => featureName is JsonRpcStrings.VSTestProviderSupport; + } + + private sealed class ServerModeCommandLineOptions : ICommandLineOptions + { + public bool IsOptionSet(string optionName) => optionName is PlatformCommandLineProvider.ServerOptionKey; + + public bool TryGetOptionArgumentList(string optionName, [NotNullWhen(true)] out string[]? arguments) => throw new NotImplementedException(); + } + + private sealed class ConsoleCommandLineOptions : ICommandLineOptions + { + public bool IsOptionSet(string optionName) => false; + + public bool TryGetOptionArgumentList(string optionName, [NotNullWhen(true)] out string[]? arguments) => throw new NotImplementedException(); + } } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs index 47d988925c..db22bb0f74 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/FormatterUtilitiesTests.cs @@ -176,7 +176,7 @@ private static void AssertSerialize(Type type, string instanceSerialized) if (type == typeof(TestNode)) { - Assert.AreEqual("""{"uid":"uid","display-name":"DisplayName","traits":[{"testmetadata-key":"testmetadata-value"}],"array-key":["1","2"],"standardError":"textProperty2","standardOutput":"textProperty","time.start-utc":"2023-01-01T01:01:01.0000000+00:00","time.stop-utc":"2023-01-01T01:01:01.0000000+00:00","time.duration-ms":0,"location.namespace":"namespace","location.type":"typeName","location.method":"methodName(param1,param2)","location.file":"filePath","location.line-start":1,"location.line-end":2,"key":"value","node-type":"action","execution-state":"failed","error.message":"sample","error.stacktrace":"","assert.actual":"","assert.expected":""}""".Replace(" ", string.Empty), instanceSerialized, because); + Assert.AreEqual("""{"uid":"uid","display-name":"DisplayName","traits":[{"testmetadata-key":"testmetadata-value"}],"array-key":["1","2"],"standardError":"textProperty2","standardOutput":"textProperty","time.start-utc":"2023-01-01T01:01:01.0000000+00:00","time.stop-utc":"2023-01-01T01:01:01.0000000+00:00","time.duration-ms":0,"location.type":"namespace.typeName","location.method":"methodName(param1,param2)","location.file":"filePath","location.line-start":1,"location.line-end":2,"key":"value","node-type":"action","execution-state":"failed","error.message":"sample","error.stacktrace":"","assert.actual":"","assert.expected":""}""".Replace(" ", string.Empty), instanceSerialized, because); return; } @@ -188,7 +188,7 @@ private static void AssertSerialize(Type type, string instanceSerialized) if (type == typeof(TestNodeUpdateMessage)) { - Assert.AreEqual("""{"node":{"uid":"uid","display-name":"DisplayName","traits":[{"testmetadata-key":"testmetadata-value"}],"array-key":["1","2"],"standardError":"textProperty2","standardOutput":"textProperty","time.start-utc":"2023-01-01T01:01:01.0000000+00:00","time.stop-utc":"2023-01-01T01:01:01.0000000+00:00","time.duration-ms":0,"location.namespace":"namespace","location.type":"typeName","location.method":"methodName(param1,param2)","location.file":"filePath","location.line-start":1,"location.line-end":2,"key":"value","node-type":"action","execution-state":"failed","error.message":"sample","error.stacktrace":"","assert.actual":"","assert.expected":""},"parent":"parent-uid"}""".Replace(" ", string.Empty), instanceSerialized, because); + Assert.AreEqual("""{"node":{"uid":"uid","display-name":"DisplayName","traits":[{"testmetadata-key":"testmetadata-value"}],"array-key":["1","2"],"standardError":"textProperty2","standardOutput":"textProperty","time.start-utc":"2023-01-01T01:01:01.0000000+00:00","time.stop-utc":"2023-01-01T01:01:01.0000000+00:00","time.duration-ms":0,"location.type":"namespace.typeName","location.method":"methodName(param1,param2)","location.file":"filePath","location.line-start":1,"location.line-end":2,"key":"value","node-type":"action","execution-state":"failed","error.message":"sample","error.stacktrace":"","assert.actual":"","assert.expected":""},"parent":"parent-uid"}""".Replace(" ", string.Empty), instanceSerialized, because); return; }