Skip to content
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

Support .slnf file format in dotnet test #46392

Merged
merged 23 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fb5e9a6
Support .slnf file formats
mariam-abdulla Jan 29, 2025
11cda24
remove Debugger.Launch()
mariam-abdulla Jan 29, 2025
3fb5d35
Add the scenario: if we run dotnet test inside a directory where we o…
mariam-abdulla Jan 29, 2025
367660c
refactor
mariam-abdulla Jan 29, 2025
1f835dd
Add a test
mariam-abdulla Jan 29, 2025
8e46388
Update src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs
mariam-abdulla Jan 30, 2025
599f74d
refactor
mariam-abdulla Jan 30, 2025
3e1c673
Merge branch 'dev/mabdullah/support-slnf-in-dotnet-test' of https://g…
mariam-abdulla Jan 30, 2025
9fd82a5
Rename method
mariam-abdulla Jan 30, 2025
36b9689
Fix bug
mariam-abdulla Jan 30, 2025
6441a3d
Update src/Cli/dotnet/commands/dotnet-test/TestApplicationEventHandle…
mariam-abdulla Jan 30, 2025
03b7beb
Apply comments
mariam-abdulla Jan 30, 2025
173ad50
Merge branch 'dev/mabdullah/support-slnf-in-dotnet-test' of https://g…
mariam-abdulla Jan 30, 2025
ce53c38
Update method name
mariam-abdulla Jan 30, 2025
4f571ba
rename vars
mariam-abdulla Jan 30, 2025
a45a810
Clean code
mariam-abdulla Jan 30, 2025
1d0690e
refactor
mariam-abdulla Jan 30, 2025
e1562b3
Fix test
mariam-abdulla Jan 30, 2025
a253f7b
Fix RunWithSolutionFilterPathWithFailingTests_ShouldReturnOneAsExitCode
mariam-abdulla Jan 30, 2025
192ad8a
Merge branch 'main' into dev/mabdullah/support-slnf-in-dotnet-test
mariam-abdulla Jan 30, 2025
3927d95
Use JsonNode
mariam-abdulla Jan 31, 2025
da5331e
Merge branch 'dev/mabdullah/support-slnf-in-dotnet-test' of https://g…
mariam-abdulla Jan 31, 2025
828647d
Merge branch 'main' into dev/mabdullah/support-slnf-in-dotnet-test
mariam-abdulla Jan 31, 2025
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
20 changes: 19 additions & 1 deletion src/Cli/dotnet/SlnFileFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.VisualStudio.SolutionPersistence;
using Microsoft.VisualStudio.SolutionPersistence.Model;
Expand Down Expand Up @@ -94,7 +95,8 @@ public static SolutionModel CreateFromFilteredSolutionFile(string filteredSoluti
.Select(project => project.GetString())
.ToArray();
}
catch (Exception ex) {
catch (Exception ex)
{
mariam-abdulla marked this conversation as resolved.
Show resolved Hide resolved
throw new GracefulException(
CommonLocalizableStrings.InvalidSolutionFormatString,
filteredSolutionPath, ex.Message);
Expand Down Expand Up @@ -126,5 +128,21 @@ public static SolutionModel CreateFromFilteredSolutionFile(string filteredSoluti

return filteredSolution;
}

public static string GetSolutionPathFromFilteredSolutionFile(string filteredSolutionPath)
mariam-abdulla marked this conversation as resolved.
Show resolved Hide resolved
{
try
{
JsonNode? jsonNode = JsonNode.Parse(File.ReadAllText(filteredSolutionPath));
string? originalSolutionPath = jsonNode?["solution"]?["path"]?.GetValue<string>();
return originalSolutionPath!;
}
catch (Exception ex)
{
throw new GracefulException(
CommonLocalizableStrings.InvalidSolutionFormatString,
filteredSolutionPath, ex.Message);
}
}
}
}
5 changes: 3 additions & 2 deletions src/Cli/dotnet/commands/dotnet-test/CliConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ internal static class CliConstants
public const string RuntimeIdentifier = "RuntimeIdentifier";

public static readonly string[] ProjectExtensions = { ".proj", ".csproj", ".vbproj", ".fsproj" };
public static readonly string[] SolutionExtensions = { ".sln", ".slnx" };
public static readonly string[] SolutionExtensions = { ".sln", ".slnx", ".slnf" };

public const string ProjectExtensionPattern = "*.*proj";
public const string SolutionExtensionPattern = "*.sln";
public const string SolutionXExtensionPattern = "*.slnx";
public const string SolutionFilterExtensionPattern = "*.slnf";

public const string BinLogFileName = "msbuild.binlog";

public const string TestingPlatformVsTestBridgeRunSettingsFileEnvVar = "TESTINGPLATFORM_VSTESTBRIDGE_RUNSETTINGS_FILE";
public const string DLLExtension = "dll";
public const string DLLExtension = ".dll";
}

internal static class TestStates
Expand Down
306 changes: 21 additions & 285 deletions src/Cli/dotnet/commands/dotnet-test/MSBuildHandler.cs

