|
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Shouldly;
using Xunit;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
#nullable disable
namespace Microsoft.Build.UnitTests.BackEnd
{
/*
* Class: OnErrorHandling
*
* Tests that exercise the <OnError> tag.
*/
public sealed class OnError_Tests
{
/*
* Method: Basic
*
* Construct a simple OnError tag.
*/
[Fact]
public void Basic()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
}
#if FEATURE_TASK_GENERATERESOURCES
/// <summary>
/// Target items and properties should be published to the project level even when a task that
/// outputs them fails. (Of course the task must have populated its property before it errors.)
/// Then these items and properties should be visible to the onerror targets.
/// </summary>
[Fact]
public void FailingTaskStillPublishesOutputs()
{
MockLogger l = new MockLogger();
string resx = Path.Combine(Path.GetTempPath(), "FailingTaskStillPublishesOutputs.resx");
try
{
File.WriteAllText(resx, @"
<root>
<resheader name=""resmimetype"">
<value>text/microsoft-resx</value>
</resheader>
<resheader name=""version"">
<value>2.0</value>
</resheader>
<resheader name=""reader"">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name=""writer"">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name=""a"">
<value>aa</value>
</data>
<data name=""b"">
<value>bb</value>
</data>
</root>");
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='Build'>
<GenerateResource
ExecuteAsTool='false'
Sources='" + resx + @"'
StronglyTypedLanguage='!@:|'>
<Output TaskParameter='FilesWritten' ItemName='FilesWrittenItem'/>
<Output TaskParameter='FilesWritten' PropertyName='FilesWrittenProperty'/>
</GenerateResource>
<OnError ExecuteTargets='ErrorTarget'/>
</Target>
<Target Name='ErrorTarget'>
<Message Text='[@(fileswrittenitem)]'/>
<Message Text='[$(fileswrittenproperty)]'/>
</Target>
</Project>"));
Project project = projectFromString.Project;
ProjectInstance p = project.CreateProjectInstance();
p.Build(new string[] { "Build" }, new ILogger[] { l });
string resource = Path.ChangeExtension(resx, ".resources");
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
l.AssertLogContains("[" + resource + "]", "[" + resource + "]");
// And outputs are visible at the project level
Assert.Equal(resource, Helpers.MakeList(p.GetItems("FilesWrittenItem"))[0].EvaluatedInclude);
Assert.Equal(resource, p.GetPropertyValue("FilesWrittenProperty"));
p = project.CreateProjectInstance();
// But are gone after resetting of course
Assert.Empty(Helpers.MakeList(p.GetItems("FilesWrittenItem")));
Assert.Equal(String.Empty, p.GetPropertyValue("FilesWrittenProperty"));
}
finally
{
File.Delete(resx);
}
}
#endif
/// <summary>
/// Target items and properties should be published to the project level when an OnError
/// target runs, and those items and properties should be visible to the OnError targets.
/// </summary>
[Fact]
public void OnErrorSeesPropertiesAndItemsFromFirstTarget()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='Build'>
<!-- Create a bunch of items and properties -->
<CreateItem Include='a1'>
<Output ItemName='i1' TaskParameter='Include'/>
</CreateItem>
<ItemGroup>
<i1 Include='a2'/>
</ItemGroup>
<CreateProperty Value='v1'>
<Output PropertyName='p1' TaskParameter='Value'/>
</CreateProperty>
<PropertyGroup>
<p2>v2</p2>
</PropertyGroup>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='ErrorTarget'/>
</Target>
<Target Name='ErrorTarget'>
<Message Text='[@(i1)][$(p1)][$(p2)]'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
l.AssertLogContains("[a1;a2][v1][v2]");
}
/*
* Method: TwoExecuteTargets
*
* Make sure two execute targets can be called.
*/
[Fact]
public void TwoExecuteTargets()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='CleanUp2'>
<Message Text='CleanUp2-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='CleanUp;CleanUp2'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
Assert.True(l.FullLog.IndexOf("CleanUp2-was-called") != -1); // "The CleanUp2 target should have been called."
}
/*
* Method: TwoOnErrorClauses
*
* Make sure two OnError clauses can be used.
*/
[Fact]
public void TwoOnErrorClauses()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='CleanUp2'>
<Message Text='CleanUp2-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='CleanUp'/>
<OnError ExecuteTargets='CleanUp2'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
Assert.True(l.FullLog.IndexOf("CleanUp2-was-called") != -1); // "The CleanUp2 target should have been called."
}
/*
* Method: DependentTarget
*
* Make sure that a target that is a dependent of a target called because of an
* OnError clause is called
*/
[Fact]
public void DependentTarget()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp' DependsOnTargets='CleanUp2'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='CleanUp2'>
<Message Text='CleanUp2-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
Assert.True(l.FullLog.IndexOf("CleanUp2-was-called") != -1); // "The CleanUp2 target should have been called."
}
/*
* Method: ErrorInChildIsHandledInParent
*
* If a target is dependent on a child target and that child target errors,
* then the parent's OnError clauses should fire.
*/
[Fact]
public void ErrorInChildIsHandledInParent()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='BuildStep1'>
<Error Text='Error-in-build-step-1.'/>
</Target>
<Target Name='Build' DependsOnTargets='BuildStep1'>
<OnError ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'BuildStep1' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
Assert.True(l.FullLog.IndexOf("Error-in-build-step-1") != -1); // "The BuildStep1 target should have been called."
}
/*
* Method: NonExistentExecuteTarget
*
* Construct a simple OnError tag.
*/
[Fact]
public void NonExistentExecuteTarget()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(2, l.ErrorCount); // "Expected at least one error because 'Build' failed and one error because 'CleanUp' didn't exist."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") == -1); // "The CleanUp target should not have been called."
}
/*
* Method: TrueCondition
*
* Test the case when the result of the condition is 'true'
*/
[Fact]
public void TrueCondition()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError Condition=""'A'!='B'"" ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
}
/*
* Method: FalseCondition
*
* Test the case when the result of the condition is 'false'
*/
[Fact]
public void FalseCondition()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError Condition=""'A'=='B'"" ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") == -1); // "The CleanUp target should not have been called."
}
/*
* Method: PropertiesInExecuteTargets
*
* Make sure that properties in ExecuteTargets are properly expanded.
*/
[Fact]
public void PropertiesInExecuteTargets()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<PropertyGroup>
<Part1>Clean</Part1>
<Part2>Up</Part2>
</PropertyGroup>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError Condition=""'A'!='B'"" ExecuteTargets='$(Part1)$(Part2)'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
}
/*
* Method: ErrorTargetsContinueAfterErrorsInErrorHandler
*
* If an error occurs in an error handling target, then continue processing
* remaining error targets
*/
[Fact]
public void ErrorTargetsContinueAfterErrorsInErrorHandler()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp1'>
<Message Text='CleanUp1-was-called.'/>
<Error Text='Error in CleanUp1.'/>
</Target>
<Target Name='CleanUp2'>
<Message Text='CleanUp2-was-called.'/>
<Error Text='Error in CleanUp2.'/>
</Target>
<Target Name='CleanUp3'>
<Message Text='CleanUp3-was-called.'/>
<Error Text='Error in CleanUp3.'/>
</Target>
<Target Name='CoreBuild'>
<Error Text='This is an error.'/>
<OnError ExecuteTargets='CleanUp1;CleanUp2'/>
</Target>
<Target Name='Build' DependsOnTargets='CoreBuild'>
<OnError ExecuteTargets='CleanUp3'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(4, l.ErrorCount); // "Four build errors expect: One from CoreBuild and on each from the error handlers."
Assert.True(l.FullLog.IndexOf("CleanUp1-was-called") != -1); // "The CleanUp1 target should have been called."
Assert.True(l.FullLog.IndexOf("CleanUp2-was-called") != -1); // "The CleanUp2 target should have been called."
Assert.True(l.FullLog.IndexOf("CleanUp3-was-called") != -1); // "The CleanUp3 target should have been called."
}
/*
* Method: ExecuteTargetIsMissing
*
* If an OnError specifies an ExecuteTarget that is missing, that's an error
*/
[Fact]
public void ExecuteTargetIsMissing()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='Build'>
<Error Text='This is an error.'/>
<OnError Condition=""'A'!='B'"" ExecuteTargets='CleanUp'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(2, l.ErrorCount); // "Expected one error because 'Build' failed and one because 'CleanUp' doesn't exist."
}
/*
* Method: CommentsAroundOnError
*
* Since there is special-case code to ignore comments around OnError blocks,
* let's test this case.
*/
[Fact]
public void CommentsAroundOnError()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='Build'>
<Error Text='This is an error.'/>
<!-- Comment before OnError -->
<OnError Condition=""'A'!='B'"" ExecuteTargets='CleanUp'/>
<!-- Comment after OnError -->
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("CleanUp-was-called") != -1); // "The CleanUp target should have been called."
}
/*
* Method: CircularDependency
*
* Detect circular dependencies and break out.
*/
[Fact]
public void CircularDependency()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='Build'>
<Error Text='Error in Build-Target'/>
<OnError ExecuteTargets='Build'/>
</Target>
</Project>"));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(2, l.ErrorCount); // "Expected one error because 'Build' failed and one error because of the circular dependency."
}
/*
* Method: OutOfOrderOnError
*
* OnError clauses must come at the end of a Target, it can't be sprinkled in-between tasks. Catch this case.
*/
[Fact]
public void OutOfOrderOnError()
{
Assert.Throws<InvalidProjectFileException>(() =>
{
MockLogger l = new MockLogger();
Project p = new Project(XmlReader.Create(new StringReader(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='CleanUp'>
<Message Text='CleanUp-was-called.'/>
</Target>
<Target Name='Build'>
<OnError ExecuteTargets='CleanUp'/>
<Delete Files='__non__existent_file__'/>
</Target>
</Project>"))));
/* No build required */
});
}
[Theory]
[InlineData("True")]
[InlineData("False")]
[InlineData("Default")]
public void ErrorWhenTaskFailsWithoutLoggingErrorEscapeHatch(string failureResponse)
{
MockLogger logger = ObjectModelHelpers.BuildProjectExpectFailure($@"
<Project>
<UsingTask TaskName=""FailingTask"" AssemblyFile=""{Assembly.GetExecutingAssembly().Location}"" />
<Target Name=""MyTarget"">
<FailingTask AllowFailureWithoutError=""{failureResponse}"" />
</Target>
</Project>");
if (!string.Equals(failureResponse, "True"))
{
logger.ErrorCount.ShouldBe(1);
logger.Errors.First().Code.ShouldBe("MSB4181");
}
else
{
logger.ErrorCount.ShouldBe(0);
logger.AssertLogContains(String.Format(MockLogger.GetString("TaskReturnedFalseButDidNotLogError"), "FailingTask"));
}
}
#region Postbuild
/*
* Method: PostBuildBasic
*
* Handle the basic post-build case where the user has asked for 'On_Success' and
* none of the build steps fail.
*/
[Fact]
public void PostBuildBasic()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(PostBuildBuilder("On_Success", FailAt.Nowhere));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(0, l.ErrorCount); // "Expected no error because 'Build' succeeded."
Assert.True(l.FullLog.IndexOf("ResGen-was-called") != -1); // "The ResGen target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-was-called") != -1); // "The Compile target should have been called."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-was-called") != -1); // "The GenerateSatellites target should have been called."
Assert.True(l.FullLog.IndexOf("PostBuild-was-called") != -1); // "The PostBuild target should have been called."
}
/*
* Method: PostBuildOnSuccessWhereCompileFailed
*
* User asked for 'On_Success' but the compile step failed. We don't expect post-build
* to be called.
*/
[Fact]
public void PostBuildOnSuccessWhereCompileFailed()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(PostBuildBuilder("On_Success", FailAt.Compile));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("ResGen-was-called") != -1); // "The ResGen target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-was-called") != -1); // "The Compile target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-step-failed") != -1); // "The Compile target should have failed."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-was-called") == -1); // "The GenerateSatellites target should not have been called."
Assert.True(l.FullLog.IndexOf("PostBuild-was-called") == -1); // "The PostBuild target should not have been called."
}
/*
* Method: PostBuildOnSuccessWhereGenerateSatellitesFailed
*
* User asked for 'On_Success' but the PostBuildOnSuccessWhereGenerateSatellitesFailed step
* failed. We don't expect post-build to be called.
*/
[Fact]
public void PostBuildOnSuccessWhereGenerateSatellitesFailed()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(PostBuildBuilder("On_Success", FailAt.GenerateSatellites));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("ResGen-was-called") != -1); // "The ResGen target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-was-called") != -1); // "The Compile target should have been called."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-was-called") != -1); // "The GenerateSatellites target should have been called."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-step-failed") != -1); // "The GenerateSatellites target should have failed."
Assert.True(l.FullLog.IndexOf("PostBuild-was-called") == -1); // "The PostBuild target should not have been called."
}
/*
* Method: PostBuildAlwaysWhereCompileFailed
*
* User asked for 'Always' but the compile step failed. We expect the post-build
* to be called.
*/
[Fact]
public void PostBuildAlwaysWhereCompileFailed()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(PostBuildBuilder("Always", FailAt.Compile));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("ResGen-was-called") != -1); // "The ResGen target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-was-called") != -1); // "The Compile target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-step-failed") != -1); // "The Compile target should have failed."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-was-called") == -1); // "The GenerateSatellites target should not have been called."
Assert.True(l.FullLog.IndexOf("PostBuild-was-called") != -1); // "The PostBuild target should have been called."
}
/*
* Method: PostBuildFinalOutputChangedWhereCompileFailed
*
* User asked for 'Final_Output_Changed' but the Compile step failed.
* We expect post-build to be called.
*/
[Fact]
public void PostBuildFinalOutputChangedWhereCompileFailed()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(PostBuildBuilder("Final_Output_Changed", FailAt.Compile));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("ResGen-was-called") != -1); // "The ResGen target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-was-called") != -1); // "The Compile target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-step-failed") != -1); // "The Compile target should have failed."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-was-called") == -1); // "The GenerateSatellites target should not have been called."
Assert.True(l.FullLog.IndexOf("PostBuild-was-called") == -1); // "The PostBuild target should not have been called."
}
/*
* Method: PostBuildFinalOutputChangedWhereGenerateSatellitesFailed
*
* User asked for 'Final_Output_Changed' but the GenerateSatellites step failed.
* We expect post-build to be called because Compile succeeded (and wrote to the output).
*/
[Fact]
public void PostBuildFinalOutputChangedWhereGenerateSatellitesFailed()
{
MockLogger l = new MockLogger();
using ProjectFromString projectFromString = new(PostBuildBuilder("Final_Output_Changed", FailAt.GenerateSatellites));
Project p = projectFromString.Project;
p.Build(new string[] { "Build" }, new ILogger[] { l });
Assert.Equal(1, l.ErrorCount); // "Expected one error because 'Build' failed."
Assert.True(l.FullLog.IndexOf("ResGen-was-called") != -1); // "The ResGen target should have been called."
Assert.True(l.FullLog.IndexOf("Compile-was-called") != -1); // "The Compile target should have been called."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-was-called") != -1); // "The GenerateSatellites target should have been called."
Assert.True(l.FullLog.IndexOf("GenerateSatellites-step-failed") != -1); // "The GenerateSatellites target should have failed."
Assert.True(l.FullLog.IndexOf("PostBuild-was-called") != -1); // "The PostBuild target should have been called."
}
/*
* The different places that PostBuildBuilder might be instructed to fail at
*/
private enum FailAt
{
Compile,
GenerateSatellites,
Nowhere
}
/*
* Method: PostBuildBuilder
*
* Build a project file that mimics the fairly complex way we plan to use OnError
* to handle all the different combinations of project failures and post-build
* conditions.
*
*/
private static string PostBuildBuilder(
string controlFlag, // On_Success, Always, Final_Output_Changed
FailAt failAt)
{
string compileStep = "";
if (FailAt.Compile == failAt)
{
compileStep = "<Error Text='Compile-step-failed'/>";
}
string generateSatellites = "";
if (FailAt.GenerateSatellites == failAt)
{
generateSatellites = "<Error Text='GenerateSatellites-step-failed'/>";
}
return String.Format(ObjectModelHelpers.CleanupFileContents(@"
<Project DefaultTargets='Build' ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<PropertyGroup>
<Flag>{0}</Flag>
</PropertyGroup>
<Target Name='ResGen'>
<Message Text='ResGen-was-called.'/>
</Target>
<Target Name='Compile'>
<Message Text='Compile-was-called.'/>
<RemoveDir Directories='c:\__Hopefully_Nonexistent_Directory__'/>
{1}
<CreateItem Include='Yes'>
<Output TaskParameter='Include' ItemName='FinalOutputChanged'/>
</CreateItem>
</Target>
<Target Name='GenerateSatellites'>
<Message Text='GenerateSatellites-was-called.'/>
{2}
</Target>
<Target Name='PostBuild'>
<Message Text='PostBuild-was-called.'/>
</Target>
<Target Name='PostBuildOnSuccess' Condition=""'$(Flag)'!='Final_Output_Changed'"" DependsOnTargets='PostBuild'>
<Message Text='PostBuildOnSuccess-was-called.'/>
</Target>
<Target Name='PostBuildOnOutputChange' Condition=""'$(Flag)_@(FinalOutputChanged)'=='Final_Output_Changed_Yes'"" DependsOnTargets='PostBuild'>
<Message Text='PostBuildOnOutputChange-was-called.'/>
</Target>
<Target Name='CoreBuildSucceeded' DependsOnTargets='PostBuildOnSuccess;PostBuildOnOutputChange'>
<Message Text='CoreBuildSucceeded-was-called.'/>
</Target>
<Target Name='CoreBuild' DependsOnTargets='ResGen;Compile;GenerateSatellites'>
<Message Text='CoreBuild-was-called.'/>
<OnError Condition=""'$(Flag)'=='Always'"" ExecuteTargets='PostBuild'/>
<OnError ExecuteTargets='PostBuildOnOutputChange'/>
</Target>
<Target Name='Build' DependsOnTargets='CoreBuild;CoreBuildSucceeded'>
<Message Text='Build-target-was-called.'/>
</Target>
</Project>
"), controlFlag, compileStep, generateSatellites);
}
#endregion
}
}
|