Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Testing.Platform.Hosts;

internal sealed class TestFrameworkBuilderData(ServiceProvider serviceProvider, ITestExecutionRequestFactory testExecutionRequestFactory,
ITestFrameworkInvoker testExecutionRequestInvoker, ITestExecutionFilterFactory testExecutionFilterFactory,
IPlatformOutputDevice platformOutputDisplayService, IEnumerable<IDataConsumer> serverPerCallConsumers,
IPlatformOutputDevice? platformOutputDisplayService, IEnumerable<IDataConsumer> serverPerCallConsumers,
TestFrameworkManager testFrameworkManager, TestHostManager testSessionManager, MessageBusProxy messageBusProxy,
bool isForDiscoveryRequest,
bool isJsonRpcProtocol)
Expand All @@ -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<IDataConsumer> ServerPerCallConsumers { get; } = serverPerCallConsumers;

Expand Down
27 changes: 19 additions & 8 deletions src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,12 @@ public async Task<IHost> 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)
Expand All @@ -227,14 +230,18 @@ public async Task<IHost> 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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -717,7 +724,11 @@ private static async Task<ITestFramework> BuildTestFrameworkAsync(TestFrameworkB
// creations and we could lose interesting diagnostic information.
List<IDataConsumer> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,20 @@ public void SetPlatformOutputDevice(Func<IServiceProvider, IPlatformOutputDevice
_platformOutputDeviceFactory = platformOutputDeviceFactory;
}

internal async Task<ProxyOutputDevice> BuildAsync(ServiceProvider serviceProvider, bool useServerModeOutputDevice)
internal async Task<ProxyOutputDevice> 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(
Expand All @@ -43,8 +45,13 @@ internal async Task<ProxyOutputDevice> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task<bool> IsCompatibleProtocolAsync(string hostType)
{
RoslynDebug.Assert(_dotnetTestPipeClient is not null);

string supportedProtocolVersions = ProtocolConstants.Version;
const string supportedProtocolVersions = ProtocolConstants.SupportedVersions;
HandshakeMessage handshakeMessage = new(new Dictionary<byte, string>
{
{ HandshakeMessagePropertyNames.PID, _environment.ProcessId.ToString(CultureInfo.InvariantCulture) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ internal static IClock GetSystemClock(this IServiceProvider serviceProvider)
internal static ITask GetTask(this IServiceProvider serviceProvider)
=> serviceProvider.GetRequiredServiceInternal<ITask>();

internal static IPlatformOutputDevice GetPlatformOutputDevice(this IServiceProvider serviceProvider)
=> serviceProvider.GetRequiredServiceInternal<IPlatformOutputDevice>();
internal static IPlatformOutputDevice? GetPlatformOutputDevice(this IServiceProvider serviceProvider)
=> serviceProvider.GetServiceInternal<IPlatformOutputDevice>();

internal static IEnvironment GetEnvironment(this IServiceProvider serviceProvider)
=> serviceProvider.GetRequiredServiceInternal<IEnvironment>();
Expand Down