Large diffs are not rendered by default.

209 changes: 209 additions & 0 deletions src/Cli/dotnet/commands/dotnet-test/MSBuildUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// 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.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.Common;
using Microsoft.VisualStudio.SolutionPersistence.Model;

namespace Microsoft.DotNet.Cli
{
internal static class MSBuildUtility
{
public static (IEnumerable<Module>, bool) GetProjectsFromSolution(string solutionFilePath, BuildOptions buildOptions)
{
var projectCollection = new ProjectCollection();
string rootDirectory = SolutionAndProjectUtility.GetRootDirectory(solutionFilePath);

SolutionModel solutionModel = SlnFileFactory.CreateFromFileOrDirectory(solutionFilePath, includeSolutionFilterFiles: true, includeSolutionXmlFiles: true);

if (solutionFilePath.HasExtension(".slnf"))
{
solutionFilePath = HandleFilteredSolutionFilePath(solutionFilePath, out rootDirectory);
}

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

ConcurrentBag<Module> projects = GetProjectsProperties(projectCollection, solutionModel.SolutionProjects.Select(p => Path.Combine(rootDirectory, p.FilePath)), buildOptions.DegreeOfParallelism);
return (projects, isBuiltOrRestored);
}

public static (IEnumerable<Module>, bool) GetProjectsFromProject(string projectFilePath, BuildOptions buildOptions)
fhnaseer marked this conversation as resolved.
Show resolved Hide resolved
{
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]);
}

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

return (projects, isBuiltOrRestored);
}

private static bool BuildOrRestoreProjectOrSolution(string filePath, ProjectCollection projectCollection, BuildOptions buildOptions, string[] commands)
{
var parameters = GetBuildParameters(projectCollection, buildOptions);
var globalProperties = GetGlobalProperties(buildOptions);

var buildRequestData = new BuildRequestData(filePath, globalProperties, null, commands, null);

BuildResult buildResult = BuildManager.DefaultBuildManager.Build(parameters, buildRequestData);

return buildResult.OverallResult == BuildResultCode.Success;
}

private static ConcurrentBag<Module> GetProjectsProperties(ProjectCollection projectCollection, IEnumerable<string> projects, int degreeOfParallelism)
{
var allProjects = new ConcurrentBag<Module>();

Parallel.ForEach(
projects,
new ParallelOptions { MaxDegreeOfParallelism = degreeOfParallelism },
(project) =>
{
IEnumerable<Module> projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project, projectCollection);
foreach (var projectMetadata in projectsMetadata)
{
allProjects.Add(projectMetadata);
}
});

return allProjects;
}

private static string HandleFilteredSolutionFilePath(string solutionFilterFilePath, out string rootDirectory)
{
string solution = SlnFileFactory.GetSolutionPathFromFilteredSolutionFile(solutionFilterFilePath);

// Resolve the solution path relative to the .slnf file directory
string solutionFilterDirectory = Path.GetDirectoryName(solutionFilterFilePath);
string solutionFullPath = Path.GetFullPath(solution, solutionFilterDirectory);
rootDirectory = Path.GetDirectoryName(solutionFullPath);

return solutionFullPath;
}

internal static bool IsBinaryLoggerEnabled(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>();

if (!string.IsNullOrEmpty(buildOptions.Configuration))
{
globalProperties[CliConstants.Configuration] = buildOptions.Configuration;
}

if (!string.IsNullOrEmpty(buildOptions.RuntimeIdentifier))
{
globalProperties[CliConstants.RuntimeIdentifier] = buildOptions.RuntimeIdentifier;
}

return globalProperties;
}

private static string[] GetCommands(bool hasNoRestore, bool hasNoBuild)
mariam-abdulla marked this conversation as resolved.
Show resolved Hide resolved
{
var commands = ImmutableArray.CreateBuilder<string>();

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

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

return [.. commands];
}
}
}
2 changes: 1 addition & 1 deletion src/Cli/dotnet/commands/dotnet-test/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Microsoft.DotNet.Cli
{
internal sealed record Module(string? DllOrExePath, string? ProjectPath, string? TargetFramework, string? RunSettingsFilePath, bool IsTestingPlatformApplication, bool IsTestProject);
internal sealed record Module(string? TargetPath, string? ProjectFullPath, string? TargetFramework, string? RunSettingsFilePath, bool IsTestingPlatformApplication, bool IsTestProject);

internal sealed record Handshake(Dictionary<byte, string>? Properties);

Expand Down
2 changes: 1 addition & 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,5 @@ namespace Microsoft.DotNet.Cli
{
internal record BuildConfigurationOptions(bool HasListTests, string Configuration, string Architecture);

internal record BuildPathsOptions(string ProjectPath, string SolutionPath, string DirectoryPath, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier);
internal record BuildOptions(string ProjectPath, string SolutionPath, string DirectoryPath, bool HasNoRestore, bool HasNoBuild, string Configuration, string RuntimeIdentifier, bool AllowBinLog, string BinLogFileName, int DegreeOfParallelism);
}
Loading
Loading