| File: Commands\Test\TestCommandDefinition.MicrosoftTestingPlatform.cs | Web Access |
| Project: src\sdk\src\Cli\Microsoft.DotNet.Cli.Definitions\Microsoft.DotNet.Cli.Definitions.csproj (Microsoft.DotNet.Cli.Definitions) |
// 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.ObjectModel; using System.CommandLine; using System.CommandLine.Parsing; using System.Globalization; using System.Text.RegularExpressions; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Help; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Cli.Commands.Test; internal abstract partial class TestCommandDefinition { public sealed partial class MicrosoftTestingPlatform : TestCommandDefinition, ICustomHelp { private const long MaxSupportedTimeoutMilliseconds = 0xfffffffe; public readonly Option<string> ProjectOrSolutionOption = new("--project") { Description = CommandDefinitionStrings.CmdProjectOrSolutionDescriptionFormat, HelpName = CommandDefinitionStrings.CmdProjectOrSolutionPathName, Arity = ArgumentArity.ExactlyOne }; public readonly Option<string> SolutionOption = new("--solution") { Description = CommandDefinitionStrings.CmdSolutionDescription, HelpName = CommandDefinitionStrings.CmdSolutionPathName, Arity = ArgumentArity.ExactlyOne }; public readonly Option<string> TestModulesFilterOption = new("--test-modules") { Description = CommandDefinitionStrings.CmdTestModulesDescription, HelpName = CommandDefinitionStrings.CmdExpressionName }; public readonly Option<string> TestModulesRootDirectoryOption = new("--root-directory") { Description = CommandDefinitionStrings.CmdTestModulesRootDirectoryDescription, HelpName = CommandDefinitionStrings.CmdRootPathName, }; public const string ResultsDirectoryOptionName = "--results-directory"; public readonly Option<string> ResultsDirectoryOption = new(ResultsDirectoryOptionName) { Description = CommandDefinitionStrings.CmdResultsDirectoryDescription, HelpName = CommandDefinitionStrings.CmdPathToResultsDirectory, Arity = ArgumentArity.ExactlyOne }; public readonly Option<string> ResultsDirectoryLayoutOption = new Option<string>("--results-directory-layout") { Description = CommandDefinitionStrings.CmdResultsDirectoryLayoutDescription, HelpName = CommandDefinitionStrings.CmdResultsDirectoryLayoutName, Arity = ArgumentArity.ExactlyOne }.AcceptOnlyFromAmong("flat", "per-module"); public const string ConfigFileOptionName = "--config-file"; public readonly Option<string> ConfigFileOption = new(ConfigFileOptionName) { Description = CommandDefinitionStrings.CmdConfigFileDescription, HelpName = CommandDefinitionStrings.CmdConfigFilePath, Arity = ArgumentArity.ExactlyOne }; public const string DiagnosticOutputDirectoryOptionName = "--diagnostic-output-directory"; public readonly Option<string> DiagnosticOutputDirectoryOption = new(DiagnosticOutputDirectoryOptionName) { Description = CommandDefinitionStrings.CmdDiagnosticOutputDirectoryDescription, HelpName = CommandDefinitionStrings.CmdDiagnosticOutputDirectoryPath, Arity = ArgumentArity.ExactlyOne }; public readonly Option<int> MaxParallelTestModulesOption = new("--max-parallel-test-modules") { Description = CommandDefinitionStrings.CmdMaxParallelTestModulesDescription, HelpName = CommandDefinitionStrings.CmdNumberName }; public readonly Option<int> MinimumExpectedTestsOption = new("--minimum-expected-tests") { Description = CommandDefinitionStrings.CmdMinimumExpectedTestsDescription, HelpName = CommandDefinitionStrings.CmdNumberName }; public readonly Option<int?> MaximumFailedTestsOption = new("--maximum-failed-tests") { Description = CommandDefinitionStrings.CmdMaximumFailedTestsDescription, HelpName = CommandDefinitionStrings.CmdNumberName, Arity = ArgumentArity.ExactlyOne }; public readonly Option<TimeSpan?> TimeoutOption = new("--timeout") { Description = CommandDefinitionStrings.CmdTimeoutDescription, HelpName = CommandDefinitionStrings.CmdDurationName, Arity = ArgumentArity.ExactlyOne, CustomParser = ParseTimeout }; public readonly Option<IReadOnlyDictionary<string, string>> EnvOption = CommonOptions.CreateEnvOption(); public readonly Option<ReadOnlyDictionary<string, string>?> PropertiesOption = CommonOptions.CreatePropertyOption(); public readonly Option<bool> NoRestoreOption = CommonOptions.CreateNoRestoreOption(); public readonly Option<bool> NoBuildOption = new("--no-build") { Description = CommandDefinitionStrings.CmdNoBuildDescription }; public readonly Option<bool> NoLogoOption = CommonOptions.CreateNoLogoOption( defaultValue: false, forwardAs: null, description: CommandDefinitionStrings.TestCmdNoLogo); public readonly Option<bool> UseCurrentRuntimeOption = CommonOptions.CreateUseCurrentRuntimeOption(CommandDefinitionStrings.CmdCurrentRuntimeOptionDescription); public readonly Option<bool> NoDependenciesOption = new Option<bool>("--no-dependencies") { Description = CommandDefinitionStrings.NoDependenciesOptionDescription, Arity = ArgumentArity.Zero }.ForwardAs("--property:BuildProjectReferences=false"); public readonly Option<bool> NoAnsiOption = new("--no-ansi") { Description = CommandDefinitionStrings.CmdNoAnsiDescription, Arity = ArgumentArity.Zero }; public readonly Option<bool> NoProgressOption = new("--no-progress") { Description = CommandDefinitionStrings.CmdNoProgressDescription, Arity = ArgumentArity.Zero }; public readonly Option<bool> NoArtifactPostProcessingOption = new("--no-artifact-post-processing") { Description = CommandDefinitionStrings.CmdNoArtifactPostProcessingDescription, Arity = ArgumentArity.Zero }; public readonly Option<OutputOptions> OutputOption = new("--output") { Description = CommandDefinitionStrings.CmdTestOutputDescription, Arity = ArgumentArity.ExactlyOne }; public const string ListTestsOptionName = "--list-tests"; public const string ListTestsFormatText = "text"; public const string ListTestsFormatJson = "json"; public readonly Option<string> ListTestsOption = new Option<string>(ListTestsOptionName) { Description = CommandDefinitionStrings.CmdListTestsDescription, HelpName = $"{ListTestsFormatText}|{ListTestsFormatJson}", Arity = ArgumentArity.ZeroOrOne }.AcceptOnlyFromAmong(ListTestsFormatText, ListTestsFormatJson); public readonly Option<bool> NoLaunchProfileOption = new("--no-launch-profile") { Description = CommandDefinitionStrings.CommandOptionNoLaunchProfileDescription, Arity = ArgumentArity.Zero }; public readonly Option<bool> NoLaunchProfileArgumentsOption = new("--no-launch-profile-arguments") { Description = CommandDefinitionStrings.CommandOptionNoLaunchProfileArgumentsDescription }; public readonly Option<string> DeviceOption = new("--device") { Description = CommandDefinitionStrings.CommandOptionDeviceDescriptionForTest, HelpName = CommandDefinitionStrings.CommandOptionDeviceHelpName }; public readonly Option<bool> ListDevicesOption = new("--list-devices") { Description = CommandDefinitionStrings.CommandOptionListDevicesDescriptionForTest, Arity = ArgumentArity.Zero }; public const string EnableAffectedTestsEnvironmentVariable = "DOTNET_CLI_ENABLE_AFFECTED_TESTS"; public const string CollectTestMapOptionName = "--collect-test-map"; public readonly Option<bool> CollectTestMapOption; public const string AffectedTestsOptionName = "--affected-tests"; public readonly Option<bool> AffectedTestsOption; public bool AffectedTestsEnabled { get; } public readonly Option<string> ArtifactsPathOption = CommonOptions.CreateArtifactsPathOption(); public const string BuildTargetName = "_MTPBuild"; public readonly Option<string[]> MTPTargetOption = CommonOptions.CreateRequiredMSBuildTargetOption(BuildTargetName); public ICustomHelp? CustomHelpLayoutProvider { get; set; } public MicrosoftTestingPlatform() : base(CommandDefinitionStrings.DotnetTestCommandMTPDescription) { MinimumExpectedTestsOption.Validators.Add(ValidatePositiveInteger); MaximumFailedTestsOption.Validators.Add(ValidatePositiveInteger); AffectedTestsEnabled = EnvironmentVariableParser.ParseBool( Environment.GetEnvironmentVariable(EnableAffectedTestsEnvironmentVariable), defaultValue: false); CollectTestMapOption = new(CollectTestMapOptionName) { Description = CommandDefinitionStrings.CmdCollectTestMapDescription, Arity = ArgumentArity.Zero, Hidden = !AffectedTestsEnabled, }; AffectedTestsOption = new(AffectedTestsOptionName) { Description = CommandDefinitionStrings.CmdAffectedTestsDescription, Arity = ArgumentArity.Zero, Hidden = !AffectedTestsEnabled, }; Options.Add(ProjectOrSolutionOption); Options.Add(SolutionOption); Options.Add(TestModulesFilterOption); Options.Add(TestModulesRootDirectoryOption); Options.Add(ResultsDirectoryOption); Options.Add(ResultsDirectoryLayoutOption); Options.Add(ConfigFileOption); Options.Add(DiagnosticOutputDirectoryOption); Options.Add(MaxParallelTestModulesOption); Options.Add(MinimumExpectedTestsOption); Options.Add(MaximumFailedTestsOption); Options.Add(TimeoutOption); Options.Add(EnvOption); Options.Add(PropertiesOption); Options.Add(ConfigurationOption); Options.Add(FrameworkOption); TargetPlatformOptions.AddTo(Options); Options.Add(VerbosityOption); Options.Add(NoRestoreOption); Options.Add(NoBuildOption); NoLogoOption.Aliases.Add("--no-banner"); Options.Add(NoLogoOption); Options.Add(NoDependenciesOption); Options.Add(ArtifactsPathOption); Options.Add(UseCurrentRuntimeOption); Options.Add(NoAnsiOption); Options.Add(NoProgressOption); Options.Add(NoArtifactPostProcessingOption); Options.Add(OutputOption); Options.Add(ListTestsOption); Options.Add(NoLaunchProfileOption); Options.Add(NoLaunchProfileArgumentsOption); Options.Add(DeviceOption); Options.Add(ListDevicesOption); Options.Add(CollectTestMapOption); Options.Add(AffectedTestsOption); Options.Add(MTPTargetOption); Validators.Add(commandResult => { bool collectTestMap = commandResult.HasOption(CollectTestMapOption); bool affectedTests = commandResult.HasOption(AffectedTestsOption); if (!AffectedTestsEnabled && (collectTestMap || affectedTests)) { commandResult.AddError(string.Format( CommandDefinitionStrings.CmdAffectedTestsFeatureDisabled, EnableAffectedTestsEnvironmentVariable)); } else if (collectTestMap && affectedTests) { commandResult.AddError(CommandDefinitionStrings.CmdAffectedTestsOptionsMutuallyExclusive); } else if (collectTestMap && commandResult.HasOption(MaxParallelTestModulesOption)) { commandResult.AddError(CommandDefinitionStrings.CmdCollectTestMapCannotRunModulesInParallel); } else if (collectTestMap && commandResult.HasOption(MinimumExpectedTestsOption)) { commandResult.AddError(CommandDefinitionStrings.CmdCollectTestMapCannotRequireMinimumTests); } }); } public IEnumerable<Action<HelpContext>> CustomHelpLayout() => CustomHelpLayoutProvider?.CustomHelpLayout() ?? []; private static void ValidatePositiveInteger(OptionResult optionResult) { if (optionResult.Tokens.Count == 1 && int.TryParse(optionResult.Tokens[0].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value) && value <= 0) { optionResult.AddError(CommandDefinitionStrings.CmdTestPositiveIntegerRequired); } } private static TimeSpan? ParseTimeout(ArgumentResult argumentResult) { if (argumentResult.Tokens.Count != 1) { return null; } string value = argumentResult.Tokens[0].Value; Match match = TimeoutPattern().Match(value); if (!match.Success || !double.TryParse(match.Groups["value"].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double number)) { argumentResult.AddError(CommandDefinitionStrings.CmdTestInvalidTimeout); return null; } string suffix = match.Groups["suffix"].Value; TimeSpan timeout; try { timeout = suffix.StartsWith("ms", StringComparison.OrdinalIgnoreCase) || suffix.StartsWith("mil", StringComparison.OrdinalIgnoreCase) ? TimeSpan.FromMilliseconds(number) : suffix.StartsWith("s", StringComparison.OrdinalIgnoreCase) ? TimeSpan.FromSeconds(number) : suffix.StartsWith("m", StringComparison.OrdinalIgnoreCase) ? TimeSpan.FromMinutes(number) : suffix.StartsWith("h", StringComparison.OrdinalIgnoreCase) ? TimeSpan.FromHours(number) : TimeSpan.FromDays(number); } catch (Exception ex) when (ex is OverflowException or ArgumentException) { argumentResult.AddError(CommandDefinitionStrings.CmdTestInvalidTimeout); return null; } if (timeout <= TimeSpan.Zero || timeout.TotalMilliseconds > MaxSupportedTimeoutMilliseconds) { argumentResult.AddError(CommandDefinitionStrings.CmdTestInvalidTimeout); return null; } return timeout; } [GeneratedRegex( @"^(?<value>\d+(?:\.\d+)?)(?:\s*(?<suffix>ms|mils?|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?|d|days?))$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex TimeoutPattern(); } }