diff --git a/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs b/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs index 83e9052af349..69c996b562dd 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs @@ -7,6 +7,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; @@ -556,6 +557,7 @@ private static (string? device, string? runtimeIdentifier) SelectDeviceForTfm( runProperties = DeployAndGetRunProperties( project, logger, + AnalyzeStandardTestMSBuildArgs(buildOptions.MSBuildArgs), buildOptions.EnvironmentVariables, out runtimeEnvironmentVariables); @@ -603,6 +605,7 @@ private static (string? device, string? runtimeIdentifier) SelectDeviceForTfm( static RunProperties DeployAndGetRunProperties( ProjectInstance project, FacadeLogger? logger, + MSBuildArgs msbuildArgs, IReadOnlyDictionary environmentVariables, out IReadOnlyDictionary runtimeEnvironmentVariables) { @@ -618,20 +621,19 @@ static RunProperties DeployAndGetRunProperties( // NOTE: BuildManager is singleton. lock (s_buildLock) { - var loggers = logger is null ? null : new[] { logger }; if (project.Targets.ContainsKey(Constants.DeployToDevice)) { // Deploy on a fresh ProjectInstance to avoid accumulating state (existing item // groups) that would leak into the ComputeRunArguments build below, which has to // build the original instance since the run properties are read back from it. // Same reason as dotnet run, see RunCommandSelector.OpenProjectIfNeeded. - if (!project.DeepCopy().Build([Constants.DeployToDevice], loggers)) + if (!project.DeepCopy().Build([Constants.DeployToDevice], CreateBuildLoggers(msbuildArgs, logger))) { throw new GracefulException(CliCommandStrings.RunCommandDeployFailed); } } - if (!project.Build(s_computeRunArgumentsTarget, loggers)) + if (!project.Build(s_computeRunArgumentsTarget, CreateBuildLoggers(msbuildArgs, logger))) { throw new GracefulException(CliCommandStrings.RunCommandEvaluationExceptionBuildFailed, s_computeRunArgumentsTarget[0]); } @@ -644,6 +646,34 @@ static RunProperties DeployAndGetRunProperties( } } + /// + /// Gets the loggers to attach to an in-process build. + /// A console logger is attached (unless -noConsoleLogger was passed) so that MSBuild errors are + /// actually reported to the user: the binary logger only forwards events to binlogs, and it is only + /// created when -bl was passed, so without a console logger these builds fail silently and the user + /// is only told to "fix the errors and warnings" without any error being printed anywhere. + /// This mirrors what dotnet run does in RunCommand.InvokeRunArgumentsTarget. + /// + /// + /// A fresh console logger is created for each build to avoid disposal issues when calling + /// multiple times. + /// + private static IEnumerable CreateBuildLoggers(MSBuildArgs msbuildArgs, FacadeLogger? binaryLogger) + { + if (binaryLogger is not null) + { + yield return binaryLogger; + } + + if (!LoggerUtility.HasNoConsoleLoggerArgument(msbuildArgs.OtherMSBuildArgs)) + { + // These builds only compute run arguments and deploy, so keep them quiet - at this verbosity + // MSBuild still reports errors and warnings. + yield return CommonRunHelpers.GetConsoleLogger( + msbuildArgs.CloneWithExplicitArgs([$"--verbosity:{LoggerVerbosity.Quiet.ToString().ToLowerInvariant()}", .. msbuildArgs.OtherMSBuildArgs])); + } + } + private static LaunchProfile? TryGetLaunchProfileSettings(string projectDirectory, string projectNameWithoutExtension, string appDesignerFolder, BuildOptions buildOptions, string? profileName) { if (buildOptions.NoLaunchProfile) diff --git a/test/TestAssets/TestProjects/DotnetTestDevices/DotnetTestDevices.csproj b/test/TestAssets/TestProjects/DotnetTestDevices/DotnetTestDevices.csproj index 28eeb1ab580c..be3f52c712c9 100644 --- a/test/TestAssets/TestProjects/DotnetTestDevices/DotnetTestDevices.csproj +++ b/test/TestAssets/TestProjects/DotnetTestDevices/DotnetTestDevices.csproj @@ -51,6 +51,12 @@ Overwrite="true" /> + + + + diff --git a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestSelectsDevice.cs b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestSelectsDevice.cs index b1cf25ffd1d5..2cb641b2aad7 100644 --- a/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestSelectsDevice.cs +++ b/test/dotnet.Tests/CommandTests/Test/GivenDotnetTestSelectsDevice.cs @@ -608,7 +608,33 @@ public void ItFailsWhenDeployToDeviceTargetFails() "-p:FailDeployToDevice=true"); result.Should().Fail() - .And.HaveStdErrContaining(CliCommandStrings.RunCommandDeployFailed); + .And.HaveStdErrContaining(CliCommandStrings.RunCommandDeployFailed) + // The MSBuild error itself must be reported, otherwise the user is told to fix errors + // that were never printed anywhere. + .And.HaveStdOutContaining("DeployToDevice failed as requested."); + } + + [TestMethod] + public void ItFailsWhenComputeRunArgumentsTargetFails() + { + var testInstance = TestAssetsManager.CopyTestAsset("DotnetTestDevices", identifier: "ComputeRunArgumentsFailure") + .WithSource(); + + var result = new DotnetTestCommand(Log, disableNewOutput: false) + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .Execute( + "--framework", + ToolsetInfo.CurrentTargetFramework, + "--device", + "test-device-1", + "-p:FailComputeRunArguments=true"); + + result.Should().Fail() + .And.HaveStdErrContaining(string.Format(CliCommandStrings.RunCommandEvaluationExceptionBuildFailed, "ComputeRunArguments")) + // The MSBuild error itself must be reported, otherwise the user is told to fix errors + // that were never printed anywhere. + .And.HaveStdOutContaining("ComputeRunArguments failed as requested."); } [TestMethod]