Skip to content

Commit

Permalink
Support msbuild params in dotnet test (#46562)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariam-abdulla authored Feb 6, 2025
1 parent 81f73a0 commit 6b0ee2d
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 277 deletions.
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

0 comments on commit 6b0ee2d

Please sign in to comment.