File: Commands\Test\MTP\MSBuildHandler.cs
Web Access
Project: ..\..\..\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.Concurrent;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
 
namespace Microsoft.DotNet.Cli.Commands.Test;
 
internal sealed class MSBuildHandler(BuildOptions buildOptions)
{
    private readonly BuildOptions _buildOptions = buildOptions;
 
    private readonly ConcurrentBag<ParallelizableTestModuleGroupWithSequentialInnerModules> _testApplications = [];
    private bool _areTestingPlatformApplications = true;
 
    public bool RunMSBuild()
    {
        if (!ValidationUtility.ValidateBuildPathOptions(_buildOptions))
        {
            return false;
        }
 
        int msBuildExitCode;
        string path;
        PathOptions pathOptions = _buildOptions.PathOptions;
 
        if (!string.IsNullOrEmpty(pathOptions.ProjectPath))
        {
            path = PathUtility.GetFullPath(pathOptions.ProjectPath);
 
            msBuildExitCode = Directory.Exists(path)
                ? RunBuild(path, expectProject: true)
                : RunBuild(path, isSolution: false);
        }
        else if (!string.IsNullOrEmpty(pathOptions.SolutionPath))
        {
            path = PathUtility.GetFullPath(pathOptions.SolutionPath);
 
            msBuildExitCode = Directory.Exists(path)
                ? RunBuild(path, expectSolution: true)
                : RunBuild(path, isSolution: true);
        }
        else
        {
            path = PathUtility.GetFullPath(Directory.GetCurrentDirectory());
            msBuildExitCode = RunBuild(path);
        }
 
        if (msBuildExitCode != ExitCode.Success)
        {
            Reporter.Error.WriteLine(string.Format(CliCommandStrings.CmdMSBuildProjectsPropertiesErrorDescription, msBuildExitCode));
            return false;
        }
 
        return true;
    }
 
    private int RunBuild(string directoryPath, bool expectProject = false, bool expectSolution = false)
    {
        bool solutionOrProjectFileFound;
        string message;
        string projectOrSolutionFilePath;
        bool isSolution;
 
        if (expectProject)
        {
            (solutionOrProjectFileFound, message) = SolutionAndProjectUtility.TryGetProjectFilePath(directoryPath, out projectOrSolutionFilePath);
            isSolution = false;
        }
        else if (expectSolution)
        {
            (solutionOrProjectFileFound, message) = SolutionAndProjectUtility.TryGetSolutionFilePath(directoryPath, out projectOrSolutionFilePath);
            isSolution = true;
        }
        else
        {
            (solutionOrProjectFileFound, message) = SolutionAndProjectUtility.TryGetProjectOrSolutionFilePath(directoryPath, out projectOrSolutionFilePath, out isSolution);
        }
 
        if (!solutionOrProjectFileFound)
        {
            Reporter.Error.WriteLine(message);
            return ExitCode.GenericFailure;
        }
 
        (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projects, bool restored) = GetProjectsProperties(projectOrSolutionFilePath, isSolution);
 
        InitializeTestApplications(projects);
 
        return restored && !_testApplications.IsEmpty ? ExitCode.Success : ExitCode.GenericFailure;
    }
 
    private int RunBuild(string filePath, bool isSolution)
    {
        (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projects, bool restored) = GetProjectsProperties(filePath, isSolution);
 
        InitializeTestApplications(projects);
 
        return restored && !_testApplications.IsEmpty ? ExitCode.Success : ExitCode.GenericFailure;
    }
 
    private void InitializeTestApplications(IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> moduleGroups)
    {
        // If one test app has IsTestingPlatformApplication set to false (VSTest and not MTP), then we will not run any of the test apps
        IEnumerable<TestModule> vsTestTestProjects = moduleGroups.SelectMany(group => group.GetVSTestAndNotMTPModules());
 
        if (vsTestTestProjects.Any())
        {
            _areTestingPlatformApplications = false;
 
            Reporter.Error.WriteLine(
                string.Format(
                    CliCommandStrings.CmdUnsupportedVSTestTestApplicationsDescription,
                    string.Join(Environment.NewLine, vsTestTestProjects.Select(module => Path.GetFileName(module.ProjectFullPath))).Red().Bold()));
 
            return;
        }
 
        foreach (ParallelizableTestModuleGroupWithSequentialInnerModules moduleGroup in moduleGroups)
        {
            _testApplications.Add(moduleGroup);
        }
    }
 
    public bool EnqueueTestApplications(TestApplicationActionQueue queue)
    {
        if (!_areTestingPlatformApplications)
        {
            return false;
        }
 
        foreach (var testApp in _testApplications)
        {
            queue.Enqueue(testApp);
        }
        return true;
    }
 
    private (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> Projects, bool Restored) GetProjectsProperties(string solutionOrProjectFilePath, bool isSolution)
    {
        (IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> projects, bool isBuiltOrRestored) = isSolution ?
            MSBuildUtility.GetProjectsFromSolution(solutionOrProjectFilePath, _buildOptions) :
            MSBuildUtility.GetProjectsFromProject(solutionOrProjectFilePath, _buildOptions);
 
        LogProjectProperties(projects);
 
        return (projects, isBuiltOrRestored);
    }
 
    private static void LogProjectProperties(IEnumerable<ParallelizableTestModuleGroupWithSequentialInnerModules> moduleGroups)
    {
        if (!Logger.TraceEnabled)
        {
            return;
        }
 
        var logMessageBuilder = new StringBuilder();
 
        foreach (var moduleGroup in moduleGroups)
        {
            foreach (var module in moduleGroup)
            {
                logMessageBuilder.AppendLine($"{ProjectProperties.ProjectFullPath}: {module.ProjectFullPath}");
                logMessageBuilder.AppendLine($"{ProjectProperties.IsTestingPlatformApplication}: {module.IsTestingPlatformApplication}");
                logMessageBuilder.AppendLine($"{ProjectProperties.TargetFramework}: {module.TargetFramework}");
                logMessageBuilder.AppendLine($"{ProjectProperties.RunCommand}: {module.RunProperties.Command}");
                logMessageBuilder.AppendLine($"{ProjectProperties.RunArguments}: {module.RunProperties.Arguments}");
                logMessageBuilder.AppendLine($"{ProjectProperties.RunWorkingDirectory}: {module.RunProperties.WorkingDirectory}");
                logMessageBuilder.AppendLine();
            }
        }
 
        Logger.LogTrace(logMessageBuilder, static logMessageBuilder => logMessageBuilder.ToString());
    }
}