// 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.Builder;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Services;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.TestingPlatformAdapter;
namespace Microsoft.VisualStudio.TestTools.UnitTesting;
///
/// Extension methods for .
///
[SuppressMessage("ApiDesign", "RS0030:Do not use banned APIs", Justification = "We can use MTP from this folder")]
public static class TestApplicationBuilderExtensions
{
private static readonly ConditionalWeakTable Options = CreateOptions();
// NOTE: We intentionally do not use the bridge's VSTestBridgeExtensionBaseCapabilities because we don't want
// MSTest to use the vstestProvider capability. This implements MSTest's native TRX capability, read by
// MSTestTestFramework.
private sealed class MSTestCapabilities : IMSTestTrxReportCapability
{
public bool IsTrxEnabled { get; private set; }
bool ITrxReportCapability.IsSupported { get; } = true;
void ITrxReportCapability.Enable()
=> IsTrxEnabled = true;
}
///
/// Register MSTest as the test framework and register the necessary services.
///
/// The test application builder on which to register.
/// The function to get the test assemblies.
public static void AddMSTest(this ITestApplicationBuilder testApplicationBuilder, Func> getTestAssemblies)
{
MSTestTestApplicationBuilderOptions options = Options.GetOrCreateValue(testApplicationBuilder);
MSTestExtension extension = new();
var discoveryCache = new MSTestDiscoveryCache();
// Register MSTest's own command-line options, runsettings configuration source and environment-variable
// provider natively (identical option names/descriptions to the VSTest bridge), so both the native and the
// bridged framework read them by name.
if (testApplicationBuilder is TestApplicationBuilder concreteBuilder)
{
concreteBuilder.Configuration.AddConfigurationSource(() => new MSTestRunSettingsConfigurationProvider(extension, new SystemFileSystem()));
}
testApplicationBuilder.CommandLine.AddProvider(() => new MSTestRunSettingsCommandLineOptionsProvider(extension));
testApplicationBuilder.CommandLine.AddProvider(() => new MSTestTestCaseFilterCommandLineOptionsProvider(extension));
testApplicationBuilder.CommandLine.AddProvider(() => new MSTestTestRunParametersCommandLineOptionsProvider(extension));
testApplicationBuilder.AddMaximumFailedTestsService(extension);
// The environment-variable provider is a test-host-controller extension: it applies runsettings
// by relaunching the test host with those variables set. That requires a
// controller process + restart, which browser-wasm does not support (TestHostControllersManager
// throws PlatformNotSupportedException there). Skip the registration on browser. Silently dropping
// a requested section would change test semantics, so
// RunSettingsCommandLineOptionsProviderBase.ValidateCommandLineOptionsAsync fails with a clear
// unsupported-platform diagnostic when the resolved runsettings (from --settings or either
// runsettings environment variable) actually declares that section on browser.
// See https://github.com/microsoft/testfx/issues/2196.
if (!OperatingSystem.IsBrowser())
{
testApplicationBuilder.TestHostControllers.AddEnvironmentVariableProvider(serviceProvider
=> new MSTestRunSettingsEnvironmentVariableProvider(extension, serviceProvider.GetCommandLineOptions(), serviceProvider.GetFileSystem(), serviceProvider.GetEnvironment()));
}
// MSTest plugs into Microsoft.Testing.Platform directly (no VSTest bridge object model on the request path).
testApplicationBuilder.RegisterTestFramework(
serviceProvider =>
{
// Root registration survives the server's cloned per-request service providers and is
// disposed only when the application closes.
((Microsoft.Testing.Platform.Services.ServiceProvider)serviceProvider).TryAddService(discoveryCache);
return new TestFrameworkCapabilities(
new MSTestCapabilities(),
new MSTestBannerCapability(serviceProvider.GetRequiredService()),
MSTestGracefulStopTestExecutionCapability.Create());
},
(capabilities, serviceProvider) => new MSTestTestFramework(extension, getTestAssemblies, serviceProvider, capabilities)
{
TestClassInstanceFactory = options.TestClassInstanceFactory,
DiscoveryCache = discoveryCache,
});
}
internal static void SetTestClassInstanceFactory(
ITestApplicationBuilder testApplicationBuilder,
ITestClassInstanceFactory testClassInstanceFactory)
{
_ = testApplicationBuilder ?? throw new ArgumentNullException(nameof(testApplicationBuilder));
_ = testClassInstanceFactory ?? throw new ArgumentNullException(nameof(testClassInstanceFactory));
Options.GetOrCreateValue(testApplicationBuilder).TestClassInstanceFactory = testClassInstanceFactory;
}
private sealed class MSTestTestApplicationBuilderOptions
{
public ITestClassInstanceFactory? TestClassInstanceFactory { get; set; }
}
#pragma warning disable IDE0028 // Collection expressions cannot construct ConditionalWeakTable on .NET Framework.
private static ConditionalWeakTable CreateOptions()
=> new();
#pragma warning restore IDE0028
}