Skip to content

Support msbuild params in dotnet test #46562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
11 changes: 6 additions & 5 deletions src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,21 @@ public bool RunMSBuild(BuildOptions buildOptions)

int msBuildExitCode;
string path;
PathOptions pathOptions = buildOptions.PathOptions;

if (!string.IsNullOrEmpty(buildOptions.ProjectPath))
if (!string.IsNullOrEmpty(pathOptions.ProjectPath))
{
path = PathUtility.GetFullPath(buildOptions.ProjectPath);
path = PathUtility.GetFullPath(pathOptions.ProjectPath);
msBuildExitCode = RunBuild(path, isSolution: false, buildOptions);
}
else if (!string.IsNullOrEmpty(buildOptions.SolutionPath))
else if (!string.IsNullOrEmpty(pathOptions.SolutionPath))
{
path = PathUtility.GetFullPath(buildOptions.SolutionPath);
path = PathUtility.GetFullPath(pathOptions.SolutionPath);
msBuildExitCode = RunBuild(path, isSolution: true, buildOptions);
}
else
{
path = PathUtility.GetFullPath(buildOptions.DirectoryPath ?? Directory.GetCurrentDirectory());
path = PathUtility.GetFullPath(pathOptions.DirectoryPath ?? Directory.GetCurrentDirectory());
msBuildExitCode = RunBuild(path, buildOptions);
}

Expand Down
137 changes: 24 additions & 113 deletions src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Collections.Immutable;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.DotNet.Tools;
using Microsoft.DotNet.Tools.Common;
using Microsoft.VisualStudio.SolutionPersistence.Model;

Expand All @@ -27,10 +25,7 @@ public static (IEnumerable<Module> Projects, bool IsBuiltOrRestored) GetProjects
}

bool isBuiltOrRestored = BuildOrRestoreProjectOrSolution(
solutionFilePath,
projectCollection,
buildOptions,
GetCommands(buildOptions.HasNoRestore, buildOptions.HasNoBuild));
solutionFilePath, buildOptions.MSBuildArgs, buildOptions.HasNoRestore, buildOptions.HasNoBuild);

ConcurrentBag<Module> projects = GetProjectsProperties(projectCollection, solutionModel.SolutionProjects.Select(p => Path.Combine(rootDirectory, p.FilePath)), buildOptions);
return (projects, isBuiltOrRestored);
Expand All @@ -39,41 +34,39 @@ public static (IEnumerable<Module> Projects, bool IsBuiltOrRestored) GetProjects
public static (IEnumerable<Module> Projects, bool IsBuiltOrRestored) GetProjectsFromProject(string projectFilePath, BuildOptions buildOptions)
{
var projectCollection = new ProjectCollection();
bool isBuiltOrRestored = true;

if (!buildOptions.HasNoRestore)
{
isBuiltOrRestored = BuildOrRestoreProjectOrSolution(
projectFilePath,
projectCollection,
buildOptions,
[CliConstants.RestoreCommand]);
}

if (!buildOptions.HasNoBuild)
{
isBuiltOrRestored = isBuiltOrRestored && BuildOrRestoreProjectOrSolution(
projectFilePath,
projectCollection,
buildOptions,
[CliConstants.BuildCommand]);
}
bool isBuiltOrRestored = BuildOrRestoreProjectOrSolution(projectFilePath, buildOptions.MSBuildArgs, buildOptions.HasNoRestore, buildOptions.HasNoBuild);

IEnumerable<Module> projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, GetGlobalProperties(buildOptions), projectCollection);

return (projects, isBuiltOrRestored);
}

private static bool BuildOrRestoreProjectOrSolution(string filePath, ProjectCollection projectCollection, BuildOptions buildOptions, string[] commands)
public static IEnumerable<string> GetPropertyTokens(IEnumerable<string> unmatchedTokens)
{
return unmatchedTokens.Where(token =>
token.StartsWith("--property:", StringComparison.OrdinalIgnoreCase) ||
token.StartsWith("/property:", StringComparison.OrdinalIgnoreCase) ||
token.StartsWith("-p:", StringComparison.OrdinalIgnoreCase) ||
token.StartsWith("/p:", StringComparison.OrdinalIgnoreCase));
}

public static IEnumerable<string> GetBinaryLoggerTokens(IEnumerable<string> args)
{
var parameters = GetBuildParameters(projectCollection, buildOptions);
var globalProperties = GetGlobalProperties(buildOptions);
return args.Where(arg =>
arg.StartsWith("/bl:", StringComparison.OrdinalIgnoreCase) || arg.Equals("/bl", StringComparison.OrdinalIgnoreCase) ||
arg.StartsWith("--binaryLogger:", StringComparison.OrdinalIgnoreCase) || arg.Equals("--binaryLogger", StringComparison.OrdinalIgnoreCase) ||
arg.StartsWith("-bl:", StringComparison.OrdinalIgnoreCase) || arg.Equals("-bl", StringComparison.OrdinalIgnoreCase));
}

