|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
using Xunit.NetCore.Extensions;
#nullable disable
namespace Microsoft.Build.UnitTests
{
/// <summary>
/// Tests for the Exec task
/// </summary>
public sealed class Exec_Tests
{
private readonly ITestOutputHelper _output;
public Exec_Tests(ITestOutputHelper output)
{
_output = output;
}
private Exec PrepareExec(string command)
{
IBuildEngine2 mockEngine = new MockEngine(_output);
Exec exec = new Exec();
exec.BuildEngine = mockEngine;
exec.Command = command;
return exec;
}
private ExecWrapper PrepareExecWrapper(string command)
{
IBuildEngine2 mockEngine = new MockEngine(_output);
ExecWrapper exec = new ExecWrapper();
exec.BuildEngine = mockEngine;
exec.Command = command;
return exec;
}
[Fact]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
public void EscapeSpecifiedCharactersInPathToGeneratedBatchFile()
{
using (var testEnvironment = TestEnvironment.Create())
{
var newTempPath = testEnvironment.CreateNewTempPathWithSubfolder("hello()w]o(rld)").TempPath;
string tempPath = Path.GetTempPath();
Assert.StartsWith(newTempPath, tempPath);
// Now run the Exec task on a simple command.
Exec exec = PrepareExec("echo Hello World!");
exec.Execute().ShouldBeTrue();
}
}
[UnixOnlyTheory]
[InlineData(true)]
[InlineData(false)]
public void ExecSetsLocaleOnUnix(bool enableChangeWave)
{
using (var env = TestEnvironment.Create())
{
env.SetEnvironmentVariable("LANG", null);
env.SetEnvironmentVariable("LC_ALL", null);
if (enableChangeWave)
{
ChangeWaves.ResetStateForTests();
// Important: use the version here
env.SetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION", ChangeWaves.Wave17_10.ToString());
BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly();
}
Exec exec = PrepareExec("echo LANG=$LANG; echo LC_ALL=$LC_ALL;");
bool result = exec.Execute();
Assert.True(result);
MockEngine engine = (MockEngine)exec.BuildEngine;
if (enableChangeWave)
{
engine.AssertLogContains("LANG=en_US.UTF-8");
engine.AssertLogContains("LC_ALL=en_US.UTF-8");
}
else
{
engine.AssertLogDoesntContain("LANG=en_US.UTF-8");
engine.AssertLogDoesntContain("LC_ALL=en_US.UTF-8");
}
}
}
/// <summary>
/// Ensures that calling the Exec task does not leave any extra TEMP files
/// lying around.
/// </summary>
[Fact]
public void NoTempFileLeaks()
{
using (var testEnvironment = TestEnvironment.Create())
{
// This test counts files in TEMP. If it uses the system TEMP, some
// other process may interfere. Use a private TEMP instead.
var newTempPath = testEnvironment.CreateNewTempPath().TempPath;
string tempPath = Path.GetTempPath();
Assert.StartsWith(newTempPath, tempPath);
// Get a count of how many temp files there are right now.
string[] tempFiles = Directory.GetFiles(tempPath);
Assert.Empty(tempFiles);
// Now run the Exec task on a simple command.
Exec exec = PrepareExec("echo Four days until ZBB!");
bool result = exec.Execute();
// Get the new count of temp files.
tempFiles = Directory.GetFiles(tempPath);
// Ensure that Exec succeeded.
Assert.True(result);
// Ensure that no files linger in TEMP.
Assert.Empty(tempFiles);
}
}
[Fact]
public void ExitCodeCausesFailure()
{
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? "xcopy thisisanonexistentfile" : "cp thisisanonexistentfile thatisanonexistentfile");
bool result = exec.Execute();
Assert.False(result);
Assert.Equal(NativeMethodsShared.IsWindows ? 4 : 1, exec.ExitCode);
((MockEngine)exec.BuildEngine).AssertLogContains("MSB3073");
if (!NativeMethodsShared.IsWindows)
{
((MockEngine)exec.BuildEngine).AssertLogContains("cp: ");
}
}
[Fact]
public void Timeout()
{
// On non-Windows the exit code of a killed process is SIGKILL (137)
int expectedExitCode = NativeMethodsShared.IsWindows ? -1 : 137;
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? ":foo \n goto foo" : "while true; do sleep 1; done");
exec.Timeout = 5;
bool result = exec.Execute();
Assert.False(result);
Assert.Equal(expectedExitCode, exec.ExitCode);
((MockEngine)exec.BuildEngine).AssertLogContains("MSB5002");
int warningsCount = ((MockEngine)exec.BuildEngine).Warnings;
warningsCount.ShouldBe(1,
$"Expected 1 warning, encountered {warningsCount}: " + string.Join(",",
((MockEngine)exec.BuildEngine).WarningEvents.Select(w => w.Message)));
// ToolTask does not log an error on timeout.
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors);
}
[Fact]
public void TimeoutFailsEvenWhenExitCodeIsIgnored()
{
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? ":foo \n goto foo" : "while true; do sleep 1; done");
exec.Timeout = 5;
exec.IgnoreExitCode = true;
bool result = exec.Execute();
result.ShouldBeFalse();
MockEngine mockEngine = (MockEngine)exec.BuildEngine;
mockEngine.AssertLogContains("MSB5002");
mockEngine.Warnings.ShouldBe(1);
// ToolTask does not log an error on timeout.
mockEngine.Errors.ShouldBe(0);
// On non-Windows the exit code of a killed process is 128 + SIGKILL = 137
exec.ExitCode.ShouldBe(NativeMethodsShared.IsWindows ? -1 : 137);
}
[UnixOnlyFact]
public void WindowsNewLineCharactersInCommandOnUnix()
{
var exec = PrepareExec("echo hello\r\n\r\n");
bool result = exec.Execute();
Assert.True(result);
Assert.Equal(0, exec.ExitCode);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Warnings);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors);
}
[Fact]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
public void ExitCodeGetter()
{
Exec exec = PrepareExec("exit 120");
exec.Execute();
Assert.Equal(120, exec.ExitCode);
}
[Fact]
public void LoggedErrorsCauseFailureDespiteExitCode0()
{
var cmdLine = NativeMethodsShared.IsWindows
? "echo myfile(88,37): error AB1234: thisisacanonicalerror"
: "echo \"myfile(88,37): error AB1234: thisisacanonicalerror\"";
// This will return 0 exit code, but emitted a canonical error
Exec exec = PrepareExec(cmdLine);
bool result = exec.Execute();
Assert.False(result);
// Exitcode is set to -1
Assert.Equal(-1, exec.ExitCode);
((MockEngine)exec.BuildEngine).AssertLogContains("MSB3073");
}
[Fact]
public void IgnoreExitCodeTrueMakesTaskSucceedDespiteLoggingErrors()
{
var cmdLine = NativeMethodsShared.IsWindows
? "echo myfile(88,37): error AB1234: thisisacanonicalerror"
: "echo \"myfile(88,37): error AB1234: thisisacanonicalerror\"";
Exec exec = PrepareExec(cmdLine);
exec.IgnoreExitCode = true;
bool result = exec.Execute();
Assert.True(result);
}
[Fact]
public void IgnoreExitCodeTrueMakesTaskSucceedDespiteExitCode1()
{
Exec exec = PrepareExec("dir ||invalid||");
exec.IgnoreExitCode = true;
bool result = exec.Execute();
Assert.True(result);
}
[Fact]
public void NonUNCWorkingDirectoryUsed()
{
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? "echo [%cd%]" : "echo [$PWD]");
string working = !NativeMethodsShared.IsWindows ? "/usr/lib" :
Environment.GetFolderPath(Environment.SpecialFolder.Windows); // not desktop etc - IT redirection messes it up
exec.WorkingDirectory = working;
bool result = exec.Execute();
Assert.True(result);
((MockEngine)exec.BuildEngine).AssertLogContains("[" + working + "]");
}
[WindowsOnlyFact(additionalMessage: "UNC is Windows-Only.")]
public void UNCWorkingDirectoryUsed()
{
Exec exec = PrepareExec("echo [%cd%]");
string working = @"\\" + Environment.MachineName + @"\c$";
exec.WorkingDirectory = working;
bool result = exec.ValidateParametersAccessor();
Assert.True(result);
Assert.True(exec.workingDirectoryIsUNC);
Assert.Equal(working, exec.WorkingDirectory);
// Should give ToolTask the system folder as the working directory, when it's a UNC
string system = Environment.GetFolderPath(Environment.SpecialFolder.System);
Assert.Equal(system, exec.GetWorkingDirectoryAccessor());
}
[Fact]
public void NoWorkingDirectorySet()
{
var cd = Directory.GetCurrentDirectory();
try
{
Directory.SetCurrentDirectory(NativeMethodsShared.IsWindows ?
Environment.GetFolderPath(Environment.SpecialFolder.Windows) : "/usr/lib");
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? "echo [%cd%]" : "echo [$PWD]");
bool result = exec.Execute();
string expected = Directory.GetCurrentDirectory();
Assert.True(result);
((MockEngine)exec.BuildEngine).AssertLogContains("[" + expected + "]");
}
finally
{
Directory.SetCurrentDirectory(cd);
}
}
/// <summary>
/// Tests that Exec still executes properly when there's an '&' in the temp directory path
/// </summary>
[Fact]
public void TempPathContainsAmpersand1()
{
string directoryWithAmpersand = "nospace&nospace";
string newTmp = Path.Combine(Path.GetTempPath(), directoryWithAmpersand);
string oldTmp = Environment.GetEnvironmentVariable("TMP");
try
{
Directory.CreateDirectory(newTmp);
if (NativeMethodsShared.GetShortFilePath(newTmp) == newTmp)
{
// Short file paths not supported, this test will fail.
// See: https://github.com/dotnet/msbuild/issues/1803
return;
}
Environment.SetEnvironmentVariable("TMP", newTmp);
Exec exec = PrepareExec("echo [hello]");
Assert.True(exec.Execute()); // "Task should have succeeded"
((MockEngine)exec.BuildEngine).AssertLogContains("[hello]");
}
finally
{
Environment.SetEnvironmentVariable("TMP", oldTmp);
if (Directory.Exists(newTmp))
{
FileUtilities.DeleteWithoutTrailingBackslash(newTmp);
}
}
}
/// <summary>
/// Tests that Exec still executes properly when there's an ' &' in the temp directory path
/// </summary>
[Fact]
public void TempPathContainsAmpersand2()
{
string directoryWithAmpersand = "space &nospace";
string newTmp = Path.Combine(Path.GetTempPath(), directoryWithAmpersand);
string oldTmp = Environment.GetEnvironmentVariable("TMP");
try
{
Directory.CreateDirectory(newTmp);
if (NativeMethodsShared.GetShortFilePath(newTmp) == newTmp)
{
// Short file paths not supported, this test will fail.
// See: https://github.com/dotnet/msbuild/issues/1803
return;
}
Environment.SetEnvironmentVariable("TMP", newTmp);
Exec exec = PrepareExec("echo [hello]");
bool taskSucceeded = exec.Execute();
Assert.True(taskSucceeded); // "Task should have succeeded"
((MockEngine)exec.BuildEngine).AssertLogContains("[hello]");
}
finally
{
Environment.SetEnvironmentVariable("TMP", oldTmp);
if (Directory.Exists(newTmp))
{
FileUtilities.DeleteWithoutTrailingBackslash(newTmp);
}
}
}
/// <summary>
/// Tests that Exec still executes properly when there's an '& ' in the temp directory path
/// </summary>
[Fact]
public void TempPathContainsAmpersand3()
{
string directoryWithAmpersand = "nospace& space";
string newTmp = Path.Combine(FileUtilities.TempFileDirectory, directoryWithAmpersand);
string oldTmp = Environment.GetEnvironmentVariable("TMP");
try
{
Directory.CreateDirectory(newTmp);
if (NativeMethodsShared.GetShortFilePath(newTmp) == newTmp)
{
// Short file paths not supported, this test will fail.
// See: https://github.com/dotnet/msbuild/issues/1803
return;
}
Environment.SetEnvironmentVariable("TMP", newTmp);
Exec exec = PrepareExec("echo [hello]");
Assert.True(exec.Execute()); // "Task should have succeeded"
((MockEngine)exec.BuildEngine).AssertLogContains("[hello]");
}
finally
{
Environment.SetEnvironmentVariable("TMP", oldTmp);
if (Directory.Exists(newTmp))
{
FileUtilities.DeleteWithoutTrailingBackslash(newTmp);
}
}
}
/// <summary>
/// Tests that Exec still executes properly when there's an ' & ' in the temp directory path
/// </summary>
[Fact]
public void TempPathContainsAmpersand4()
{
string directoryWithAmpersand = "space & space";
string newTmp = Path.Combine(Path.GetTempPath(), directoryWithAmpersand);
string oldTmp = Environment.GetEnvironmentVariable("TMP");
try
{
Directory.CreateDirectory(newTmp);
if (NativeMethodsShared.GetShortFilePath(newTmp) == newTmp)
{
// Short file paths not supported, this test will fail.
// See: https://github.com/dotnet/msbuild/issues/1803
return;
}
Environment.SetEnvironmentVariable("TMP", newTmp);
Exec exec = PrepareExec("echo [hello]");
Assert.True(exec.Execute()); // "Task should have succeeded"
((MockEngine)exec.BuildEngine).AssertLogContains("[hello]");
}
finally
{
Environment.SetEnvironmentVariable("TMP", oldTmp);
if (Directory.Exists(newTmp))
{
FileUtilities.DeleteWithoutTrailingBackslash(newTmp);
}
}
}
/// <summary>
/// Tests that Exec still executes properly when there's a non-ANSI character in the command
/// </summary>
[Fact]
public void ExecTaskUnicodeCharacterInCommand()
{
RunExec(true, new UTF8Encoding(false).EncodingName);
}
/// <summary>
/// Tests that Exec task will choose the default code page when UTF8 is not needed.
/// </summary>
[Fact]
public void ExecTaskWithoutUnicodeCharacterInCommand()
{
RunExec(false, EncodingUtilities.CurrentSystemOemEncoding.EncodingName);
}
/// <summary>
/// Exec task will use UTF8 when UTF8 Always is specified (with non-ANSI characters in the Command)
/// </summary>
[Fact]
public void ExecTaskUtf8AlwaysWithNonAnsi()
{
RunExec(true, new UTF8Encoding(false).EncodingName, "Always");
}
/// <summary>
/// Exec task will use UTF8 when UTF8 Always is specified (without non-ANSI characters in the Command)
/// </summary>
[Fact]
public void ExecTaskUtf8AlwaysWithAnsi()
{
RunExec(false, new UTF8Encoding(false).EncodingName, "Always");
}
/// <summary>
/// Exec task will NOT use UTF8 when UTF8 Never is specified and non-ANSI characters are in the Command
/// <remarks>Exec task will fail as the cmd processor will not be able to run the command.</remarks>
/// </summary>
[WindowsOnlyTheory]
[InlineData("Never")]
[InlineData("System")]
public void ExecTaskUtf8NeverWithNonAnsi(string useUtf8)
{
RunExec(true, EncodingUtilities.CurrentSystemOemEncoding.EncodingName, useUtf8, false);
}
/// <summary>
/// Exec task will NOT use UTF8 when UTF8 Never is specified and only ANSI characters are in the Command
/// </summary>
[Theory]
[InlineData("Never")]
[InlineData("System")]
public void ExecTaskUtf8NeverWithAnsi(string useUtf8)
{
RunExec(false, EncodingUtilities.CurrentSystemOemEncoding.EncodingName, useUtf8);
}
[Theory]
[InlineData("MSBUILDUSERAUTORUNINCMD", null, true)]
[InlineData("MSBUILDUSERAUTORUNINCMD", "1", false)]
[Trait("Category", "nonosxtests")]
[Trait("Category", "nonlinuxtests")]
public void ExecTaskDisablesAutoRun(string environmentVariableName, string environmentVariableValue, bool autoRunShouldBeDisabled)
{
using (TestEnvironment testEnvironment = TestEnvironment.Create())
{
testEnvironment.SetEnvironmentVariable(environmentVariableName, environmentVariableValue);
Exec exec = PrepareExec("exit 0");
Type execType = typeof(Exec);
MethodInfo generateCommandLineCommandsMethod = execType.GetMethod("GenerateCommandLineCommands", BindingFlags.Instance | BindingFlags.NonPublic);
string commandLine = generateCommandLineCommandsMethod.Invoke(exec, Array.Empty<object>()) as string;
if (autoRunShouldBeDisabled)
{
commandLine.ShouldContain("/D ");
}
else
{
commandLine.ShouldNotContain("/D ");
}
}
}
/// <summary>
/// Helper function to run the Exec task with or without ANSI characters in the Command and check for an expected encoding.
/// </summary>
/// <param name="includeNonAnsiInCommand">True to include non-ANSI characters in the Command</param>
/// <param name="expectedEncoding">Expected EncodingName</param>
/// <param name="useUtf8">Optional parameter to specify the UseUtf8Encoding on the Exec task</param>
/// <param name="expectSuccess">Optional parameter if the Exec task should succeed or not. Default true.</param>
/// <returns></returns>
private void RunExec(bool includeNonAnsiInCommand, string expectedEncoding, string useUtf8 = null, bool expectSuccess = true)
{
string ansiCharacters = "test";
string nonAnsiCharacters = "\u521B\u5EFA";
string folder = Path.Combine(Path.GetTempPath(), includeNonAnsiInCommand ? nonAnsiCharacters : ansiCharacters);
string command = Path.Combine(folder, "test.cmd");
Exec exec;
try
{
Directory.CreateDirectory(folder);
File.WriteAllText(command, "echo [hello]");
if (!NativeMethodsShared.IsWindows)
{
command = ". " + command;
}
exec = PrepareExec(command);
if (!string.IsNullOrEmpty(useUtf8))
{
exec.UseUtf8Encoding = useUtf8;
}
Assert.Equal(expectSuccess, exec.Execute());
if (expectSuccess)
{
((MockEngine)exec.BuildEngine).AssertLogContains("[hello]");
}
Assert.Equal(expectedEncoding, exec.StdOutEncoding);
Assert.Equal(expectedEncoding, exec.StdErrEncoding);
}
finally
{
if (Directory.Exists(folder))
{
FileUtilities.DeleteWithoutTrailingBackslash(folder, true);
}
}
return;
}
[Fact]
public void InvalidUncDirectorySet()
{
Exec exec = PrepareExec("echo [%cd%]");
exec.WorkingDirectory = @"\\thiscomputerdoesnotexistxyz\thiscomputerdoesnotexistxyz";
bool result = exec.Execute();
Assert.False(result);
((MockEngine)exec.BuildEngine).AssertLogContains("MSB6003");
}
[Fact]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
public void InvalidWorkingDirectorySet()
{
Exec exec = PrepareExec("echo [%cd%]");
exec.WorkingDirectory = @"||invalid||";
bool result = exec.Execute();
Assert.False(result);
((MockEngine)exec.BuildEngine).AssertLogContains("MSB6003");
}
[Fact]
public void BogusCustomRegexesCauseOneErrorEach()
{
Exec exec;
if (NativeMethodsShared.IsWindows)
{
exec = PrepareExec("echo Some output & echo Some output & echo Some output & echo Some output ");
}
else
{
exec = PrepareExec("echo Some output ; echo Some output ; echo Some output ; echo Some output ");
}
exec.CustomErrorRegularExpression = "~!@#$%^_)(*&^%$#@@#XF &%^%T$REd((((([[[[";
exec.CustomWarningRegularExpression = "*";
exec.Execute();
MockEngine e = (MockEngine)exec.BuildEngine;
Console.WriteLine(e.Log);
Assert.Equal(3, e.Errors);
e.AssertLogContains("MSB3076");
}
[Fact]
public void CustomErrorRegexSupplied()
{
string cmdLine;
if (NativeMethodsShared.IsWindows)
{
cmdLine = "echo Some output & echo ALERT:This is an error & echo Some more output";
}
else
{
cmdLine = "echo Some output ; echo ALERT:This is an error ; echo Some more output";
}
Exec exec = PrepareExec(cmdLine);
bool result = exec.Execute();
MockEngine e = (MockEngine)exec.BuildEngine;
Console.WriteLine(e.Log);
Assert.Equal(0, e.Errors);
e.AssertLogContains("ALERT:This is an error");
exec = PrepareExec(cmdLine);
exec.CustomErrorRegularExpression = ".*ALERT.*";
result = exec.Execute();
e = (MockEngine)exec.BuildEngine;
Console.WriteLine(e.Log);
Assert.Equal(2, e.Errors);
e.AssertLogContains("ALERT:This is an error");
}
[Fact]
public void CustomWarningRegexSupplied()
{
string cmdLine;
if (NativeMethodsShared.IsWindows)
{
cmdLine = "echo Some output & echo YOOHOO:This is a warning & echo Some more output";
}
else
{
cmdLine = "echo Some output ; echo YOOHOO:This is a warning ; echo Some more output";
}
Exec exec = PrepareExec(cmdLine);
bool result = exec.Execute();
MockEngine e = (MockEngine)exec.BuildEngine;
Console.WriteLine(e.Log);
Assert.Equal(0, e.Errors);
Assert.Equal(0, e.Warnings);
e.AssertLogContains("YOOHOO:This is a warning");
exec = PrepareExec(cmdLine);
exec.CustomWarningRegularExpression = ".*YOOHOO.*";
result = exec.Execute();
e = (MockEngine)exec.BuildEngine;
Console.WriteLine(e.Log);
Assert.Equal(0, e.Errors);
Assert.Equal(1, e.Warnings);
e.AssertLogContains("YOOHOO:This is a warning");
}
[Fact]
public void ErrorsAndWarningsWithIgnoreStandardErrorWarningFormatTrue()
{
var cmdLine = NativeMethodsShared.IsWindows
? "echo myfile(88,37): error AB1234: thisisacanonicalerror & echo foo: warning CDE1234: thisisacanonicalwarning"
: "echo \"myfile(88,37): error AB1234: thisisacanonicalerror\" ; echo foo: warning CDE1234: thisisacanonicalwarning";
Exec exec = PrepareExec(cmdLine);
exec.IgnoreStandardErrorWarningFormat = true;
bool result = exec.Execute();
Assert.True(result);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Warnings);
}
[Fact]
public void CustomAndStandardErrorsAndWarnings()
{
var cmdLine = NativeMethodsShared.IsWindows
? "echo myfile(88,37): error AB1234: thisisacanonicalerror & echo foo: warning CDE1234: thisisacanonicalwarning & echo YOGI & echo BEAR & echo some content"
: "echo \"myfile(88,37): error AB1234: thisisacanonicalerror\" ; echo foo: warning CDE1234: thisisacanonicalwarning ; echo YOGI ; echo BEAR ; echo some content";
Exec exec = PrepareExec(cmdLine);
exec.CustomWarningRegularExpression = ".*BEAR.*";
exec.CustomErrorRegularExpression = ".*YOGI.*";
bool result = exec.Execute();
Assert.False(result);
Assert.Equal(3, ((MockEngine)exec.BuildEngine).Errors);
Assert.Equal(2, ((MockEngine)exec.BuildEngine).Warnings);
}
/// <summary>
/// Nobody should try to run a string emitted from the task through String.Format.
/// Firstly that's unnecessary and secondly if there's eg an unmatched curly it will throw.
/// </summary>
[Fact]
public void DoNotAttemptToFormatTaskOutput()
{
Exec exec = PrepareExec("echo unmatched curly {");
bool result = exec.Execute();
Assert.True(result);
((MockEngine)exec.BuildEngine).AssertLogContains("unmatched curly {");
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Warnings);
}
/// <summary>
/// Nobody should try to run a string emitted from the task through String.Format.
/// Firstly that's unnecessary and secondly if there's eg an unmatched curly it will throw.
/// </summary>
[Fact]
public void DoNotAttemptToFormatTaskOutput2()
{
Exec exec = PrepareExec("echo unmatched curly {");
exec.IgnoreStandardErrorWarningFormat = true;
bool result = exec.Execute();
Assert.True(result);
((MockEngine)exec.BuildEngine).AssertLogContains("unmatched curly {");
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Warnings);
}
[Fact]
public void NoDuplicateMessagesWhenCustomRegexAndRegularRegexBothMatch()
{
var cmdLine = NativeMethodsShared.IsWindows
? "echo myfile(88,37): error AB1234: thisisacanonicalerror & echo foo: warning CDE1234: thisisacanonicalwarning "
: "echo \"myfile(88,37): error AB1234: thisisacanonicalerror\" ; echo foo: warning CDE1234: thisisacanonicalwarning ";
Exec exec = PrepareExec(cmdLine);
exec.CustomErrorRegularExpression = ".*canonicale.*";
exec.CustomWarningRegularExpression = ".*canonicalw.*";
bool result = exec.Execute();
Assert.False(result);
Assert.Equal(2, ((MockEngine)exec.BuildEngine).Errors);
Assert.Equal(1, ((MockEngine)exec.BuildEngine).Warnings);
}
[Fact]
public void OnlySingleErrorWhenCustomWarningAndCustomErrorRegexesBothMatch()
{
Exec exec = PrepareExec("echo YOGI BEAR ");
exec.CustomErrorRegularExpression = ".*YOGI.*";
exec.CustomWarningRegularExpression = ".*BEAR.*";
bool result = exec.Execute();
Assert.False(result);
Assert.Equal(2, ((MockEngine)exec.BuildEngine).Errors);
Assert.Equal(0, ((MockEngine)exec.BuildEngine).Warnings);
}
[Fact]
public void GettersSetters()
{
Exec exec = PrepareExec("echo [%cd%]");
exec.WorkingDirectory = "foo";
Assert.Equal("foo", exec.WorkingDirectory);
exec.IgnoreExitCode = true;
Assert.True(exec.IgnoreExitCode);
exec.Outputs = null;
Assert.Empty(exec.Outputs);
ITaskItem[] items = { new TaskItem("hi"), new TaskItem("ho") };
exec.Outputs = items;
Assert.Equal(items, exec.Outputs);
}
[Fact]
public void StdEncodings()
{
ExecWrapper exec = PrepareExecWrapper("echo [%cd%]");
exec.StdErrEncoding = "US-ASCII";
Assert.Contains("US-ASCII", exec.StdErrEncoding);
Assert.Contains("US-ASCII", exec.StdErrorEncoding.EncodingName);
exec.StdOutEncoding = "US-ASCII";
Assert.Contains("US-ASCII", exec.StdOutEncoding);
Assert.Contains("US-ASCII", exec.StdOutputEncoding.EncodingName);
}
[Fact]
public void AnyExistingEnvVarCalledErrorLevelIsIgnored()
{
string oldValue = Environment.GetEnvironmentVariable("errorlevel");
try
{
Exec exec = PrepareExec("echo this is an innocuous successful command");
Environment.SetEnvironmentVariable("errorlevel", "1");
bool result = exec.Execute();
Assert.True(result);
}
finally
{
Environment.SetEnvironmentVariable("errorlevel", oldValue);
}
}
[Fact]
public void ValidateParametersNoCommand()
{
Exec exec = PrepareExec(" ");
bool result = exec.Execute();
Assert.False(result);
((MockEngine)exec.BuildEngine).AssertLogContains("MSB3072");
}
/// <summary>
/// Verify that the EnvironmentVariables parameter exposed publicly
/// by ToolTask can be used to modify the environment of the cmd.exe spawned.
/// </summary>
[Fact]
public void SetEnvironmentVariableParameter()
{
Exec exec = new Exec();
exec.BuildEngine = new MockEngine();
exec.Command = NativeMethodsShared.IsWindows ? "echo [%MYENVVAR%]" : "echo [$myenvvar]";
exec.EnvironmentVariables = new[] { "myenvvar=myvalue" };
exec.Execute();
((MockEngine)exec.BuildEngine).AssertLogContains("[myvalue]");
}
/// <summary>
/// Execute return output as an Item
/// Test include ConsoleToMSBuild, StandardOutput
/// </summary>
[Fact]
public void ConsoleToMSBuild()
{
// Exec with no output
Exec exec = PrepareExec("set foo=blah");
// Test Set and Get of ConsoleToMSBuild
exec.ConsoleToMSBuild = true;
Assert.True(exec.ConsoleToMSBuild);
bool result = exec.Execute();
Assert.True(result);
// Nothing to run, so the list should be empty
Assert.Empty(exec.ConsoleOutput);
// first echo prints "Hello stderr" to stderr, second echo prints to stdout
string testString = "echo Hello stderr 1>&2\necho Hello stdout";
exec = PrepareExec(testString);
// Test Set and Get of ConsoleToMSBuild
exec.ConsoleToMSBuild = true;
Assert.True(exec.ConsoleToMSBuild);
result = exec.Execute();
Assert.True(result);
// Both two lines should had gone to stdout
Assert.Equal(2, exec.ConsoleOutput.Length);
}
[Fact]
public void EndToEndMultilineExec()
{
using (var env = TestEnvironment.Create(_output))
{
var testProject = env.CreateTestProjectWithFiles(@"<Project>
<Target Name=""MultilineExec"">
<Exec Command=""echo line 1
echo line 2
echo line 3"" />
</Target>
</Project>");
using (var buildManager = new BuildManager())
{
MockLogger logger = new MockLogger(_output, profileEvaluation: false, printEventsToStdout: false);
var parameters = new BuildParameters()
{
Loggers = new[] { logger },
};
using var collection = new ProjectCollection(
new Dictionary<string, string>(),
new[] { logger },
remoteLoggers: null,
ToolsetDefinitionLocations.Default,
maxNodeCount: 1,
onlyLogCriticalEvents: false,
loadProjectsReadOnly: true);
var project = collection.LoadProject(testProject.ProjectFile).CreateProjectInstance();
var request = new BuildRequestData(
project,
targetsToBuild: new[] { "MultilineExec" },
hostServices: null);
var result = buildManager.Build(parameters, request);
logger.AssertLogContains("line 2");
logger.AssertLogContains("line 3");
// To be correct, these need to be on separate lines, not
// all together on one.
logger.AssertLogDoesntContain("1 echo line");
result.OverallResult.ShouldBe(BuildResultCode.Success);
}
}
}
[Fact]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
public void EndToEndMultilineExec_EscapeSpecialCharacters()
{
using (var env = TestEnvironment.Create(_output))
{
var testProject = env.CreateTestProjectWithFiles(@"<Project>
<Target Name=""ExecCommand"">
<Exec Command=""echo Hello, World!"" />
</Target>
</Project>");
// Ensure path has subfolders
var newTempPath = env.CreateNewTempPathWithSubfolder("hello()wo(rld)").TempPath;
string tempPath = Path.GetTempPath();
Assert.StartsWith(newTempPath, tempPath);
using (var buildManager = new BuildManager())
{
MockLogger logger = new MockLogger(_output, profileEvaluation: false, printEventsToStdout: false);
var parameters = new BuildParameters()
{
Loggers = new[] { logger },
};
using var collection = new ProjectCollection(
new Dictionary<string, string>(),
new[] { logger },
remoteLoggers: null,
ToolsetDefinitionLocations.Default,
maxNodeCount: 1,
onlyLogCriticalEvents: false,
loadProjectsReadOnly: true);
var project = collection.LoadProject(testProject.ProjectFile).CreateProjectInstance();
var request = new BuildRequestData(
project,
targetsToBuild: new[] { "ExecCommand" },
hostServices: null);
var result = buildManager.Build(parameters, request);
logger.AssertLogContains("Hello, World!");
result.OverallResult.ShouldBe(BuildResultCode.Success);
}
}
}
[Fact]
public void ConsoleOutputDoesNotTrimLeadingWhitespace()
{
string lineWithLeadingWhitespace = " line with some leading whitespace";
using (var env = TestEnvironment.Create(_output))
{
var textFilePath = env.CreateFile("leading-whitespace.txt", lineWithLeadingWhitespace).Path;
Exec exec = PrepareExec(NativeMethodsShared.IsWindows ? $"type {textFilePath}" : $"cat {textFilePath}");
exec.ConsoleToMSBuild = true;
bool result = exec.Execute();
result.ShouldBeTrue();
exec.ConsoleOutput.Length.ShouldBe(1);
exec.ConsoleOutput[0].ItemSpec.ShouldBe(lineWithLeadingWhitespace);
}
}
}
internal sealed class ExecWrapper : Exec
{
public Encoding StdOutputEncoding
{
get
{
return StandardOutputEncoding;
}
}
public Encoding StdErrorEncoding
{
get
{
return StandardErrorEncoding;
}
}
}
}
|