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 @@ -6,6 +6,7 @@
using Microsoft.Testing.Platform.Configurations;
using Microsoft.Testing.Platform.Extensions.TestFramework;
using Microsoft.Testing.Platform.Logging;
using Microsoft.Testing.Platform.OutputDevice;
using Microsoft.Testing.Platform.TestHost;
using Microsoft.Testing.Platform.TestHostControllers;
using Microsoft.Testing.Platform.TestHostOrchestrator;
Expand Down Expand Up @@ -50,6 +51,12 @@ public interface ITestApplicationBuilder
[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")]
ILoggingManager Logging { get; }

/// <summary>
/// Gets the output device manager that allows registering a custom output device.
/// </summary>
[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")]
IOutputDeviceManager OutputDevice { get; }

/// <summary>
/// Registers a test framework with the application builder.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Hosts;
using Microsoft.Testing.Platform.Logging;
using Microsoft.Testing.Platform.OutputDevice;
using Microsoft.Testing.Platform.Resources;
using Microsoft.Testing.Platform.Services;
using Microsoft.Testing.Platform.Telemetry;
Expand Down Expand Up @@ -68,6 +69,9 @@ internal TestApplicationBuilder(
[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")]
public ILoggingManager Logging => _testHostBuilder.Logging;

[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")]
public IOutputDeviceManager OutputDevice => _testHostBuilder.OutputDevice;

internal ITelemetryManager Telemetry => _testHostBuilder.Telemetry;

internal IToolsManager Tools => _testHostBuilder.Tools;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Testing.Platform.Configurations;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Logging;
using Microsoft.Testing.Platform.OutputDevice;
using Microsoft.Testing.Platform.Telemetry;
using Microsoft.Testing.Platform.TestHost;
using Microsoft.Testing.Platform.TestHostControllers;
Expand All @@ -31,6 +32,8 @@ internal interface ITestHostBuilder

ITestHostOrchestratorManager TestHostOrchestrator { get; }

IOutputDeviceManager OutputDevice { get; }

ITelemetryManager Telemetry { get; }

IToolsManager Tools { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ internal sealed partial class TestHostBuilder(IFileSystem fileSystem, IRuntimeFe

public IToolsManager Tools { get; } = new ToolsManager();

public IOutputDeviceManager OutputDevice => _outputDisplay;

private readonly TestHostOrchestratorManager _testHostOrchestratorManager = new Extensions.TestHostOrchestrator.TestHostOrchestratorManager();

public ITestHostOrchestratorManager TestHostOrchestrator => _testHostOrchestratorManager;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.OutputDevice;

namespace Microsoft.Testing.Platform.OutputDevice;

internal sealed class CustomOutputDeviceAdapter : IPlatformOutputDevice
{
private readonly ICustomOutputDevice _customOutputDevice;

public CustomOutputDeviceAdapter(ICustomOutputDevice customOutputDevice)
=> _customOutputDevice = customOutputDevice ?? throw new ArgumentNullException(nameof(customOutputDevice));

public string Uid => _customOutputDevice.Uid;

public string Version => _customOutputDevice.Version;

public string DisplayName => _customOutputDevice.DisplayName;

public string Description => _customOutputDevice.Description;

public Task<bool> IsEnabledAsync() => _customOutputDevice.IsEnabledAsync();

public Task DisplayBannerAsync(string? bannerMessage, CancellationToken cancellationToken)
=> _customOutputDevice.DisplayBannerAsync(bannerMessage, cancellationToken);

public Task DisplayBeforeSessionStartAsync(CancellationToken cancellationToken)
=> _customOutputDevice.DisplayBeforeSessionStartAsync(cancellationToken);

public Task DisplayAfterSessionEndRunAsync(CancellationToken cancellationToken)
=> _customOutputDevice.DisplayAfterSessionEndRunAsync(cancellationToken);

public Task DisplayAsync(IOutputDeviceDataProducer producer, IOutputDeviceData data, CancellationToken cancellationToken)
=> _customOutputDevice.DisplayAsync(producer, data, cancellationToken);

public Task HandleProcessRoleAsync(TestProcessRole processRole, CancellationToken cancellationToken)
=> Task.CompletedTask;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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;
using Microsoft.Testing.Platform.Extensions.OutputDevice;

namespace Microsoft.Testing.Platform.OutputDevice;

/// <summary>
/// Represents a custom output device that can be registered via
/// <see cref="IOutputDeviceManager.SetOutputDevice(System.Func{System.IServiceProvider, ICustomOutputDevice})"/>
/// to replace the default terminal output of Microsoft.Testing.Platform.
/// </summary>
/// <remarks>
/// When server mode (JSON-RPC) is active, the platform still routes server output through its
/// built-in server output device in parallel with the custom output device.
/// </remarks>
[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")]
public interface ICustomOutputDevice : IExtension
{
/// <summary>
/// Displays the platform banner.
/// </summary>
/// <param name="bannerMessage">An optional banner message. If <see langword="null"/>, the implementation may render its own banner.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task DisplayBannerAsync(string? bannerMessage, CancellationToken cancellationToken);

/// <summary>
/// Called before the test session starts.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task DisplayBeforeSessionStartAsync(CancellationToken cancellationToken);

/// <summary>
/// Called after the test session ends.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task DisplayAfterSessionEndRunAsync(CancellationToken cancellationToken);

/// <summary>
/// Displays the output data asynchronously.
/// </summary>
/// <param name="producer">The data producer.</param>
/// <param name="data">The output data.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task DisplayAsync(IOutputDeviceDataProducer producer, IOutputDeviceData data, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +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.

namespace Microsoft.Testing.Platform.OutputDevice;

/// <summary>
/// Manages the registration of a custom output device for Microsoft.Testing.Platform.
/// </summary>
/// <remarks>
/// Only one custom output device can be registered. Calling
/// <see cref="SetOutputDevice(Func{IServiceProvider, ICustomOutputDevice})"/>
/// more than once throws <see cref="InvalidOperationException"/>.
/// </remarks>
[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")]
public interface IOutputDeviceManager
{
/// <summary>
/// Sets the custom output device factory. The factory is invoked once when the test host is built.
/// </summary>
/// <param name="outputDeviceFactory">A factory that builds the <see cref="ICustomOutputDevice"/> from the service provider.</param>
/// <remarks>
/// If the resolved <see cref="ICustomOutputDevice"/> reports itself as disabled via
/// <see cref="Microsoft.Testing.Platform.Extensions.IExtension.IsEnabledAsync"/>, the platform falls back
/// to its default terminal output device.
/// </remarks>
void SetOutputDevice(Func<IServiceProvider, ICustomOutputDevice> outputDeviceFactory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,43 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.Logging;
#if !NET7_0_OR_GREATER
using Microsoft.Testing.Platform.Resources;
#endif
using Microsoft.Testing.Platform.ServerMode;
using Microsoft.Testing.Platform.Services;

namespace Microsoft.Testing.Platform.OutputDevice;

internal sealed class PlatformOutputDeviceManager
internal sealed class PlatformOutputDeviceManager : IOutputDeviceManager
{
private Func<IServiceProvider, IPlatformOutputDevice>? _platformOutputDeviceFactory;

public void SetPlatformOutputDevice(Func<IServiceProvider, IPlatformOutputDevice> platformOutputDeviceFactory)
=> _platformOutputDeviceFactory = platformOutputDeviceFactory ?? throw new ArgumentNullException(nameof(platformOutputDeviceFactory));
{
if (platformOutputDeviceFactory is null)
{
throw new ArgumentNullException(nameof(platformOutputDeviceFactory));
}

if (_platformOutputDeviceFactory is not null)
{
throw new InvalidOperationException(PlatformResources.PlatformOutputDeviceAlreadyRegisteredErrorMessage);
}

_platformOutputDeviceFactory = platformOutputDeviceFactory;
}

public void SetOutputDevice(Func<IServiceProvider, ICustomOutputDevice> outputDeviceFactory)
{
if (outputDeviceFactory is null)
{
throw new ArgumentNullException(nameof(outputDeviceFactory));
}

SetPlatformOutputDevice(serviceProvider => new CustomOutputDeviceAdapter(outputDeviceFactory(serviceProvider)));
}

internal async Task<ProxyOutputDevice> BuildAsync(ServiceProvider serviceProvider, bool useServerModeOutputDevice)
{
// 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)
: _platformOutputDeviceFactory(serviceProvider);
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
#nullable enable
Microsoft.Testing.Platform.Builder.ITestApplicationBuilder.OutputDevice.get -> Microsoft.Testing.Platform.OutputDevice.IOutputDeviceManager!
[TPEXP]Microsoft.Testing.Platform.OutputDevice.ICustomOutputDevice
[TPEXP]Microsoft.Testing.Platform.OutputDevice.ICustomOutputDevice.DisplayAfterSessionEndRunAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
[TPEXP]Microsoft.Testing.Platform.OutputDevice.ICustomOutputDevice.DisplayAsync(Microsoft.Testing.Platform.Extensions.OutputDevice.IOutputDeviceDataProducer! producer, Microsoft.Testing.Platform.OutputDevice.IOutputDeviceData! data, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
[TPEXP]Microsoft.Testing.Platform.OutputDevice.ICustomOutputDevice.DisplayBannerAsync(string? bannerMessage, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
[TPEXP]Microsoft.Testing.Platform.OutputDevice.ICustomOutputDevice.DisplayBeforeSessionStartAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
[TPEXP]Microsoft.Testing.Platform.OutputDevice.IOutputDeviceManager
[TPEXP]Microsoft.Testing.Platform.OutputDevice.IOutputDeviceManager.SetOutputDevice(System.Func<System.IServiceProvider!, Microsoft.Testing.Platform.OutputDevice.ICustomOutputDevice!>! outputDeviceFactory) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -794,4 +794,7 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<data name="WaitDebuggerAttachNotSupportedInWasiErrorMessage" xml:space="preserve">
<value>Waiting for debugger to attach (TESTINGPLATFORM_WAIT_ATTACH_DEBUGGER=1) is not supported on wasi.</value>
</data>
<data name="PlatformOutputDeviceAlreadyRegisteredErrorMessage" xml:space="preserve">
<value>A custom output device has already been registered.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Může mít jenom jeden argument jako řetězec ve formátu &lt;value&gt;[h|m|s], kde value je hodnota datového typu float.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">Proces měl být ukončen před tím, než jsme mohli určit tuto hodnotu.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Nimmt ein Argument als Zeichenfolge im Format &lt;value&gt;[h|m|s], wobei "value" auf "float" festgelegt ist.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">Der Prozess hätte beendet werden müssen, bevor dieser Wert ermittelt werden kann</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Toma un argumento como cadena con el formato &lt;value&gt;[h|m|s] donde 'value' es float.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">El proceso debería haberse terminado para poder determinar este valor</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Prend un argument sous forme de chaîne au format &lt;value&gt;[h|m|s] où « value » est float.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">Le processus aurait dû s’arrêter avant que nous puissions déterminer cette valeur</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Acquisisce un argomento come stringa nel formato &lt;value&gt;[h|m|s] dove 'value' è float.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">Il processo dovrebbe essere terminato prima di poter determinare questo valore</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
1 つの引数を文字列として &lt;value&gt;[h|m|s] の形式で使用します。この場合、'value' は float です。</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">この値を決定する前にプロセスを終了する必要があります</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
'value'가 float인 &lt;value&gt;[h|m|s] 형식의 문자열로 인수 하나를 사용합니다.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">이 값을 결정하려면 프로세스가 종료되어야 합니다.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Pobiera jeden argument jako ciąg w formacie &lt;value&gt;[h|m|s], gdzie element „value” ma wartość zmiennoprzecinkową.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">Proces powinien zakończyć się przed ustaleniem tej wartości</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
Recebe um argumento como cadeia de caracteres no formato &lt;valor&gt;[h|m|s] em que 'valor' é float.</target>
<note />
</trans-unit>
<trans-unit id="PlatformOutputDeviceAlreadyRegisteredErrorMessage">
<source>A custom output device has already been registered.</source>
<target state="new">A custom output device has already been registered.</target>
<note />
</trans-unit>
<trans-unit id="ProcessHasNotYetExitedErrorMessage">
<source>Process should have exited before we can determine this value</source>
<target state="translated">O processo deve ter sido encerrado antes que possamos determinar esse valor</target>
Expand Down
Loading