|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Immutable;
using System.CommandLine;
using System.Diagnostics.CodeAnalysis;
using Microsoft.DotNet.Cli.Commands.Test.Terminal;
using Microsoft.DotNet.Cli.Extensions;
using Microsoft.TemplateEngine.Cli.Commands;
namespace Microsoft.DotNet.Cli.Commands.Test;
internal partial class MicrosoftTestingPlatformTestCommand : Command, ICustomHelp, ICommandDocument
{
private TerminalTestReporter? _output;
public MicrosoftTestingPlatformTestCommand(string name, string? description = null) : base(name, description)
{
TreatUnmatchedTokensAsErrors = false;
}
public string DocsLink => "https://aka.ms/dotnet-test";
public int Run(ParseResult parseResult, bool isHelp = false)
{
int? exitCode = null;
try
{
exitCode = RunInternal(parseResult, isHelp);
return exitCode.Value;
}
finally
{
_output?.TestExecutionCompleted(DateTimeOffset.Now, exitCode);
}
}
private int RunInternal(ParseResult parseResult, bool isHelp)
{
ValidationUtility.ValidateMutuallyExclusiveOptions(parseResult);
ValidationUtility.ValidateSolutionOrProjectOrDirectoryOrModulesArePassedCorrectly(parseResult);
int degreeOfParallelism = GetDegreeOfParallelism(parseResult);
var testOptions = new TestOptions(
IsHelp: isHelp,
IsDiscovery: parseResult.HasOption(MicrosoftTestingPlatformOptions.ListTestsOption),
EnvironmentVariables: parseResult.GetValue(CommonOptions.EnvOption) ?? ImmutableDictionary<string, string>.Empty);
BuildOptions buildOptions = MSBuildUtility.GetBuildOptions(parseResult);
bool filterModeEnabled = parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesFilterOption);
TestApplicationActionQueue actionQueue;
if (filterModeEnabled)
{
InitializeOutput(degreeOfParallelism, parseResult, testOptions);
actionQueue = new TestApplicationActionQueue(degreeOfParallelism, buildOptions, testOptions, _output, OnHelpRequested);
var testModulesFilterHandler = new TestModulesFilterHandler(actionQueue, _output);
if (!testModulesFilterHandler.RunWithTestModulesFilter(parseResult))
{
return ExitCode.GenericFailure;
}
}
else
{
var msBuildHandler = new MSBuildHandler(buildOptions);
if (!msBuildHandler.RunMSBuild())
{
return ExitCode.GenericFailure;
}
InitializeOutput(degreeOfParallelism, parseResult, testOptions);
// NOTE: Don't create TestApplicationActionQueue before RunMSBuild.
// The constructor will do Task.Run calls matching the degree of parallelism, and if we did that before the build, that can
// be slowing us down unnecessarily.
// Alternatively, if we can enqueue right after every project evaluation without waiting all evaluations to be done, we can enqueue early.
actionQueue = new TestApplicationActionQueue(degreeOfParallelism, buildOptions, testOptions, _output, OnHelpRequested);
if (!msBuildHandler.EnqueueTestApplications(actionQueue))
{
return ExitCode.GenericFailure;
}
}
actionQueue.EnqueueCompleted();
// Don't inline exitCode variable. We want to always call WaitAllActions first.
var exitCode = actionQueue.WaitAllActions();
exitCode = _output.HasHandshakeFailure ? ExitCode.GenericFailure : exitCode;
if (exitCode == ExitCode.Success &&
parseResult.HasOption(MicrosoftTestingPlatformOptions.MinimumExpectedTestsOption) &&
parseResult.GetValue(MicrosoftTestingPlatformOptions.MinimumExpectedTestsOption) is { } minimumExpectedTests &&
_output.TotalTests < minimumExpectedTests)
{
exitCode = ExitCode.MinimumExpectedTestsPolicyViolation;
}
return exitCode;
}
[MemberNotNull(nameof(_output))]
private void InitializeOutput(int degreeOfParallelism, ParseResult parseResult, TestOptions testOptions)
{
var console = new SystemConsole();
var showPassedTests = parseResult.GetValue(MicrosoftTestingPlatformOptions.OutputOption) == OutputOptions.Detailed;
var noProgress = parseResult.HasOption(MicrosoftTestingPlatformOptions.NoProgressOption);
var noAnsi = parseResult.HasOption(MicrosoftTestingPlatformOptions.NoAnsiOption);
// TODO: Replace this with proper CI detection that we already have in telemetry. https://github.com/microsoft/testfx/issues/5533#issuecomment-2838893327
bool inCI = string.Equals(Environment.GetEnvironmentVariable("TF_BUILD"), "true", StringComparison.OrdinalIgnoreCase) || string.Equals(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), "true", StringComparison.OrdinalIgnoreCase);
_output = new TerminalTestReporter(console, new TerminalTestReporterOptions()
{
ShowPassedTests = showPassedTests,
ShowProgress = !noProgress,
UseAnsi = !noAnsi,
UseCIAnsi = inCI,
ShowAssembly = true,
ShowAssemblyStartAndComplete = true,
MinimumExpectedTests = parseResult.GetValue(MicrosoftTestingPlatformOptions.MinimumExpectedTestsOption),
});
Console.CancelKeyPress += (s, e) =>
{
_output.StartCancelling();
};
// This is ugly, and we need to replace it by passing out some info from testing platform to inform us that some process level retry plugin is active.
var isRetry = parseResult.GetArguments().Contains("--retry-failed-tests");
_output.TestExecutionStarted(DateTimeOffset.Now, degreeOfParallelism, testOptions.IsDiscovery, testOptions.IsHelp, isRetry);
}
private static int GetDegreeOfParallelism(ParseResult parseResult)
{
var degreeOfParallelism = parseResult.GetValue(MicrosoftTestingPlatformOptions.MaxParallelTestModulesOption);
if (degreeOfParallelism <= 0)
degreeOfParallelism = Environment.ProcessorCount;
return degreeOfParallelism;
}
}
|