diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestFrameworkBuilderData.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestFrameworkBuilderData.cs index b9a558ceb4..601f024a77 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestFrameworkBuilderData.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestFrameworkBuilderData.cs @@ -13,7 +13,7 @@ namespace Microsoft.Testing.Platform.Hosts; internal sealed class TestFrameworkBuilderData(ServiceProvider serviceProvider, ITestExecutionRequestFactory testExecutionRequestFactory, ITestFrameworkInvoker testExecutionRequestInvoker, ITestExecutionFilterFactory testExecutionFilterFactory, - IPlatformOutputDevice platformOutputDisplayService, IEnumerable serverPerCallConsumers, + IPlatformOutputDevice? platformOutputDisplayService, IEnumerable serverPerCallConsumers, TestFrameworkManager testFrameworkManager, TestHostManager testSessionManager, MessageBusProxy messageBusProxy, bool isForDiscoveryRequest, bool isJsonRpcProtocol) @@ -26,7 +26,7 @@ internal sealed class TestFrameworkBuilderData(ServiceProvider serviceProvider, public ITestExecutionFilterFactory TestExecutionFilterFactory { get; } = testExecutionFilterFactory; - public IPlatformOutputDevice PlatformOutputDisplayService { get; } = platformOutputDisplayService; + public IPlatformOutputDevice? PlatformOutputDisplayService { get; } = platformOutputDisplayService; public IEnumerable ServerPerCallConsumers { get; } = serverPerCallConsumers; diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs index bd69e72c36..71119e1c62 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs @@ -207,9 +207,12 @@ public async Task BuildAsync( serviceProvider.AddService(policiesService); bool hasServerFlag = commandLineHandler.TryGetOptionArgumentList(PlatformCommandLineProvider.ServerOptionKey, out string[]? protocolName); - bool isJsonRpcProtocol = protocolName is null || protocolName.Length == 0 || protocolName[0].Equals(PlatformCommandLineProvider.JsonRpcProtocolName, StringComparison.OrdinalIgnoreCase); + bool isJsonRpcProtocol = hasServerFlag && + (protocolName is null || protocolName.Length == 0 || protocolName[0].Equals(PlatformCommandLineProvider.JsonRpcProtocolName, StringComparison.OrdinalIgnoreCase)); - ProxyOutputDevice proxyOutputDevice = await _outputDisplay.BuildAsync(serviceProvider, hasServerFlag && isJsonRpcProtocol).ConfigureAwait(false); + bool isPipeProtocol = hasServerFlag && protocolName?.Length == 1 && protocolName[0].Equals(PlatformCommandLineProvider.DotnetTestCliProtocolName, StringComparison.Ordinal); + + ProxyOutputDevice proxyOutputDevice = await _outputDisplay.BuildAsync(serviceProvider, isJsonRpcProtocol, isPipeProtocol).ConfigureAwait(false); // Add FileLoggerProvider if needed if (loggingState.FileLoggerProvider is not null) @@ -227,14 +230,18 @@ public async Task BuildAsync( loggerFactoryProxy.SetLoggerFactory(loggerFactory); // Initialize the output device if needed. - if (await proxyOutputDevice.OriginalOutputDevice.IsEnabledAsync().ConfigureAwait(false)) + if (proxyOutputDevice.OriginalOutputDevice is { } originalOutputDevice && + await originalOutputDevice.IsEnabledAsync().ConfigureAwait(false)) { - await proxyOutputDevice.OriginalOutputDevice.TryInitializeAsync().ConfigureAwait(false); + await originalOutputDevice.TryInitializeAsync().ConfigureAwait(false); } // Add the platform output device to the service provider for both modes. serviceProvider.TryAddService(proxyOutputDevice); - serviceProvider.TryAddService(proxyOutputDevice.OriginalOutputDevice); + if (proxyOutputDevice.OriginalOutputDevice is not null) + { + serviceProvider.TryAddService(proxyOutputDevice.OriginalOutputDevice); + } // Create the test framework capabilities ITestFrameworkCapabilities testFrameworkCapabilities = TestFramework.TestFrameworkCapabilitiesFactory(serviceProvider); @@ -433,7 +440,7 @@ await LogTestHostCreatedAsync( && !commandLineHandler.IsOptionSet(PlatformCommandLineProvider.DiscoverTestsOptionKey)) { PassiveNode? passiveNode = null; - if (hasServerFlag && isJsonRpcProtocol) + if (isJsonRpcProtocol) { // Build the IMessageHandlerFactory for the PassiveNode IMessageHandlerFactory messageHandlerFactory = ServerModeManager.Build(serviceProvider); @@ -507,7 +514,7 @@ await LogTestHostCreatedAsync( serviceProvider.AddServices(testApplicationLifecycleCallback); // ServerMode and Console mode uses different host - if (hasServerFlag && isJsonRpcProtocol) + if (isJsonRpcProtocol) { // Build the server mode with the user preferences IMessageHandlerFactory messageHandlerFactory = ServerModeManager.Build(serviceProvider); @@ -717,7 +724,11 @@ private static async Task BuildTestFrameworkAsync(TestFrameworkB // creations and we could lose interesting diagnostic information. List dataConsumersBuilder = []; - await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.PlatformOutputDisplayService, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); + if (testFrameworkBuilderData.PlatformOutputDisplayService is not null) + { + await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.PlatformOutputDisplayService, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); + } + await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.TestExecutionRequestFactory, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.TestExecutionRequestInvoker, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.TestExecutionFilterFactory, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs index 192b07b483..8b54712469 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs @@ -20,18 +20,20 @@ public void SetPlatformOutputDevice(Func BuildAsync(ServiceProvider serviceProvider, bool useServerModeOutputDevice) + internal async Task BuildAsync(ServiceProvider serviceProvider, bool useServerModeOutputDevice, bool isPipeProtocol) { // TODO: SetPlatformOutputDevice isn't public yet. // Before exposing it, do we want to pass the "useServerModeOutputDevice" info to it? - IPlatformOutputDevice nonServerOutputDevice = _platformOutputDeviceFactory is null - ? GetDefaultTerminalOutputDevice(serviceProvider) + IPlatformOutputDevice? nonServerOutputDevice = _platformOutputDeviceFactory is null + ? GetDefaultTerminalOutputDevice(serviceProvider, isPipeProtocol) : _platformOutputDeviceFactory(serviceProvider); // If the externally provided output device is not enabled, we opt-in the default terminal output device. - if (_platformOutputDeviceFactory is not null && !await nonServerOutputDevice.IsEnabledAsync().ConfigureAwait(false)) + if (_platformOutputDeviceFactory is not null + && nonServerOutputDevice is not null && + !await nonServerOutputDevice.IsEnabledAsync().ConfigureAwait(false)) { - nonServerOutputDevice = GetDefaultTerminalOutputDevice(serviceProvider); + nonServerOutputDevice = GetDefaultTerminalOutputDevice(serviceProvider, isPipeProtocol); } return new ProxyOutputDevice( @@ -43,8 +45,13 @@ internal async Task BuildAsync(ServiceProvider serviceProvide : null); } - public static IPlatformOutputDevice GetDefaultTerminalOutputDevice(ServiceProvider serviceProvider) + private static IPlatformOutputDevice? GetDefaultTerminalOutputDevice(ServiceProvider serviceProvider, bool isPipeProtocol) { + if (isPipeProtocol) + { + return null; + } + if (OperatingSystem.IsBrowser()) { #if NET7_0_OR_GREATER diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/ProxyOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/ProxyOutputDevice.cs index ac63375ea0..ba6106d132 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/ProxyOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/ProxyOutputDevice.cs @@ -11,17 +11,21 @@ internal sealed class ProxyOutputDevice : IOutputDevice { private readonly ServerModePerCallOutputDevice? _serverModeOutputDevice; - public ProxyOutputDevice(IPlatformOutputDevice originalOutputDevice, ServerModePerCallOutputDevice? serverModeOutputDevice) + public ProxyOutputDevice(IPlatformOutputDevice? originalOutputDevice, ServerModePerCallOutputDevice? serverModeOutputDevice) { OriginalOutputDevice = originalOutputDevice; _serverModeOutputDevice = serverModeOutputDevice; } - internal IPlatformOutputDevice OriginalOutputDevice { get; } + internal IPlatformOutputDevice? OriginalOutputDevice { get; } public async Task DisplayAsync(IOutputDeviceDataProducer producer, IOutputDeviceData data, CancellationToken cancellationToken) { - await OriginalOutputDevice.DisplayAsync(producer, data, cancellationToken).ConfigureAwait(false); + if (OriginalOutputDevice is not null) + { + await OriginalOutputDevice.DisplayAsync(producer, data, cancellationToken).ConfigureAwait(false); + } + if (_serverModeOutputDevice is not null) { await _serverModeOutputDevice.DisplayAsync(producer, data, cancellationToken).ConfigureAwait(false); @@ -30,7 +34,11 @@ public async Task DisplayAsync(IOutputDeviceDataProducer producer, IOutputDevice internal async Task DisplayBannerAsync(string? bannerMessage, CancellationToken cancellationToken) { - await OriginalOutputDevice.DisplayBannerAsync(bannerMessage, cancellationToken).ConfigureAwait(false); + if (OriginalOutputDevice is not null) + { + await OriginalOutputDevice.DisplayBannerAsync(bannerMessage, cancellationToken).ConfigureAwait(false); + } + if (_serverModeOutputDevice is not null) { await _serverModeOutputDevice.DisplayBannerAsync(bannerMessage, cancellationToken).ConfigureAwait(false); @@ -39,7 +47,11 @@ internal async Task DisplayBannerAsync(string? bannerMessage, CancellationToken internal async Task DisplayBeforeSessionStartAsync(CancellationToken cancellationToken) { - await OriginalOutputDevice.DisplayBeforeSessionStartAsync(cancellationToken).ConfigureAwait(false); + if (OriginalOutputDevice is not null) + { + await OriginalOutputDevice.DisplayBeforeSessionStartAsync(cancellationToken).ConfigureAwait(false); + } + if (_serverModeOutputDevice is not null) { await _serverModeOutputDevice.DisplayBeforeSessionStartAsync(cancellationToken).ConfigureAwait(false); @@ -48,7 +60,11 @@ internal async Task DisplayBeforeSessionStartAsync(CancellationToken cancellatio internal async Task DisplayAfterSessionEndRunAsync(CancellationToken cancellationToken) { - await OriginalOutputDevice.DisplayAfterSessionEndRunAsync(cancellationToken).ConfigureAwait(false); + if (OriginalOutputDevice is not null) + { + await OriginalOutputDevice.DisplayAfterSessionEndRunAsync(cancellationToken).ConfigureAwait(false); + } + if (_serverModeOutputDevice is not null) { await _serverModeOutputDevice.DisplayAfterSessionEndRunAsync(cancellationToken).ConfigureAwait(false); @@ -65,7 +81,11 @@ internal async Task InitializeAsync(ServerTestHost serverTestHost) internal async Task HandleProcessRoleAsync(TestProcessRole processRole, CancellationToken cancellationToken) { - await OriginalOutputDevice.HandleProcessRoleAsync(processRole, cancellationToken).ConfigureAwait(false); + if (OriginalOutputDevice is not null) + { + await OriginalOutputDevice.HandleProcessRoleAsync(processRole, cancellationToken).ConfigureAwait(false); + } + if (_serverModeOutputDevice is not null) { await _serverModeOutputDevice.HandleProcessRoleAsync(processRole, cancellationToken).ConfigureAwait(false); diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/DotnetTestConnection.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/DotnetTestConnection.cs index 01cae2e860..7fb01a6044 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/DotnetTestConnection.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/DotnetTestConnection.cs @@ -92,7 +92,7 @@ public async Task IsCompatibleProtocolAsync(string hostType) { RoslynDebug.Assert(_dotnetTestPipeClient is not null); - string supportedProtocolVersions = ProtocolConstants.Version; + const string supportedProtocolVersions = ProtocolConstants.SupportedVersions; HandshakeMessage handshakeMessage = new(new Dictionary { { HandshakeMessagePropertyNames.PID, _environment.ProcessId.ToString(CultureInfo.InvariantCulture) }, diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Constants.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Constants.cs index 11239b8039..4da57534cc 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Constants.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Constants.cs @@ -37,5 +37,9 @@ internal static class HandshakeMessagePropertyNames internal static class ProtocolConstants { - internal const string Version = "1.0.0"; + // The change between 1.0.0 and 1.1.0 is that TerminalOutputDevice is no longer plugged in. + // That's not really a protocol change, but we use the version to signify to SDK that it can avoid output redirection. + // So, when SDK declares itself as supporting 1.1.0, and MTP is also using 1.1.0, and we negotiate to that version. + // Then SDK can assume that MTP output doesn't interfere with SDK output, and we can safely let live output to work. + internal const string SupportedVersions = "1.0.0;1.1.0"; } diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs b/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs index 0b44510b11..1e55ede381 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs @@ -141,8 +141,8 @@ internal static IClock GetSystemClock(this IServiceProvider serviceProvider) internal static ITask GetTask(this IServiceProvider serviceProvider) => serviceProvider.GetRequiredServiceInternal(); - internal static IPlatformOutputDevice GetPlatformOutputDevice(this IServiceProvider serviceProvider) - => serviceProvider.GetRequiredServiceInternal(); + internal static IPlatformOutputDevice? GetPlatformOutputDevice(this IServiceProvider serviceProvider) + => serviceProvider.GetServiceInternal(); internal static IEnvironment GetEnvironment(this IServiceProvider serviceProvider) => serviceProvider.GetRequiredServiceInternal();