| File: Commands\Test\MTP\TestCommandOptions.cs | Web Access |
| Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet) |
// 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 Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Utils; #if !CLI_AOT using Microsoft.DotNet.FileBasedPrograms; #endif namespace Microsoft.DotNet.Cli.Commands.Test; internal static class TestCommandOptions { public static BuildOptions GetBuildOptions(ParseResult parseResult) { var definition = (TestCommandDefinition.MicrosoftTestingPlatform)parseResult.CommandResult.Command; ImmutableArray<string> unmatchedTokens = [.. parseResult.UnmatchedTokens]; ImmutableArray<string> otherArgs; #if CLI_AOT // Eligibility requires an explicit test-module expression and permits test-application // arguments only after the separator, so no positional project/module inference is needed. if (!CommonRunHelpers.TrySplitApplicationArgumentsAtDoubleDash( parseResult, unmatchedTokens, out int unmatchedTokenCountBeforeDoubleDash, out string[] argumentsAfterDoubleDash) || unmatchedTokenCountBeforeDoubleDash != 0) { otherArgs = []; } else { otherArgs = [.. argumentsAfterDoubleDash]; } IEnumerable<string> msbuildArgs = []; #else ImmutableArray<string> loggerArgs; int positionalArgumentCount; if (CommonRunHelpers.TrySplitApplicationArgumentsAtDoubleDash( parseResult, unmatchedTokens, out int unmatchedTokenCountBeforeDoubleDash, out string[] argumentsAfterDoubleDash)) { LoggerUtility.SeparateLoggerArguments( unmatchedTokens[..unmatchedTokenCountBeforeDoubleDash], out loggerArgs, out var argumentsBeforeDoubleDash); positionalArgumentCount = argumentsBeforeDoubleDash.Length; otherArgs = [.. argumentsBeforeDoubleDash, .. argumentsAfterDoubleDash]; } else { LoggerUtility.SeparateLoggerArguments(unmatchedTokens, out loggerArgs, out otherArgs); positionalArgumentCount = otherArgs.Length; } #endif if (parseResult.GetValue(definition.NoLogoOption) && !otherArgs.Contains("--no-banner")) { otherArgs = otherArgs.Add("--no-banner"); } #if !CLI_AOT var (positionalProjectOrSolution, positionalTestModules) = GetPositionalArguments( positionalArgumentCount, ref otherArgs); IEnumerable<string> msbuildArgs = parseResult.OptionValuesToBeForwarded(definition) .Concat(loggerArgs); #endif string? resultsDirectory = parseResult.GetValue(definition.ResultsDirectoryOption); if (resultsDirectory is not null) { resultsDirectory = Path.GetFullPath(resultsDirectory); } string? configFile = parseResult.GetValue(definition.ConfigFileOption); if (configFile is not null) { configFile = Path.GetFullPath(configFile); } string? diagnosticOutputDirectory = parseResult.GetValue(definition.DiagnosticOutputDirectoryOption); if (diagnosticOutputDirectory is not null) { diagnosticOutputDirectory = Path.GetFullPath(diagnosticOutputDirectory); } string? projectOrSolutionOptionValue = parseResult.GetValue(definition.ProjectOrSolutionOption); string? testModulesFilterOptionValue = parseResult.GetValue(definition.TestModulesFilterOption); #if !CLI_AOT if ((projectOrSolutionOptionValue is not null && positionalProjectOrSolution is not null) || (testModulesFilterOptionValue is not null && positionalTestModules is not null)) { throw new GracefulException(CliCommandStrings.CmdMultipleBuildPathOptionsErrorDescription); } #endif PathOptions pathOptions = new( #if CLI_AOT projectOrSolutionOptionValue, #else positionalProjectOrSolution ?? projectOrSolutionOptionValue, #endif parseResult.GetValue(definition.SolutionOption), #if CLI_AOT testModulesFilterOptionValue, #else positionalTestModules ?? testModulesFilterOptionValue, #endif resultsDirectory, parseResult.GetValue(definition.ResultsDirectoryLayoutOption) == "per-module" ? ResultsDirectoryLayout.PerModule : ResultsDirectoryLayout.Flat, configFile, diagnosticOutputDirectory, parseResult.HasOption(definition.ResultsDirectoryLayoutOption)); return new BuildOptions( pathOptions, parseResult.GetValue(definition.NoRestoreOption), parseResult.GetValue(definition.NoBuildOption), parseResult.HasOption(definition.VerbosityOption) ? parseResult.GetValue(definition.VerbosityOption) : null, parseResult.GetValue(definition.NoLaunchProfileOption), parseResult.GetValue(definition.NoLaunchProfileArgumentsOption), otherArgs, msbuildArgs, Device: parseResult.GetValue(definition.DeviceOption), ListDevices: parseResult.GetValue(definition.ListDevicesOption), EnvironmentVariables: parseResult.GetValue(definition.EnvOption) ?? ImmutableDictionary<string, string>.Empty); } #if !CLI_AOT private static (string? PositionalProjectOrSolution, string? PositionalTestModules) GetPositionalArguments( int positionalArgumentCount, ref ImmutableArray<string> otherArgs) { string? positionalProjectOrSolution = null; string? positionalTestModules = null; // This validation only improves diagnostics for inputs that would otherwise fail. Users can // disable it for a valid extension-specific scenario. bool throwOnUnexpectedFilePassedAsNonFirstPositionalArgument = Environment.GetEnvironmentVariable("DOTNET_TEST_DISABLE_SWITCH_VALIDATION") is not ("true" or "1"); for (int i = 0; i < positionalArgumentCount; i++) { string token = otherArgs[i]; if ((token.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) || token.EndsWith(".slnf", StringComparison.OrdinalIgnoreCase) || token.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase)) && File.Exists(token)) { if (i == 0) { positionalProjectOrSolution = token; otherArgs = otherArgs.RemoveAt(0); break; } else if (throwOnUnexpectedFilePassedAsNonFirstPositionalArgument) { throw new GracefulException(CliCommandStrings.TestCommandUseSolution); } } else if (Path.GetExtension(token).EndsWith("proj", StringComparison.OrdinalIgnoreCase) && File.Exists(token)) { // Accept every MSBuild project extension ending in "proj", matching project-path validation. if (i == 0) { positionalProjectOrSolution = token; otherArgs = otherArgs.RemoveAt(0); break; } else if (throwOnUnexpectedFilePassedAsNonFirstPositionalArgument) { throw new GracefulException(CliCommandStrings.TestCommandUseProject); } } else if (VirtualProjectBuilder.IsValidEntryPointPath(token, requireFileToExist: i != 0)) { if (i == 0) { positionalProjectOrSolution = token; otherArgs = otherArgs.RemoveAt(0); break; } else if (throwOnUnexpectedFilePassedAsNonFirstPositionalArgument) { throw new GracefulException(CliCommandStrings.TestCommandUseProject); } } else if ((token.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || token.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) && File.Exists(token)) { if (i == 0) { positionalTestModules = token; otherArgs = otherArgs.RemoveAt(0); break; } else if (throwOnUnexpectedFilePassedAsNonFirstPositionalArgument) { throw new GracefulException(CliCommandStrings.TestCommandUseTestModules); } } else if (Directory.Exists(token)) { if (i == 0) { positionalProjectOrSolution = token; otherArgs = otherArgs.RemoveAt(0); break; } else if (throwOnUnexpectedFilePassedAsNonFirstPositionalArgument) { throw new GracefulException(CliCommandStrings.TestCommandUseDirectoryWithSwitch); } } } return (positionalProjectOrSolution, positionalTestModules); } #endif }