var buildRequestData = new BuildRequestData(filePath, globalProperties, null, commands, null);
private static bool BuildOrRestoreProjectOrSolution(string filePath, List<string> arguments, bool hasNoRestore, bool hasNoBuild)
{
arguments.Add(filePath);
arguments.Add("-target:_MTPBuild");

BuildResult buildResult = BuildManager.DefaultBuildManager.Build(parameters, buildRequestData);
int result = new RestoringCommand(arguments, hasNoRestore || hasNoBuild).Execute();

return buildResult.OverallResult == BuildResultCode.Success;
return result == (int)BuildResultCode.Success;
}

private static ConcurrentBag<Module> GetProjectsProperties(ProjectCollection projectCollection, IEnumerable<string> projects, BuildOptions buildOptions)
Expand Down Expand Up @@ -107,71 +100,6 @@ private static string HandleFilteredSolutionFilePath(string solutionFilterFilePa
return solutionFullPath;
}

internal static bool IsBinaryLoggerEnabled(ref List<string> args, out string binLogFileName)
{
binLogFileName = string.Empty;
var binLogArgs = new List<string>();

foreach (var arg in args)
{
if (arg.StartsWith("/bl:") || arg.Equals("/bl")
|| arg.StartsWith("--binaryLogger:") || arg.Equals("--binaryLogger")
|| arg.StartsWith("-bl:") || arg.Equals("-bl"))
{
binLogArgs.Add(arg);

}
}

if (binLogArgs.Count > 0)
{
// Remove all BinLog args from the list of args
args.RemoveAll(arg => binLogArgs.Contains(arg));

// Get BinLog filename
var binLogArg = binLogArgs.LastOrDefault();

if (binLogArg.Contains(CliConstants.Colon))
{
var parts = binLogArg.Split(CliConstants.Colon, 2);
binLogFileName = !string.IsNullOrEmpty(parts[1]) ? parts[1] : CliConstants.BinLogFileName;
}
else
{
binLogFileName = CliConstants.BinLogFileName;
}

return true;
}

return false;
}

private static BuildParameters GetBuildParameters(ProjectCollection projectCollection, BuildOptions buildOptions)
{
BuildParameters parameters = new(projectCollection)
{
Loggers = [new ConsoleLogger(LoggerVerbosity.Quiet)]
};

if (!buildOptions.AllowBinLog)
return parameters;

parameters.Loggers =
[
.. parameters.Loggers,
.. new[]
{
new BinaryLogger
{
Parameters = buildOptions.BinLogFileName
}
},
];

return parameters;
}

private static Dictionary<string, string> GetGlobalProperties(BuildOptions buildOptions)
{
var globalProperties = new Dictionary<string, string>();
Expand All @@ -188,22 +116,5 @@ private static Dictionary<string, string> GetGlobalProperties(BuildOptions build

return globalProperties;
}

private static string[] GetCommands(bool hasNoRestore, bool hasNoBuild)
{
var commands = ImmutableArray.CreateBuilder<string>();

if (!hasNoRestore)
{
commands.Add(CliConstants.RestoreCommand);
}

if (!hasNoBuild)
{
commands.Add(CliConstants.BuildCommand);
}

return [.. commands];
}
}
}
4 changes: 3 additions & 1 deletion src/Cli/dotnet/commands/dotnet-test/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ namespace Microsoft.DotNet.Cli
{
internal record TestOptions(bool HasListTests, string Configuration, string Architecture, bool HasFilterMode, bool IsHelp);

internal record BuildOptions(string ProjectPath, string SolutionPath, string DirectoryPath, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier, bool AllowBinLog, string BinLogFileName, int DegreeOfParallelism, List<string> UnmatchedTokens);
internal record PathOptions(string ProjectPath, string SolutionPath, string DirectoryPath);

internal record BuildOptions(PathOptions PathOptions, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier, int DegreeOfParallelism, List<string> UnmatchedTokens, List<string> MSBuildArgs);
}
2 changes: 1 addition & 1 deletion src/Cli/dotnet/commands/dotnet-test/TestApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ private string BuildArgsWithDotnetRun(TestOptions testOptions)
builder.Append($"{CliConstants.DotnetRunCommand} {TestingPlatformOptions.ProjectOption.Name} \"{_module.ProjectFullPath}\"");

// Because we restored and built before in MSHandler, we will skip those with dotnet run
builder.Append($" {TestingPlatformOptions.NoRestoreOption.Name}");
builder.Append($" {CommonOptions.NoRestoreOption.Name}");
builder.Append($" {TestingPlatformOptions.NoBuildOption.Name}");

