From 01a2df3fa69f0bd37be658519a332ff632d5d557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Thu, 10 Oct 2024 19:35:45 +0200 Subject: [PATCH] Remove new platform doc --- docs/testingplatform/Index.md | 38 -- .../Source/Directory.Build.props | 2 - .../Source/Directory.Build.targets | 2 - .../CompositeExtensionFactorySample.cs | 93 ----- .../DisplayDataConsumer.cs | 76 ---- ...isplayTestApplicationLifecycleCallbacks.cs | 39 -- .../DisplayTestSessionLifeTimeHandler.cs | 62 ---- .../MonitorTestHost.cs | 46 --- .../SetEnvironmentVariableForTestHost.cs | 32 -- .../Source/TestingPlatformExplorer/Program.cs | 49 --- .../Properties/launchSettings.json | 9 - .../TestingFramework/AssertionException.cs | 22 -- .../TestingFramework.Assertions.cs | 25 -- .../TestingFramework.Attributes.cs | 20 -- .../TestingFramework.CommandLineOptions.cs | 55 --- .../TestingFramework.Registration.cs | 21 -- .../TestingFramework/TestingFramework.cs | 336 ------------------ .../TestingFrameworkCapabilities.cs | 23 -- .../TestingPlatformExplorer.csproj | 35 -- ...latformExplorer.testingplatformconfig.json | 5 - .../TestingPlatformExplorer/UnitTests.cs | 39 -- .../Source/TestingPlatformSamples.sln | 22 -- docs/testingplatform/architecture.md | 103 ------ docs/testingplatform/asyncinitcleanup.md | 26 -- docs/testingplatform/bus.png | Bin 51343 -> 0 bytes docs/testingplatform/capabilities.md | 106 ------ docs/testingplatform/codesample.md | 8 - .../compositeextensionfactory.md | 42 --- docs/testingplatform/configuration.md | 82 ----- docs/testingplatform/executionorder.md | 21 -- docs/testingplatform/exitcodes.md | 17 - docs/testingplatform/extensionintro.md | 38 -- .../ibannermessageownercapability.md | 7 - docs/testingplatform/icommandlineoptions.md | 17 - .../icommandlineoptionsprovider.md | 122 ------- docs/testingplatform/idataconsumer.md | 98 ----- docs/testingplatform/iextension.md | 27 -- docs/testingplatform/iloggerfactory.md | 94 ----- docs/testingplatform/imessagebus.md | 43 --- docs/testingplatform/ioutputdevice.md | 61 ---- docs/testingplatform/iplatforminformation.md | 3 - docs/testingplatform/irequest.md | 201 ----------- docs/testingplatform/iserviceprovider.md | 43 --- .../itestapplicationlifecyclecallbacks.md | 42 --- docs/testingplatform/itestframework.md | 121 ------- .../itestframeworkcapability.md | 3 - .../itesthostenvironmentvariableprovider.md | 71 ---- .../itesthostprocesslifetimehandler.md | 54 --- .../itestsessionlifetimehandler.md | 48 --- docs/testingplatform/pillars.md | 26 -- docs/testingplatform/ppt.pptx | Bin 37536 -> 0 bytes docs/testingplatform/registertestframework.md | 67 ---- docs/testingplatform/testnodeupdatemessage.md | 161 --------- .../CompositeExtensionFactorySample.cs | 2 +- .../DisplayDataConsumer.cs | 2 +- ...isplayTestApplicationLifecycleCallbacks.cs | 2 +- .../DisplayTestSessionLifeTimeHandler.cs | 2 +- .../MonitorTestHost.cs | 2 +- .../SetEnvironmentVariableForTestHost.cs | 2 +- .../TestingPlatformExplorer/Program.cs | 5 + .../Properties/launchSettings.json | 2 +- .../TestingFramework.Attributes.cs | 6 + .../TestingFramework.Services.cs | 0 .../TestingFramework/TestingFramework.cs | 99 +++++- .../TestingFrameworkCapabilities.cs | 14 +- .../TestingPlatformExplorer.csproj | 15 +- .../TestingPlatformExplorer/UnitTests.cs | 22 +- 67 files changed, 154 insertions(+), 2824 deletions(-) delete mode 100644 docs/testingplatform/Index.md delete mode 100644 docs/testingplatform/Source/Directory.Build.props delete mode 100644 docs/testingplatform/Source/Directory.Build.targets delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/Program.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/Properties/launchSettings.json delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/AssertionException.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Assertions.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.CommandLineOptions.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Registration.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.csproj delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.testingplatformconfig.json delete mode 100644 docs/testingplatform/Source/TestingPlatformExplorer/UnitTests.cs delete mode 100644 docs/testingplatform/Source/TestingPlatformSamples.sln delete mode 100644 docs/testingplatform/architecture.md delete mode 100644 docs/testingplatform/asyncinitcleanup.md delete mode 100644 docs/testingplatform/bus.png delete mode 100644 docs/testingplatform/capabilities.md delete mode 100644 docs/testingplatform/codesample.md delete mode 100644 docs/testingplatform/compositeextensionfactory.md delete mode 100644 docs/testingplatform/configuration.md delete mode 100644 docs/testingplatform/executionorder.md delete mode 100644 docs/testingplatform/exitcodes.md delete mode 100644 docs/testingplatform/extensionintro.md delete mode 100644 docs/testingplatform/ibannermessageownercapability.md delete mode 100644 docs/testingplatform/icommandlineoptions.md delete mode 100644 docs/testingplatform/icommandlineoptionsprovider.md delete mode 100644 docs/testingplatform/idataconsumer.md delete mode 100644 docs/testingplatform/iextension.md delete mode 100644 docs/testingplatform/iloggerfactory.md delete mode 100644 docs/testingplatform/imessagebus.md delete mode 100644 docs/testingplatform/ioutputdevice.md delete mode 100644 docs/testingplatform/iplatforminformation.md delete mode 100644 docs/testingplatform/irequest.md delete mode 100644 docs/testingplatform/iserviceprovider.md delete mode 100644 docs/testingplatform/itestapplicationlifecyclecallbacks.md delete mode 100644 docs/testingplatform/itestframework.md delete mode 100644 docs/testingplatform/itestframeworkcapability.md delete mode 100644 docs/testingplatform/itesthostenvironmentvariableprovider.md delete mode 100644 docs/testingplatform/itesthostprocesslifetimehandler.md delete mode 100644 docs/testingplatform/itestsessionlifetimehandler.md delete mode 100644 docs/testingplatform/pillars.md delete mode 100644 docs/testingplatform/ppt.pptx delete mode 100644 docs/testingplatform/registertestframework.md delete mode 100644 docs/testingplatform/testnodeupdatemessage.md rename {docs/testingplatform/Source => samples/public/TestingPlatformExamples}/TestingPlatformExplorer/TestingFramework/TestingFramework.Services.cs (100%) diff --git a/docs/testingplatform/Index.md b/docs/testingplatform/Index.md deleted file mode 100644 index 34e78d2c84..0000000000 --- a/docs/testingplatform/Index.md +++ /dev/null @@ -1,38 +0,0 @@ -# The `Microsoft.Testing.Platform` a.k.a. `TestAnywhere` - -## Table of content - -1. [Pillars](pillars.md) -1. [High level architecture](architecture.md) -1. How to write and register a testing framework - 1. [Register the testing framework](registertestframework.md) - 1. [Implement the ITestFramework](itestframework.md) - 1. [Available requests](irequest.md) - 1. [Well known TestNodeUpdateMessage.TestNode properties](testnodeupdatemessage.md) -1. [Capabilities](capabilities.md) - 1. [ITestFrameworkCapability](itestframeworkcapability.md) - 1. [IBannerMessageOwnerCapability](ibannermessageownercapability.md) -1. Extensions - 1. [Introduction](extensionintro.md) - 1. In-process & out-of-process - 1. [ICommandLineOptionsProvider](icommandlineoptionsprovider.md) - 1. In-process - 1. [ITestSessionLifetimeHandler](itestsessionlifetimehandler.md) - 1. [ITestApplicationLifecycleCallbacks](itestapplicationlifecyclecallbacks.md) - 1. [IDataConsumer](idataconsumer.md) - 1. Out-of-process - 1. [ITestHostEnvironmentVariableProvider](itesthostenvironmentvariableprovider.md) - 1. [ITestHostProcessLifetimeHandler](itesthostprocesslifetimehandler.md) -1. Extensions miscellaneous - 1. [IAsyncInitializableExtension & IAsyncCleanableExtension](asyncinitcleanup.md) - 1. [CompositeExtensionFactory\](compositeextensionfactory.md) -1. [Testing framework & extensions execution order](executionorder.md) -1. Services - 1. [IServiceProvider](iserviceprovider.md) - 1. [IConfiguration](configuration.md) - 1. [ILoggerFactory](iloggerfactory.md) - 1. [IMessageBus](imessagebus.md) - 1. [ICommandLineOptions](icommandlineoptions.md) - 1. [IOutputDevice](ioutputdevice.md) - 1. [IPlatformInformation](iplatforminformation.md) -1. [Code sample](codesample.md) diff --git a/docs/testingplatform/Source/Directory.Build.props b/docs/testingplatform/Source/Directory.Build.props deleted file mode 100644 index 8c119d5413..0000000000 --- a/docs/testingplatform/Source/Directory.Build.props +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/docs/testingplatform/Source/Directory.Build.targets b/docs/testingplatform/Source/Directory.Build.targets deleted file mode 100644 index 8c119d5413..0000000000 --- a/docs/testingplatform/Source/Directory.Build.targets +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs b/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs deleted file mode 100644 index ce9516c32d..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs +++ /dev/null @@ -1,93 +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.Testing.Platform.Extensions.OutputDevice; -using Microsoft.Testing.Platform.Extensions.TestHost; -using Microsoft.Testing.Platform.OutputDevice; -using Microsoft.Testing.Platform.TestHost; - -namespace TestingPlatformExplorer.InProcess; - -internal sealed class DisplayCompositeExtensionFactorySample : ITestSessionLifetimeHandler, IDataConsumer, IOutputDeviceDataProducer -{ - private readonly IOutputDevice _outputDevice; - private int _testNodeUpdateMessageCount; - - public Type[] DataTypesConsumed => new[] { typeof(TestNodeUpdateMessage) }; - - public string Uid => nameof(DisplayCompositeExtensionFactorySample); - - public string Version => "1.0.0"; - - public string DisplayName => nameof(DisplayCompositeExtensionFactorySample); - - public string Description => ""; - - public DisplayCompositeExtensionFactorySample(IOutputDevice outputDevice) - { - _outputDevice = outputDevice; - } - - public Task IsEnabledAsync() => Task.FromResult(true); - - public async Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) - { - _testNodeUpdateMessageCount++; - var testNodeUpdateMessage = (TestNodeUpdateMessage)value; - string testNodeDisplayName = testNodeUpdateMessage.TestNode.DisplayName; - TestNodeUid testNodeId = testNodeUpdateMessage.TestNode.Uid; - - TestNodeStateProperty nodeState = testNodeUpdateMessage.TestNode.Properties.Single(); - - switch (nodeState) - { - case InProgressTestNodeStateProperty _: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayCompositeExtensionFactorySample]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is in progress") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); - break; - } - case PassedTestNodeStateProperty _: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayCompositeExtensionFactorySample]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is completed") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); - break; - } - case FailedTestNodeStateProperty failedTestNodeStateProperty: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayCompositeExtensionFactorySample]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is failed with '{failedTestNodeStateProperty?.Exception?.Message}'") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Red } - }); - break; - } - case SkippedTestNodeStateProperty _: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayCompositeExtensionFactorySample]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is skipped") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.White } - }); - break; - } - default: - break; - } - } - - public async Task OnTestSessionStartingAsync(SessionUid sessionUid, CancellationToken cancellationToken) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("[DisplayCompositeExtensionFactorySample]Hello from OnTestSessionStartingAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - - public async Task OnTestSessionFinishingAsync(SessionUid sessionUid, CancellationToken cancellationToken) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayCompositeExtensionFactorySample]Total received 'TestNodeUpdateMessage': {_testNodeUpdateMessageCount}") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs b/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs deleted file mode 100644 index 568bc602a1..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs +++ /dev/null @@ -1,76 +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.Testing.Platform.Extensions.OutputDevice; -using Microsoft.Testing.Platform.Extensions.TestHost; -using Microsoft.Testing.Platform.OutputDevice; - -namespace TestingPlatformExplorer.InProcess; -internal sealed class DisplayDataConsumer : IDataConsumer, IOutputDeviceDataProducer -{ - private readonly IOutputDevice _outputDevice; - - public Type[] DataTypesConsumed => new[] { typeof(TestNodeUpdateMessage) }; - - public string Uid => nameof(DisplayDataConsumer); - - public string Version => "1.0.0"; - - public string DisplayName => nameof(DisplayDataConsumer); - - public string Description => "This extension display in console the testnode id and display name of TestNodeUpdateMessage data type."; - - public DisplayDataConsumer(IOutputDevice outputDevice) - { - _outputDevice = outputDevice; - } - - public async Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) - { - var testNodeUpdateMessage = (TestNodeUpdateMessage)value; - string testNodeDisplayName = testNodeUpdateMessage.TestNode.DisplayName; - TestNodeUid testNodeId = testNodeUpdateMessage.TestNode.Uid; - - TestNodeStateProperty nodeState = testNodeUpdateMessage.TestNode.Properties.Single(); - - switch (nodeState) - { - case InProgressTestNodeStateProperty _: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayDataConsumer]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is in progress") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); - break; - } - case PassedTestNodeStateProperty _: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayDataConsumer]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is completed") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); - break; - } - case FailedTestNodeStateProperty failedTestNodeStateProperty: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayDataConsumer]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is failed with '{failedTestNodeStateProperty?.Exception?.Message}'") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Red } - }); - break; - } - case SkippedTestNodeStateProperty _: - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"[DisplayDataConsumer]TestNode '{testNodeId}' with display name '{testNodeDisplayName}' is skipped") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.White } - }); - break; - } - default: - break; - } - } - public Task IsEnabledAsync() => Task.FromResult(true); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs b/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs deleted file mode 100644 index 7ebd6f9ee9..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs +++ /dev/null @@ -1,39 +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.OutputDevice; -using Microsoft.Testing.Platform.Extensions.TestHost; -using Microsoft.Testing.Platform.OutputDevice; - -namespace TestingPlatformExplorer.InProcess; -internal sealed class DisplayTestApplicationLifecycleCallbacks : ITestApplicationLifecycleCallbacks, IOutputDeviceDataProducer -{ - private readonly IOutputDevice _outputDevice; - - public string Uid => nameof(DisplayTestApplicationLifecycleCallbacks); - - public string Version => "1.0.0"; - - public string DisplayName => nameof(DisplayTestApplicationLifecycleCallbacks); - - public string Description => "This extension display in console the before/after run"; - - public DisplayTestApplicationLifecycleCallbacks(IOutputDevice outputDevice) - { - _outputDevice = outputDevice; - } - - public async Task AfterRunAsync(int exitCode, CancellationToken cancellation) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"Hello from AfterRunAsync, exit code: {exitCode}") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - - public async Task BeforeRunAsync(CancellationToken cancellationToken) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("Hello from BeforeRunAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - - public Task IsEnabledAsync() => Task.FromResult(true); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs b/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs deleted file mode 100644 index ded9dba5d5..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs +++ /dev/null @@ -1,62 +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; -using Microsoft.Testing.Platform.Extensions.OutputDevice; -using Microsoft.Testing.Platform.Extensions.TestHost; -using Microsoft.Testing.Platform.OutputDevice; -using Microsoft.Testing.Platform.TestHost; - -namespace TestingPlatformExplorer.InProcess; -internal sealed class DisplayTestSessionLifeTimeHandler : ITestSessionLifetimeHandler, - IOutputDeviceDataProducer, - IAsyncInitializableExtension, - IAsyncCleanableExtension, - IAsyncDisposable -{ - private readonly IOutputDevice _outputDevice; - - public string Uid => "This extension display in console the session start/end"; - - public string Version => "1.0.0"; - - public string DisplayName => nameof(DisplayTestSessionLifeTimeHandler); - - public string Description => "This extension display in console the session start/end"; - - public DisplayTestSessionLifeTimeHandler(IOutputDevice outputDevice) - { - _outputDevice = outputDevice; - } - - public Task IsEnabledAsync() => Task.FromResult(true); - - public async Task OnTestSessionStartingAsync(SessionUid sessionUid, CancellationToken cancellationToken) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("Hello from OnTestSessionStartingAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - - public async Task OnTestSessionFinishingAsync(SessionUid sessionUid, CancellationToken cancellationToken) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("Hello from OnTestSessionFinishingAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - public async Task InitializeAsync() - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("Hello from InitializeAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - - public async Task CleanupAsync() - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("Hello from CleanupAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); - - public async ValueTask DisposeAsync() - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("Hello from DisposeAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.DarkGreen } - }); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs b/docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs deleted file mode 100644 index dd7d89489e..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs +++ /dev/null @@ -1,46 +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.OutputDevice; -using Microsoft.Testing.Platform.Extensions.TestHostControllers; -using Microsoft.Testing.Platform.OutputDevice; - -namespace TestingPlatformExplorer.OutOfProcess; - -internal sealed class MonitorTestHost : ITestHostProcessLifetimeHandler, IOutputDeviceDataProducer -{ - private readonly IOutputDevice _outputDevice; - - public MonitorTestHost(IOutputDevice outputDevice) - { - _outputDevice = outputDevice; - } - - public string Uid => nameof(MonitorTestHost); - - public string Version => "1.0.0"; - - public string DisplayName => nameof(MonitorTestHost); - - public string Description => "Example of monitoring the test host process."; - - public Task IsEnabledAsync() => Task.FromResult(true); - - public async Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData("BeforeTestHostProcessStartAsync") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); - - public async Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellation) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"OnTestHostProcessExitedAsync, test host exited with exit code {testHostProcessInformation.ExitCode}") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); - - public async Task OnTestHostProcessStartedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellation) - => await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"OnTestHostProcessStartedAsync, test host started with PID {testHostProcessInformation.PID}") - { - ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } - }); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs b/docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs deleted file mode 100644 index 8101e8e50d..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs +++ /dev/null @@ -1,32 +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; -using Microsoft.Testing.Platform.Extensions.TestHostControllers; - -namespace TestingPlatformExplorer.OutOfProcess; - -internal sealed class SetEnvironmentVariableForTestHost : ITestHostEnvironmentVariableProvider -{ - public string Uid => nameof(SetEnvironmentVariableForTestHost); - - public string Version => "1.0.0"; - - public string DisplayName => nameof(SetEnvironmentVariableForTestHost); - - public string Description => "Example of setting environment variables for the test host."; - - public Task IsEnabledAsync() => Task.FromResult(true); - - public Task UpdateAsync(IEnvironmentVariables environmentVariables) - { - environmentVariables.SetVariable(new EnvironmentVariable("SAMPLE", "SAMPLE_VALUE", false, true)); - return Task.CompletedTask; - } - - public Task ValidateTestHostEnvironmentVariablesAsync(IReadOnlyEnvironmentVariables environmentVariables) - => environmentVariables.TryGetVariable("SAMPLE", out OwnedEnvironmentVariable? value) && value.Value == "SAMPLE_VALUE" - ? ValidationResult.ValidTask - : ValidationResult.InvalidTask("The environment variable 'SAMPLE' is not set to 'SAMPLE_VALUE'."); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/Program.cs b/docs/testingplatform/Source/TestingPlatformExplorer/Program.cs deleted file mode 100644 index 3ca8c8f6fc..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/Program.cs +++ /dev/null @@ -1,49 +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 System.Reflection; - -using Microsoft.Testing.Extensions; -using Microsoft.Testing.Platform.Builder; -using Microsoft.Testing.Platform.Extensions; -using Microsoft.Testing.Platform.Services; - -using TestingPlatformExplorer.InProcess; -using TestingPlatformExplorer.OutOfProcess; -using TestingPlatformExplorer.TestingFramework; - -// Create the test application builder -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); - -// Register the testing framework -testApplicationBuilder.AddTestingFramework(() => new[] { Assembly.GetExecutingAssembly() }); - -// In-process & out-of-process extensions -// Register the testing framework command line options -testApplicationBuilder.CommandLine.AddProvider(() => new TestingFrameworkCommandLineOptions()); - -// In-process extensions -testApplicationBuilder.TestHost.AddTestApplicationLifecycleCallbacks(serviceProvider - => new DisplayTestApplicationLifecycleCallbacks(serviceProvider.GetOutputDevice())); -testApplicationBuilder.TestHost.AddTestSessionLifetimeHandle(serviceProvider - => new DisplayTestSessionLifeTimeHandler(serviceProvider.GetOutputDevice())); -testApplicationBuilder.TestHost.AddDataConsumer(serviceProvider - => new DisplayDataConsumer(serviceProvider.GetOutputDevice())); - -// Out-of-process extensions -testApplicationBuilder.TestHostControllers.AddEnvironmentVariableProvider(_ - => new SetEnvironmentVariableForTestHost()); -testApplicationBuilder.TestHostControllers.AddProcessLifetimeHandler(serviceProvider => - new MonitorTestHost(serviceProvider.GetOutputDevice())); - -// In-process composite extension SessionLifeTimeHandler+DataConsumer -CompositeExtensionFactory compositeExtensionFactory = new(serviceProvider => new DisplayCompositeExtensionFactorySample(serviceProvider.GetOutputDevice())); -testApplicationBuilder.TestHost.AddTestSessionLifetimeHandle(compositeExtensionFactory); -testApplicationBuilder.TestHost.AddDataConsumer(compositeExtensionFactory); - -// Register public extensions -// Trx -testApplicationBuilder.AddTrxReportProvider(); - -using ITestApplication testApplication = await testApplicationBuilder.BuildAsync(); -return await testApplication.RunAsync(); diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/Properties/launchSettings.json b/docs/testingplatform/Source/TestingPlatformExplorer/Properties/launchSettings.json deleted file mode 100644 index 81ddf2512b..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/Properties/launchSettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "profiles": { - "TestingPlatformExplorer": { - "commandName": "Project", - "commandLineArgs": "--dop 1 --generatereport --reportfilename testframeworkreport.txt --diagnostic --report-trx", - "environmentVariables": { "TestingFramework__DisableParallelism": "True" } - } - } -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/AssertionException.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/AssertionException.cs deleted file mode 100644 index a42aab42e2..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/AssertionException.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. - -namespace TestingPlatformExplorer.TestingFramework; - -[Serializable] -public class AssertionException : Exception -{ - public AssertionException() - { - } - - public AssertionException(string? message) - : base(message) - { - } - - public AssertionException(string? message, Exception? innerException) - : base(message, innerException) - { - } -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Assertions.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Assertions.cs deleted file mode 100644 index 5330e13656..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Assertions.cs +++ /dev/null @@ -1,25 +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. - -namespace TestingPlatformExplorer.TestingFramework; - -public static class Assert -{ - public static void AreEqual(T expected, T actual) - { - if (expected is null) - { - throw new ArgumentException("'expected' cannot be null", nameof(expected)); - } - - if (actual is null) - { - throw new ArgumentException("'actual' cannot be null", nameof(actual)); - } - - if (!expected.Equals(actual)) - { - throw new AssertionException($"Expected: {expected}, Actual: {actual}"); - } - } -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs deleted file mode 100644 index efa7adde16..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs +++ /dev/null @@ -1,20 +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. - -namespace TestingPlatformExplorer.TestingFramework; - -[AttributeUsage(AttributeTargets.Method)] -public class SkipAttribute : Attribute -{ - public string Reason { get; private set; } - - public SkipAttribute(string reason) - { - Reason = reason; - } -} - -[AttributeUsage(AttributeTargets.Method)] -public class TestMethodAttribute : Attribute -{ -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.CommandLineOptions.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.CommandLineOptions.cs deleted file mode 100644 index 788efe5c75..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.CommandLineOptions.cs +++ /dev/null @@ -1,55 +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.CommandLine; -using Microsoft.Testing.Platform.Extensions; -using Microsoft.Testing.Platform.Extensions.CommandLine; - -namespace TestingPlatformExplorer.TestingFramework; - -internal sealed class TestingFrameworkCommandLineOptions : ICommandLineOptionsProvider -{ - public const string DopOption = "dop"; - public const string GenerateReportOption = "generatereport"; - public const string ReportFilenameOption = "reportfilename"; - - public string Uid => nameof(TestingFrameworkCommandLineOptions); - - public string Version => "1.0.0"; - - public string DisplayName => nameof(TestingFrameworkCommandLineOptions); - - public string Description => "Testing framework command line options"; - - public IReadOnlyCollection GetCommandLineOptions() => new[] - { - new CommandLineOption(DopOption,"Degree of parallelism", ArgumentArity.ExactlyOne, false), - new CommandLineOption(GenerateReportOption,"Generate a test report file", ArgumentArity.Zero, false), - new CommandLineOption(ReportFilenameOption,"Report file name to use together with --generatereport", ArgumentArity.ExactlyOne, false), - }; - - public Task IsEnabledAsync() => Task.FromResult(true); - - public Task ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments) - { - if (commandOption.Name == DopOption) - { - if (!int.TryParse(arguments[0], out int dopValue) || dopValue <= 0) - { - return ValidationResult.InvalidTask("Dop must be a positive integer"); - } - } - - return ValidationResult.ValidTask; - } - - public Task ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions) - { - bool generateReportEnabled = commandLineOptions.IsOptionSet(GenerateReportOption); - bool reportFileName = commandLineOptions.TryGetOptionArgumentList(ReportFilenameOption, out string[]? _); - - return (generateReportEnabled || reportFileName) && !(generateReportEnabled && reportFileName) - ? ValidationResult.InvalidTask("--generatereport and --reportfilename must be specified together") - : ValidationResult.ValidTask; - } -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Registration.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Registration.cs deleted file mode 100644 index 5d2c2ddb9a..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Registration.cs +++ /dev/null @@ -1,21 +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 System.Reflection; -using Microsoft.Testing.Platform.Builder; -using Microsoft.Testing.Platform.Logging; -using Microsoft.Testing.Platform.Services; - -namespace TestingPlatformExplorer.TestingFramework; - -public static class TestingFrameworkExtensions -{ - public static void AddTestingFramework(this ITestApplicationBuilder builder, Func assemblies) - => builder.RegisterTestFramework(_ => new TestingFrameworkCapabilities(), - (capabilities, serviceProvider) => new TestingFramework(capabilities, - serviceProvider.GetCommandLineOptions(), - serviceProvider.GetConfiguration(), - serviceProvider.GetLoggerFactory().CreateLogger(), - serviceProvider.GetOutputDevice(), - assemblies)); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.cs deleted file mode 100644 index 63f5568838..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.cs +++ /dev/null @@ -1,336 +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. - -#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 System.Globalization; -using System.Reflection; -using System.Text; - -using Microsoft.Testing.Extensions.TrxReport.Abstractions; -using Microsoft.Testing.Platform.Capabilities.TestFramework; -using Microsoft.Testing.Platform.CommandLine; -using Microsoft.Testing.Platform.Configurations; -using Microsoft.Testing.Platform.Extensions.Messages; -using Microsoft.Testing.Platform.Extensions.OutputDevice; -using Microsoft.Testing.Platform.Extensions.TestFramework; -using Microsoft.Testing.Platform.Logging; -using Microsoft.Testing.Platform.OutputDevice; -using Microsoft.Testing.Platform.Requests; - -namespace TestingPlatformExplorer.TestingFramework; - -internal sealed class TestingFramework : ITestFramework, IDataProducer, IDisposable, IOutputDeviceDataProducer -{ - private readonly TestingFrameworkCapabilities _capabilities; - private readonly ICommandLineOptions _commandLineOptions; - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - private readonly IOutputDevice _outputDevice; - private readonly Assembly[] _assemblies; - private readonly SemaphoreSlim _dop; - private readonly string _reportFile = string.Empty; - private readonly int _dopValue; - - public TestingFramework( - ITestFrameworkCapabilities capabilities, - ICommandLineOptions commandLineOptions, - IConfiguration configuration, - ILogger logger, - IOutputDevice outputDevice, - Func assemblies) - { - _capabilities = (TestingFrameworkCapabilities)capabilities; - _commandLineOptions = commandLineOptions; - _configuration = configuration; - _logger = logger; - _outputDevice = outputDevice; - _assemblies = assemblies(); - - if (_commandLineOptions.TryGetOptionArgumentList(TestingFrameworkCommandLineOptions.DopOption, out string[]? argumentList)) - { - _dopValue = int.Parse(argumentList[0], CultureInfo.InvariantCulture); - _dop = new SemaphoreSlim(_dopValue, _dopValue); - } - else - { - _dop = new SemaphoreSlim(int.MaxValue, int.MaxValue); - } - - if (_configuration["TestingFramework:DisableParallelism"] == bool.TrueString) - { - _dop?.Dispose(); - _dop = new SemaphoreSlim(1, 1); - _dopValue = 1; - } - - if (_commandLineOptions.IsOptionSet(TestingFrameworkCommandLineOptions.GenerateReportOption)) - { - if (_commandLineOptions.TryGetOptionArgumentList(TestingFrameworkCommandLineOptions.ReportFilenameOption, out string[]? reportFile)) - { - _reportFile = reportFile[0]; - } - } - } - - public string Uid => nameof(TestingFramework); - - public string Version => "1.0.0"; - - public string DisplayName => "TestingFramework"; - - public string Description => "Testing framework sample"; - - public Type[] DataTypesProduced => new[] { typeof(TestNodeUpdateMessage), typeof(SessionFileArtifact) }; - - public Task CloseTestSessionAsync(CloseTestSessionContext context) - => Task.FromResult(new CloseTestSessionResult() { IsSuccess = true }); - - public Task CreateTestSessionAsync(CreateTestSessionContext context) - => Task.FromResult(new CreateTestSessionResult() { IsSuccess = true }); - - public async Task ExecuteRequestAsync(ExecuteRequestContext context) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - await _logger.LogDebugAsync($"Executing request of type '{context.Request}'"); - } - - switch (context.Request) - { - case DiscoverTestExecutionRequest discoverTestExecutionRequest: - { - try - { - MethodInfo[] tests = GetTestsMethodFromAssemblies(); - foreach (MethodInfo test in tests) - { - var testNode = new TestNode() - { - Uid = $"{test.DeclaringType!.FullName}.{test.Name}", - DisplayName = test.Name, - Properties = new PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance), - }; - - TestMethodIdentifierProperty testMethodIdentifierProperty = new(test.DeclaringType!.Assembly!.FullName!, - test.DeclaringType!.Namespace!, - test.DeclaringType.Name!, - test.Name, - test.GetParameters().Select(x => x.ParameterType.FullName).ToArray()!, - test.ReturnType.FullName!); - - testNode.Properties.Add(testMethodIdentifierProperty); - - await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(discoverTestExecutionRequest.Session.SessionUid, testNode)); - } - } - finally - { - // Ensure to complete the request also in case of exception - context.Complete(); - } - - break; - } - - case RunTestExecutionRequest runTestExecutionRequest: - { - try - { - await _outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"TestingFramework version '{Version}' running tests with parallelism of {_dopValue}") { ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } }); - - StringBuilder reportBody = new(); - MethodInfo[] tests = GetTestsMethodFromAssemblies(); - List results = new(); - foreach (MethodInfo test in tests) - { - if (runTestExecutionRequest.Filter is TestNodeUidListFilter filter) - { - if (!filter.TestNodeUids.Any(testId => testId == $"{test.DeclaringType!.FullName}.{test.Name}")) - { - continue; - } - } - - SkipAttribute? skipAttribute = test.GetCustomAttribute(); - if (skipAttribute != null) - { - var skippedTestNode = new TestNode() - { - Uid = $"{test.DeclaringType!.FullName}.{test.Name}", - DisplayName = test.Name, - Properties = new PropertyBag(new SkippedTestNodeStateProperty(skipAttribute.Reason)), - }; - - if (_capabilities.TrxCapability.IsTrxEnabled) - { - FillTrxProperties(skippedTestNode, test); - } - - await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, skippedTestNode)); - - lock (reportBody) - { - reportBody.AppendLine(CultureInfo.InvariantCulture, $"Test {skippedTestNode.Uid} skipped"); - } - - continue; - } - - results.Add(Task.Run(async () => - { - await _dop.WaitAsync(); - try - { - var testOutputHelper = new TestOutputHelper(); - object? instance = Activator.CreateInstance(test.DeclaringType!); - try - { - if (test.GetParameters().Length == 1 && test.GetParameters()[0].ParameterType == typeof(ITestOutputHelper)) - { - test.Invoke(instance, new object[] { testOutputHelper }); - - } - else - { - test.Invoke(instance, null); - } - - var successfulTestNode = new TestNode() - { - Uid = $"{test.DeclaringType!.FullName}.{test.Name}", - DisplayName = test.Name, - Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance), - }; - - if (_capabilities.TrxCapability.IsTrxEnabled) - { - FillTrxProperties(successfulTestNode, test); - } - - if (testOutputHelper.Output.Length > 0) - { - successfulTestNode.Properties.Add(new StandardOutputProperty(testOutputHelper.Output.ToString())); - } - - if (testOutputHelper.Error.Length > 0) - { - successfulTestNode.Properties.Add(new StandardErrorProperty(testOutputHelper.Error.ToString())); - } - - await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, successfulTestNode)); - - lock (reportBody) - { - reportBody.AppendLine(CultureInfo.InvariantCulture, $"Test {successfulTestNode.Uid} succeeded"); - } - } - catch (TargetInvocationException ex) when (ex.InnerException is AssertionException assertionException) - { - var assertionFailedTestNode = new TestNode() - { - Uid = $"{test.DeclaringType!.FullName}.{test.Name}", - DisplayName = test.Name, - Properties = new PropertyBag(new FailedTestNodeStateProperty(assertionException)), - }; - - if (_capabilities.TrxCapability.IsTrxEnabled) - { - FillTrxProperties(assertionFailedTestNode, test, assertionException); - } - - if (testOutputHelper.Output.Length > 0) - { - assertionFailedTestNode.Properties.Add(new StandardOutputProperty(testOutputHelper.Output.ToString())); - } - - if (testOutputHelper.Error.Length > 0) - { - assertionFailedTestNode.Properties.Add(new StandardErrorProperty(testOutputHelper.Error.ToString())); - } - - await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, assertionFailedTestNode)); - - reportBody.AppendLine(CultureInfo.InvariantCulture, $"Test {assertionFailedTestNode.Uid} failed"); - } - catch (TargetInvocationException ex) - { - var failedTestNode = new TestNode() - { - Uid = $"{test.DeclaringType!.FullName}.{test.Name}", - DisplayName = test.Name, - Properties = new PropertyBag(new ErrorTestNodeStateProperty(ex.InnerException!)), - }; - - if (_capabilities.TrxCapability.IsTrxEnabled) - { - FillTrxProperties(failedTestNode, test, ex); - } - - if (testOutputHelper.Output.Length > 0) - { - failedTestNode.Properties.Add(new StandardOutputProperty(testOutputHelper.Output.ToString())); - } - - if (testOutputHelper.Error.Length > 0) - { - failedTestNode.Properties.Add(new StandardErrorProperty(testOutputHelper.Error.ToString())); - } - - await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, failedTestNode)); - - lock (reportBody) - { - reportBody.AppendLine(CultureInfo.InvariantCulture, $"Test {failedTestNode.Uid} failed"); - } - } - } - finally - { - _dop.Release(); - } - })); - } - - await Task.WhenAll(results); - - if (!string.IsNullOrEmpty(_reportFile)) - { - File.WriteAllText(_reportFile, reportBody.ToString()); - await context.MessageBus.PublishAsync(this, new SessionFileArtifact(runTestExecutionRequest.Session.SessionUid, new FileInfo(_reportFile), "Testing framework report")); - } - } - finally - { - // Ensure to complete the request also in case of exception - context.Complete(); - } - - break; - } - - default: - throw new NotSupportedException($"Request {context.GetType()} not supported"); - } - } - - private void FillTrxProperties(TestNode testNode, MethodInfo test, Exception? ex = null) - { - testNode.Properties.Add(new TrxFullyQualifiedTypeNameProperty(test.DeclaringType!.FullName!)); - - if (ex is not null) - { - testNode.Properties.Add(new TrxExceptionProperty(ex.Message, ex.StackTrace)); - } - } - private MethodInfo[] GetTestsMethodFromAssemblies() - => _assemblies - .SelectMany(x => x.GetTypes()) - .SelectMany(x => x.GetMethods()) - .Where(x => x.GetCustomAttributes().Any()) - .ToArray(); - - public Task IsEnabledAsync() => Task.FromResult(true); - public void Dispose() - => _dop.Dispose(); -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs b/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs deleted file mode 100644 index 4afd247067..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs +++ /dev/null @@ -1,23 +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.Extensions.TrxReport.Abstractions; -using Microsoft.Testing.Platform.Capabilities.TestFramework; - -namespace TestingPlatformExplorer.TestingFramework; - -internal sealed class TestingFrameworkCapabilities : ITestFrameworkCapabilities -{ - public TrxCapability TrxCapability { get; } = new(); - - public IReadOnlyCollection Capabilities => [TrxCapability]; -} - -internal sealed class TrxCapability : ITrxReportCapability -{ - public bool IsTrxEnabled { get; set; } - - public bool IsSupported => true; - - public void Enable() => IsTrxEnabled = true; -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.csproj b/docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.csproj deleted file mode 100644 index f51a61a15f..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - Exe - net8.0 - enable - enable - TestingPlatformExplorer - false - - - - - - - - - - - - - - - - PreserveNewest - - - - - - - - - - diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.testingplatformconfig.json b/docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.testingplatformconfig.json deleted file mode 100644 index 193b3111e0..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/TestingPlatformExplorer.testingplatformconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "TestingFramework": { - "DisableParallelism": false - } -} diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/UnitTests.cs b/docs/testingplatform/Source/TestingPlatformExplorer/UnitTests.cs deleted file mode 100644 index b96ab23e67..0000000000 --- a/docs/testingplatform/Source/TestingPlatformExplorer/UnitTests.cs +++ /dev/null @@ -1,39 +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 TestingPlatformExplorer.TestingFramework; - -namespace TestingPlatformExplorer.UnitTests; - -public class SomeTests -{ - [TestMethod] - public static void TestMethod1() => Assert.AreEqual(1, 1); - - [TestMethod] - public static void TestMethod2() => Assert.AreEqual(1, 2); - - [TestMethod] - public static void TestMethod3(ITestOutputHelper testOutputHelper) - { - testOutputHelper.WriteLine("I'm running TestMethod3"); - - try - { - int a = 1; - int b = 0; - int c = a / b; - - Assert.AreEqual(c, 2); - } - catch (Exception ex) - { - testOutputHelper.WriteErrorLine(ex.ToString()); - throw; - } - } - - [Skip("Temporary disabled")] - [TestMethod] - public static void TestMethod4() => Assert.AreEqual(1, 1); -} diff --git a/docs/testingplatform/Source/TestingPlatformSamples.sln b/docs/testingplatform/Source/TestingPlatformSamples.sln deleted file mode 100644 index d385c6012e..0000000000 --- a/docs/testingplatform/Source/TestingPlatformSamples.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestingPlatformExplorer", "TestingPlatformExplorer\TestingPlatformExplorer.csproj", "{0BC2CCB3-993F-4FEB-8BB6-E2F310C3AD0D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0BC2CCB3-993F-4FEB-8BB6-E2F310C3AD0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BC2CCB3-993F-4FEB-8BB6-E2F310C3AD0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BC2CCB3-993F-4FEB-8BB6-E2F310C3AD0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BC2CCB3-993F-4FEB-8BB6-E2F310C3AD0D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/docs/testingplatform/architecture.md b/docs/testingplatform/architecture.md deleted file mode 100644 index 498b3987de..0000000000 --- a/docs/testingplatform/architecture.md +++ /dev/null @@ -1,103 +0,0 @@ -# High level architecture - -To introduce the new testing platform, we will use the classic console application (for Windows) as the host. The samples in this document are written in C#, but you can use the testing platform with any language that supports the .NET Ecma specification, and run on any OS supported by .NET. To use the platform, simply reference the `Microsoft.Testing.Platform.dll` assembly, which can be consumed through the official NuGet package available at . - -In a console project `Contoso.UnitTests.exe` the following `Main` method defines the entry point: - -```c# -public static async Task Main(string[] args) -{ - ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); - testApplicationBuilder.RegisterTestFramework(/* test framework registration factories */); - using ITestApplication testApplication = await testApplicationBuilder.BuildAsync(); - return await testApplication.RunAsync(); -} -``` - -This code includes everything needed to execute a test session, except for registering a test framework such as MSTest through `RegisterTestFramework`. This code is shown and explained in later sections. - -Please also note that in a typical setup this code is automatically generated through MSBuild, and is not visible in your project. By typical setup we mean for example generating new project from .NET 9 `mstest` template via `dotnet new mstest`. - -When `Contoso.UnitTests.exe` application is started a standard Windows process is created, and the testing platform interacts with the registered testing framework to execute the testing session. - -A single process is created to carry out this work: - -```mermaid -graph TD; - TestHost:'Contoso.UnitTests.exe'; -``` - -The testing platform includes a built-in display device that writes the testing session information in the terminal, similar to: - -```bash -Microsoft(R) Testing Platform Execution Command Line Tool -Version: 1.1.0+8c0a8fd8e (UTC 2024/04/03) -RuntimeInformation: win-x64 - .NET 9.0.0-preview.1.24080.9 -Copyright(c) Microsoft Corporation.  All rights reserved. -Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: 5ms - Contoso.UnitTests.dll (win-x64 - .NET 9.0.0-preview.1.24080.9) -``` - -## Observing a test host - -Test runs commonly collect code coverage information, or similar information to evaluate code quality. Such workloads may require configuration to be done before the test host process starts, for example setting environment variables. - -The testing platform accommodates this by having **out-of-process** extensions. When running with an out-of-process extensions, the testing platform will start multiple processes and it will manage them appropriately. - -The following example demonstrates how to register a code coverage feature using a **TestHostController** extension. - -```c# -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -testApplicationBuilder.RegisterTestFramework(/* test framework registration factories */); -testApplicationBuilder.AddCodeCoverage(); -using ITestApplication testApplication = await testApplicationBuilder.BuildAsync(); -return await testApplication.RunAsync(); -``` - -The `testApplicationBuilder.AddCodeCoverage();` internally uses the **TestHostController** extensibility point, which is an out-of-process extensibility point. - -```c# -public static class TestApplicationBuilderExtensions -{ - public static ITestApplicationBuilder AddCodeCoverage(this ITestApplicationBuilder testApplicationBuilder) - { - testApplicationBuilder.TestHostControllers.AddEnvironmentVariableProvider(...); - .... - return testApplicationBuilder; - } -} -``` - -The parameters for the api `AddEnvironmentVariableProvider` will be explained in later sections. - -When we run `Contoso.UnitTests.exe` this time, the testing platform detects that a `TestHostController` extension is registered. As a result, it starts another instance of the `Contoso.UnitTests.exe` process as a child process. This is done to properly set the environment variables as required by the extension registered with the `AddEnvironmentVariableProvider` API. - -The process layout looks like this: - -```mermaid -graph TD; - TestHostController:'Contoso.UnitTests.exe'-->TestHost:'Contoso.UnitTests.exe; -``` - -> [!NOTE] -> The provided example assumes a console application layout, which handles the start process correctly and propagates all command line arguments to the child process. -> If you are using a different host, you need to ensure that the entry point code correctly forwards the process entry point (the "Main") to the appropriate code block. -> The runtime simply starts itself with the same command line arguments. - -The above section provides a brief introduction to the architecture of the testing platform. The current extensibility points are divided into two categories: - -1. **In process** extensions can be accessed through the `TestHost` property of the test application builder. In process means that they will run in the same process as the test framework. - - ```cs - ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); - testApplicationBuilder.RegisterTestFramework(/* test framework registration factories */); - testApplicationBuilder.TestHost.AddXXX(...); - ``` - - As observed, the most crucial extension point is the in-process *testing framework* (`RegisterTestFramework`), which is the only **mandatory** one. - -1. **Out of process** extensions can be accessed through the `TestHostControllers` property of the test application builder. These extensions run in a separate process from the test framework to "observe" it. - - ```cs - ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); - testApplicationBuilder.TestHostControllers.AddXXX(...); - ``` diff --git a/docs/testingplatform/asyncinitcleanup.md b/docs/testingplatform/asyncinitcleanup.md deleted file mode 100644 index 5883cf5f4a..0000000000 --- a/docs/testingplatform/asyncinitcleanup.md +++ /dev/null @@ -1,26 +0,0 @@ -# Async extension initialization and cleanup - -The creation of the testing framework and extensions through factories adheres to the standard .NET object creation mechanism, which uses synchronous constructors. If an extension requires intensive initialization (such as accessing the file system or network), it cannot employ the *async/await* pattern in the constructor because constructors return void, not `Task`. - -Therefore, the testing platform provides a method to initialize an extension using the async/await pattern through a simple interface. For symmetry, it also offers an async interface for cleanup that extensions can implement seamlessly. - -```cs -public interface IAsyncInitializableExtension -{ - Task InitializeAsync(); -} - -public interface IAsyncCleanableExtension -{ - Task CleanupAsync(); -} -``` - -`IAsyncInitializableExtension.InitializeAsync`: This method is assured to be invoked following the creation factory. - -`IAsyncCleanableExtension.CleanupAsync`: This method is assured to be invoked *at least one time* during the termination of the testing session, prior to the default `DisposeAsync` or `Dispose`. - -> [!IMPORTANT] -> Similar to the standard `Dispose` method, `CleanupAsync` may be invoked multiple times. If an object's `CleanupAsync` method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its `CleanupAsync` method is called multiple times. -> [!NOTE] -> By default, the testing platform will call `DisposeAsync` if it's available, or `Dispose` if it's implemented. It's important to note that the testing platform will not call both dispose methods but will prioritize the async one if implemented. diff --git a/docs/testingplatform/bus.png b/docs/testingplatform/bus.png deleted file mode 100644 index b325c10bbfa41ff7d65dd4ad36363a01a666718d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51343 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 diff --git a/docs/testingplatform/capabilities.md b/docs/testingplatform/capabilities.md deleted file mode 100644 index b723631024..0000000000 --- a/docs/testingplatform/capabilities.md +++ /dev/null @@ -1,106 +0,0 @@ -# The capabilities system - -## What is a capability in the context of the testing platform? - -In the context of the testing platform, a *capability* refers to the *potential to perform a specific action or provide specific information*. It is a means for the testing framework and extensions to *declare* their *ability* to *operate* in a certain manner or provide specific information to the *requesters*. - -The *requesters* can be any component involved in a test session, such as the platform itself, an extension, or the testing framework itself. - -The primary objective of the capability system is to facilitate effective communication among the components involved in a test session, enabling them to exchange information and meet their respective needs accurately. - -### Sample: Disable parallelism capability - -Let's consider a hypothetical example to demonstrate the necessity of a capability system. **Please note that this example is purely for illustrative purposes and is not currently implemented within the testing platform or any testing framework.** - -Imagine a situation where we have an extension that requires the testing framework to execute no more than one test at a time. Furthermore, after each test, the extension needs to know the CPU usage for that specific test. - -To accommodate the above scenario, we need to inquire from the testing framework if: - -1. It has the capability to execute only one test at a time. -2. It can provide information regarding the amount of CPU consumed by each test. - -How can the extension determine if the testing framework has the ability to operate in this mode and provide CPU usage information for a test session? -In the testing platform, this capability is represented by an implementation of an interface that inherits from a base one named `Microsoft.Testing.Platform.Capabilities.ICapability`: - -```cs -// Base capablities contracts - -public interface ICapability -{ -} - -public interface ICapabilities - where TCapability : ICapability -{ - IReadOnlyCollection Capabilities { get; } -} - -// Specific testing framework capabilities - -public interface ITestFrameworkCapabilities : ICapabilities -{ -} - -public interface ITestFrameworkCapability : ICapability -{ -} -``` - -As you can see, the interface `ICapability` is *empty* because it can represent *any capability*, and the actual implementation will be context-dependent. You'll also observe the `ITestFrameworkCapability`, which inherits from `ICapability` to classify the capability. The capability system's generic nature allows for convenient grouping by context. The `ITestFrameworkCapability` groups all the capabilities implemented by the [testing framework](itestframework.md). The `ICapabilities` interface reveals the *set* of all capabilities implemented by an extension. Similarly, for the base one, we have a context-specific testing framework called `ITestFrameworkCapabilities`. The `ITestFrameworkCapabilities` is provided to the platform during the [testing framework registration](registertestframework.md) process. - -Now, let's attempt to create our capability to address the aforementioned scenario. We could define it as follows: - -```cs -public interface IDisableParallelismCapability : ITestFrameworkCapability -{ - bool CanDisableParallelism {get;} - bool CanProvidePerTestCPUConsumption {get;} - void Enable(); -} -``` - -If the testing framework implements this interface and we can query it at runtime, we can: - -* Verify if the testing framework has the ability to turn off parallelism `CanDisableParallelism = true` -* Determine if the testing framework can supply CPU usage data `CanProvidePerTestCPUConsumption = true` -* Request the testing adapter to activate this mode by invoking the `Enable()` method before the test session commences - -The hypothetical code fragment inside the extension could be something like: - -```cs -IServiceProvider serviceProvider = ...get service provider... -ITestFrameworkCapabilities testFrameworkCapabilities = serviceProvider.GetRequiredService(); - -// We utilize the handy `GetCapability` API to search for the specific capability we wish to query. -IDisableParallelismCapability? capability = testFrameworkCapabilities.GetCapability(); -if (capability is null) -{ - ...capability not supported... -} -else -{ - capability.Enable(); - if(capability.CanDisableParallelism) - { - ...do something... - } - - if(capability.CanProvidePerTestCPUConsumption) - { - ...do something... - } -} -``` - -The example above illustrates how the capability infrastructure enables a powerful mechanism for communicating abilities between the components involved in a test session. While the sample demonstrates a capability specifically designed for the testing framework, any component can expose and implement extensions that inherit from `ICapability`. - -It's evident that not all details can be communicated through an interface. For example, in the scenario above, what should the extension expected if the `CanProvidePerTestCPUConsumption` is supported? What kind of custom information is expected to be transmitted via the [`IMessageBus`](imessagebus.md) by the testing framework? The solution to this is **DOCUMENTATION OF THE CAPABILITY**. It's the responsibility of the capability *owner* to design, ship, and document it as clearly as possible to assist implementors who want to effectively *collaborate* with the extension that requires the specific capability. - -For instance, at the time of writing, the TRX report extension enables the testing framework to implement the necessary capability to accurately generate a TRX report. The extension to register is included in the package , but the capability to implement is found in the *contract only* package . - -In conclusion, let's summarize the primary aspects of the capability system: - -* It is essential for facilitating clear and stable communication between components. -* All capabilities should inherit from `ICapability` or an interface that inherits from it, and are exposed through a collection with the `ICapabilities` interface. -* It aids in the evolution of features without causing breaking changes. If a certain capability is not supported, appropriate action can be taken. -* The responsibility of designing, shipping, and documenting the usage of a capability lies with the *capability owner*. The testing platform can also *own* a capability in the same way as any other extension. diff --git a/docs/testingplatform/codesample.md b/docs/testingplatform/codesample.md deleted file mode 100644 index b740f1a617..0000000000 --- a/docs/testingplatform/codesample.md +++ /dev/null @@ -1,8 +0,0 @@ -# Code sample - -You can find a practical example of the concepts discussed in this documentation by opening the solution located at [./Source/TestingPlatformSamples.sln](Source). - -This project demonstrates a basic `TestingFramework` testing framework, showcasing how to utilize the extensibility point and various available services. - -> [!WARNING] -> Please note that the provided code sample is for demonstration purposes only. It does not represent a fully-fledged testing framework and lacks coverage for many scenarios and error checks. diff --git a/docs/testingplatform/compositeextensionfactory.md b/docs/testingplatform/compositeextensionfactory.md deleted file mode 100644 index 3c178508aa..0000000000 --- a/docs/testingplatform/compositeextensionfactory.md +++ /dev/null @@ -1,42 +0,0 @@ -# The CompositeExtensionFactory\ - -As outlined in the [extensions](extensionintro.md) section, the testing platform enables you to implement interfaces to incorporate custom extensions both in and out of process. - -Each interface addresses a particular feature, and according to .NET design, you implement this interface in a specific object. You can register the extension itself using the specific registration API `AddXXX` from the `TestHost` or `TestHostController` object from the `ITestApplicationBuilder` as detailed in the corresponding sections. - -However, if you need to *share state* between two extensions, the fact that you can implement and register different objects implementing different interfaces makes sharing a challenging task. Without any assistance, you would need a way to pass one extension to the other to share information, which complicates the design. - -Hence, the testing platform provides a sophisticated method to implement multiple extension points using the same type, making data sharing a straightforward task. All you need to do is utilize the `CompositeExtensionFactory`, which can then be registered using the same API as you would for a single interface implementation. - -For instance, consider a type that implements both `ITestSessionLifetimeHandler` and `IDataConsumer`. This is a common scenario because you often want to gather information from the [testing framework](itestframework.md) and then, when the testing session concludes, you'll dispatch your artifact using the [`IMessageBus`](imessagebus.md) within the `ITestSessionLifetimeHandler.OnTestSessionFinishingAsync`. - -What you should do is to normally implement the interfaces: - -```cs -internal class CustomExtension : ITestSessionLifetimeHandler, IDataConsumer, ... -{ - ... -} -``` - -Once you've created the `CompositeExtensionFactory` for your type, you can register it with both the `IDataConsumer` and `ITestSessionLifetimeHandler` APIs, which offer an overload for the `CompositeExtensionFactory`: - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -CompositeExtensionFactory compositeExtensionFactory = new(serviceProvider => new CustomExtension()); -testApplicationBuilder.TestHost.AddTestSessionLifetimeHandle(compositeExtensionFactory); -testApplicationBuilder.TestHost.AddDataConsumer(compositeExtensionFactory); -... -``` - -The factory constructor employs the [IServiceProvider](iserviceprovider.md) to access the services provided by the testing platform. - -The testing platform will be responsible for managing the lifecycle of the composite extension. - -It's important to note that due to the testing platform's support for both *in-process* and *out-of-process* extensions, you can't combine any extension point arbitrarily. The creation and utilization of extensions are contingent on the host type, meaning you can only group *in-process* (TestHost) and *out-of-process* (TestHostController) extensions together. - -The following combinations are possible: - -* For `ITestApplicationBuilder.TestHost`, you can combine `IDataConsumer` and `ITestSessionLifetimeHandler`. -* For `ITestApplicationBuilder.TestHostControllers`, you can combine `ITestHostEnvironmentVariableProvider` and `ITestHostProcessLifetimeHandler`. diff --git a/docs/testingplatform/configuration.md b/docs/testingplatform/configuration.md deleted file mode 100644 index e970e9b890..0000000000 --- a/docs/testingplatform/configuration.md +++ /dev/null @@ -1,82 +0,0 @@ -# The `IConfiguration` - -The `IConfiguration` interface can be retrived using the [`IServiceProvider`](iserviceprovider.md) and provides access to the configuration settings for the testing framework and any extension points. By default, these configurations are loaded from: - -* Environment variables -* A JSON file named `[assemblyName].testconfig.json` located near the entry point assembly. - -**The order of precedence is maintained, which means that if a configuration is found in the environment variables, the JSON file will not be processed.** - -The interface is a straightforward key-value pair of strings: - -```cs -public interface IConfiguration -{ - string? this[string key] { get; } -} -``` - -## JSON configuration file - -The JSON file follows a hierarchical structure. To access child properties, you need to use the `:` separator. For example, consider a configuration for a potential testing framework like: - -```json -{ - "CustomTestingFramework": { - "DisableParallelism": true - } -} -``` - -The code snippet would look something like this: - -```cs -IServiceProvider serviceProvider = ...get the service provider... -IConfiguration configuration = serviceProvider.GetConfiguration(); -if (configuration["CustomTestingFramework:DisableParallelism"] == bool.TrueString) -{ - ... -} -``` - -In the case of an array, such as: - -```json -{ - "CustomTestingFramework": { - "Engine": [ - "ThreadPool", - "CustomThread" - ] - } -} -``` - -The syntax to access to the fist element ("ThreadPool") is: - -```cs -IServiceProvider serviceProvider = ...get the service provider... -IConfiguration configuration = serviceProvider.GetConfiguration(); -var fistElement = configuration["CustomTestingFramework:Engine:0"]; -``` - -## Environment variables - -The `:` separator doesn't work with environment variable hierarchical keys on all platforms. `__`, the double underscore, is: - -* Supported by all platforms. For example, the `:` separator is not supported by [Bash](https://linuxhint.com/bash-environment-variables/), but `__` is. -* Automatically replaced by a `:` - -For instance, the environment variable can be set as follows (This example is applicable for Windows): - -```bash -setx CustomTestingFramework__DisableParallelism=True -``` - -You can choose not to use the environment variable configuration source when creating the `ITestApplicationBuilder`: - -```cs -var testApplicationOptions = new TestApplicationOptions(); -testApplicationOptions.Configuration.ConfigurationSources.RegisterEnvironmentVariablesConfigurationSource = false; -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args, testApplicationOptions); -``` diff --git a/docs/testingplatform/executionorder.md b/docs/testingplatform/executionorder.md deleted file mode 100644 index 115414e530..0000000000 --- a/docs/testingplatform/executionorder.md +++ /dev/null @@ -1,21 +0,0 @@ -# Testing framework & extensions execution order - -The testing platform consists of a [testing framework](itestframework.md) and any number of extensions that can operate [*in-process*](extensionintro.md) or [*out-of-process*](extensionintro.md). This document outlines the **sequence of calls** to all potential extensibility points to provide clarity on when a feature is anticipated to be invoked. - -While a *sequence* could be used to depict this, we opt for a straightforward order of invocation calls, which allows for a more comprehensive commentary on the workflow. - -1. [ITestHostEnvironmentVariableProvider.UpdateAsync](itesthostenvironmentvariableprovider.md) : Out-of-process -1. [ITestHostEnvironmentVariableProvider.ValidateTestHostEnvironmentVariablesAsync](itesthostenvironmentvariableprovider.md) : Out-of-process -1. [ITestHostProcessLifetimeHandler.BeforeTestHostProcessStartAsync](itesthostprocesslifetimehandler.md) : Out-of-process -1. Test host process start -1. [ITestHostProcessLifetimeHandler.OnTestHostProcessStartedAsync](itesthostprocesslifetimehandler.md) : Out-of-process, this event can intertwine the actions of *in-process* extensions, depending on race conditions. -1. [ITestApplicationLifecycleCallbacks.BeforeRunAsync](itestsessionlifetimehandler.md): In-process -1. [ITestSessionLifetimeHandler.OnTestSessionStartingAsync](itestsessionlifetimehandler.md): In-process -1. [ITestFramework.CreateTestSessionAsync](itestframework.md): In-process -1. [ITestFramework.ExecuteRequestAsync](itestframework.md): In-process, this method can be called one or more times. At this point, the testing framework will transmit information to the [IMessageBus](imessagebus.md) that can be utilized by the [IDataConsumer](idataconsumer.md). -1. [ITestFramework.CloseTestSessionAsync](itestframework.md): In-process -1. [ITestSessionLifetimeHandler.OnTestSessionFinishingAsync](itestsessionlifetimehandler.md): In-process -1. [ITestApplicationLifecycleCallbacks.AfterRunAsync](itestsessionlifetimehandler.md): In-process -1. In-process cleanup, involves calling dispose and [IAsyncCleanableExtension](asyncinitcleanup.md) on all extension points. -1. [ITestHostProcessLifetimeHandler.OnTestHostProcessExitedAsync](itesthostprocesslifetimehandler.md) : Out-of-process -1. Out-of-process cleanup, involves calling dispose and [IAsyncCleanableExtension](asyncinitcleanup.md) on all extension points. diff --git a/docs/testingplatform/exitcodes.md b/docs/testingplatform/exitcodes.md deleted file mode 100644 index 3ea2d3ed58..0000000000 --- a/docs/testingplatform/exitcodes.md +++ /dev/null @@ -1,17 +0,0 @@ -# Exit codes - -[`int ITestApplication.RunAsync()`](architecture.md) exit codes: - -| Exit code | Details | -|-----|----------| -| `0` | The `0` exit code indicates success. All tests that were chosen to run ran to completion and there were no errors. | -| `1` | The `1` exit code indicates unknown errors and acts as a _catch all_. To find additional error information and details, look in the output. | -| `2` | An exit code of `2` is used to indicate that there was at least one test failure. | -| `3` | The exit code `3` indicates that the test session was aborted. A session can be aborted using Ctrl+C, as an example. | -| `4` | The exit code `4` indicates that the setup of used extensions is invalid and the tests session cannot run. | -| `5` | The exit code `5` indicates that the command line arguments passed to the test app are invalid. | -| `6` | The exit code `6` indicates that the test session is using a nonimplemented feature. | -| `7` | The exit code `7` indicates that a test session was unable to complete successfully, and likely crashed. It's possible that this was caused by a test session that was run via a test controller's extension point. | -| `8` | The exit code `8` indicates that the test session ran zero tests. | -| `9` | The exit code `9` indicates that the minimum execution policy for the executed tests was violated. | -| `10` | The exit code `10` indicates that the test adapter, Testing.Platform Test Framework, MSTest, NUnit, or xUnit, failed to run tests for an infrastructure reason unrelated to the test's self. An example is failing to create a fixture needed by tests. | diff --git a/docs/testingplatform/extensionintro.md b/docs/testingplatform/extensionintro.md deleted file mode 100644 index de02a7cf60..0000000000 --- a/docs/testingplatform/extensionintro.md +++ /dev/null @@ -1,38 +0,0 @@ -# Extensions introduction - -As outlined in the [architecture](architecture.md) section, the testing platform is designed to accommodate a variety of scenarios and extensibility points. The primary and essential extension is undoubtedly the [testing framework](itestframework.md) that our tests will utilize. Failing to register it will result in a startup error. **The [testing framework](itestframework.md) is the sole mandatory extension required to execute a testing session.** - -To support scenarios such as generating test reports, code coverage, retrying failed tests, and other potential features, we need to provide a mechanism that allows other extensions to work in conjunction with the [testing framework](itestframework.md) to deliver these features not inherently provided by the [testing framework](itestframework.md) itself. - -In essence, the [testing framework](itestframework.md) is the primary extension that supplies information about each test that makes up our test suite. It reports whether a specific test has succeeded, failed, skipped, etc., and can provide additional information about each test, such as a human-readable name (referred to as the display name), the source file, and the line where our test begins, among other things. - -The extensibility point allows us to utilize the information provided by the [testing framework](itestframework.md) to generate new artifacts or enhance existing ones with additional features. A commonly used extension is the TRX report generator, which subscribes to the [TestNodeUpdateMessage](testnodeupdatemessage.md) and generates an XML report file from it. - -As discussed in the [architecture](architecture.md), there are certain extension points that *cannot* operate within the same process as the [testing framework](itestframework.md). The reasons typically include: - -* The need to modify the *environment variables* of the *test host*. Acting within the test host process itself is *too late*. -* The requirement to *monitor* the process from the outside because the *test host*, where our tests and user code run, might have some *user code bugs* that render the process itself *unstable*, leading to potential *hangs* or *crashes*. In such cases, the extension would crash or hang along with the *test host* process. - -Due to the reasons mentioned above, the extension points are categorized into two types: - -* *In-process extensions*: These extensions operate within the same process as the [testing framework](itestframework.md). - -You can register *in-process extensions* via the `ITestApplicationBuilder.TestHost` property: - -```cs -... -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -testApplicationBuilder.TestHost.AddXXX(...); -... -``` - -* *Out-of-process extensions*: These extensions function in a separate process, allowing them to monitor the test host without being influenced by the test host itself. - -You can register *out-of-process extensions* via the `ITestApplicationBuilder.TestHostControllers`. - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -testApplicationBuilder.TestHostControllers.AddXXX(...); -``` - -Lastly, some extensions are designed to function in both scenarios. These common extensions behave identically in both *hosts*. You can register these extensions either through the *TestHost* and *TestHostController* interfaces or directly at the `ITestApplicationBuilder` level. An example of such an extension is the [ICommandLineOptionsProvider](icommandlineoptionsprovider.md). diff --git a/docs/testingplatform/ibannermessageownercapability.md b/docs/testingplatform/ibannermessageownercapability.md deleted file mode 100644 index a30cb68849..0000000000 --- a/docs/testingplatform/ibannermessageownercapability.md +++ /dev/null @@ -1,7 +0,0 @@ -# `IBannerMessageOwnerCapability` - -An optional [test framework capability](itestframeworkcapability.md) that allows the test framework to provide the banner message to the platform. If the message is null or if the capability is not present, the platform will use its default banner message. - -This capability implementation allows to abstract away the various conditions that the test framework may need to consider to decide whether or not the banner message should be displayed. - -The platform exposes the [`IPlatformInformation` service](iplatforminformation.md) to provide some information about the platform that could be useful when building your custom banner message. diff --git a/docs/testingplatform/icommandlineoptions.md b/docs/testingplatform/icommandlineoptions.md deleted file mode 100644 index bc55326caa..0000000000 --- a/docs/testingplatform/icommandlineoptions.md +++ /dev/null @@ -1,17 +0,0 @@ -# The `ICommandLineOptions` - -The `ICommandLineOptions` service is utilized to fetch details regarding the command-line options that the platform has parsed. The APIs available include: - -```cs -public interface ICommandLineOptions -{ - bool IsOptionSet(string optionName); - bool TryGetOptionArgumentList(string optionName, out string[]? arguments); -} -``` - -The `ICommandLineOptions` can be obtained through certain APIs, such as the [ICommandLineOptionsProvider](icommandlineoptionsprovider.md), or you can retrieve an instance of it from the [IServiceProvider](iserviceprovider.md) via the extension method `serviceProvider.GetCommandLineOptions()`. - -`ICommandLineOptions.IsOptionSet(string optionName)`: This method allows you to verify whether a specific option has been specified. When specifying the `optionName`, omit the `--` prefix. For example, if the user inputs `--myOption`, you should simply pass `myOption`. - -`ICommandLineOptions.TryGetOptionArgumentList(string optionName, out string[]? arguments)`: This method enables you to check whether a specific option has been set and, if so, retrieve the corresponding value or values (if the arity is more than one). Similar to the previous case, the `optionName` should be provided without the `--` prefix. diff --git a/docs/testingplatform/icommandlineoptionsprovider.md b/docs/testingplatform/icommandlineoptionsprovider.md deleted file mode 100644 index c6dda5b564..0000000000 --- a/docs/testingplatform/icommandlineoptionsprovider.md +++ /dev/null @@ -1,122 +0,0 @@ -# The `ICommandLineOptionsProvider` interface - -As discussed in the [architecture](architecture.md) section, the initial step involves creating the `ITestApplicationBuilder` to register the testing framework and extensions with it. - -```cs -... -var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -``` - -The `CreateBuilderAsync` method accepts an array of strings (`string[]`) named `args`. These arguments can be used to pass command-line options to all components of the testing platform (including built-in components, testing frameworks, and extensions), allowing for customization of their behavior. - -Typically, the arguments passed are those received in the standard `Main(string[] args)` method. However, if the hosting environment differs, any list of arguments can be supplied. - -Arguments **must be prefixed** with a double dash `--`. For example, `--filter`. - -If a component such as a testing framework or an extension point wishes to offer custom command-line options, it can do so by implementing the `ICommandLineOptionsProvider` interface. This implementation can then be registered with the `ITestApplicationBuilder` via the registration factory of the `CommandLine` property, as shown: - -```cs -... -testApplicationBuilder.CommandLine.AddProvider(() => new CustomCommandLineOptions()); -... -``` - -In the example provided, `CustomCommandLineOptions` is an implementation of the `ICommandLineOptionsProvider` interface, This interface comprises the following members and data types: - -```cs -public interface ICommandLineOptionsProvider : IExtension -{ - IReadOnlyCollection GetCommandLineOptions(); - Task ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments); - Task ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions); -} - -public sealed class CommandLineOption -{ - public CommandLineOption(string name, string description, ArgumentArity arity, bool isHidden) - public string Name { get; } - public string Description { get; } - public ArgumentArity Arity { get; } - public bool IsHidden { get; } -} - -public interface ICommandLineOptions -{ - bool IsOptionSet(string optionName); - bool TryGetOptionArgumentList(string optionName, out string[]? arguments); -} -``` - -As observed, the `ICommandLineOptionsProvider` extends the [`IExtension`](iextension.md) interface. Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. - -The order of execution of the `ICommandLineOptionsProvider` is: - -```mermaid -sequenceDiagram - Testing platform->>ICommandLineOptionsProvider: GetCommandLineOptions() - ICommandLineOptionsProvider-->>Testing platform: returns the list of `CommandLineOption` - Testing platform->>ICommandLineOptionsProvider: ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments) validate every option argument - ICommandLineOptionsProvider-->>Testing platform: returns a `ValidationResult` indicating success or failure. - Testing platform->>ICommandLineOptionsProvider: ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions) Ensure the consistency of all arguments in unison. - ICommandLineOptionsProvider-->>Testing platform: returns a `ValidationResult` indicating success or failure. -``` - -Let's examine the apis and their mean: - -`ICommandLineOptionsProvider.GetCommandLineOptions()`: This method is utilized to retrieve all the options offered by the component. Each `CommandLineOption` requires the following properties to be specified: - -`string name`: This is the option's name, presented without a dash. For example, *filter* would be used as `--filter` by users. - -`string description`: This is a description of the option. It will be displayed when users pass `--help` as an argument to the application builder. - -`ArgumentArity arity`: The arity of an option is the number of values that can be passed if that option or command is specified. Current available arities are: - -* `Zero`: Represents an argument arity of zero. -* `ZeroOrOne`: Represents an argument arity of zero or one. -* `ZeroOrMore`: Represents an argument arity of zero or more. -* `OneOrMore`: Represents an argument arity of one or more. -* `ExactlyOne`: Represents an argument arity of exactly one. - -For examples, refer to the [System.CommandLine arity table](https://learn.microsoft.com/en-us/dotnet/standard/commandline/syntax#argument-arity). - -`bool isHidden`: This property signifies that the option is available for use but will not be displayed in the description when `--help` is invoked. - -`ICommandLineOptionsProvider.ValidateOptionArgumentsAsync`: This method is employed to *validate* the argument provided by the user. - -For instance, if we have a parameter named `--dop` that represents the degree of parallelism for our custom testing framework, a user might input `--dop 0`. In this scenario, the value 0 would be invalid because we anticipate a degree of parallelism of 1 or more. By using `ValidateOptionArgumentsAsync`, we can perform upfront validation and return an error message if necessary. -A possible implementation for the sample above could be: - -```cs - public Task ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments) - { - if (commandOption.Name == "dop") - { - if (!int.TryParse(arguments[0], out int dopValue) || dopValue <= 0) - { - return ValidationResult.InvalidTask("--dop must be a positive integer"); - } - } - - return ValidationResult.ValidTask; - } -``` - -`ICommandLineOptionsProvider.ValidateCommandLineOptionsAsync`: This method is called as last one and allows to do global coherency check. - -For example, let's say our testing framework has the capability to generate a test result report and save it to a file. This feature is accessed using the `--generatereport` option, and the filename is specified with `--reportfilename myfile.rep`. In this scenario, if a user only provides the `--generatereport` option without specifying a filename, the validation should fail because the report cannot be generated without a filename. -A possible implementation for the sample above could be: - -```cs - public Task ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions) - { - bool generateReportEnabled = commandLineOptions.IsOptionSet(GenerateReportOption); - bool reportFileName = commandLineOptions.TryGetOptionArgumentList(ReportFilenameOption, out string[]? _); - - return (generateReportEnabled || reportFileName) && !(generateReportEnabled && reportFileName) - ? ValidationResult.InvalidTask("Both `--generatereport` and `--reportfilename` need to be provided simultaneously.") - : ValidationResult.ValidTask; - } -``` - -Please note that the `ValidateCommandLineOptionsAsync` method provides the [`ICommandLineOptions`](icommandlineoptions.md) service, which is used to fetch the argument information parsed by the platform itself. diff --git a/docs/testingplatform/idataconsumer.md b/docs/testingplatform/idataconsumer.md deleted file mode 100644 index b2926f4963..0000000000 --- a/docs/testingplatform/idataconsumer.md +++ /dev/null @@ -1,98 +0,0 @@ -# The `IDataConsumer` - -The `IDataConsumer` is an *in-process* extension capable of subscribing to and receiving `IData` information that is pushed to the [IMessageBus](imessagebus.md) by the [testing framework](itestframework.md) and its extensions. - -*This extension point is crucial as it enables developers to gather and process all the information generated during a test session.* - -To register a custom `IDataConsumer`, utilize the following api: - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -testApplicationBuilder.TestHost.AddDataConsumer(serviceProvider - => new CustomDataConsumer()); -... -``` - -The factory utilizes the [IServiceProvider](iserviceprovider.md) to gain access to the suite of services offered by the testing platform. - -> [!IMPORTANT] -> The sequence of registration is significant, as the APIs are called in the order they were registered. - -The `IDataConsumer` interface includes the following methods: - -```cs -public interface IDataConsumer : ITestHostExtension -{ - Type[] DataTypesConsumed { get; } - Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken); -} - -public interface IData -{ - string DisplayName { get; } - string? Description { get; } -} -``` - -The `IDataConsumer` is a type of `ITestHostExtension`, which serves as a base for all *test host* extensions. Like all other extension points, it also inherits from [IExtension](iextension.md). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. - -`DataTypesConsumed`: This property returns a list of `Type` that this extension plans to consume. It corresponds to `IDataProducer.DataTypesProduced`. Notably, an `IDataConsumer` can subscribe to multiple types originating from different `IDataProducer` instances without any issues. - -`ConsumeAsync`: This method is triggered whenever data of a type to which the current consumer is subscribed is pushed onto the [`IMessageBus`](imessagebus.md). It receives the `IDataProducer` to provide details about the data payload's producer, as well as the `IData` payload itself. As you can see, `IData` is a generic placeholder interface that contains general informative data. The ability to push different types of `IData` implies that the consumer needs to *switch* on the type itself to cast it to the correct type and access the specific information. - -A sample implementation of a consumer that wants to elaborate the [`TestNodeUpdateMessage`](testnodeupdatemessage.md) produced by a [testing framework](itestframework.md) could be: - -```cs -internal class CustomDataConsumer : IDataConsumer, IOutputDeviceDataProducer -{ - public Type[] DataTypesConsumed => new[] { typeof(TestNodeUpdateMessage) }; - ... - public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) - { - var testNodeUpdateMessage = (TestNodeUpdateMessage)value; - - TestNodeStateProperty nodeState = testNodeUpdateMessage.TestNode.Properties.Single(); - - switch (nodeState) - { - case InProgressTestNodeStateProperty _: - { - ... - break; - } - case PassedTestNodeStateProperty _: - { - ... - break; - } - case FailedTestNodeStateProperty failedTestNodeStateProperty: - { - ... - break; - } - case SkippedTestNodeStateProperty _: - { - ... - break; - } - ... - } - - return Task.CompletedTask; - } -... -} -``` - -Finally, the api takes a `CancellationToken` which the extension is expected to honor. - -> [!IMPORTANT] -> It's crucial to process the payload directly within the `ConsumeAsync` method. The [IMessageBus](imessagebus.md) can manage both synchronous and asynchronous processing, coordinating the execution with the [testing framework](itestframework.md). Although the consumption process is entirely asynchronous and doesn't block the [IMessageBus.Push](imessagebus.md) at the time of writing, this is an implementation detail that may change in the future due to feature requirements. However, we aim to maintain this interface's simplicity and ensure that this method is always called once, eliminating the need for complex synchronization. Additionally, we automatically manage the scalability of the consumers. - - - -> [!WARNING] -> When using `IDataConsumer` in conjunction with [ITestHostProcessLifetimeHandler](itestsessionlifetimehandler.md) within a [composite extension point](compositeextensionfactory.md), **it's crucial to disregard any data received post the execution of [ITestSessionLifetimeHandler.OnTestSessionFinishingAsync](itestsessionlifetimehandler.md)**. The `OnTestSessionFinishingAsync` is the final opportunity to process accumulated data and transmit new information to the [IMessageBus](imessagebus.md), hence, any data consumed beyond this point will not be *utilizable* by the extension. - -If your extension requires intensive initialization and you need to use the async/await pattern, you can refer to the [`Async extension initialization and cleanup`](asyncinitcleanup.md). If you need to *share state* between extension points, you can refer to the [`CompositeExtensionFactory`](compositeextensionfactory.md) section. diff --git a/docs/testingplatform/iextension.md b/docs/testingplatform/iextension.md deleted file mode 100644 index 66750504d4..0000000000 --- a/docs/testingplatform/iextension.md +++ /dev/null @@ -1,27 +0,0 @@ -# The `IExtension` interface - -The `IExtension` interface serves as the foundational interface for all extensibility points within the testing platform. It is primarily used to obtain descriptive information about the extension and, most importantly, to enable or disable the extension itself. - -Let's delve into the specifics: - -```cs -public interface IExtension -{ - string Uid { get; } - string Version { get; } - string DisplayName { get; } - string Description { get; - Task IsEnabledAsync(); -} -``` - -`Uid`: This is the unique identifier for the extension. It's crucial to choose a unique value for this string to avoid conflicts with other extensions. - -`Version`: This represents the version of the interface. It MUST use [**semantic versioning**](https://semver.org/). - -`DisplayName`: This is a user-friendly name that will appear in logs and when you request information using the `--info` command line option. - -`Description`: The description of the extension, will appear when you request information using the `--info` command line option. - -`IsEnabledAsync()`: This method is invoked by the testing platform when the extension is being instantiated. If the method returns false, the extension will be excluded. -This method typically makes decisions based on the [configuration file](configuration.md) file or some [custom command line options](icommandlineoptions.md). Users often specify `--customExtensionOption` in the command line to opt into the extension itself. diff --git a/docs/testingplatform/iloggerfactory.md b/docs/testingplatform/iloggerfactory.md deleted file mode 100644 index 99d630b615..0000000000 --- a/docs/testingplatform/iloggerfactory.md +++ /dev/null @@ -1,94 +0,0 @@ -# The logging system - -The testing platform comes with an integrated logging system that generates a log file. You can view the logging options by running the `--help` command. -The options you can choose from include: - -```dotnetcli ---diagnostic Enable the diagnostic logging. The default log level is 'Trace'. The file will be written in the output directory with the name log_[MMddHHssfff].diag ---diagnostic-filelogger-synchronouswrite Force the built-in file logger to write the log synchronously. Useful for scenario where you don't want to lose any log (i.e. in case of crash). Note that this is slowing down the test execution. ---diagnostic-output-directory Output directory of the diagnostic logging, if not specified the file will be generated inside the default 'TestResults' directory. ---diagnostic-output-fileprefix Prefix for the log file name that will replace '[log]_.' ---diagnostic-verbosity Define the level of the verbosity for the --diagnostic. The available values are 'Trace', 'Debug', 'Information', 'Warning', 'Error', and 'Critical' -``` - -From a coding standpoint, to log information, you need to obtain the `ILoggerFactory` from the [`IServiceProvider`](iserviceprovider.md). -The `ILoggerFactory` API is as follows: - -```cs -public interface ILoggerFactory -{ - ILogger CreateLogger(string categoryName); -} - -public static class LoggerFactoryExtensions -{ - public static ILogger CreateLogger(this ILoggerFactory factory); -} -``` - -The logger factory allows you to create an `ILogger` object using the `CreateLogger` API. There's also a convenient API that accepts a generic argument, which will be used as the category name. - -```cs -public interface ILogger -{ - Task LogAsync(LogLevel logLevel, TState state, Exception? exception, Func formatter); - void Log(LogLevel logLevel, TState state, Exception? exception, Func formatter); - bool IsEnabled(LogLevel logLevel); -} - -public interface ILogger : ILogger -{ -} - -public static class LoggingExtensions -{ - public static Task LogTraceAsync(this ILogger logger, string message); - public static Task LogDebugAsync(this ILogger logger, string message); - public static Task LogInformationAsync(this ILogger logger, string message); - public static Task LogWarningAsync(this ILogger logger, string message); - public static Task LogErrorAsync(this ILogger logger, string message); - public static Task LogErrorAsync(this ILogger logger, string message, Exception ex); - public static Task LogErrorAsync(this ILogger logger, Exception ex); - public static Task LogCriticalAsync(this ILogger logger, string message); - public static void LogTrace(this ILogger logger, string message); - public static void LogDebug(this ILogger logger, string message); - public static void LogInformation(this ILogger logger, string message); - public static void LogWarning(this ILogger logger, string message); - public static void LogError(this ILogger logger, string message); - public static void LogError(this ILogger logger, string message, Exception ex); - public static void LogError(this ILogger logger, Exception ex); - public static void LogCritical(this ILogger logger, string message); -} -``` - -The `ILogger` object, which is created by the `ILoggerFactory`, offers APIs for logging information at various levels. These logging levels include: - -```cs -public enum LogLevel -{ - Trace, - Debug, - Information, - Warning, - Error, - Critical, - None -} -``` - -Here's an example of how you might use the logging API: - -```cs -... -IServiceProvider serviceProvider = ...get the service provider... -ILoggerFactory loggerFactory = serviceProvider.GetLoggerFactory(); -ILogger logger = loggerFactory.CreateLogger(); -... -if (_logger.IsEnabled(LogLevel.Information)) -{ - await _logger.LogInformationAsync($"Executing request of type '{context.Request}'"); -} -... -``` - -Keep in mind that to prevent unnecessary allocation, you should check if the level is *enabled* using the `ILogger.IsEnabled(LogLevel)` API. diff --git a/docs/testingplatform/imessagebus.md b/docs/testingplatform/imessagebus.md deleted file mode 100644 index 33d1428b35..0000000000 --- a/docs/testingplatform/imessagebus.md +++ /dev/null @@ -1,43 +0,0 @@ -# The `IMessageBus` - -The message bus service is the central mechanism that facilitates information exchange between the test framework and its extensions. - -The message bus of the testing platform employs the publish-subscribe pattern, as described here: . - -The overarching structure of the shared bus is as follows: - -![bus](bus.png) - -As illustrated in the diagram, which includes an extensions and a test framework, there are two potential actions: pushing information to the bus or consuming information from the bus. - -The `IMessageBus` satisfied the *pushing action* to the bus and the api is: - -```cs -public interface IMessageBus -{ - Task PublishAsync(IDataProducer dataProducer, IData data); -} - -public interface IDataProducer : IExtension -{ - Type[] DataTypesProduced { get; } -} - -public interface IData -{ - string DisplayName { get; } - string? Description { get; } -} -``` - -Let's discuss the parameters: - -* `IDataProducer`: The `IDataProducer` communicates to the message bus the `Type` of information it can supply and establishes ownership through inheritance from the base interface [IExtension](iextension.md). This implies that you can't indiscriminately push data to the message bus; you must declare the data type produced in advance. If you push unexpected data, an exception will be triggered. - -* `IData`: This interface serves as a placeholder where you only need to provide descriptive details such as the name and a description. The interface doesn't reveal much about the data's nature, which is intentional. It implies that the test framework and extensions can push any type of data to the bus, and this data can be consumed by any registered extension or the test framework itself. -This approach facilitates the evolution of the information exchange process, preventing breaking changes when an extension is unfamiliar with new data. **It allows different versions of extensions and the test framework to operate in harmony, based on their mutual understanding**. - -The opposite end of the bus is what we refer to as a [consumer](idataconsumer.md), which is subscribed to a specific type of data and can thus consume it. - -> [!IMPORTANT] -> Always use *await* the call to `PublishAsync`. If you don't, the `IData` might not be processed correctly by the testing platform and extensions, which could lead to subtle bugs. It's only after you've returned from the *await* that you can be assured that the `IData` has been queued for processing on the message bus. Regardless of the extension point you're working on, ensure that you've awaited all `PublishAsync` calls before exiting the extension. For example, if you're implementing the [`testing framework`](itestframework.md), you should not call `Complete` on the [requests](irequest.md) until you've awaited all `PublishAsync` calls for that specific request. diff --git a/docs/testingplatform/ioutputdevice.md b/docs/testingplatform/ioutputdevice.md deleted file mode 100644 index 1028fe7089..0000000000 --- a/docs/testingplatform/ioutputdevice.md +++ /dev/null @@ -1,61 +0,0 @@ -# The `IOutputDevice` - -The testing platform encapsulates the idea of an *output device*, allowing the testing framework and extensions to *present* information by transmitting any kind of data to the currently utilized display system. - -The most traditional example of an *output device* is the console output. - -> [!NOTE] -> While the testing platform is engineered to support custom *output devices*, currently, this extension point is not available. - -To transmit data to the *output device*, you must obtain the `IOutputDevice` from the [`IServiceProvider`](iserviceprovider.md). -The API consists of: - -```cs -public interface IOutputDevice -{ - Task DisplayAsync(IOutputDeviceDataProducer producer, IOutputDeviceData data); -} - -public interface IOutputDeviceDataProducer : IExtension -{ -} - -public interface IOutputDeviceData -{ -} -``` - -The `IOutputDeviceDataProducer` extends the [`IExtension`](iextension.md) and provides information about the sender to the *output device*. -The `IOutputDeviceData` serves as a placeholder interface. The concept behind `IOutputDevice` is to accommodate more intricate information than just colored text. For instance, it could be a complex object that can be graphically represented. - -The testing platform, by default, offers a traditional colored text model for the `IOutputDeviceData` object: - -```cs -public class TextOutputDeviceData : IOutputDeviceData -{ - public TextOutputDeviceData(string text) - public string Text { get; } -} - -public sealed class FormattedTextOutputDeviceData : TextOutputDeviceData -{ - public FormattedTextOutputDeviceData(string text) - public IColor? ForegroundColor { get; init; } - public IColor? BackgroundColor { get; init; } -} - -public sealed class SystemConsoleColor : IColor -{ - public ConsoleColor ConsoleColor { get; init; } -} -``` - -Here's an example of how you might use the colored text with the *active* output device: - -```cs -IServiceProvider serviceProvider = ...get the service provider... -IOutputDevice outputDevice = serviceProvider.GetOutputDevice(); -await outputDevice.DisplayAsync(this, new FormattedTextOutputDeviceData($"TestingFramework version '{Version}' running tests with parallelism of {_dopValue}") { ForegroundColor = new SystemConsoleColor() { ConsoleColor = ConsoleColor.Green } }); -``` - -Beyond the standard use of colored text, the main advantage of `IOutputDevice` and `IOutputDeviceData` is that the *output device* is entirely independent and unknown to the user. This allows for the development of complex user interfaces. For example, it's entirely feasible to implement a *real-time* web application that displays the progress of tests. diff --git a/docs/testingplatform/iplatforminformation.md b/docs/testingplatform/iplatforminformation.md deleted file mode 100644 index bb3510fad2..0000000000 --- a/docs/testingplatform/iplatforminformation.md +++ /dev/null @@ -1,3 +0,0 @@ -# `IPlatformInformation` - -Provides information about the platform such as: name, version, commit hash and build date. diff --git a/docs/testingplatform/irequest.md b/docs/testingplatform/irequest.md deleted file mode 100644 index 43e71bd192..0000000000 --- a/docs/testingplatform/irequest.md +++ /dev/null @@ -1,201 +0,0 @@ -# Available requests - -The subsequent section provides a detailed description of the various requests that a test framework may receive and process. - -Before proceeding to the next section, it's crucial to thoroughly comprehend the concept of the [IMessageBus](imessagebus.md), which is the essential service for conveying test execution information to the testing platform. - -## The `TestNodeUpdateMessage` data payload - -As mentioned in the [IMessageBus](imessagebus.md) section, before utilizing the message bus, you must specify the type of data you intend to supply. The testing platform has defined a well-known type, `TestNodeUpdateMessage`, to represent the concept of a *test update information*. -This part of the document will explain how to utilize this payload data. Let's examine the surface: - -```cs -public sealed class TestNodeUpdateMessage(SessionUid sessionUid, TestNode testNode, TestNodeUid? parentTestNodeUid = null) -{ - public TestNode TestNode { get; } - public TestNodeUid? ParentTestNodeUid { get; } -} - -public class TestNode -{ - public required TestNodeUid Uid { get; init; } - public required string DisplayName { get; init; } - public PropertyBag Properties { get; init; } = new(); -} - -public sealed class TestNodeUid(string value) - -public sealed partial class PropertyBag -{ - public PropertyBag(); - public PropertyBag(params IProperty[] properties); - public PropertyBag(IEnumerable properties); - public int Count { get; } - public void Add(IProperty property); - public bool Any(); - public TProperty? SingleOrDefault(); - public TProperty Single(); - public TProperty[] OfType(); - public IEnumerable AsEnumerable(); - public IEnumerator GetEnumerator(); - ... -} - -public interface IProperty -{ -} -``` - -* `TestNodeUpdateMessage`: The `TestNodeUpdateMessage` consists of two properties: a `TestNode`, which we will discuss in this section, and a `ParentTestNodeUid`. The `ParentTestNodeUid` indicates that a test may have a parent test, introducing the concept of a **test tree** where `TestNode`s can be arranged in relation to each other. This structure allows for future enhancements and features based on the *tree* relationship between the nodes. If your test framework doesn't require a test tree structure, you can opt not to use it and simply set it to null, resulting in a straightforward flat list of `TestNode`s. - -* `TestNode`: The `TestNode` is composed of three properties, one of which is the `Uid` of type `TestNodeUid`. This `Uid` serves as the **UNIQUE STABLE ID** for the node. The term **UNIQUE STABLE ID** implies that the same `TestNode` should maintain an **IDENTICAL** `Uid` across different runs and operating systems. The `TestNodeUid` is an **arbitrary opaque string** that the testing platform accepts as is. - -> [!IMPORTANT] -> The stability and uniqueness of the ID are crucial in the testing domain. They enable the precise targeting of a single test for execution and allow the ID to serve as a persistent identifier for a test, facilitating powerful extensions and features. - -The second property is `DisplayName`, which is the human-friendly name for the test. For example, this name is displayed when you execute the `--list-tests` command line. - -The third attribute is `Properties`, which is a `PropertyBag` type. As demonstrated in the code, this is a specialized property bag that holds generic properties about the `TestNodeUpdateMessage`. This implies that you can append any property to the node that implements the placeholder interface `IProperty`. - -***The testing platform identifies specific properties added to a `TestNode.Properties` to determine whether a test has passed, failed, or been skipped.*** - -You can find the current list of available properties with the relative description in the section [TestNodeUpdateMessage.TestNode](testnodeupdatemessage.md) - -The `PropertyBag` type is typically accessible in every `IData` and is utilized to store miscellaneous properties that can be queried by the platform and extensions. This mechanism allows us to enhance the platform with new information without introducing breaking changes. If a component recognizes the property, it can query it; otherwise, it will disregard it. - -Finally this section makes clear that you test framework implementaion needs to implement the `IDataProducer` that produces `TestNodeUpdateMessage`s like in the sample below: - -```cs -internal sealed class TestingFramework : ITestFramework, IDataProducer -{ - ... - public Type[] DataTypesProduced => new[] { typeof(TestNodeUpdateMessage) }; - ... -} -``` - -If your test adapter requires the publication of *files* during execution, you can find the recognized properties in this source file: . As you can see, you can provide file assets in a general manner or associate them with a specific `TestNode`. Remember, if you intend to push a `SessionFileArtifact`, you must declare it to the platform in advance, as shown below: - -```cs -internal sealed class TestingFramework : ITestFramework, IDataProducer -{ - ... - public Type[] DataTypesProduced => new[] { typeof(TestNodeUpdateMessage), typeof(SessionFileArtifact) }; - ... -} -``` - -## DiscoverTestExecutionRequest - -```cs -public class DiscoverTestExecutionRequest -{ - // Detailed in the custom section below - public TestSessionContext Session { get; } - - // This is experimental and intended for future use, please disregard for now. - public ITestExecutionFilter Filter { get; } -} -``` - -The `DiscoverTestExecutionRequest` instructs the test framework **to discover** the tests and communicate this information thought to the [IMessageBus](imessagebus.md). - -As outlined in the previous section, the property for a discovered test is `DiscoveredTestNodeStateProperty`. Here is a generic code snippet for reference: - -```cs -... -var testNode = new TestNode() -{ - Uid = GenerateUniqueStableId(), - DisplayName = GetDisplayName(), - Properties = new PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance), -}; - -await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(discoverTestExecutionRequest.Session.SessionUid, testNode)); -... -``` - -You can visit the [code sample](codesample.md) for a working discovery sample. - -## RunTestExecutionRequest - -```cs -public class RunTestExecutionRequest -{ - // Detailed in the custom section below - public TestSessionContext Session { get; } - - // This is experimental and intended for future use, please disregard for now. - public ITestExecutionFilter Filter { get; } -} -``` - -The `RunTestExecutionRequest` instructs the test framework **to execute** the tests and communicate this information thought to the [IMessageBus](imessagebus.md). - -Here is a generic code snippet for reference: - -```cs -... -var skippedTestNode = new TestNode() -{ - Uid = GenerateUniqueStableId(), - DisplayName = GetDisplayName(), - Properties = new PropertyBag(SkippedTestNodeStateProperty.CachedInstance), -}; - -await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, skippedTestNode)); -... -var successfulTestNode = new TestNode() -{ - Uid = GenerateUniqueStableId(), - DisplayName = GetDisplayName(), - Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance), -}; - -await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, successfulTestNode)); -... -var assertionFailedTestNode = new TestNode() -{ - Uid = GenerateUniqueStableId(), - DisplayName = GetDisplayName(), - Properties = new PropertyBag(new FailedTestNodeStateProperty(assertionException)), -}; - -await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, assertionFailedTestNode)); -... -var failedTestNode = new TestNode() -{ - Uid = GenerateUniqueStableId(), - DisplayName = GetDisplayName(), - Properties = new PropertyBag(new ErrorTestNodeStateProperty(ex.InnerException!)), -}; - -await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, failedTestNode)); -``` - -You can visit the [code sample](codesample.md) for a working execution sample. - -### TestSessionContext - -The `TestSessionContext` is a shared property across all requests, providing information about the ongoing test session: - -```cs -public class TestSessionContext -{ - public SessionUid SessionUid { get; } - public ClientInfo Client { get; } -} - -public readonly struct SessionUid(string value) -{ - public string Value { get; } -} - -public sealed class ClientInfo -{ - public string Id { get; } - public string Version { get; } -} -``` - -The `TestSessionContext` consists of the `SessionUid`, a unique identifier for the ongoing test session that aids in logging and correlating test session data. It also includes the `ClientInfo` type, which provides details about the *initiator* of the test session. The test framework may choose different routes or publish varying information based on the identity of the test session's *initiator*. diff --git a/docs/testingplatform/iserviceprovider.md b/docs/testingplatform/iserviceprovider.md deleted file mode 100644 index bb2f89a174..0000000000 --- a/docs/testingplatform/iserviceprovider.md +++ /dev/null @@ -1,43 +0,0 @@ -# The `IServiceProvider` - -The testing platform offers valuable services to both the testing framework and extension points. These services cater to common needs such as accessing the configuration, parsing and retrieving command-line arguments, obtaining the logging factory, and accessing the logging system, among others. `IServiceProvider` implements the [service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern) for the testing platform. - -The `IServiceProvider` is derived directly from the base class library. - -```cs -namespace System -{ - public interface IServiceProvider - { - object? GetService(Type serviceType); - } -} -``` - -The testing platform offers handy extension methods to access well-known service objects. All these methods are housed in a static class within the `Microsoft.Testing.Platform.Services` namespace. - -```cs -public static class ServiceProviderExtensions -{ - public static TService GetRequiredService(this IServiceProvider provider) - public static TService? GetService(this IServiceProvider provider) - public static IMessageBus GetMessageBus(this IServiceProvider serviceProvider) - public static IConfiguration GetConfiguration(this IServiceProvider serviceProvider) - public static ICommandLineOptions GetCommandLineOptions(this IServiceProvider serviceProvider) - public static ILoggerFactory GetLoggerFactory(this IServiceProvider serviceProvider) - public static IOutputDevice GetOutputDevice(this IServiceProvider serviceProvider) - ...and more -} -``` - -Most of the registration factories exposed by extension points, which can be registered using the `ITestApplicationBuilder` during the setup of the testing application, provide access to the `IServiceProvider`. - -For example, we encountered it earlier when discussing [registering the testing framework](itestframework.md). - -```cs -ITestApplicationBuilder RegisterTestFramework( - Func capabilitiesFactory, - Func adapterFactory); -``` - -As observed above, both the `capabilitiesFactory` and the `adapterFactory` supply the `IServiceProvider` as a parameter. diff --git a/docs/testingplatform/itestapplicationlifecyclecallbacks.md b/docs/testingplatform/itestapplicationlifecyclecallbacks.md deleted file mode 100644 index 31117d61aa..0000000000 --- a/docs/testingplatform/itestapplicationlifecyclecallbacks.md +++ /dev/null @@ -1,42 +0,0 @@ -# The `ITestApplicationLifecycleCallbacks` - -The `ITestApplicationLifecycleCallbacks` is an *in-process* extension that enables the execution of code before everything, it's like to have access to the first line of the ipotetical *main* of the *test host*. - -To register a custom `ITestApplicationLifecycleCallbacks`, utilize the following api: - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -testApplicationBuilder.TestHost.AddTestApplicationLifecycleCallbacks(serviceProvider - => new CustomTestApplicationLifecycleCallbacks()); -... -``` - -The factory utilizes the [IServiceProvider](iserviceprovider.md) to gain access to the suite of services offered by the testing platform. - -> [!IMPORTANT] -> The sequence of registration is significant, as the APIs are called in the order they were registered. - -The `ITestApplicationLifecycleCallbacks` interface includes the following methods: - -```cs -public interface ITestApplicationLifecycleCallbacks : ITestHostExtension -{ - Task BeforeRunAsync(CancellationToken cancellationToken); - Task AfterRunAsync(int exitCode, CancellationToken cancellation); -} - -public interface ITestHostExtension : IExtension -{ -} -``` - -The `ITestApplicationLifecycleCallbacks` is a type of `ITestHostExtension`, which serves as a base for all *test host* extensions. Like all other extension points, it also inherits from [IExtension](iextension.md). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. - -`BeforeRunAsync`: This method serves as the initial point of contact for the *test host* and is the first opportunity for an *in-process* extension to execute a feature. It's typically used to establish a connection with any corresponding *out-of-process* extensions if a feature is designed to operate across both environments. - -*For example, the built-in hang dump feature is composed of both *in-process* and *out-of-process* extensions, and this method is used to exchange information with the *out-of-process* component of the extension.* - -`AfterRunAsync`: This method is the final call before exiting the [`int ITestApplication.RunAsync()`](architecture.md) and it provides the [`exit code`](exitcodes.md). It should be used solely for cleanup tasks and to notify any corresponding *out-of-process* extension that the *test host* is about to terminate. - -Finally, both APIs take a `CancellationToken` which the extension is expected to honor. diff --git a/docs/testingplatform/itestframework.md b/docs/testingplatform/itestframework.md deleted file mode 100644 index ae0b1e9b2a..0000000000 --- a/docs/testingplatform/itestframework.md +++ /dev/null @@ -1,121 +0,0 @@ -# The `Microsoft.Testing.Platform.Extensions.TestFramework.ITestFramework` - -The `Microsoft.Testing.Platform.Extensions.TestFramework.ITestFramework` is implemented by extensions that provide a test framework: - -```cs -public interface ITestFramework : IExtension -{ - Task CreateTestSessionAsync(CreateTestSessionContext context); - Task ExecuteRequestAsync(ExecuteRequestContext context); - Task CloseTestSessionAsync(CloseTestSessionContext context); -} -``` - -The `ITestFramework` interface inherits from the [IExtension](iextension.md) interface, which is an interface that all extension points inherit from. `IExtension` is used to retrieve the name and description of the extension. The `IExtension` also provides a way to dynamically enable or disable the extension in setup, through `Task IsEnabledAsync()`, please make sure that you return `true` from this method if you have no special needs. - -## CreateTestSessionAsync - -The `CreateTestSessionAsync` is called at the start of the test session and is used to initialize the test framework. As we can see the api accepts a `CloseTestSessionContext` object and returns a `CloseTestSessionResult`. - -```cs -public sealed class CreateTestSessionContext : TestSessionContext -{ - public SessionUid SessionUid { get; } - public ClientInfo Client { get; } - public CancellationToken CancellationToken { get; } -} - -public readonly struct SessionUid -{ - public string Value { get; } -} - -public sealed class ClientInfo -{ - public string Id { get; } - public string Version { get; } -} -``` - -The `SessionUid` serves as the unique identifier for the current test session, providing a logical connection to the session's results. -The `ClientInfo` provides details about the entity invoking the test framework. This information can be utilized by the test framework to modify its behavior. For example, as of the time this document was written, a console execution would report a client name such as "testingplatform-console". -The `CancellationToken` is used to halt the execution of `CreateTestSessionAsync`. - -The return object is a `CloseTestSessionResult`: - -```cs -public sealed class CreateTestSessionResult -{ - public string? WarningMessage { get; set; } - public string? ErrorMessage { get; set; } - public bool IsSuccess { get; set; } -} -``` - -The `IsSuccess` can be used to indicate whether the session creation was successful. If it returns false, the test execution will be halted. - -## CloseTestSessionAsync - -The `CloseTestSessionAsync` mirrors the `CreateTestSessionAsync` in functionality, with the only difference being the object names. For more details, refer to the `CreateTestSessionAsync` section. - -## ExecuteRequestAsync - -The `ExecuteRequestAsync` accepts an object of type `ExecuteRequestContext`. This object, as suggested by its name, holds the specifics about the action that the test framework is expected to perform. -The `ExecuteRequestContext` definition is: - -```cs -public sealed class ExecuteRequestContext -{ - public IRequest Request { get; } - public IMessageBus MessageBus { get; } - public CancellationToken CancellationToken { get; } - public void Complete(); -} -``` - -`IRequest`: This is the base interface for any type of request. We should think about the test framework as an **in-process statefull server** where the lifecycle is: - -```mermaid -sequenceDiagram - Testing platform->>ITestFramework: adapterFactory() from 'RegisterTestFramework' - ITestFramework-->>Testing platform: - Testing platform->>ITestFramework: CreateTestSessionAsync(CreateTestSessionContext) - ITestFramework-->>Testing platform: CreateTestSessionResult - Testing platform->>ITestFramework: ExecuteRequestAsync(ExecuteRequestContext_1) - Testing platform->>ITestFramework: ExecuteRequestAsync(ExecuteRequestContext_2) - ITestFramework->>IMessageBus: PublishAsync() for ExecuteRequestContext_1 - Testing platform->>ITestFramework: ExecuteRequestAsync(ExecuteRequestContext_3) - ITestFramework->>IMessageBus: PublishAsync() for ExecuteRequestContext_3 - ITestFramework->>IMessageBus: PublishAsync() for ExecuteRequestContext_2 - ITestFramework->>IMessageBus: PublishAsync() for ExecuteRequestContext_2 - ITestFramework->>IMessageBus: PublishAsync() for ... - ITestFramework->>ExecuteRequestContext_1: Complete() - ITestFramework->>ExecuteRequestContext_3: Complete() - ITestFramework->>ExecuteRequestContext_2: Complete() - Testing platform->>ITestFramework: CloseTestSessionAsync(CloseTestSessionContext) - ITestFramework-->>Testing platform: CloseTestSessionResult -``` - -The diagram above illustrates that the testing platform issues 3 requests after creating the test framework instance. The test framework processes these requests and utilizes the `IMessageBus` service, which is included in the request itself, to deliver the result for each specific request. Once a particular request has been handled, the test framework invokes the `Complete()` method on it, indicating to the testing platform that the request has been fulfilled. -The testing platform monitors all dispatched requests. Once all requests have been fulfilled, it invokes `CloseTestSessionAsync` and disposes of the instance (if `IDisposable/IAsyncDisposable` is implemented). -It's evident that the requests and their completions can overlap, enabling concurrent and asynchronous execution of requests. -> [!NOTE] -> Currently, the testing platform does not send overlapping requests and waits for the completion of a request >> before sending the next one. However, this behavior may change in the future. The support for concurrent requests will be determined through the [capabilities](capabilities.md) system. - -The `IRequest` implementation specifies the precise request that needs to be fulfilled. The test framework identifies the type of request and handles it accordingly. If the request type is unrecognized, an exception should be raised. - -You can find details about the available requests in the [IRequest](irequest.md) section. - -`IMessageBus`: This service, linked with the request, allows the test framework to *asynchronously* to publish information about the ongoing request to the testing platform. -The message bus serves as the central hub for the platform, facilitating asynchronous communication among all platform components and extensions. -For a comprehensive list of information that can be published to the testing platform, refer to the [IMessageBus](imessagebus.md) section. - -`CancellationToken`: This token is utilized to interrupt the processing of a particular request. - -`Complete()`: As depicted in the previous sequence, the `Complete` method notifies the platform that the request has been successfully processed and all relevant information has been transmitted to the [IMessageBus](imessagebus.md). -> [!WARNING] -> Neglecting to invoke `Complete()` on the request will result in the test application becoming unresponsive. - -To customize your test framework according to your requirements or those of your users, you can use a personalized section inside the [configuration](configuration.md) file or with custom [command line options](icommandlineoptionsprovider.md). - -A practical example of a test framework can be found in the [code sample](codesample.md) section. diff --git a/docs/testingplatform/itestframeworkcapability.md b/docs/testingplatform/itestframeworkcapability.md deleted file mode 100644 index 1186395fe4..0000000000 --- a/docs/testingplatform/itestframeworkcapability.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test framework capabilities - -The platform exposes a specialized interface named `ITestFrameworkCapability` that is the base of all capabilities exposed for test frameworks. These capabilities are provided when [registering the test framework to the platform](registertestframework.md). diff --git a/docs/testingplatform/itesthostenvironmentvariableprovider.md b/docs/testingplatform/itesthostenvironmentvariableprovider.md deleted file mode 100644 index 30b5b39f62..0000000000 --- a/docs/testingplatform/itesthostenvironmentvariableprovider.md +++ /dev/null @@ -1,71 +0,0 @@ -# The `ITestHostEnvironmentVariableProvider` - -The `ITestHostEnvironmentVariableProvider` is an *out-of-process* extension that enables you to establish custom environment variables for the test host. Utilizing this extension point ensures that the testing platform will initiate a new host with the appropriate environment variables, as detailed in the [architecture](architecture.md) section. - -To register a custom `ITestHostEnvironmentVariableProvider`, utilize the following API: - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -testApplicationBuilder.TestHostControllers.AddEnvironmentVariableProvider(serviceProvider => - => new CustomEnvironmentVariableForTestHost()); -... -``` - -The factory utilizes the [IServiceProvider](iserviceprovider.md) to gain access to the suite of services offered by the testing platform. - -> [!IMPORTANT] -> The sequence of registration is significant, as the APIs are called in the order they were registered. - -The `ITestHostEnvironmentVariableProvider` interface includes the following methods and types: - -```cs -public interface ITestHostEnvironmentVariableProvider : ITestHostControllersExtension, IExtension -{ - Task UpdateAsync(IEnvironmentVariables environmentVariables); - Task ValidateTestHostEnvironmentVariablesAsync(IReadOnlyEnvironmentVariables environmentVariables); -} - -public interface IEnvironmentVariables : IReadOnlyEnvironmentVariables -{ - void SetVariable(EnvironmentVariable environmentVariable); - void RemoveVariable(string variable); -} - -public interface IReadOnlyEnvironmentVariables -{ - bool TryGetVariable(string variable, [NotNullWhen(true)] out OwnedEnvironmentVariable? environmentVariable); -} - -public sealed class OwnedEnvironmentVariable : EnvironmentVariable -{ - public IExtension Owner { get; } - public OwnedEnvironmentVariable(IExtension owner, string variable, string? value, bool isSecret, bool isLocked); -} - -public class EnvironmentVariable -{ - public string Variable { get; } - public string? Value { get; } - public bool IsSecret { get; } - public bool IsLocked { get; } -} -``` - -The `ITestHostEnvironmentVariableProvider` is a type of `ITestHostControllersExtension`, which serves as a base for all *test host controller* extensions. Like all other extension points, it also inherits from [IExtension](iextension.md). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. - -Let's describe the api: - -`UpdateAsync`: This update API provides an instance of the `IEnvironmentVariables` object, from which you can call the `SetVariable` or `RemoveVariable` methods. When using `SetVariable`, you must pass an object of type `EnvironmentVariable`, which requires the following specifications: - -* `Variable`: The name of the environment variable. -* `Value`: The value of the environment variable. -* `IsSecret`: This indicates whether the environment variable contains sensitive information that should not be logged or accessible via the `TryGetVariable`. -* `IsLocked`: This determines whether other `ITestHostEnvironmentVariableProvider` extensions can modify this value. - -`ValidateTestHostEnvironmentVariablesAsync`: This method is invoked after all the `UpdateAsync` methods of the registered `ITestHostEnvironmentVariableProvider` instances have been called. It allows you to *verify* the correct setup of the environment variables. It takes an object that implements `IReadOnlyEnvironmentVariables`, which provides the `TryGetVariable` method to fetch specific environment variable information with the `OwnedEnvironmentVariable` object type. After validation, you return a `ValidationResult` containing any failure reasons. - -> [!NOTE] -> The testing platform, by default, implements and registers the `SystemEnvironmentVariableProvider`. This provider loads all the *current* environment variables. As the first registered provider, it executes first, granting access to the default environment variables for all other `ITestHostEnvironmentVariableProvider` user extensions. - -If your extension requires intensive initialization and you need to use the async/await pattern, you can refer to the [`Async extension initialization and cleanup`](asyncinitcleanup.md). If you need to *share state* between extension points, you can refer to the [`CompositeExtensionFactory`](compositeextensionfactory.md) section. diff --git a/docs/testingplatform/itesthostprocesslifetimehandler.md b/docs/testingplatform/itesthostprocesslifetimehandler.md deleted file mode 100644 index 565455eb3e..0000000000 --- a/docs/testingplatform/itesthostprocesslifetimehandler.md +++ /dev/null @@ -1,54 +0,0 @@ -# The `ITestHostProcessLifetimeHandler` - -The `ITestHostProcessLifetimeHandler` is an *out-of-process* extension that allows you to observe the test host process from an external standpoint. This ensures that your extension remains unaffected by potential crashes or hangs that could be induced by the code under test. Utilizing this extension point will prompt the testing platform to initiate a new host, as detailed in the [architecture](architecture.md) section. - -To register a custom `ITestHostProcessLifetimeHandler`, utilize the following API: - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -testApplicationBuilder.TestHostControllers.AddProcessLifetimeHandler(serviceProvider => - new CustomMonitorTestHost()); -... -``` - -The factory utilizes the [IServiceProvider](iserviceprovider.md) to gain access to the suite of services offered by the testing platform. - -> [!IMPORTANT] -> The sequence of registration is significant, as the APIs are called in the order they were registered. - -The `ITestHostProcessLifetimeHandler` interface includes the following methods: - -```cs -public interface ITestHostProcessLifetimeHandler : ITestHostControllersExtension -{ - Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken); - Task OnTestHostProcessStartedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellation); - Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellation); -} - -public interface ITestHostProcessInformation -{ - int PID { get; } - int ExitCode { get; } - bool HasExitedGracefully { get; } -} -``` - -The `ITestHostProcessLifetimeHandler` is a type of `ITestHostControllersExtension`, which serves as a base for all *test host controller* extensions. Like all other extension points, it also inherits from [IExtension](iextension.md). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. - -Let's describe the api: - -`BeforeTestHostProcessStartAsync`: This method is invoked prior to the testing platform initiating the test hosts. - -`OnTestHostProcessStartedAsync`: This method is invoked immediately after the test host starts. This method offers an object that implements the `ITestHostProcessInformation` interface, which provides key details about the test host process result. -> [!IMPORTANT] -> The invocation of this method does not halt the test host's execution. If you need to pause it, you should register an [*in-process*](extensionintro.md) extension such as [`ITestApplicationLifecycleCallbacks`](itestapplicationlifecyclecallbacks.md) and synchronize it with the *out-of-process* extension. - -`OnTestHostProcessExitedAsync`: This method is invoked when the test suite execution is complete. This method supplies an object that adheres to the `ITestHostProcessInformation` interface, which conveys crucial details about the outcome of the test host process. - -The `ITestHostProcessInformation` interface provides the following details: - -* `PID`: The process ID of the test host. -* `ExitCode`: The exit code of the process. This value is only available within the `OnTestHostProcessExitedAsync` method. Attempting to access it within the `OnTestHostProcessStartedAsync` method will result in an exception. -* `HasExitedGracefully`: A boolean value indicating whether the test host has crashed. If true, it signifies that the test host did not exit gracefully. diff --git a/docs/testingplatform/itestsessionlifetimehandler.md b/docs/testingplatform/itestsessionlifetimehandler.md deleted file mode 100644 index cff0081f28..0000000000 --- a/docs/testingplatform/itestsessionlifetimehandler.md +++ /dev/null @@ -1,48 +0,0 @@ -# The `ITestSessionLifeTimeHandler` - -The `ITestSessionLifeTimeHandler` is an *in-process* extension that enables the execution of code *before* and *after* the test session. - -To register a custom `ITestSessionLifeTimeHandler`, utilize the following API: - -```cs -ITestApplicationBuilder testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -... -testApplicationBuilder.TestHost.AddTestSessionLifetimeHandle(serviceProvider => new CustomTestSessionLifeTimeHandler()); -... -``` - -The factory utilizes the [IServiceProvider](iserviceprovider.md) to gain access to the suite of services offered by the testing platform. - -> [!IMPORTANT] -> The sequence of registration is significant, as the APIs are called in the order they were registered. - -The `ITestSessionLifeTimeHandler` interface includes the following methods: - -```cs -public interface ITestSessionLifetimeHandler : ITestHostExtension -{ - Task OnTestSessionStartingAsync(SessionUid sessionUid, CancellationToken cancellationToken); - Task OnTestSessionFinishingAsync(SessionUid sessionUid, CancellationToken cancellationToken); -} - -public readonly struct SessionUid(string value) -{ - public string Value { get; } -} - -public interface ITestHostExtension : IExtension -{ -} -``` - -The `ITestSessionLifetimeHandler` is a type of `ITestHostExtension`, which serves as a base for all *test host* extensions. Like all other extension points, it also inherits from [IExtension](iextension.md). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. - -Let's describe the api: - -`OnTestSessionStartingAsync`: This method is invoked prior to the commencement of the test session and receives the `SessionUid` object, which provides an opaque identifier for the current test session. - -`OnTestSessionFinishingAsync`: This method is invoked after the completion of the test session, ensuring that the [testing framework](itestframework.md) has finished executing all tests and has reported all relevant data to the platform. Typically, in this method, the extension employs the [`IMessageBus`](imessagebus.md) to transmit custom assets or data to the shared platform bus. This method can also signal to any custom *out-of-process* extension that the test session has concluded. - -Finally, both APIs take a `CancellationToken` which the extension is expected to honor. - -If your extension requires intensive initialization and you need to use the async/await pattern, you can refer to the [`Async extension initialization and cleanup`](asyncinitcleanup.md). If you need to *share state* between extension points, you can refer to the [`CompositeExtensionFactory`](compositeextensionfactory.md) section. diff --git a/docs/testingplatform/pillars.md b/docs/testingplatform/pillars.md deleted file mode 100644 index 5b8463f425..0000000000 --- a/docs/testingplatform/pillars.md +++ /dev/null @@ -1,26 +0,0 @@ -# Pillars - -The new testing platform is a result of the Microsoft testing team's experience. It aims to address the challenges encountered since the release of .NET Core in 2016. While there is a high level of compatibility between the .NET Framework and the new .NET, certain key features like the plugin system and the new possible form factors of .NET compilations have made it complex to evolve or fully support the new runtime feature with the current [VSTest](https://github.com/microsoft/vstest) testing platform architecture. - -The main driving factors for the evolution of the new testing platform are: - -* **Determinism**: Ensuring that running the same tests in different contexts (local, CI) will produce the same result. The new runtime does not rely on reflection or any other dynamic .NET runtime feature to coordinate a test run. -* **Runtime transparency**: The test runtime does not interfere with the test framework code, it does not create isolated contexts like `AppDomain` or `AssemblyLoadContext`, and it does not use reflection or custom assembly resolvers. -* **Compile-time registration of extensions**: Extensions, such as test frameworks and in/out-of-process extensions, are registered during compile-time to ensure determinism. -* **0 dependencies**: The core of the platform is a single .NET assembly, `Microsoft.Testing.Platform.dll`, which has no dependencies other than the supported runtimes. -* **Hostable**: The test runtime can be hosted in any .NET application. While a console application is commonly used to run tests, you can create a test application in any type of .NET application. This allows you to run tests within special contexts, such as devices or browsers, where there may be limitations. -* **Support all .NET form factors**: Support current and future .NET form factors, including Native AOT. -* **Performant**: Finding the right balance between features and extension points to avoid bloating the runtime with non-fundamental code. The new test platform is designed to "orchestrate" a test run, rather than providing implementation details on how to do it. -* **Extensible enough**: The new platform is built on extensibility points to allow for maximum customization of runtime execution. It allows you to configure the test process host, observe the test process, and consume information from the test framework within the test host process. -* **Single module deploy**: The hostability feature enables a single module deploy model, where a single compilation result can be used to support all extensibility points, both out-of-process and in-process, without the need to ship different executable modules. - -The following factors should enhance the overall quality of the testing platform: - -* Shifting all to compile time aids in identifying issues prior to runtime. -* Determinism is beneficial in all aspects, as it reduces bugs and, in the event of a bug, provides a clear, straightforward stack trace that directly indicates the problem. -* Determinism facilitates straightforward reproduction of issues, independent of the execution context (such as machine setup, local environment, CI, etc.). If the issue is related to the execution context, determinism makes it evident. -* Dynamic code, often associated with "indirect execution", can result in performance degradation. By avoiding it, performance can be enhanced. -* Eliminating dynamic code simplifies the logic behind feature development. The code you see is exactly what will be executed at runtime. -* Eliminating dependencies and dynamic code guarantees compatibility with upcoming runtime features. -* Eliminating dependencies means we can run everywhere. -* Operating a self-contained testing platform without dependencies ensures that there's always a mechanism to execute user tests. This is because `Microsoft.Testing.Platform.dll` is viewed as a fundamental part of the runtime itself, ideally `System.Testing.Platform`. diff --git a/docs/testingplatform/ppt.pptx b/docs/testingplatform/ppt.pptx deleted file mode 100644 index e29607c54234a0aeb66e78cfbdb17ca05f784148..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37536 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 diff --git a/docs/testingplatform/registertestframework.md b/docs/testingplatform/registertestframework.md deleted file mode 100644 index 5647f54ded..0000000000 --- a/docs/testingplatform/registertestframework.md +++ /dev/null @@ -1,67 +0,0 @@ -# Register the testing framework - -This section explains how to register the test framework to the testing platform. -You can register only one testing framework per test application builder using the api `TestApplication.RegisterTestFramework` as shown [here](architecture.md) - -The API's signature is as follows: - -```cs -ITestApplicationBuilder RegisterTestFramework( - Func capabilitiesFactory, - Func adapterFactory); -``` - -The `RegisterTestFramework` API expects two factories: - -1. `Func`: This is a lambda function that accepts an object implementing the [`IServiceProvider`](iserviceprovider.md) interface and returns an object implementing the [`ITestFrameworkCapabilities`](capabilities.md) interface. The [`IServiceProvider`](iserviceprovider.md) provides access to platform services such as configurations, loggers, command line arguments, etc. - - The [`ITestFrameworkCapabilities`](capabilities.md) interface is used to announce the capabilities supported by the testing framework to the platform and extensions. It allows the platform and extensions to interact correctly by implementing and supporting specific behaviors. For a better understanding of the [concept of capabilities](capabilities.md), refer to the respective section. - -1. `Func`: This is a lambda function that takes in an [ITestFrameworkCapabilities](capabilities.md) object, which is the instance returned by the `Func`, and an [IServiceProvider](iserviceprovider.md) to provide access to platform services once more. The expected return object is one that implements the [ITestFramework](itestframework.md) interface. The ITestFramework serves as the execution engine that discovers and runs tests, and communicates the results back to the testing platform. - -The need for the platform to separate the creation of the [`ITestFrameworkCapabilities`](capabilities.md) and the creation of the [ITestFramework](itestframework.md) is an optimization to avoid creating the test framework if the supported capabilities are not sufficient to execute the current testing session. - -Below a sample of a test framework registration that returns empty capabilities. - -User code: - -```cs -internal class TestingFrameworkCapabilities : ITestFrameworkCapabilities -{ - public IReadOnlyCollection Capabilities => []; -} - -internal class TestingFramework : ITestFramework -{ - public TestingFramework(ITestFrameworkCapabilities capabilities, IServiceProvider serviceProvider) - { - ... - } - ... -} - -public static class TestingFrameworkExtensions -{ - public static void AddTestingFramework(this ITestApplicationBuilder builder) - { - builder.RegisterTestFramework( - _ => new TestingFrameworkCapabilities(), - (capabilities, serviceProvider) => new TestingFramework(capabilities, serviceProvider)); - } -} - -... -``` - -Entry point with the registration: - -```cs -var testApplicationBuilder = await TestApplication.CreateBuilderAsync(args); -// Register the testing framework -testApplicationBuilder.AddTestingFramework(); -using var testApplication = await testApplicationBuilder.BuildAsync(); -return await testApplication.RunAsync(); -``` - -> [!NOTE] -> Returning empty [ITestFrameworkCapabilities](capabilities.md) should not prevent the execution of the test session. All test frameworks should be capable of discovering and running tests. The impact should be limited to extensions that may opt out if the test framework lacks a certain feature. diff --git a/docs/testingplatform/testnodeupdatemessage.md b/docs/testingplatform/testnodeupdatemessage.md deleted file mode 100644 index b857f52050..0000000000 --- a/docs/testingplatform/testnodeupdatemessage.md +++ /dev/null @@ -1,161 +0,0 @@ -# Well known `TestNodeUpdateMessage.TestNode` properties - -As detailed in the [requests section](irequest.md), the testing platform identifies specific properties added to the `TestNodeUpdateMessage` to determine the status of a `TestNode` (e.g., successful, failed, skipped, etc.). This allows the runtime to accurately display a list of failed tests with their corresponding information in the console, and to set the appropriate exit code for the test process. - -In this segment, we'll elucidate the various well-known `IProperty` options and their respective implications. - -If you're looking for a comprehensive list of well-known properties, you can find it [here](https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs). If you notice that a property description is missing, please don't hesitate to file an issue. - -We can divide the properties in: - -1. *Generic information*: Properties that can be included in any kind of request. -1. *Discovery information*: Properties that are supplied during a `DiscoverTestExecutionRequest` discovery request. -1. *Execution information*: Properties that are supplied during a test execution request `RunTestExecutionRequest`. - -Certain properties are **required**, while others are optional. The mandatory properties are required to provide basic testing functionality, such as reporting failed tests and indicating whether the entire test session was successful or not. - -Optional properties, on the other hand, enhance the testing experience by providing additional information. They are particularly useful in IDE scenarios (like VS, VSCode, etc.), console runs, or when supporting specific extensions that require more detailed information to function correctly. However, these optional properties do not affect the execution of the tests. - -> [!NOTE] -> Extensions are tasked with alerting and managing exceptions when they require specific information to operate correctly. If an extension lacks the necessary information, it should not cause the test execution to fail, but rather, it should simply opt-out. - -## Generic information - -```cs -public record KeyValuePairStringProperty(string Key, string Value) : IProperty; -``` - -The `KeyValuePairStringProperty` stands for a general key/value pair data. - -```cs -public record struct LinePosition(int Line, int Column); -public record struct LinePositionSpan(LinePosition Start, LinePosition End); -public abstract record FileLocationProperty(string FilePath, LinePositionSpan LineSpan) : IProperty; -public sealed record TestFileLocationProperty(string FilePath, LinePositionSpan LineSpan) : FileLocationProperty(FilePath, LineSpan); -``` - -`TestFileLocationProperty` is used to pinpoint the location of the test within the source file. This is particularly useful when the initiator is an IDE like Visual Studio or Visual Studio Code. - -```cs -public sealed record TestMethodIdentifierProperty(string AssemblyFullName, string Namespace, string TypeName, string MethodName, string[] ParameterTypeFullNames, string ReturnTypeFullName) -``` - -`TestMethodIdentifierProperty` is a unique identifier for a test method, adhering to the ECMA-335 standard. - -> [!NOTE] -> The data needed to create this property can be conveniently obtained using the .NET reflection feature, using types from the `System.Reflection` namespace. - -```cs -public sealed record TestMetadataProperty(string Key, string Value) -``` - -`TestMetadataProperty` is utilized to convey the characteristics or *traits* of a `TestNode`. - -## Discovery information - -```cs -public sealed record DiscoveredTestNodeStateProperty(string? Explanation = null) -{ - public static DiscoveredTestNodeStateProperty CachedInstance { get; } -} -``` - -The `DiscoveredTestNodeStateProperty` indicates that this TestNode has been discovered. It is utilized when a `DiscoverTestExecutionRequest` is sent to the test framework. -Take note of the handy cached value offered by the `CachedInstance` property. -This property is **required**. - -## Execution information - -```cs -public sealed record InProgressTestNodeStateProperty(string? Explanation = null) -{ - public static InProgressTestNodeStateProperty CachedInstance { get; } -} -``` - -The `InProgressTestNodeStateProperty` informs the testing platform that the `TestNode` has been scheduled for execution and is currently in progress. -Take note of the handy cached value offered by the `CachedInstance` property. - -```cs -public readonly record struct TimingInfo(DateTimeOffset StartTime, DateTimeOffset EndTime, TimeSpan Duration); -public sealed record StepTimingInfo(string Id, string Description, TimingInfo Timing); - -public sealed record TimingProperty : IProperty -{ - public TimingProperty(TimingInfo globalTiming) - public TimingProperty(TimingInfo globalTiming, StepTimingInfo[] stepTimings) - public TimingInfo GlobalTiming { get; } - public StepTimingInfo[] StepTimings { get; } -} -``` - -The `TimingProperty` is utilized to relay timing details about the `TestNode` execution. It also allows for the timing of individual execution steps via `StepTimingInfo`. This is particularly useful when your test concept is divided into multiple phases such as initialization, execution, and cleanup. - -### Execution information - test outcome information - -***One and only one*** of the following properties is **required** per `TestNode` and communicates the result of the `TestNode` to the testing platform. - -```cs -public sealed record PassedTestNodeStateProperty(string? Explanation = null) -{ - public static PassedTestNodeStateProperty CachedInstance { get; } -} -``` - -`PassedTestNodeStateProperty` informs the testing platform that this `TestNode` is passed. -Take note of the handy cached value offered by the `CachedInstance` property. - -```cs -public sealed record SkippedTestNodeStateProperty(string? Explanation = null) -{ - public static SkippedTestNodeStateProperty CachedInstance { get; } -} -``` - -`SkippedTestNodeStateProperty` informs the testing platform that this `TestNode` was skipped. -Take note of the handy cached value offered by the `CachedInstance` property. - -```cs -public sealed record FailedTestNodeStateProperty -{ - public FailedTestNodeStateProperty(string explanation) - public FailedTestNodeStateProperty(Exception exception, string? explanation = null) - public Exception? Exception { get; } -} -``` - -`FailedTestNodeStateProperty` informs the testing platform that this `TestNode` is failed after an assertion. - -```cs -public sealed record ErrorTestNodeStateProperty -{ - public ErrorTestNodeStateProperty(string explanation) - public ErrorTestNodeStateProperty(Exception exception, string? explanation = null) - public Exception? Exception { get; } -} -``` - -`ErrorTestNodeStateProperty` informs the testing platform that this `TestNode` has failed. This type of failure is different from the `FailedTestNodeStateProperty`, which is used for assertion failures. For example, you can report issues like test initialization errors with `ErrorTestNodeStateProperty`. - -```cs -public sealed record TimeoutTestNodeStateProperty -{ - public TimeoutTestNodeStateProperty(string explanation) - public TimeoutTestNodeStateProperty(Exception exception, string? explanation = null) - public Exception? Exception { get; } - public TimeSpan? Timeout { get; init; } -} -``` - -`TimeoutTestNodeStateProperty` informs the testing platform that this `TestNode` is failed for a timeout reason. You can report the timeout using the `Timeout` property. - -```cs -public sealed record CancelledTestNodeStateProperty -{ - public CancelledTestNodeStateProperty(string explanation) - public CancelledTestNodeStateProperty(Exception exception, string? explanation = null) - public Exception? Exception { get; } -} -``` - -`CancelledTestNodeStateProperty` informs the testing platform that this `TestNode` has failed due to cancellation. diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs index ae0d59ef39..ce9516c32d 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/CompositeExtensionFactorySample.cs @@ -9,7 +9,7 @@ namespace TestingPlatformExplorer.InProcess; -internal class DisplayCompositeExtensionFactorySample : ITestSessionLifetimeHandler, IDataConsumer, IOutputDeviceDataProducer +internal sealed class DisplayCompositeExtensionFactorySample : ITestSessionLifetimeHandler, IDataConsumer, IOutputDeviceDataProducer { private readonly IOutputDevice _outputDevice; private int _testNodeUpdateMessageCount; diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs index b8ef1188c3..568bc602a1 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayDataConsumer.cs @@ -7,7 +7,7 @@ using Microsoft.Testing.Platform.OutputDevice; namespace TestingPlatformExplorer.InProcess; -internal class DisplayDataConsumer : IDataConsumer, IOutputDeviceDataProducer +internal sealed class DisplayDataConsumer : IDataConsumer, IOutputDeviceDataProducer { private readonly IOutputDevice _outputDevice; diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs index 20776d3148..7ebd6f9ee9 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestApplicationLifecycleCallbacks.cs @@ -6,7 +6,7 @@ using Microsoft.Testing.Platform.OutputDevice; namespace TestingPlatformExplorer.InProcess; -internal class DisplayTestApplicationLifecycleCallbacks : ITestApplicationLifecycleCallbacks, IOutputDeviceDataProducer +internal sealed class DisplayTestApplicationLifecycleCallbacks : ITestApplicationLifecycleCallbacks, IOutputDeviceDataProducer { private readonly IOutputDevice _outputDevice; diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs index 230a3436d8..ded9dba5d5 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/In-process extensions/DisplayTestSessionLifeTimeHandler.cs @@ -8,7 +8,7 @@ using Microsoft.Testing.Platform.TestHost; namespace TestingPlatformExplorer.InProcess; -internal class DisplayTestSessionLifeTimeHandler : ITestSessionLifetimeHandler, +internal sealed class DisplayTestSessionLifeTimeHandler : ITestSessionLifetimeHandler, IOutputDeviceDataProducer, IAsyncInitializableExtension, IAsyncCleanableExtension, diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs index a685152567..dd7d89489e 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/MonitorTestHost.cs @@ -7,7 +7,7 @@ namespace TestingPlatformExplorer.OutOfProcess; -internal class MonitorTestHost : ITestHostProcessLifetimeHandler, IOutputDeviceDataProducer +internal sealed class MonitorTestHost : ITestHostProcessLifetimeHandler, IOutputDeviceDataProducer { private readonly IOutputDevice _outputDevice; diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs index b3b0fb7160..8101e8e50d 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Out-of-process extensions/SetEnvironmentVariableForTestHost.cs @@ -7,7 +7,7 @@ namespace TestingPlatformExplorer.OutOfProcess; -internal class SetEnvironmentVariableForTestHost : ITestHostEnvironmentVariableProvider +internal sealed class SetEnvironmentVariableForTestHost : ITestHostEnvironmentVariableProvider { public string Uid => nameof(SetEnvironmentVariableForTestHost); diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Program.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Program.cs index 441c6d7cfd..3ca8c8f6fc 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Program.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Program.cs @@ -3,6 +3,7 @@ using System.Reflection; +using Microsoft.Testing.Extensions; using Microsoft.Testing.Platform.Builder; using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Services; @@ -40,5 +41,9 @@ testApplicationBuilder.TestHost.AddTestSessionLifetimeHandle(compositeExtensionFactory); testApplicationBuilder.TestHost.AddDataConsumer(compositeExtensionFactory); +// Register public extensions +// Trx +testApplicationBuilder.AddTrxReportProvider(); + using ITestApplication testApplication = await testApplicationBuilder.BuildAsync(); return await testApplication.RunAsync(); diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Properties/launchSettings.json b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Properties/launchSettings.json index 696dc6823e..81ddf2512b 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Properties/launchSettings.json +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "TestingPlatformExplorer": { "commandName": "Project", - "commandLineArgs": "--dop 1 --generatereport --reportfilename testframeworkreport.txt --diagnostic", + "commandLineArgs": "--dop 1 --generatereport --reportfilename testframeworkreport.txt --diagnostic --report-trx", "environmentVariables": { "TestingFramework__DisableParallelism": "True" } } } diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs index c1c83b63a1..efa7adde16 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.Attributes.cs @@ -6,6 +6,12 @@ namespace TestingPlatformExplorer.TestingFramework; [AttributeUsage(AttributeTargets.Method)] public class SkipAttribute : Attribute { + public string Reason { get; private set; } + + public SkipAttribute(string reason) + { + Reason = reason; + } } [AttributeUsage(AttributeTargets.Method)] diff --git a/docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Services.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.Services.cs similarity index 100% rename from docs/testingplatform/Source/TestingPlatformExplorer/TestingFramework/TestingFramework.Services.cs rename to samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.Services.cs diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.cs index 0176a0c973..63f5568838 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFramework.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#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 System.Globalization; using System.Reflection; using System.Text; +using Microsoft.Testing.Extensions.TrxReport.Abstractions; using Microsoft.Testing.Platform.Capabilities.TestFramework; using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Configurations; @@ -109,6 +112,15 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) Properties = new PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance), }; + TestMethodIdentifierProperty testMethodIdentifierProperty = new(test.DeclaringType!.Assembly!.FullName!, + test.DeclaringType!.Namespace!, + test.DeclaringType.Name!, + test.Name, + test.GetParameters().Select(x => x.ParameterType.FullName).ToArray()!, + test.ReturnType.FullName!); + + testNode.Properties.Add(testMethodIdentifierProperty); + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(discoverTestExecutionRequest.Session.SessionUid, testNode)); } } @@ -132,15 +144,29 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) List results = new(); foreach (MethodInfo test in tests) { - if (test.GetCustomAttribute() != null) + if (runTestExecutionRequest.Filter is TestNodeUidListFilter filter) + { + if (!filter.TestNodeUids.Any(testId => testId == $"{test.DeclaringType!.FullName}.{test.Name}")) + { + continue; + } + } + + SkipAttribute? skipAttribute = test.GetCustomAttribute(); + if (skipAttribute != null) { var skippedTestNode = new TestNode() { Uid = $"{test.DeclaringType!.FullName}.{test.Name}", DisplayName = test.Name, - Properties = new PropertyBag(SkippedTestNodeStateProperty.CachedInstance), + Properties = new PropertyBag(new SkippedTestNodeStateProperty(skipAttribute.Reason)), }; + if (_capabilities.TrxCapability.IsTrxEnabled) + { + FillTrxProperties(skippedTestNode, test); + } + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, skippedTestNode)); lock (reportBody) @@ -156,10 +182,19 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) await _dop.WaitAsync(); try { + var testOutputHelper = new TestOutputHelper(); object? instance = Activator.CreateInstance(test.DeclaringType!); try { - test.Invoke(instance, null); + if (test.GetParameters().Length == 1 && test.GetParameters()[0].ParameterType == typeof(ITestOutputHelper)) + { + test.Invoke(instance, new object[] { testOutputHelper }); + + } + else + { + test.Invoke(instance, null); + } var successfulTestNode = new TestNode() { @@ -168,6 +203,21 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance), }; + if (_capabilities.TrxCapability.IsTrxEnabled) + { + FillTrxProperties(successfulTestNode, test); + } + + if (testOutputHelper.Output.Length > 0) + { + successfulTestNode.Properties.Add(new StandardOutputProperty(testOutputHelper.Output.ToString())); + } + + if (testOutputHelper.Error.Length > 0) + { + successfulTestNode.Properties.Add(new StandardErrorProperty(testOutputHelper.Error.ToString())); + } + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, successfulTestNode)); lock (reportBody) @@ -184,6 +234,21 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) Properties = new PropertyBag(new FailedTestNodeStateProperty(assertionException)), }; + if (_capabilities.TrxCapability.IsTrxEnabled) + { + FillTrxProperties(assertionFailedTestNode, test, assertionException); + } + + if (testOutputHelper.Output.Length > 0) + { + assertionFailedTestNode.Properties.Add(new StandardOutputProperty(testOutputHelper.Output.ToString())); + } + + if (testOutputHelper.Error.Length > 0) + { + assertionFailedTestNode.Properties.Add(new StandardErrorProperty(testOutputHelper.Error.ToString())); + } + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, assertionFailedTestNode)); reportBody.AppendLine(CultureInfo.InvariantCulture, $"Test {assertionFailedTestNode.Uid} failed"); @@ -197,6 +262,21 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) Properties = new PropertyBag(new ErrorTestNodeStateProperty(ex.InnerException!)), }; + if (_capabilities.TrxCapability.IsTrxEnabled) + { + FillTrxProperties(failedTestNode, test, ex); + } + + if (testOutputHelper.Output.Length > 0) + { + failedTestNode.Properties.Add(new StandardOutputProperty(testOutputHelper.Output.ToString())); + } + + if (testOutputHelper.Error.Length > 0) + { + failedTestNode.Properties.Add(new StandardErrorProperty(testOutputHelper.Error.ToString())); + } + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(runTestExecutionRequest.Session.SessionUid, failedTestNode)); lock (reportBody) @@ -234,6 +314,15 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) } } + private void FillTrxProperties(TestNode testNode, MethodInfo test, Exception? ex = null) + { + testNode.Properties.Add(new TrxFullyQualifiedTypeNameProperty(test.DeclaringType!.FullName!)); + + if (ex is not null) + { + testNode.Properties.Add(new TrxExceptionProperty(ex.Message, ex.StackTrace)); + } + } private MethodInfo[] GetTestsMethodFromAssemblies() => _assemblies .SelectMany(x => x.GetTypes()) @@ -243,7 +332,5 @@ private MethodInfo[] GetTestsMethodFromAssemblies() public Task IsEnabledAsync() => Task.FromResult(true); public void Dispose() - { - _dop.Dispose(); - } + => _dop.Dispose(); } diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs index 1b110c3dbd..4afd247067 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingFramework/TestingFrameworkCapabilities.cs @@ -1,11 +1,23 @@ // 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.Extensions.TrxReport.Abstractions; using Microsoft.Testing.Platform.Capabilities.TestFramework; namespace TestingPlatformExplorer.TestingFramework; internal sealed class TestingFrameworkCapabilities : ITestFrameworkCapabilities { - public IReadOnlyCollection Capabilities => []; + public TrxCapability TrxCapability { get; } = new(); + + public IReadOnlyCollection Capabilities => [TrxCapability]; +} + +internal sealed class TrxCapability : ITrxReportCapability +{ + public bool IsTrxEnabled { get; set; } + + public bool IsSupported => true; + + public void Enable() => IsTrxEnabled = true; } diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingPlatformExplorer.csproj b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingPlatformExplorer.csproj index bebb9ba07e..f51a61a15f 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingPlatformExplorer.csproj +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/TestingPlatformExplorer.csproj @@ -6,11 +6,18 @@ enable enable TestingPlatformExplorer + false - + + + + + + + @@ -19,4 +26,10 @@ + + + + + + diff --git a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/UnitTests.cs b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/UnitTests.cs index 5c676ccbc7..b96ab23e67 100644 --- a/samples/public/TestingPlatformExamples/TestingPlatformExplorer/UnitTests.cs +++ b/samples/public/TestingPlatformExamples/TestingPlatformExplorer/UnitTests.cs @@ -14,16 +14,26 @@ public class SomeTests public static void TestMethod2() => Assert.AreEqual(1, 2); [TestMethod] - public static void TestMethod3() + public static void TestMethod3(ITestOutputHelper testOutputHelper) { - int a = 1; - int b = 0; - int c = a / b; + testOutputHelper.WriteLine("I'm running TestMethod3"); - Assert.AreEqual(c, 2); + try + { + int a = 1; + int b = 0; + int c = a / b; + + Assert.AreEqual(c, 2); + } + catch (Exception ex) + { + testOutputHelper.WriteErrorLine(ex.ToString()); + throw; + } } - [Skip] + [Skip("Temporary disabled")] [TestMethod] public static void TestMethod4() => Assert.AreEqual(1, 1); }