הערה
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות להיכנס או לשנות מדריכי כתובות.
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות לשנות מדריכי כתובות.
In the context of MTP, a capability refers to the potential to perform a specific action or provide specific information. It's 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, 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.
Guided example
Let's consider a hypothetical example to demonstrate the necessity of a capability system.
Note
This example is purely for illustrative purposes and isn't currently implemented within MTP or any testing framework.
Imagine a situation where you 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 preceding scenario, you need to inquire from the testing framework if:
- It has the capability to execute only one test at a time.
- 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 MTP, this capability is represented by an implementation the Microsoft.Testing.Platform.Capabilities.ICapability interface:
// Base capabilities contracts
public interface ICapability
{
}
public interface ICapabilities<TCapability>
where TCapability : ICapability
{
IReadOnlyCollection<TCapability> Capabilities { get; }
}
// Specific testing framework capabilities
public interface ITestFrameworkCapabilities : ICapabilities<ITestFrameworkCapability>
{
}
public interface ITestFrameworkCapability : ICapability
{
}
As you can see, the ICapability interface is a marker interface 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. The ICapabilities<TCapability> interface reveals the set of all capabilities implemented by an extension. Similarly, for the base one, there's a context-specific testing framework called ITestFrameworkCapabilities. The ITestFrameworkCapabilities is provided to the platform during the testing framework registration process.
To create a capability that addresses the aforementioned scenario, you define it as follows:
public interface IDisableParallelismCapability : ITestFrameworkCapability
{
bool CanDisableParallelism { get; }
bool CanProvidePerTestCpuConsumption { get; }
void Enable();
}
If the testing framework implements this interface, at runtime, the following can be queried:
- 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:
IServiceProvider serviceProvider = null; // TODO: Get IServiceProvider.
var capabilities = serviceProvider.GetRequiredService<ITestFrameworkCapabilities>();
// Utilize the `GetCapability` API to search for the specific capability to query.
var capability = capabilities.GetCapability<IDisableParallelismCapability>();
if (capability is null)
{
// Capability not supported...
}
else
{
capability.Enable();
if (capability.CanDisableParallelism)
{
// Do something...
}
if (capability.CanProvidePerTestCpuConsumption)
{
// Do something...
}
}
The preceding example 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. Considering the previous example, what should the extension expect if the CanProvidePerTestCpuConsumption is supported? What kind of custom information is expected to be transmitted via the IMessageBus by the testing framework? The solution 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, 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 https://www.nuget.org/packages/Microsoft.Testing.Extensions.TrxReport NuGet 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's essential for facilitating clear and stable communication between components.
- All capabilities should inherit from
ICapabilityor an interface that inherits from it, and are exposed through a collection with theICapabilitiesinterface. - It aids in the evolution of features without causing breaking changes. If a certain capability isn't supported, appropriate action can be taken.
- The responsibility of designing, shipping, and documenting the usage of a capability lies with the capability owner. MTP can also own a capability in the same way as any other extension.
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.
IBannerMessageOwnerCapability
An optional test framework capability allows the test framework to provide the banner message to the platform. If the message is null or the capability isn't present, the platform uses its default banner message.
This capability implementation allows you to abstract away the various conditions that the test framework may need to consider when deciding whether or not the banner message should be displayed.
The platform exposes the IPlatformInformation service to provide some information about the platform that could be useful when building your custom banner message.
IGracefulStopTestExecutionCapability
To support --maximum-failed-tests in a custom test framework, implement Microsoft.Testing.Platform.Capabilities.TestFramework.IGracefulStopTestExecutionCapability. The capability is part of the Microsoft.Testing.Platform package and is available starting with MTP 1.5. Test authors who use a framework that already supports the option don't need to implement the capability.
The interface defines Task StopTestExecutionAsync(CancellationToken cancellationToken). When the failure count reaches the requested maximum, MTP calls this method. Your implementation must tell the framework to stop scheduling new tests while it allows tests that have already started to finish, run cleanup, and publish their results.
Important
The capability and the AddMaximumFailedTestsService helper are experimental APIs that produce compiler diagnostic TPEXP. Their contracts can change or be removed. The example projects suppress TPEXP explicitly.
Follow these requirements when you implement the capability:
- Share stop state between the capability and the framework's test scheduler. Merely returning a completed task doesn't stop tests.
- Make stop requests thread-safe and idempotent. More than one platform component can request a stop.
- Return after you signal the stop. Don't wait for the entire run or the message bus to finish: MTP can call the method while it processes a test result, so that wait can deadlock.
- Keep graceful-stop state separate from request cancellation. Don't terminate the process or cancel result publication to implement a graceful stop. Honor the method's cancellation token if your stop operation requires asynchronous work.
- After active tests finish, complete the normal request lifecycle, including result publication, cleanup, and the call to
ExecuteRequestContext.Complete().
The following implementation uses a thread-safe flag. In your framework, check IsStopRequested before you schedule each test and stop taking new work when the flag is true.
public sealed class GracefulStopCapability : IGracefulStopTestExecutionCapability
{
private int _stopRequested;
public bool IsStopRequested => Volatile.Read(ref _stopRequested) != 0;
public Task StopTestExecutionAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Interlocked.Exchange(ref _stopRequested, 1);
return Task.CompletedTask;
}
}
Public NotInheritable Class GracefulStopCapability
Implements IGracefulStopTestExecutionCapability
Private _stopRequested As Integer
Public ReadOnly Property IsStopRequested As Boolean
Get
Return Volatile.Read(_stopRequested) <> 0
End Get
End Property
Public Function StopTestExecutionAsync(cancellationToken As CancellationToken) As Task _
Implements IGracefulStopTestExecutionCapability.StopTestExecutionAsync
cancellationToken.ThrowIfCancellationRequested()
Interlocked.Exchange(_stopRequested, 1)
Return Task.CompletedTask
End Function
End Class
Expose the capability through ITestFrameworkCapabilities.Capabilities. The registration example uses MTP's TestFrameworkCapabilities collection.
During test framework registration, pass the registered capability instance to your framework. The following helper accepts your framework factory and an IExtension that owns the command-line option. It also calls AddMaximumFailedTestsService from the Microsoft.Testing.Platform.Helpers namespace to register the --maximum-failed-tests option provider. Without that provider, MTP doesn't recognize the option.
public static void Register(
ITestApplicationBuilder builder,
IExtension owner,
Func<GracefulStopCapability, ITestFramework> createFramework)
{
builder.RegisterTestFramework(
_ => new TestFrameworkCapabilities(new GracefulStopCapability()),
(capabilities, _) => createFramework(
capabilities.Capabilities.OfType<GracefulStopCapability>().Single()));
builder.AddMaximumFailedTestsService(owner);
}
Public Sub Register(
builder As ITestApplicationBuilder,
owner As IExtension,
createFramework As Func(Of GracefulStopCapability, ITestFramework))
builder.RegisterTestFramework(
Function(services) New TestFrameworkCapabilities(New GracefulStopCapability()),
Function(capabilities, services) createFramework(
capabilities.Capabilities.OfType(Of GracefulStopCapability)().Single()))
builder.AddMaximumFailedTestsService(owner)
End Sub
The capability factory creates fresh stop state, and the framework factory retrieves that same instance. Don't create a second, disconnected capability in your framework. For frameworks that support multiple run requests, keep stop state scoped to the corresponding execution. Don't clear an accepted stop request when execution starts.
MTP rejects --maximum-failed-tests during command-line validation if the option provider is registered but the framework doesn't expose this capability. When the failure threshold triggers a stop, MTP returns exit code 13. The final failure count can exceed the threshold because result processing is asynchronous and tests might already be in progress.
For the interface contract, see the IGracefulStopTestExecutionCapability source.