if (!string.IsNullOrEmpty(testOptions.Architecture))
Expand Down
3 changes: 1 addition & 2 deletions src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,10 @@ private static CliCommand GetTestingPlatformCliCommand()
var command = new TestingPlatformCommand("test");
command.SetAction(parseResult => command.Run(parseResult));
command.Options.Add(TestingPlatformOptions.MaxParallelTestModulesOption);
command.Options.Add(TestingPlatformOptions.AdditionalMSBuildParametersOption);
command.Options.Add(TestingPlatformOptions.TestModulesFilterOption);
command.Options.Add(TestingPlatformOptions.TestModulesRootDirectoryOption);
command.Options.Add(TestingPlatformOptions.NoBuildOption);
command.Options.Add(TestingPlatformOptions.NoRestoreOption);
command.Options.Add(CommonOptions.NoRestoreOption);
command.Options.Add(TestingPlatformOptions.ArchitectureOption);
command.Options.Add(TestingPlatformOptions.ConfigurationOption);
command.Options.Add(TestingPlatformOptions.ProjectOption);
Expand Down
36 changes: 24 additions & 12 deletions src/Cli/dotnet/commands/dotnet-test/TestingPlatformCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,22 +174,34 @@ private static TestOptions GetTestOptions(ParseResult parseResult, bool hasFilte

private static BuildOptions GetBuildOptions(ParseResult parseResult, int degreeOfParallelism)
{
IEnumerable<string> propertyTokens = MSBuildUtility.GetPropertyTokens(parseResult.UnmatchedTokens);
IEnumerable<string> binaryLoggerTokens = MSBuildUtility.GetBinaryLoggerTokens(parseResult.UnmatchedTokens);

var msbuildArgs = parseResult.OptionValuesToBeForwarded(TestCommandParser.GetCommand())
.Concat(propertyTokens)
.Concat(binaryLoggerTokens).ToList();

List<string> unmatchedTokens = [.. parseResult.UnmatchedTokens];
bool allowBinLog = MSBuildUtility.IsBinaryLoggerEnabled(ref unmatchedTokens, out string binLogFileName);
unmatchedTokens.RemoveAll(arg => propertyTokens.Contains(arg));
unmatchedTokens.RemoveAll(arg => binaryLoggerTokens.Contains(arg));

return new BuildOptions(parseResult.GetValue(TestingPlatformOptions.ProjectOption),
PathOptions pathOptions = new(parseResult.GetValue(TestingPlatformOptions.ProjectOption),
parseResult.GetValue(TestingPlatformOptions.SolutionOption),
parseResult.GetValue(TestingPlatformOptions.DirectoryOption),
parseResult.HasOption(TestingPlatformOptions.NoRestoreOption),
parseResult.HasOption(TestingPlatformOptions.NoBuildOption),
parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
parseResult.HasOption(TestingPlatformOptions.ArchitectureOption) ?
parseResult.GetValue(TestingPlatformOptions.DirectoryOption));

string runtimeIdentifier = parseResult.HasOption(TestingPlatformOptions.ArchitectureOption) ?
CommonOptions.ResolveRidShorthandOptionsToRuntimeIdentifier(string.Empty, parseResult.GetValue(TestingPlatformOptions.ArchitectureOption)) :
string.Empty,
allowBinLog,
binLogFileName,
string.Empty;

return new BuildOptions(
pathOptions,
parseResult.GetValue(CommonOptions.NoRestoreOption),
parseResult.GetValue(TestingPlatformOptions.NoBuildOption),
parseResult.GetValue(TestingPlatformOptions.ConfigurationOption),
runtimeIdentifier,
degreeOfParallelism,
unmatchedTokens);
unmatchedTokens,
msbuildArgs);
}

private static bool ContainsHelpOption(IEnumerable<string> args) => args.Contains(CliConstants.HelpOptionKey) || args.Contains(CliConstants.HelpOptionKey.Substring(0, 2));
Expand All @@ -204,7 +216,7 @@ private void CompleteRun()

private void CleanUp()
{
_msBuildHandler.Dispose();
_msBuildHandler?.Dispose();
foreach (var execution in _executions)
{
execution.Key.Dispose();
Expand Down
26 changes: 7 additions & 19 deletions src/Cli/dotnet/commands/dotnet-test/TestingPlatformOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ internal static class TestingPlatformOptions
Description = LocalizableStrings.CmdMaxParallelTestModulesDescription,
};

public static readonly CliOption<string> AdditionalMSBuildParametersOption = new("--additional-msbuild-parameters")
{
Description = LocalizableStrings.CmdAdditionalMSBuildParametersDescription,
};

public static readonly CliOption<string> TestModulesFilterOption = new("--test-modules")
{
Description = LocalizableStrings.CmdTestModulesDescription
Expand All @@ -28,29 +23,22 @@ internal static class TestingPlatformOptions
Description = LocalizableStrings.CmdTestModulesRootDirectoryDescription
};

public static readonly CliOption<string> NoBuildOption = new("--no-build")
public static readonly CliOption<bool> NoBuildOption = new ForwardedOption<bool>("--no-build")
{
Description = LocalizableStrings.CmdNoBuildDescription,
Arity = ArgumentArity.Zero
};
Description = LocalizableStrings.CmdNoBuildDescription
}.ForwardAs("-property:MTPNoBuild=true");

public static readonly CliOption<string> NoRestoreOption = new("--no-restore")
{
Description = LocalizableStrings.CmdNoRestoreDescription,
Arity = ArgumentArity.Zero
};

public static readonly CliOption<string> ArchitectureOption = new("--arch")
public static readonly CliOption<string> ArchitectureOption = new ForwardedOption<string>("--arch", "-a")
{
Description = LocalizableStrings.CmdArchitectureDescription,
Arity = ArgumentArity.ExactlyOne
};
}.SetForwardingFunction(CommonOptions.ResolveArchOptionToRuntimeIdentifier);

