diff --git a/src/Samples/TestExitCodeOverride/TestExitCodeOverride.proj b/src/Samples/TestExitCodeOverride/TestExitCodeOverride.proj new file mode 100644 index 00000000000..d24d13b3ae8 --- /dev/null +++ b/src/Samples/TestExitCodeOverride/TestExitCodeOverride.proj @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows) ? "cmd.exe" : "sh"; + + protected override string GenerateFullPathToTool() => + System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows) + ? System.IO.Path.Combine( + System.Environment.GetFolderPath(System.Environment.SpecialFolder.System), "cmd.exe") + : "/bin/sh"; + + protected override string GenerateCommandLineCommands() => + System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( + System.Runtime.InteropServices.OSPlatform.Windows) + ? "/C echo file.cs(1,1): error ERR002: Fake error from basic ToolTask" + : "-c \"echo 'file.cs(1,1): error ERR002: Fake error from basic ToolTask'\""; +} + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + diff --git a/src/Tasks.UnitTests/Exec_Tests.cs b/src/Tasks.UnitTests/Exec_Tests.cs index 2f0f9e1ace3..3a820e75421 100644 --- a/src/Tasks.UnitTests/Exec_Tests.cs +++ b/src/Tasks.UnitTests/Exec_Tests.cs @@ -246,7 +246,18 @@ public void LoggedErrorsCauseFailureDespiteExitCode0() Assert.False(result); // Exitcode is set to -1 Assert.Equal(-1, exec.ExitCode); - ((MockEngine)exec.BuildEngine).AssertLogContains("MSB3073"); + + MockEngine engine = (MockEngine)exec.BuildEngine; + + // Should log the special "exited zero with errors" message + engine.AssertLogContains("MSB3077"); + + // Should not log other failure-related error codes + engine.AssertLogDoesntContain("MSB3073"); + engine.AssertLogDoesntContain("MSB6006"); + + // Errors: canonical error from tool output + MSB3077 from HandleTaskExecutionErrors + engine.Errors.ShouldBe(2); } [Fact] diff --git a/src/Tasks.UnitTests/XamlDataDrivenToolTask_Tests.cs b/src/Tasks.UnitTests/XamlDataDrivenToolTask_Tests.cs index cefb96e614a..060a42bb9a3 100644 --- a/src/Tasks.UnitTests/XamlDataDrivenToolTask_Tests.cs +++ b/src/Tasks.UnitTests/XamlDataDrivenToolTask_Tests.cs @@ -5,8 +5,14 @@ using System.IO; using System.Reflection; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; +using Microsoft.Build.Tasks; +using Microsoft.Build.Tasks.Xaml; +using Microsoft.Build.Utilities; +using Shouldly; using Xunit; +using Xunit.Abstractions; #nullable disable @@ -382,4 +388,65 @@ public void SquareBracketEscaping() logger.AssertLogContains("echo 14) [[notaproperty]] end"); } } + + /// + /// Tests for XamlDataDrivenToolTask error handling using direct task instantiation. + /// + public class ErrorHandlingTests + { + private readonly ITestOutputHelper _output; + + public ErrorHandlingTests(ITestOutputHelper output) + { + _output = output; + } + + /// + /// Tests that when a XamlDataDrivenTask's tool exits with code 0 but canonical errors + /// were logged during execution, the special "exited zero with errors" message (MSB3725) + /// is used instead of the normal command failure message (MSB3721). + /// + [Fact] + public void ExitCodeZeroWithLoggedErrors_LogsMSB3725() + { + var cmdLine = NativeMethodsShared.IsWindows + ? "echo myfile(88,37): error AB1234: thisisacanonicalerror" + : "echo \"myfile(88,37): error AB1234: thisisacanonicalerror\""; + + var task = new TestableXamlDataDrivenToolTask(); + task.BuildEngine = new MockEngine(_output); + task.CommandLineTemplate = cmdLine; + task.EchoOff = true; + + bool result = task.Execute(); + + result.ShouldBeFalse(); + + MockEngine engine = (MockEngine)task.BuildEngine; + + // Should log the special "exited zero with errors" message, not the normal command failure + engine.AssertLogContains("MSB3725"); + + // Should not log other error messages related to XamlDataDrivenToolTask command failure + engine.AssertLogDoesntContain("MSB3721"); + engine.AssertLogDoesntContain("MSB3722"); + + // Errors: canonical error from tool output + MSB3725 from HandleTaskExecutionErrors + engine.Errors.ShouldBe(2); + } + } + + /// + /// A concrete subclass of XamlDataDrivenToolTask for direct unit testing + /// without requiring a project file or XamlTaskFactory. + /// + internal sealed class TestableXamlDataDrivenToolTask : XamlDataDrivenToolTask + { + public TestableXamlDataDrivenToolTask() + : base(Array.Empty(), null) + { + } + + protected override string ToolName => "unused"; + } } diff --git a/src/Tasks/Exec.cs b/src/Tasks/Exec.cs index cf87ebedec1..d59c05f4d04 100644 --- a/src/Tasks/Exec.cs +++ b/src/Tasks/Exec.cs @@ -325,10 +325,15 @@ protected override bool HandleTaskExecutionErrors() { Log.LogErrorWithCodeFromResources("Exec.CommandFailedAccessDenied", commandForLog, ExitCode); } + else if (ExitCodeOverriddenToIndicateErrors) + { + Log.LogErrorWithCodeFromResources("Exec.CommandExitedZeroWithErrors", commandForLog); + } else { Log.LogErrorWithCodeFromResources("Exec.CommandFailed", commandForLog, ExitCode); } + return false; } diff --git a/src/Tasks/Resources/Strings.resx b/src/Tasks/Resources/Strings.resx index d387956f108..fb697866cd8 100644 --- a/src/Tasks/Resources/Strings.resx +++ b/src/Tasks/Resources/Strings.resx @@ -397,6 +397,10 @@ MSB3071: All drive letters from A: through Z: are currently in use. Since the working directory "{0}" is a UNC path, the "Exec" task needs a free drive letter to map the UNC path to. Disconnect from one or more shared resources to free up drive letters, or specify a local working directory before attempting this command again. {StrBegin="MSB3071: "}LOCALIZATION: "Exec", "A:", and "Z:" should not be localized. + + MSB3072: The "Exec" task needs a command to execute. + {StrBegin="MSB3072: "}LOCALIZATION: "Exec" should not be localized. + MSB3073: The command "{0}" exited with code {1}. {StrBegin="MSB3073: "} @@ -412,9 +416,9 @@ MSB3076: The regular expression "{0}" that was supplied is invalid. {1} {StrBegin="MSB3076: "} - - MSB3072: The "Exec" task needs a command to execute. - {StrBegin="MSB3072: "}LOCALIZATION: "Exec" should not be localized. + + MSB3077: The command "{0}" exited with return value 0, but errors were detected during execution. ExitCode was set to -1. + {StrBegin="MSB3077: "} The working directory "{0}" does not exist. @@ -2319,6 +2323,10 @@ MSB3724: Unable to create Xaml task. Duplicate property name '{0}'. {StrBegin="MSB3724: "} + + MSB3725: The command "{0}" exited with return value 0, but errors were detected during execution. ExitCode was set to -1. + {StrBegin="MSB3725: "} +