Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Cli/dotnet/Commands/Test/CliConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.DotNet.Cli.Commands.Test;
internal static class CliConstants
{
public const string ServerOptionKey = "--server";
public const string HelpOptionKey = "--help";
public const string DotNetTestPipeOptionKey = "--dotnet-test-pipe";

public const string ServerOptionValue = "dotnettestcli";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,38 +66,38 @@ public static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOf
var msbuildArgs = parseResult.OptionValuesToBeForwarded(TestCommandParser.GetCommand())
.Concat(binLogArgs);

string? resultsDirectory = parseResult.GetValue(TestingPlatformOptions.ResultsDirectoryOption);
string? resultsDirectory = parseResult.GetValue(MicrosoftTestingPlatformOptions.ResultsDirectoryOption);
if (resultsDirectory is not null)
{
resultsDirectory = Path.GetFullPath(resultsDirectory);
}

string? configFile = parseResult.GetValue(TestingPlatformOptions.ConfigFileOption);
string? configFile = parseResult.GetValue(MicrosoftTestingPlatformOptions.ConfigFileOption);
if (configFile is not null)
{
configFile = Path.GetFullPath(configFile);
}

string? diagnosticOutputDirectory = parseResult.GetValue(TestingPlatformOptions.DiagnosticOutputDirectoryOption);
string? diagnosticOutputDirectory = parseResult.GetValue(MicrosoftTestingPlatformOptions.DiagnosticOutputDirectoryOption);
if (diagnosticOutputDirectory is not null)
{
diagnosticOutputDirectory = Path.GetFullPath(diagnosticOutputDirectory);
}

PathOptions pathOptions = new(
parseResult.GetValue(TestingPlatformOptions.ProjectOption),
parseResult.GetValue(TestingPlatformOptions.SolutionOption),
parseResult.GetValue(MicrosoftTestingPlatformOptions.ProjectOption),
parseResult.GetValue(MicrosoftTestingPlatformOptions.SolutionOption),
resultsDirectory,
configFile,
diagnosticOutputDirectory);

return new BuildOptions(
pathOptions,
parseResult.GetValue(CommonOptions.NoRestoreOption),
parseResult.GetValue(TestingPlatformOptions.NoBuildOption),
parseResult.GetValue(MicrosoftTestingPlatformOptions.NoBuildOption),
parseResult.HasOption(TestCommandParser.VerbosityOption) ? parseResult.GetValue(TestCommandParser.VerbosityOption) : null,
parseResult.GetValue(TestingPlatformOptions.NoLaunchProfileOption),
parseResult.GetValue(TestingPlatformOptions.NoLaunchProfileArgumentsOption),
parseResult.GetValue(MicrosoftTestingPlatformOptions.NoLaunchProfileOption),
parseResult.GetValue(MicrosoftTestingPlatformOptions.NoLaunchProfileArgumentsOption),
degreeOfParallelism,
otherArgs,
msbuildArgs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Microsoft.DotNet.Cli.Commands.Test;

internal static class TestingPlatformOptions
internal static class MicrosoftTestingPlatformOptions
{
public static readonly Option<string> ProjectOption = new("--project")
{
Expand Down Expand Up @@ -111,11 +111,6 @@ internal static class TestingPlatformOptions
Description = CliCommandStrings.CmdListTestsDescription,
Arity = ArgumentArity.Zero
};

public static readonly Option<string> HelpOption = new("--help", ["-h", "-?"])
{
Arity = ArgumentArity.Zero
};
}

internal enum OutputOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Microsoft.DotNet.Cli.Commands.Test;

internal partial class TestingPlatformCommand
internal partial class MicrosoftTestingPlatformTestCommand
{
private readonly ConcurrentDictionary<string, CommandLineOption> _commandLineOptionNameToModuleNames = [];
private readonly ConcurrentDictionary<bool, List<(string, string[])>> _moduleNamesToCommandLineOptions = [];
Expand All @@ -22,7 +22,7 @@ public IEnumerable<Action<HelpContext>> CustomHelpLayout()
WriteHelpOptions(context);
Console.WriteLine(CliCommandStrings.HelpWaitingForOptionsAndExtensions);

Run(context.ParseResult);
Run(context.ParseResult, isHelp: true);

if (_commandLineOptionNameToModuleNames.IsEmpty)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,24 @@

namespace Microsoft.DotNet.Cli.Commands.Test;

internal partial class TestingPlatformCommand : Command, ICustomHelp
internal partial class MicrosoftTestingPlatformTestCommand : Command, ICustomHelp, ICommandDocument
{
private MSBuildHandler _msBuildHandler;
private TerminalTestReporter _output;
private TestApplicationActionQueue _actionQueue;

private byte _cancelled;
private bool _isDiscovery;
private bool _isRetry;

public TestingPlatformCommand(string name, string description = null) : base(name, description)
public MicrosoftTestingPlatformTestCommand(string name, string description = null) : base(name, description)
{
TreatUnmatchedTokensAsErrors = false;
}

public int Run(ParseResult parseResult)
public string DocsLink => "https://aka.ms/dotnet-test";

public int Run(ParseResult parseResult, bool isHelp = false)
{
int? exitCode = null;
try
{
exitCode = RunInternal(parseResult);
exitCode = RunInternal(parseResult, isHelp);
return exitCode.Value;
}
finally
Expand All @@ -39,49 +36,53 @@ public int Run(ParseResult parseResult)
}
}

private int RunInternal(ParseResult parseResult)
private int RunInternal(ParseResult parseResult, bool isHelp)
{
ValidationUtility.ValidateMutuallyExclusiveOptions(parseResult);
ValidationUtility.ValidateSolutionOrProjectOrDirectoryOrModulesArePassedCorrectly(parseResult);

PrepareEnvironment(parseResult, out TestOptions testOptions, out int degreeOfParallelism);
int degreeOfParallelism = GetDegreeOfParallelism(parseResult);
bool filterModeEnabled = parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesFilterOption);
var testOptions = new TestOptions(filterModeEnabled, IsHelp: isHelp, IsDiscovery: parseResult.HasOption(MicrosoftTestingPlatformOptions.ListTestsOption));

InitializeOutput(degreeOfParallelism, parseResult, testOptions);

InitializeOutput(degreeOfParallelism, parseResult, testOptions.IsHelp);
SetupCancelKeyPressHandler();

BuildOptions buildOptions = MSBuildUtility.GetBuildOptions(parseResult, degreeOfParallelism);

InitializeActionQueue(degreeOfParallelism, testOptions, buildOptions);
var actionQueue = InitializeActionQueue(degreeOfParallelism, testOptions, buildOptions);

_msBuildHandler = new(buildOptions, _actionQueue, _output);
TestModulesFilterHandler testModulesFilterHandler = new(_actionQueue, _output);
var msBuildHandler = new MSBuildHandler(buildOptions, actionQueue, _output);

if (testOptions.HasFilterMode)
{
var testModulesFilterHandler = new TestModulesFilterHandler(actionQueue, _output);
if (!testModulesFilterHandler.RunWithTestModulesFilter(parseResult))
{
return ExitCode.GenericFailure;
}
}
else
{
if (!_msBuildHandler.RunMSBuild())
if (!msBuildHandler.RunMSBuild())
{
return ExitCode.GenericFailure;
}

if (!_msBuildHandler.EnqueueTestApplications())
if (!msBuildHandler.EnqueueTestApplications())
{
return ExitCode.GenericFailure;
}
}

_actionQueue.EnqueueCompleted();
actionQueue.EnqueueCompleted();
// Don't inline exitCode variable. We want to always call WaitAllActions first.
var exitCode = _actionQueue.WaitAllActions();
var exitCode = actionQueue.WaitAllActions();
exitCode = _output.HasHandshakeFailure ? ExitCode.GenericFailure : exitCode;
if (exitCode == ExitCode.Success &&
parseResult.HasOption(TestingPlatformOptions.MinimumExpectedTestsOption) &&
parseResult.GetValue(TestingPlatformOptions.MinimumExpectedTestsOption) is { } minimumExpectedTests &&
parseResult.HasOption(MicrosoftTestingPlatformOptions.MinimumExpectedTestsOption) &&
parseResult.GetValue(MicrosoftTestingPlatformOptions.MinimumExpectedTestsOption) is { } minimumExpectedTests &&
_output.TotalTests < minimumExpectedTests)
{
exitCode = ExitCode.MinimumExpectedTestsPolicyViolation;
Expand All @@ -90,26 +91,9 @@ private int RunInternal(ParseResult parseResult)
return exitCode;
}

private void PrepareEnvironment(ParseResult parseResult, out TestOptions testOptions, out int degreeOfParallelism)
private TestApplicationActionQueue InitializeActionQueue(int degreeOfParallelism, TestOptions testOptions, BuildOptions buildOptions)
{
SetupCancelKeyPressHandler();

degreeOfParallelism = GetDegreeOfParallelism(parseResult);

bool filterModeEnabled = parseResult.HasOption(TestingPlatformOptions.TestModulesFilterOption);

var arguments = parseResult.GetArguments();
testOptions = GetTestOptions(filterModeEnabled, isHelp: ContainsHelpOption(arguments));

_isDiscovery = ContainsListTestsOption(arguments);

// 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.
_isRetry = arguments.Contains("--retry-failed-tests");
}

private void InitializeActionQueue(int degreeOfParallelism, TestOptions testOptions, BuildOptions buildOptions)
{
_actionQueue = new TestApplicationActionQueue(degreeOfParallelism, buildOptions, testOptions, _output, async (TestApplication testApp) =>
return new TestApplicationActionQueue(degreeOfParallelism, buildOptions, testOptions, _output, async (TestApplication testApp) =>
{
testApp.HelpRequested += OnHelpRequested;
return await testApp.RunAsync();
Expand All @@ -126,12 +110,12 @@ private void SetupCancelKeyPressHandler()
};
}

private void InitializeOutput(int degreeOfParallelism, ParseResult parseResult, bool isHelp)
private void InitializeOutput(int degreeOfParallelism, ParseResult parseResult, TestOptions testOptions)
{
var console = new SystemConsole();
var showPassedTests = parseResult.GetValue(TestingPlatformOptions.OutputOption) == OutputOptions.Detailed;
var noProgress = parseResult.HasOption(TestingPlatformOptions.NoProgressOption);
var noAnsi = parseResult.HasOption(TestingPlatformOptions.NoAnsiOption);
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);
Expand All @@ -144,33 +128,23 @@ private void InitializeOutput(int degreeOfParallelism, ParseResult parseResult,
UseCIAnsi = inCI,
ShowAssembly = true,
ShowAssemblyStartAndComplete = true,
MinimumExpectedTests = parseResult.GetValue(TestingPlatformOptions.MinimumExpectedTestsOption),
MinimumExpectedTests = parseResult.GetValue(MicrosoftTestingPlatformOptions.MinimumExpectedTestsOption),
});

_output.TestExecutionStarted(DateTimeOffset.Now, degreeOfParallelism, _isDiscovery, isHelp, _isRetry);
// 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(TestingPlatformOptions.MaxParallelTestModulesOption);
var degreeOfParallelism = parseResult.GetValue(MicrosoftTestingPlatformOptions.MaxParallelTestModulesOption);
if (degreeOfParallelism <= 0)
degreeOfParallelism = Environment.ProcessorCount;
return degreeOfParallelism;
}

private static TestOptions GetTestOptions(bool hasFilterMode, bool isHelp) =>
new(hasFilterMode, isHelp);

private static bool ContainsHelpOption(IEnumerable<string> args)
{
return args.Contains(TestingPlatformOptions.HelpOption.Name) || TestingPlatformOptions.HelpOption.Aliases.Any(alias => args.Contains(alias));
}

private static bool ContainsListTestsOption(IEnumerable<string> args)
{
return args.Contains(TestingPlatformOptions.ListTestsOption.Name);
}

private void CompleteRun(int? exitCode)
{
if (Interlocked.CompareExchange(ref _cancelled, 1, 0) == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Microsoft.DotNet.Cli.Commands.Test;

internal record TestOptions(bool HasFilterMode, bool IsHelp);
internal record TestOptions(bool HasFilterMode, bool IsHelp, bool IsDiscovery);

internal record PathOptions(string? ProjectPath, string? SolutionPath, string? ResultsDirectoryPath, string? ConfigFilePath, string? DiagnosticOutputDirectoryPath);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,27 @@ private string GetArguments()

if (TestOptions.IsHelp)
{
builder.Append($" {TestingPlatformOptions.HelpOption.Name}");
builder.Append($" {CliConstants.HelpOptionKey}");
}

if (TestOptions.IsDiscovery)
{
builder.Append($" {MicrosoftTestingPlatformOptions.ListTestsOption.Name}");
}

if (_buildOptions.PathOptions.ResultsDirectoryPath is { } resultsDirectoryPath)
{
builder.Append($" {TestingPlatformOptions.ResultsDirectoryOption.Name} {ArgumentEscaper.EscapeSingleArg(resultsDirectoryPath)}");
builder.Append($" {MicrosoftTestingPlatformOptions.ResultsDirectoryOption.Name} {ArgumentEscaper.EscapeSingleArg(resultsDirectoryPath)}");
}

if (_buildOptions.PathOptions.ConfigFilePath is { } configFilePath)
{
builder.Append($" {TestingPlatformOptions.ConfigFileOption.Name} {ArgumentEscaper.EscapeSingleArg(configFilePath)}");
builder.Append($" {MicrosoftTestingPlatformOptions.ConfigFileOption.Name} {ArgumentEscaper.EscapeSingleArg(configFilePath)}");
}

if (_buildOptions.PathOptions.DiagnosticOutputDirectoryPath is { } diagnosticOutputDirectoryPath)
{
builder.Append($" {TestingPlatformOptions.DiagnosticOutputDirectoryOption.Name} {ArgumentEscaper.EscapeSingleArg(diagnosticOutputDirectoryPath)}");
builder.Append($" {MicrosoftTestingPlatformOptions.DiagnosticOutputDirectoryOption.Name} {ArgumentEscaper.EscapeSingleArg(diagnosticOutputDirectoryPath)}");
}

foreach (var arg in _buildOptions.UnmatchedTokens)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ internal sealed class TestModulesFilterHandler(TestApplicationActionQueue action
public bool RunWithTestModulesFilter(ParseResult parseResult)
{
// If the module path pattern(s) was provided, we will use that to filter the test modules
string testModules = parseResult.GetValue(TestingPlatformOptions.TestModulesFilterOption);
string testModules = parseResult.GetValue(MicrosoftTestingPlatformOptions.TestModulesFilterOption);

// If the root directory was provided, we will use that to search for the test modules
// Otherwise, we will use the current directory
string rootDirectory = Directory.GetCurrentDirectory();
if (parseResult.HasOption(TestingPlatformOptions.TestModulesRootDirectoryOption))
if (parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesRootDirectoryOption))
{
rootDirectory = parseResult.GetValue(TestingPlatformOptions.TestModulesRootDirectoryOption);
rootDirectory = parseResult.GetValue(MicrosoftTestingPlatformOptions.TestModulesRootDirectoryOption);

// If the root directory is not valid, we simply return
if (string.IsNullOrEmpty(rootDirectory) || !Directory.Exists(rootDirectory))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public static void ValidateMutuallyExclusiveOptions(ParseResult parseResult)
static void ValidatePathOptions(ParseResult parseResult)
{
var count = 0;
if (parseResult.HasOption(TestingPlatformOptions.TestModulesFilterOption))
if (parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesFilterOption))
count++;

if (parseResult.HasOption(TestingPlatformOptions.SolutionOption))
if (parseResult.HasOption(MicrosoftTestingPlatformOptions.SolutionOption))
count++;

if (parseResult.HasOption(TestingPlatformOptions.ProjectOption))
if (parseResult.HasOption(MicrosoftTestingPlatformOptions.ProjectOption))
count++;

if (count > 1)
Expand All @@ -35,14 +35,14 @@ static void ValidatePathOptions(ParseResult parseResult)

static void ValidateOptionsIrrelevantToModulesFilter(ParseResult parseResult)
{
if (!parseResult.HasOption(TestingPlatformOptions.TestModulesFilterOption))
if (!parseResult.HasOption(MicrosoftTestingPlatformOptions.TestModulesFilterOption))
{
return;
}

if (parseResult.HasOption(CommonOptions.ArchitectureOption) ||
parseResult.HasOption(TestingPlatformOptions.ConfigurationOption) ||
parseResult.HasOption(TestingPlatformOptions.FrameworkOption) ||
parseResult.HasOption(MicrosoftTestingPlatformOptions.ConfigurationOption) ||
parseResult.HasOption(MicrosoftTestingPlatformOptions.FrameworkOption) ||
parseResult.HasOption(CommonOptions.OperatingSystemOption) ||
parseResult.HasOption(CommonOptions.RuntimeOptionName))
{
Expand Down
Loading