public static readonly CliOption<string> ConfigurationOption = new("--configuration")
public static readonly CliOption<string> ConfigurationOption = new ForwardedOption<string>("--configuration", "-c")
{
Description = LocalizableStrings.CmdConfigurationDescription,
Arity = ArgumentArity.ExactlyOne
};
}.ForwardAsSingle(p => $"/p:configuration={p}");

public static readonly CliOption<string> ProjectOption = new("--project")
{
Expand Down
19 changes: 10 additions & 9 deletions src/Cli/dotnet/commands/dotnet-test/ValidationUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,28 @@ internal static class ValidationUtility
{
public static bool ValidateBuildPathOptions(BuildOptions buildPathOptions, TerminalTestReporter output)
{
if ((!string.IsNullOrEmpty(buildPathOptions.ProjectPath) && !string.IsNullOrEmpty(buildPathOptions.SolutionPath)) ||
(!string.IsNullOrEmpty(buildPathOptions.ProjectPath) && !string.IsNullOrEmpty(buildPathOptions.DirectoryPath)) ||
(!string.IsNullOrEmpty(buildPathOptions.SolutionPath) && !string.IsNullOrEmpty(buildPathOptions.DirectoryPath)))
PathOptions pathOptions = buildPathOptions.PathOptions;
if ((!string.IsNullOrEmpty(pathOptions.ProjectPath) && !string.IsNullOrEmpty(pathOptions.SolutionPath)) ||
(!string.IsNullOrEmpty(pathOptions.ProjectPath) && !string.IsNullOrEmpty(pathOptions.DirectoryPath)) ||
(!string.IsNullOrEmpty(pathOptions.SolutionPath) && !string.IsNullOrEmpty(pathOptions.DirectoryPath)))
{
output.WriteMessage(LocalizableStrings.CmdMultipleBuildPathOptionsErrorDescription);
return false;
}

if (!string.IsNullOrEmpty(buildPathOptions.ProjectPath))
if (!string.IsNullOrEmpty(pathOptions.ProjectPath))
{
return ValidateFilePath(buildPathOptions.ProjectPath, CliConstants.ProjectExtensions, LocalizableStrings.CmdInvalidProjectFileExtensionErrorDescription, output);
return ValidateFilePath(pathOptions.ProjectPath, CliConstants.ProjectExtensions, LocalizableStrings.CmdInvalidProjectFileExtensionErrorDescription, output);
}

if (!string.IsNullOrEmpty(buildPathOptions.SolutionPath))
if (!string.IsNullOrEmpty(pathOptions.SolutionPath))
{
return ValidateFilePath(buildPathOptions.SolutionPath, CliConstants.SolutionExtensions, LocalizableStrings.CmdInvalidSolutionFileExtensionErrorDescription, output);
return ValidateFilePath(pathOptions.SolutionPath, CliConstants.SolutionExtensions, LocalizableStrings.CmdInvalidSolutionFileExtensionErrorDescription, output);
}

if (!string.IsNullOrEmpty(buildPathOptions.DirectoryPath) && !Directory.Exists(buildPathOptions.DirectoryPath))
if (!string.IsNullOrEmpty(pathOptions.DirectoryPath) && !Directory.Exists(pathOptions.DirectoryPath))
{
output.WriteMessage(string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, buildPathOptions.DirectoryPath));
output.WriteMessage(string.Format(LocalizableStrings.CmdNonExistentDirectoryErrorDescription, pathOptions.DirectoryPath));
return false;
}

Expand Down
Loading
Loading