// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//-----------------------------------------------------------------------
//
// Unit tests for the BuildManager object.
//-----------------------------------------------------------------------
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Engine.UnitTests;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
using static Microsoft.Build.UnitTests.ObjectModelHelpers;
namespace Microsoft.Build.UnitTests.BackEnd
{
///
/// The test fixture for the BuildManager
///
public class BuildManager_Tests : IDisposable
{
///
/// The mock logger for testing.
///
private readonly MockLogger _logger;
///
/// The standard build manager for each test.
///
private BuildManager _buildManager;
///
/// The build parameters.
///
private readonly BuildParameters _parameters;
///
/// The project collection used.
///
private readonly ProjectCollection _projectCollection;
private readonly TestEnvironment _env;
private readonly ITestOutputHelper _output;
///
/// SetUp
///
public BuildManager_Tests(ITestOutputHelper output)
{
_output = output;
// Ensure that any previous tests which may have been using the default BuildManager do not conflict with us.
BuildManager.DefaultBuildManager.Dispose();
_logger = new MockLogger(output);
_parameters = new BuildParameters
{
ShutdownInProcNodeOnBuildFinish = true,
Loggers = new ILogger[] { _logger },
EnableNodeReuse = false
};
_buildManager = new BuildManager();
_projectCollection = new ProjectCollection();
_env = TestEnvironment.Create(output);
_env.SetEnvironmentVariable("MSBUILDINPROCENVCHECK", "1");
}
///
/// TearDown
///
public void Dispose()
{
_buildManager.Dispose();
_projectCollection.Dispose();
_env.Dispose();
}
///
/// Check that we behave reasonably when passed a null ProjectCollection
///
[Fact]
public void BuildParametersWithNullCollection()
{
Assert.Throws(() => { new BuildParameters(null); });
}
///
/// A simple successful build.
///
[Fact]
public void SimpleBuild()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
BuildRequestData data = GetBuildRequestData(contents);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
string propertyValue;
Assert.True(properties.TryGetValue("InitialProperty1", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty1", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty2", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty2", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty3", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty3", StringComparison.OrdinalIgnoreCase));
}
#if FEATURE_CODETASKFACTORY
///
/// Verify that the environment between two msbuild calls to the same project are stored
/// so that on the next call we get access to them
///
[Fact]
public void VerifyEnvironmentSavedBetweenCalls()
{
string contents1 = CleanupFileContents(@"
System.Environment.SetEnvironmentVariable(""MOO"", ""When the dawn comes, tonight will be a memory too"");
");
var project = new Project(XmlReader.Create(new StringReader(contents1)), null, null, _projectCollection)
{
FullPath = _env.CreateFile(".proj").Path
};
project.Save();
string contents2 = CleanupFileContents(@"
" +
"" +
@"
");
ProjectInstance instance = CreateProjectInstance(contents2, null, _projectCollection, true);
BuildRequestData data = new BuildRequestData(instance, new[] { "Build" }, _projectCollection.HostServices);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("What does a cat say : When the dawn comes, tonight will be a memory too");
}
#endif
///
/// Verify if idle nodes are shutdown when BuildManager.ShutdownAllNodes is evoked.
/// The final number of nodes has to be less or equal the number of nodes already in
/// the system before this method was called.
///
#if RUNTIME_TYPE_NETCORE
[Theory(Skip = "https://github.com/Microsoft/msbuild/issues/1975")]
#elif MONO
[Theory(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Theory(Skip = "https://github.com/Microsoft/msbuild/issues/2057")]
[InlineData(8, false)]
#endif
public void ShutdownNodesAfterParallelBuild(int numberOfParallelProjectsToBuild, bool enbaleDebugComm)
{
// This test has previously been failing silently. With the addition of TestEnvironment the
// failure is now noticed (worker node is crashing with "Pipe is broken" exception. See #2057:
// https://github.com/Microsoft/msbuild/issues/2057
_env.ClearTestInvariants();
// Communications debug log enabled, picked up by TestEnvironment
if (enbaleDebugComm) _env.SetEnvironmentVariable("MSBUILDDEBUGCOMM", "1");
ProjectCollection projectCollection = new ProjectCollection();
// Get number of MSBuild processes currently instantiated
int numberProcsOriginally = (new List(Process.GetProcessesByName("MSBuild"))).Count;
_output.WriteLine($"numberProcsOriginally = {numberProcsOriginally}");
// Generate a theoretically unique directory to put our dummy projects in.
string shutdownProjectDirectory = Path.Combine(Path.GetTempPath(), String.Format(CultureInfo.InvariantCulture, "VSNodeShutdown_{0}_UnitTest", Process.GetCurrentProcess().Id));
// Create the dummy projects we'll be "building" as our excuse to connect to and shut down
// all the nodes.
ProjectInstance rootProject = GenerateDummyProjects(shutdownProjectDirectory, numberOfParallelProjectsToBuild, projectCollection);
// Build the projects.
var buildParameters = new BuildParameters(projectCollection)
{
OnlyLogCriticalEvents = true,
MaxNodeCount = numberOfParallelProjectsToBuild,
EnableNodeReuse = true,
DisableInProcNode = true,
SaveOperatingEnvironment = false,
Loggers = new List {new MockLogger(_output)}
};
// Tell the build manager to not disturb process wide state
BuildRequestData requestData = new BuildRequestData(rootProject, new[] { "Build" }, null);
// Use a separate BuildManager for the node shutdown build, so that we don't have
// to worry about taking dependencies on whether or not the existing ones have already
// disappeared.
BuildManager shutdownManager = new BuildManager("IdleNodeShutdown");
shutdownManager.Build(buildParameters, requestData);
// Number of nodes after the build has to be greater than the original number
int numberProcsAfterBuild = (new List(Process.GetProcessesByName("MSBuild"))).Count;
_output.WriteLine($"numberProcsAfterBuild = {numberProcsAfterBuild}");
Assert.True(numberProcsOriginally < numberProcsAfterBuild, $"Expected '{numberProcsOriginally}' < '{numberProcsAfterBuild}'");
// Shutdown all nodes
shutdownManager.ShutdownAllNodes();
// Wait until all processes shut down
Thread.Sleep(3000);
// Number of nodes after the shutdown has to be smaller or equal the original number
int numberProcsAfterShutdown = (new List(Process.GetProcessesByName("MSBuild"))).Count;
_output.WriteLine($"numberProcsAfterShutdown = {numberProcsAfterShutdown}");
Assert.True(numberProcsAfterShutdown <= numberProcsOriginally);
// Delete directory with the dummy project
if (Directory.Exists(shutdownProjectDirectory))
{
FileUtilities.DeleteWithoutTrailingBackslash(shutdownProjectDirectory, true /* recursive delete */);
}
}
///
/// A simple successful build, out of process only.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void SimpleBuildOutOfProcess()
{
RunOutOfProcBuild(_ => _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1"));
}
///
/// A simple successful build, out of process only. Triggered by setting build parameters' DisableInProcNode to true.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void DisableInProcNode()
{
RunOutOfProcBuild(buildParameters => buildParameters.DisableInProcNode = true);
}
///
/// Runs a build and verifies it happens out of proc by checking the process ID.
///
/// Runs a test out of proc.
public void RunOutOfProcBuild(Action buildParametersModifier)
{
const string Contents = @"
";
// Need to set this env variable to enable Process.GetCurrentProcess().Id in the project file.
_env.SetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS", "1");
Project project = CreateProject(CleanupFileContents(Contents), MSBuildDefaultToolsVersion, _projectCollection, false);
BuildRequestData data = new BuildRequestData(project.CreateProjectInstance(), new string[0], _projectCollection.HostServices);
BuildParameters customparameters = new BuildParameters { EnableNodeReuse = false, Loggers = new ILogger[] { _logger } };
buildParametersModifier(customparameters);
BuildResult result = _buildManager.Build(customparameters, data);
TargetResult targetresult = result.ResultsByTarget["test"];
ITaskItem[] item = targetresult.Items;
Assert.Equal(BuildResultCode.Success, result.OverallResult);
Assert.Equal(3, item.Length);
int processId;
Assert.True(int.TryParse(item[2].ItemSpec, out processId), $"Process ID passed from the 'test' target is not a valid integer (actual is '{item[2].ItemSpec}')");
Assert.NotEqual(Process.GetCurrentProcess().Id, processId); // "Build is expected to be out-of-proc. In fact it was in-proc."
}
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void RequestedResultsAreSatisfied()
{
const string contents = @"
IsUnrequested
IsRequested
Stale
FunValue
Updated
";
// Need to set this env variable to enable Process.GetCurrentProcess().Id in the project file.
_env.SetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS", "1");
Project project = CreateProject(CleanupFileContents(contents), MSBuildDefaultToolsVersion,
_projectCollection, false);
var requestedProjectState = new RequestedProjectState
{
ItemFilters = new Dictionary>
{
{"AnItem", null},
{"ItemWithMetadata", new List {"Metadatum1"}},
},
PropertyFilters = new List {"NewProperty", "RequestedProperty"},
};
BuildRequestData data = new BuildRequestData(project.CreateProjectInstance(), new [] {"test", "other"},
_projectCollection.HostServices, BuildRequestDataFlags.ProvideSubsetOfStateAfterBuild, null,
requestedProjectState);
BuildParameters customparameters = new BuildParameters
{
EnableNodeReuse = false,
Loggers = new ILogger[] {_logger},
DisableInProcNode = true,
};
BuildResult result = _buildManager.Build(customparameters, data);
result.OverallResult.ShouldBe(BuildResultCode.Success);
result.ProjectStateAfterBuild.ShouldNotBeNull();
result.ProjectStateAfterBuild.Properties.ShouldNotContain(p => p.Name == "UnrequestedProperty");
result.ProjectStateAfterBuild.Properties.ShouldContain(p => p.Name == "NewProperty");
result.ProjectStateAfterBuild.GetPropertyValue("NewProperty").ShouldBe("FunValue");
result.ProjectStateAfterBuild.Properties.ShouldContain(p => p.Name == "RequestedProperty");
result.ProjectStateAfterBuild.GetPropertyValue("RequestedProperty").ShouldBe("IsRequested");
result.ProjectStateAfterBuild.Items.Count.ShouldBe(4);
result.ProjectStateAfterBuild.GetItems("ItemWithMetadata").ShouldHaveSingleItem();
result.ProjectStateAfterBuild.GetItems("ItemWithMetadata").First().DirectMetadataCount.ShouldBe(1);
result.ProjectStateAfterBuild.GetItems("ItemWithMetadata").First().GetMetadataValue("Metadatum1")
.ShouldBe("m1");
result.ProjectStateAfterBuild.GetItems("ItemWithMetadata").First().GetMetadataValue("Metadatum2")
.ShouldBeNullOrEmpty();
result.ProjectStateAfterBuild.GetItems("AnItem").Count.ShouldBe(3);
result.ProjectStateAfterBuild.GetItems("AnItem").ShouldContain(p => p.EvaluatedInclude == "Item2");
result.ProjectStateAfterBuild.GetItemsByItemTypeAndEvaluatedInclude("AnItem", "Item1")
.ShouldHaveSingleItem();
result.ProjectStateAfterBuild.GetItemsByItemTypeAndEvaluatedInclude("AnItem", "Item1").First()
.GetMetadataValue("UnexpectedMetadatum").ShouldBe("Unexpected");
}
///
/// Make sure when we are doing an in-process build that even if the environment variable MSBUILDFORWARDPROPERTIESFROMCHILD is set that we still
/// get all of the initial properties.
///
[Fact]
public void InProcForwardPropertiesFromChild()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
_env.SetEnvironmentVariable("MSBuildForwardPropertiesFromChild", "InitialProperty2;IAMNOTREAL");
BuildRequestData data = GetBuildRequestData(contents);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
string propertyValue;
Assert.True(properties.TryGetValue("InitialProperty1", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty1", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty2", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty2", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty3", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty3", StringComparison.OrdinalIgnoreCase));
}
///
/// Make sure when we are doing an in-process build that even if the environment variable MsBuildForwardAllPropertiesFromChild is set that we still
/// get all of the initial properties.
///
[Fact]
public void InProcMsBuildForwardAllPropertiesFromChild()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
_env.SetEnvironmentVariable("MsBuildForwardAllPropertiesFromChild", "InitialProperty2;IAMNOTREAL");
BuildRequestData data = GetBuildRequestData(contents);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
string propertyValue = null;
Assert.True(properties.TryGetValue("InitialProperty1", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty1", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty2", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty2", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty3", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty3", StringComparison.OrdinalIgnoreCase));
}
///
/// Make sure when we launch a child node and set MsBuildForwardAllPropertiesFromChild that we get all of our properties. This needs to happen
/// even if the msbuildforwardpropertiesfromchild is set to something.
///
[Fact]
public void MsBuildForwardAllPropertiesFromChildLaunchChildNode()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
_env.SetEnvironmentVariable("MsBuildForwardAllPropertiesFromChild", "InitialProperty2;IAMNOTREAL");
_env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", "Something");
var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(), MSBuildDefaultToolsVersion, new string[] { }, null);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
string propertyValue;
Assert.True(properties.TryGetValue("InitialProperty1", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty1", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty2", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty2", StringComparison.OrdinalIgnoreCase));
Assert.True(properties.TryGetValue("InitialProperty3", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty3", StringComparison.OrdinalIgnoreCase));
}
///
/// Make sure when if the environment variable MsBuildForwardPropertiesFromChild is set to a value and
/// we launch a child node that we get only that value.
///
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1976")]
#elif MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void OutOfProcNodeForwardCertainproperties()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
_env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", "InitialProperty3;IAMNOTREAL");
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(),
MSBuildDefaultToolsVersion, new string[] { }, null);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
Assert.Equal(1, properties.Count);
string propertyValue;
Assert.True(properties.TryGetValue("InitialProperty3", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty3", StringComparison.OrdinalIgnoreCase));
}
///
/// Make sure when if the environment variable MsBuildForwardPropertiesFromChild is set to a value and
/// we launch a child node that we get only that value. Also, make sure that when a project is pulled from the results cache
/// and we have a list of properties to serialize that we do not crash. This is to prevent a regression of 826594
///
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1976")]
#elif MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void OutOfProcNodeForwardCertainpropertiesAlsoGetResultsFromCache()
{
string tempProject = _env.CreateFile(".proj").Path;
string contents = CleanupFileContents($@"
");
string projectContents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
File.WriteAllText(tempProject, projectContents);
_env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", "InitialProperty3;IAMNOTREAL");
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(),
MSBuildDefaultToolsVersion, new string[] { }, null);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(3, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[1];
// After conversion to xunit, this test sometimes fails at this assertion.
// Related to shared state that the test touches that's getting handled
// differently in xunit?
Assert.NotNull(projectStartedEvent.Properties);
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
Assert.NotNull(properties);
Assert.Equal(1, properties.Count);
string propertyValue;
Assert.True(properties.TryGetValue("InitialProperty3", out propertyValue));
Assert.True(String.Equals(propertyValue, "InitialProperty3", StringComparison.OrdinalIgnoreCase));
projectStartedEvent = _logger.ProjectStartedEvents[2];
Assert.Null(projectStartedEvent.Properties);
}
///
/// Make sure when if the environment variable MsBuildForwardPropertiesFromChild is set to empty and
/// we launch a child node that we get no properties
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void ForwardNoPropertiesLaunchChildNode()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
_env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", "");
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(),
MSBuildDefaultToolsVersion, new string[] { }, null);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
Assert.Null(properties);
}
///
/// We want to pass the toolsets from the parent to the child nodes so that any custom toolsets
/// defined on the parent are also available on the child nodes for tasks which use the global project
/// collection
///
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
#elif MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void VerifyCustomToolSetsPropagated()
{
string netFrameworkDirectory = ToolLocationHelper.GetPathToDotNetFrameworkReferenceAssemblies(TargetDotNetFrameworkVersion.Version45);
string contents = CleanupFileContents(@"
");
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
ProjectCollection projectCollection = new ProjectCollection();
Toolset newToolSet = new Toolset("CustomToolSet", "c:\\SomePath", projectCollection, null);
projectCollection.AddToolset(newToolSet);
var project = CreateProject(contents, null, projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(),
MSBuildDefaultToolsVersion, new string[] { }, null);
BuildParameters customParameters = new BuildParameters(projectCollection);
customParameters.Loggers = new ILogger[] { _logger };
BuildResult result = _buildManager.Build(customParameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
}
///
/// When a child node is launched by default we should not send any properties.
/// we launch a child node that we get no properties
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void ForwardNoPropertiesLaunchChildNodeDefault()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
"); _env.SetEnvironmentVariable("MsBuildForwardPropertiesFromChild", null);
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
var project = CreateProject(contents, null, _projectCollection, false);
var data = new BuildRequestData(project.FullPath, new Dictionary(),
MSBuildDefaultToolsVersion, new string[] { }, null);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
ProjectStartedEventArgs projectStartedEvent = _logger.ProjectStartedEvents[0];
Dictionary properties = ExtractProjectStartedPropertyList(projectStartedEvent.Properties);
Assert.Null(properties);
}
///
/// A simple failing build.
///
[Fact]
public void SimpleBuildWithFailure()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertLogContains("[fail]");
}
///
/// A build with a message, error and warning, verify that
/// we only get errors, warnings, and project started and finished when OnlyLogCriticalEvents is true
///
[Fact]
public void SimpleBuildWithFailureAndWarningOnlyLogCriticalEventsTrue()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
_parameters.OnlyLogCriticalEvents = true;
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertLogContains("[fail]");
_logger.AssertLogContains("[warn]");
_logger.AssertLogDoesntContain("[message]");
Assert.Equal(1, _logger.BuildStartedEvents.Count);
Assert.Equal(1, _logger.BuildFinishedEvents.Count);
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
Assert.Equal(1, _logger.ProjectFinishedEvents.Count);
Assert.Equal(0, _logger.TargetStartedEvents.Count);
Assert.Equal(0, _logger.TargetFinishedEvents.Count);
Assert.Equal(0, _logger.TaskStartedEvents.Count);
Assert.Equal(0, _logger.TaskFinishedEvents.Count);
}
///
/// A build with a message, error and warning, verify that
/// we only get errors, warnings, messages, task and target messages OnlyLogCriticalEvents is false
///
[Fact]
public void SimpleBuildWithFailureAndWarningOnlyLogCriticalEventsFalse()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
_parameters.OnlyLogCriticalEvents = false;
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertLogContains("[fail]");
_logger.AssertLogContains("[warn]");
_logger.AssertLogContains("[message]");
Assert.Equal(1, _logger.BuildStartedEvents.Count);
Assert.Equal(1, _logger.BuildFinishedEvents.Count);
Assert.Equal(1, _logger.ProjectStartedEvents.Count);
Assert.Equal(1, _logger.ProjectFinishedEvents.Count);
Assert.Equal(1, _logger.TargetStartedEvents.Count);
Assert.Equal(1, _logger.TargetFinishedEvents.Count);
Assert.Equal(3, _logger.TaskStartedEvents.Count);
Assert.Equal(3, _logger.TaskFinishedEvents.Count);
}
///
/// Submitting a synchronous build request before calling BeginBuild yields an InvalidOperationException.
///
[Fact]
public void BuildRequestWithoutBegin()
{
Assert.Throws(() =>
{
BuildRequestData data = new BuildRequestData("foo", new Dictionary(), "2.0", new string[0], null);
_buildManager.BuildRequest(data);
}
);
}
///
/// Pending a build request before calling BeginBuild yields an InvalidOperationException.
///
[Fact]
public void PendBuildRequestWithoutBegin()
{
Assert.Throws(() =>
{
BuildRequestData data = new BuildRequestData("foo", new Dictionary(), "2.0", new string[0], null);
_buildManager.PendBuildRequest(data);
}
);
}
///
/// Calling EndBuild before BeginBuild yields an InvalidOperationException.
///
[Fact]
public void EndWithoutBegin()
{
Assert.Throws(() =>
{
_buildManager.EndBuild();
}
);
}
[Fact]
public void DisposeAfterUse()
{
string contents = CleanupFileContents(@"
");
var project = CreateProject(contents, null, _projectCollection, false);
var globalProperties = new Dictionary();
var targets = new string[0];
var brd = new BuildRequestData(project.FullPath, globalProperties, null, targets, new HostServices());
using (var bm = new BuildManager())
{
bm.Build(new BuildParameters(), brd);
}
}
[Fact]
public void DisposeWithoutUse()
{
var bm = new BuildManager();
bm.Dispose();
}
///
/// Calling BeginBuild after BeginBuild has already been called yields an InvalidOperationException.
///
[Fact]
public void OverlappingBegin()
{
try
{
_buildManager.BeginBuild(new BuildParameters());
Assert.Throws(() => _buildManager.BeginBuild(new BuildParameters()));
}
finally
{
// Call EndBuild to get us back into a state that approximates reasonable
_buildManager.EndBuild();
}
}
///
/// Starting and ending a build without submitting any requests is valid.
///
[Fact]
public void EmptyBuild()
{
_buildManager.BeginBuild(_parameters);
_buildManager.EndBuild();
Assert.Equal(0, _logger.ErrorCount);
Assert.Equal(0, _logger.WarningCount);
}
///
/// Calling EndBuild after it has already been called yields an InvalidOperationException.
///
[Fact]
public void ExtraEnds()
{
Assert.Throws(() =>
{
_buildManager.BeginBuild(new BuildParameters());
_buildManager.EndBuild();
_buildManager.EndBuild();
}
);
}
///
/// Pending a request after EndBuild has been called yields an InvalidOperationException.
///
[Fact]
public void PendBuildRequestAfterEnd()
{
Assert.Throws(() =>
{
BuildRequestData data = new BuildRequestData("foo", new Dictionary(), "2.0", new string[0], null);
_buildManager.BeginBuild(new BuildParameters());
_buildManager.EndBuild();
_buildManager.PendBuildRequest(data);
}
);
}
///
/// Attempting a synchronous build when a build is in progress yields an InvalidOperationException.
///
[Fact]
public void BuildDuringBuild()
{
try
{
BuildRequestData data =
new BuildRequestData("foo", new Dictionary(), "2.0", new string[0], null);
_buildManager.BeginBuild(new BuildParameters());
Assert.Throws(() => { _buildManager.Build(new BuildParameters(), data); });
}
finally
{
// Call EndBuild to get us back into a state that approximates reasonable
_buildManager.EndBuild();
}
}
///
/// A sequential build.
///
[Fact]
public void EndBuildBlocks()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission1 = _buildManager.PendBuildRequest(data);
submission1.ExecuteAsync(null, null);
Assert.False(submission1.IsCompleted);
_buildManager.EndBuild();
Assert.True(submission1.IsCompleted);
_logger.AssertLogContains("[success 1]");
}
///
/// Validate that EndBuild can be called during a submission completion callback.
///
[Fact]
public void EndBuildCalledWithinSubmissionCallback()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission1 = _buildManager.PendBuildRequest(data);
AutoResetEvent callbackFinished = new AutoResetEvent(false);
submission1.ExecuteAsync(submission =>
{
_buildManager.EndBuild();
callbackFinished.Set();
}, null);
// Wait for the build to finish
Assert.True(callbackFinished.WaitOne(5000)); // "Build is hung."
// EndBuild should now have been called, so invoking it again should give us an invalid operation error.
Assert.Throws(() => _buildManager.EndBuild());
}
///
/// A sequential build.
///
[Fact]
public void SequentialBuild()
{
string contents = CleanupFileContents(@"
");
string contents2 = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
BuildRequestData data2 = GetBuildRequestData(contents2);
_buildManager.BeginBuild(_parameters);
BuildResult result = _buildManager.BuildRequest(data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
BuildResult result2 = _buildManager.BuildRequest(data2);
Assert.Equal(BuildResultCode.Success, result2.OverallResult);
_buildManager.EndBuild();
_logger.AssertLogContains("[success 1]");
_logger.AssertLogContains("[success 2]");
}
///
/// A sequential build.
///
[Fact]
public void OverlappingBuildSubmissions()
{
string contents = CleanupFileContents(@"
");
string contents2 = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
BuildRequestData data2 = GetBuildRequestData(contents2);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission1 = _buildManager.PendBuildRequest(data);
submission1.ExecuteAsync(null, null);
BuildResult result2 = _buildManager.BuildRequest(data2);
submission1.WaitHandle.WaitOne();
BuildResult result = submission1.BuildResult;
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Success, result2.OverallResult);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success 1]");
_logger.AssertLogContains("[success 2]");
}
///
/// If two overlapping submissions are executed against the same project, with at least one
/// target involved that skipped, make sure that the second one successfully completes
/// (retrieved from the cache).
///
[Fact]
public void OverlappingIdenticalBuildSubmissions()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
BuildRequestData data2 = new BuildRequestData(data.ProjectInstance, data.TargetNames.ToArray(), data.HostServices);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission1 = _buildManager.PendBuildRequest(data);
BuildSubmission submission2 = _buildManager.PendBuildRequest(data2);
submission2.ExecuteAsync(null, null);
submission1.ExecuteAsync(null, null);
submission1.WaitHandle.WaitOne();
submission2.WaitHandle.WaitOne();
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Success, submission1.BuildResult.OverallResult);
Assert.Equal(BuildResultCode.Success, submission2.BuildResult.OverallResult);
}
///
/// With two overlapping submissions, the first of which skips a target and the second
/// of which should not, ensure that the second submission does not, in fact, skip
/// the target. (E.g. despite the fact that the target results are in the cache already
/// as 'skipped', ensure that we retry execution in case conditions have changed.)
///
[Fact]
public void OverlappingBuildSubmissions_OnlyOneSucceeds()
{
string contents = CleanupFileContents(@"
true
false
");
BuildRequestData data = GetBuildRequestData(contents, new[] { "A" });
BuildRequestData data2 = new BuildRequestData(data.ProjectInstance, new[] { "MaySkip" }, data.HostServices);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission1 = _buildManager.PendBuildRequest(data);
BuildSubmission submission2 = _buildManager.PendBuildRequest(data2);
submission1.ExecuteAsync(null, null);
submission2.ExecuteAsync(null, null);
submission1.WaitHandle.WaitOne();
submission2.WaitHandle.WaitOne();
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Success, submission1.BuildResult.OverallResult);
Assert.Equal(BuildResultCode.Failure, submission2.BuildResult.OverallResult);
}
///
/// Calling EndBuild with an unexecuted submission.
///
[Fact]
public void EndWithUnexecutedSubmission()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
_buildManager.BeginBuild(_parameters);
_buildManager.PendBuildRequest(data);
_buildManager.EndBuild();
}
///
/// A canceled build with a submission which is not executed yet.
///
[Fact]
public void CancelledBuildWithUnexecutedSubmission()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
_buildManager.BeginBuild(_parameters);
_buildManager.PendBuildRequest(data);
_buildManager.CancelAllSubmissions();
_buildManager.EndBuild();
}
///
/// A canceled build
///
[Fact]
[Trait("Category", "mono-osx-failing")]
public void CancelledBuild()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
_buildManager.BeginBuild(_parameters);
BuildSubmission asyncResult = _buildManager.PendBuildRequest(data);
asyncResult.ExecuteAsync(null, null);
_buildManager.CancelAllSubmissions();
asyncResult.WaitHandle.WaitOne();
BuildResult result = asyncResult.BuildResult;
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Failure, result.OverallResult); // "Build should have failed."
_logger.AssertLogDoesntContain("[fail]");
}
///
/// A canceled build which waits for the task to get started before canceling. Because it is a 2.0 task, we should
/// wait until the task finishes normally (cancellation not supported.)
///
[Fact]
public void CancelledBuildWithDelay20()
{
if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) return;
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents);
_buildManager.BeginBuild(_parameters);
BuildSubmission asyncResult = _buildManager.PendBuildRequest(data);
asyncResult.ExecuteAsync(null, null);
Thread.Sleep(500);
_buildManager.CancelAllSubmissions();
asyncResult.WaitHandle.WaitOne();
BuildResult result = asyncResult.BuildResult;
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Failure, result.OverallResult); // "Build should have failed."
_logger.AssertLogDoesntContain("[fail]");
}
#if FEATURE_TASKHOST
///
/// A canceled build which waits for the task to get started before canceling. Because it is a 2.0 task, we should
/// wait until the task finishes normally (cancellation not supported.)
///
[Fact]
[Trait("Category", "mono-osx-failing")]
public void CancelledBuildInTaskHostWithDelay20()
{
if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) return;
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
_buildManager.BeginBuild(_parameters);
BuildSubmission asyncResult = _buildManager.PendBuildRequest(data);
asyncResult.ExecuteAsync(null, null);
Thread.Sleep(500);
_buildManager.CancelAllSubmissions();
asyncResult.WaitHandle.WaitOne();
BuildResult result = asyncResult.BuildResult;
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Failure, result.OverallResult); // "Build should have failed."
_logger.AssertLogDoesntContain("[fail]");
// Task host should not have exited prematurely
_logger.AssertLogDoesntContain("MSB4217");
}
#endif
///
/// A canceled build which waits for the task to get started before canceling. Because it is a 12.. task, we should
/// cancel the task and exit out after a short period wherein we wait for the task to exit cleanly.
///
[Fact]
[Trait("Category", "mono-osx-failing")]
public void CancelledBuildWithDelay40()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
_buildManager.BeginBuild(_parameters);
BuildSubmission asyncResult = _buildManager.PendBuildRequest(data);
asyncResult.ExecuteAsync(null, null);
Thread.Sleep(500);
_buildManager.CancelAllSubmissions();
asyncResult.WaitHandle.WaitOne();
BuildResult result = asyncResult.BuildResult;
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Failure, result.OverallResult); // "Build should have failed."
_logger.AssertLogDoesntContain("[fail]");
}
#if FEATURE_TASKHOST
///
/// A canceled build which waits for the task to get started before canceling. Because it is a 12.0 task, we should
/// cancel the task and exit out after a short period wherein we wait for the task to exit cleanly.
///
[Fact]
[Trait("Category", "mono-osx-failing")]
public void CancelledBuildInTaskHostWithDelay40()
{
string contents = CleanupFileContents(@"
");
BuildRequestData data = GetBuildRequestData(contents, new string[] { }, MSBuildDefaultToolsVersion);
_buildManager.BeginBuild(_parameters);
BuildSubmission asyncResult = _buildManager.PendBuildRequest(data);
asyncResult.ExecuteAsync(null, null);
Thread.Sleep(500);
_buildManager.CancelAllSubmissions();
asyncResult.WaitHandle.WaitOne();
BuildResult result = asyncResult.BuildResult;
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Failure, result.OverallResult); // "Build should have failed."
_logger.AssertLogDoesntContain("[fail]");
// Task host should not have exited prematurely
_logger.AssertLogDoesntContain("MSB4217");
}
#endif
///
/// This test verifies that builds of the same project instance in sequence are permitted.
///
[Fact]
public void SequentialBuildsOfTheSameProjectAllowed()
{
string contents = CleanupFileContents(@"
");
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
BuildResult result1 = _buildManager.BuildRequest(new BuildRequestData(instance, new[] {"target1"}));
BuildResult result2 = _buildManager.BuildRequest(new BuildRequestData(instance, new[] {"target2"}));
_buildManager.EndBuild();
Assert.Equal(BuildResultCode.Success, result1.OverallResult);
Assert.True(result1.HasResultsForTarget("target1")); // "Results for target1 missing"
Assert.Equal(BuildResultCode.Success, result2.OverallResult);
Assert.True(result2.HasResultsForTarget("target2")); // "Results for target2 missing"
}
///
/// This test verifies that overlapping builds of the same project are allowed.
///
[Fact]
public void OverlappingBuildsOfTheSameProjectDifferentTargetsAreAllowed()
{
string contents = CleanupFileContents(@"
");
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission =_buildManager.PendBuildRequest(new BuildRequestData(instance, new[] {"target1"}));
submission.ExecuteAsync(null, null);
BuildResult result2 =_buildManager.BuildRequest(new BuildRequestData(project.CreateProjectInstance(), new[] {"target2"}));
submission.WaitHandle.WaitOne();
var result1 = submission.BuildResult;
Assert.Equal(BuildResultCode.Success, result1.OverallResult);
Assert.True(result1.HasResultsForTarget("target1")); // "Results for target1 missing"
Assert.Equal(BuildResultCode.Success, result2.OverallResult);
Assert.True(result2.HasResultsForTarget("target2")); // "Results for target2 missing"
_buildManager.EndBuild();
}
///
/// This test verifies that overlapping builds of the same project are allowed.
///
[Fact]
public void OverlappingBuildsOfTheSameProjectSameTargetsAreAllowed()
{
string contents = CleanupFileContents(@"
");
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
BuildSubmission submission = _buildManager.PendBuildRequest(new BuildRequestData(instance, new[] {"target1"}));
submission.ExecuteAsync(null, null);
BuildResult result2 = _buildManager.BuildRequest(new BuildRequestData(project.CreateProjectInstance(), new[] {"target1"}));
submission.WaitHandle.WaitOne();
var result1 = submission.BuildResult;
Assert.Equal(BuildResultCode.Success, result1.OverallResult);
Assert.True(result1.HasResultsForTarget("target1")); // "Results for target1 missing"
Assert.Equal(BuildResultCode.Success, result2.OverallResult);
Assert.True(result2.HasResultsForTarget("target1")); // "Results for target1 (second call) missing"
_buildManager.EndBuild();
}
///
/// This test verifies that the out-of-proc node won't lock the directory containing the target project.
///
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
#else
[Fact]
#endif
public void OutOfProcNodeDoesntLockWorkingDirectory()
{
string contents = CleanupFileContents(@"
");
var projectFolder = _env.CreateFolder();
string projectFile = _env.CreateFile(projectFolder, ".proj").Path;
File.WriteAllText(projectFile, contents);
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", "1");
BuildRequestData data = new BuildRequestData(projectFile, new Dictionary(), MSBuildDefaultToolsVersion, new string[] { }, null);
_buildManager.Build(_parameters, data);
}
///
/// Retrieving a ProjectInstance from the BuildManager stores it in the cache
///
[Fact]
public void ProjectInstanceStoredInCache()
{
string contents = CleanupFileContents(@"
");
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
ProjectInstance instance2 = _buildManager.GetProjectInstanceForBuild(project);
Assert.Equal(instance, instance2); // "Instances don't match"
}
///
/// Retrieving a ProjectInstance from the BuildManager after a build.
///
[Fact]
public void ProjectInstanceRetrievedAfterBuildMatchesSourceProject()
{
string contents = CleanupFileContents(@"
bar
");
IBuildComponentHost host = _buildManager;
host.GetComponent(BuildComponentType.ConfigCache);
BuildRequestData data = GetBuildRequestData(contents);
_buildManager.Build(_parameters, data);
Project project = _projectCollection.LoadProject(data.ProjectFullPath);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
Assert.Equal(instance.GetPropertyValue("Foo"), "bar");
}
///
/// Retrieving a ProjectInstance after resetting the cache clears the instances.
///
[Fact]
public void ResetCacheClearsInstances()
{
string contents = CleanupFileContents(@"
bar
");
IBuildComponentHost host = _buildManager;
host.GetComponent(BuildComponentType.ConfigCache);
BuildRequestData data = GetBuildRequestData(contents);
_buildManager.Build(_parameters, data);
Project project = _projectCollection.LoadProject(data.ProjectFullPath);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
Assert.Equal("bar", instance.GetPropertyValue("Foo"));
_buildManager.BeginBuild(_parameters);
_buildManager.EndBuild();
instance = _buildManager.GetProjectInstanceForBuild(project);
Assert.Null(instance.GetProperty("Foo"));
}
///
/// Retrieving a ProjectInstance after another build without resetting the cache keeps the existing instance
///
[Fact]
public void DisablingCacheResetKeepsInstance()
{
string contents = CleanupFileContents(@"
bar
");
IBuildComponentHost host = _buildManager;
host.GetComponent(BuildComponentType.ConfigCache);
BuildRequestData data = GetBuildRequestData(contents);
_buildManager.Build(_parameters, data);
Project project = _projectCollection.LoadProject(data.ProjectFullPath);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
Assert.Equal(instance.GetPropertyValue("Foo"), "bar");
_logger.ClearLog();
_parameters.ResetCaches = false;
_buildManager.BeginBuild(_parameters);
_buildManager.BuildRequest(data);
_buildManager.EndBuild();
// We should have built the same instance, with the same results, so the target will be skipped.
string skippedMessage = ResourceUtilities.FormatResourceString("TargetAlreadyCompleteSuccess", "test");
Assert.Equal(true, _logger.FullLog.Contains(skippedMessage));
ProjectInstance instance2 = _buildManager.GetProjectInstanceForBuild(project);
Assert.Equal(instance, instance2); // "Instances are not the same"
}
///
/// Retrieving a ProjectInstance after another build without resetting the cache keeps the existing instance
///
[Fact]
public void GhostProjectRootElementCache()
{
string p2pProject = _env.CreateFile(".Project2.proj").Path;
string contents1 = CleanupFileContents($@"
");
string contents2 = CleanupFileContents(@"
Baz
");
IBuildComponentHost host = _buildManager;
host.GetComponent(BuildComponentType.ConfigCache);
// Create Project 1
ProjectInstance projectInstance = CreateProjectInstance(contents1, null, _projectCollection, false);
BuildRequestData data = new BuildRequestData(projectInstance, new string[0]);
_logger.ClearLog();
// Write the second project to disk and load it into its own project collection
ProjectCollection projectCollection2 = new ProjectCollection();
File.WriteAllText(p2pProject, contents2);
Project project2 = projectCollection2.LoadProject(p2pProject);
_parameters.ResetCaches = false;
// Build the first project to make sure we get the expected default values out for the p2p call.
_parameters.ProjectRootElementCache = _projectCollection.ProjectRootElementCache;
_buildManager.BeginBuild(_parameters);
_buildManager.BuildRequest(data);
_buildManager.EndBuild();
_logger.AssertLogContains("Value:Baz");
_logger.ClearLog();
// Modify the property in the second project and save it to disk.
project2.SetProperty("Bar", "FOO");
project2.Save();
// Create a new build.
ProjectInstance projectInstance2 = CreateProjectInstance(contents1, null, _projectCollection, false);
BuildRequestData data2 = new BuildRequestData(projectInstance2, new string[0]);
// Build again.
_parameters.ResetCaches = false;
_buildManager.BeginBuild(_parameters);
_buildManager.BuildRequest(data2);
_buildManager.EndBuild();
_logger.AssertLogContains("Value:FOO");
}
///
/// Verifies that explicitly loaded projects' imports are all marked as also explicitly loaded.
///
[Fact]
public void VerifyImportedProjectRootElementsInheritExplicitLoadFlag()
{
string contents1 = CleanupFileContents(@"
");
string contents2 = CleanupFileContents(@"
ImportedValue
");
string importedProjectPath = _env.CreateFile(".proj").Path;
string rootProjectPath = _env.CreateFile(".proj").Path;
File.WriteAllText(importedProjectPath, contents2);
File.WriteAllText(rootProjectPath, String.Format(CultureInfo.InvariantCulture, contents1, importedProjectPath));
var projectCollection = new ProjectCollection();
// Run a simple build just to prove that nothing is left in the cache.
BuildRequestData data = new BuildRequestData(rootProjectPath, ReadOnlyEmptyDictionary.Instance, null, new[] { "test" }, null);
_parameters.ResetCaches = true;
_parameters.ProjectRootElementCache = projectCollection.ProjectRootElementCache;
_buildManager.BeginBuild(_parameters);
_buildManager.BuildRequest(data);
_buildManager.EndBuild();
_buildManager.ResetCaches();
// The semantic of TryOpen is to only retrieve the PRE if it is already in the weak cache.
Assert.Null(ProjectRootElement.TryOpen(rootProjectPath, projectCollection)); // "The built project shouldn't be in the cache anymore."
Assert.Null(ProjectRootElement.TryOpen(importedProjectPath, projectCollection)); // "The built project's import shouldn't be in the cache anymore."
Project project = projectCollection.LoadProject(rootProjectPath);
ProjectRootElement preRoot, preImported;
Assert.NotNull(preRoot = ProjectRootElement.TryOpen(rootProjectPath, projectCollection)); // "The root project file should be in the weak cache."
Assert.NotNull(preImported = ProjectRootElement.TryOpen(importedProjectPath, projectCollection)); // "The imported project file should be in the weak cache."
Assert.True(preRoot.IsExplicitlyLoaded);
Assert.True(preImported.IsExplicitlyLoaded);
// Run a simple build just to prove that it doesn't impact what is in the cache.
data = new BuildRequestData(rootProjectPath, ReadOnlyEmptyDictionary.Instance, null, new[] { "test" }, null);
_parameters.ResetCaches = true;
_parameters.ProjectRootElementCache = projectCollection.ProjectRootElementCache;
_buildManager.BeginBuild(_parameters);
_buildManager.BuildRequest(data);
_buildManager.EndBuild();
_buildManager.ResetCaches();
// Now make sure they are still in the weak cache. Since they were loaded explicitly before the build, the build shouldn't have unloaded them from the cache.
Assert.Same(preRoot, ProjectRootElement.TryOpen(rootProjectPath, projectCollection)); // "The root project file should be in the weak cache after a build."
Assert.Same(preImported, ProjectRootElement.TryOpen(importedProjectPath, projectCollection)); // "The imported project file should be in the weak cache after a build."
Assert.True(preRoot.IsExplicitlyLoaded);
Assert.True(preImported.IsExplicitlyLoaded);
projectCollection.UnloadProject(project);
projectCollection.UnloadAllProjects();
Assert.Null(ProjectRootElement.TryOpen(rootProjectPath, projectCollection)); // "The unloaded project shouldn't be in the cache anymore."
Assert.Null(ProjectRootElement.TryOpen(importedProjectPath, projectCollection)); // "The unloaded project's import shouldn't be in the cache anymore."
}
///
/// Verify that using a second BuildManager doesn't cause the system to crash.
///
[Fact]
public void Regress251333()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
// First a normal build
BuildRequestData data = GetBuildRequestData(contents);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(result.OverallResult, BuildResultCode.Success);
// Now a build using a different build manager.
using (BuildManager newBuildManager = new BuildManager())
{
GetBuildRequestData(contents);
BuildResult result2 = newBuildManager.Build(_parameters, data);
Assert.Equal(result2.OverallResult, BuildResultCode.Success);
}
}
///
/// Verify that disabling the in-proc node doesn't cause projects which don't require it to fail.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void Regress239661()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
string fileName = _env.CreateFile(".proj").Path;
File.WriteAllText(fileName, contents);
BuildRequestData data = new BuildRequestData(fileName, _projectCollection.GlobalProperties, MSBuildDefaultToolsVersion, new string[0], null);
_parameters.DisableInProcNode = true;
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_logger.AssertLogContains("[success]");
}
///
/// Verify that disabling the in-proc node when a project requires it will cause the build to fail, but not crash.
///
[Fact]
public void Regress239661_NodeUnavailable()
{
string contents = CleanupFileContents(@"
InitialProperty1
InitialProperty2
InitialProperty3
");
BuildRequestData data = GetBuildRequestData(contents);
_parameters.DisableInProcNode = true;
// Require that this project build on the in-proc node, which will not be available.
data.HostServices.SetNodeAffinity(data.ProjectFullPath, NodeAffinity.InProc);
BuildResult result = _buildManager.Build(_parameters, data);
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertLogDoesntContain("[success]");
_logger.AssertLogContains("MSB4223");
}
///
/// Ensures that properties and items are transferred to the out-of-proc node when an instance is used to start the build.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void ProjectInstanceTransfersToOOPNode()
{
string contents = CleanupFileContents(@"
deleteme
unmodified
original
");
string fileName = _env.CreateFile(".proj").Path;
File.WriteAllText(fileName, contents);
Project project = new Project(fileName);
ProjectInstance instance = project.CreateProjectInstance();
instance.RemoveProperty("DeleteMe");
instance.SetProperty("VirtualProp", "overridden");
instance.SetProperty("NewProp", "new");
instance.AddItem("Baz", "baz");
instance.AddItem("Foo2", "foo21");
foreach (var item in instance.Items)
{
if (item.EvaluatedInclude == "foo")
{
instance.RemoveItem(item);
break;
}
}
BuildRequestData data = new BuildRequestData(instance, new string[0]);
// Force this to build out-of-proc
_parameters.DisableInProcNode = true;
_buildManager.Build(_parameters, data);
_logger.AssertLogDoesntContain("[deleteme]");
_logger.AssertLogContains("[overridden]");
_logger.AssertLogContains("[unmodified]");
_logger.AssertLogContains("[new]");
_logger.AssertLogDoesntContain("[foo]");
_logger.AssertLogContains("[foo2;foo21]");
_logger.AssertLogContains("[baz]");
}
///
/// Ensures that a limited set of properties are transferred from a project instance to an OOP node.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void ProjectInstanceLimitedTransferToOOPNode()
{
string contents = CleanupFileContents(@"
unmodified
original
");
string fileName = _env.CreateFile(".proj").Path;
File.WriteAllText(fileName, contents);
Project project = new Project(fileName);
ProjectInstance instance = project.CreateProjectInstance();
instance.SetProperty("VirtualProp", "overridden");
instance.SetProperty("Unmodified", "changed");
BuildRequestData data = new BuildRequestData(instance, new string[0], null, BuildRequestDataFlags.None, new string[] { "VirtualProp" });
// Force this to build out-of-proc
_parameters.DisableInProcNode = true;
_buildManager.Build(_parameters, data);
_logger.AssertLogContains("[overridden]");
_logger.AssertLogContains("[unmodified]");
_logger.AssertLogDoesntContain("[changed]");
}
///
/// Tests that cache files are created as expected and their lifetime is controlled appropriately.
///
[Fact]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
[Trait("Category", "mono-osx-failing")]
public void CacheLifetime()
{
FileUtilities.ClearCacheDirectory();
_env.SetEnvironmentVariable("MSBUILDDEBUGFORCECACHING", "1");
string outerBuildCacheDirectory;
string innerBuildCacheDirectory;
// Do a build with one build manager.
using (var outerBuildManager = new BuildManager())
{
outerBuildCacheDirectory = BuildAndCheckCache(outerBuildManager, new string[] { });
// Do another build with a second build manager while the first still exists. Since both BuildManagers
// share a process-wide cache directory, we want to verify that they don't stomp on each other, either
// by accidentally sharing results, or by clearing them away.
using (var innerBuildManager = new BuildManager())
{
innerBuildCacheDirectory = BuildAndCheckCache(innerBuildManager, new string[] { outerBuildCacheDirectory });
// Force the cache for this build manager (and only this build manager) to be cleared. It should leave
// behind the results from the other one.
innerBuildManager.ResetCaches();
}
Assert.False(Directory.Exists(innerBuildCacheDirectory)); // "Inner build cache directory still exists after inner build manager was disposed."
Assert.True(Directory.Exists(outerBuildCacheDirectory)); // "Outer build cache directory doesn't exist after inner build manager was disposed."
// Force the cache for this build manager to be cleared.
outerBuildManager.ResetCaches();
}
Assert.False(Directory.Exists(outerBuildCacheDirectory)); // "Outer build cache directory still exists after outer build manager was disposed."
}
///
/// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
/// overall build result -- and thus the return value of the MSBuild task -- should reflect
/// that failure.
///
[Fact]
public void FailedAfterTargetInP2PShouldCauseOverallBuildFailure()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null, new[] { "Build" }, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertNoWarnings();
_buildManager.EndBuild();
}
///
/// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
/// overall build result -- and thus the return value of the MSBuild task -- should reflect
/// that failure. Specifically tests where there are multiple entrypoint targets with
/// AfterTargets, only one of which fails.
///
[Fact]
public void FailedAfterTargetInP2PShouldCauseOverallBuildFailure_MultipleEntrypoints()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null, new[] { "Build" }, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertNoWarnings();
_logger.AssertLogContains("[Build]");
_logger.AssertLogContains("[Build2]");
_logger.AssertLogContains("[AT1]");
_logger.AssertLogContains("[AT2]");
_buildManager.EndBuild();
}
///
/// If there's a P2P that otherwise succeeds, but has an AfterTarget that errors out, the
/// overall build result -- and thus the return value of the MSBuild task -- should reflect
/// that failure. This should also be true if the AfterTarget is an AfterTarget of the
/// entrypoint target.
///
[Fact]
public void FailedNestedAfterTargetInP2PShouldCauseOverallBuildFailure()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null, new[] { "Build" }, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
_logger.AssertNoWarnings();
_buildManager.EndBuild();
}
///
/// If a project is called into twice, with two different entrypoint targets that
/// depend on non-overlapping sets of targets, and the first fails, the second
/// should not inherit that failure if all the targets it calls succeed.
///
[Fact]
public void NonOverlappingEnusingTrypointTargetsShouldNotInfluenceEachOthersResults()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null, new[] { "Build" }, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Success, result.OverallResult);
Assert.Equal(1, _logger.ErrorCount);
_buildManager.EndBuild();
}
///
/// In a situation where we have two requests calling into the same project, with different entry point
/// targets, one of which depends on "A;B", the other of which depends on "B", which has a dependency of
/// its own on "A", that we still properly build.
///
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/933")]
#elif MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void Regress473114()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
var projC = _env.CreateFile(".proj").Path;
var projD = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
string contentsC = @"
";
string contentsD = @"
";
File.WriteAllText(projA, contentsA);
File.WriteAllText(projB, contentsB);
File.WriteAllText(projC, contentsC);
File.WriteAllText(projD, contentsD);
_parameters.MaxNodeCount = 3;
_parameters.EnableNodeReuse = false;
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), "4.0", new[] { "Build" }, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Success, result.OverallResult);
_buildManager.EndBuild();
}
///
/// If two requests are made for the same project, and they call in with
/// just the right timing such that:
/// - request 1 builds for a while, reaches a P2P, and blocks
/// - request 2 starts building, skips for a while, reaches the above P2P, and
/// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
///
/// Then request 2 should end up exiting in the same fashion.
///
/// This simple test verifies that if there are two error targets in a row, the
/// second request will bail out where the first request did, as though it had
/// executed the target, rather than skipping and continuing.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void VerifyMultipleRequestForSameProjectWithErrors_Simple()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
var projC = _env.CreateFile(".proj").Path;
string contentsA = $@"
";
string contentsB = $@"
";
string contentsC = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
File.WriteAllText(projC, CleanupFileContents(contentsC));
_parameters.MaxNodeCount = 2;
_parameters.EnableNodeReuse = false;
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null,
new[] {"Build"}, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
// We should never get to Error2, because it's supposed to execute after Error1, which failed.
_logger.AssertLogDoesntContain("Error 2");
// We should, however, end up skipping Error1 on the second call to B.
string skippedMessage =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteFailure", "Error1");
_logger.AssertLogContains(skippedMessage);
_buildManager.EndBuild();
}
///
/// If two requests are made for the same project, and they call in with
/// just the right timing such that:
/// - request 1 builds for a while, reaches a P2P, and blocks
/// - request 2 starts building, skips for a while, reaches the above P2P, and
/// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
///
/// Then request 2 should end up exiting in the same fashion.
///
/// This simple test verifies that if there are two error targets in a row, and the
/// first has a chain of OnError targets, the OnError targets will all execute as
/// expected in the first request, but be skipped by the second (since if it's "skipping
/// unsuccessful", it can assume that all other OnError targets have also already been run)
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void VerifyMultipleRequestForSameProjectWithErrors_OnErrorChain()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
var projC = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
string contentsC = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
File.WriteAllText(projC, CleanupFileContents(contentsC));
_parameters.MaxNodeCount = 2;
_parameters.EnableNodeReuse = false;
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null,
new[] {"Build"}, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
// We should never get to Error2, because it's supposed to execute after Error1, which failed.
_logger.AssertLogDoesntContain("Error 2");
// We should, however, get to Target2, Target3, and Target4, since they're part of the OnError
// chain for Error1
_logger.AssertLogContains("Error in Target2");
_logger.AssertLogContains("Target 3");
_logger.AssertLogContains("Target 4");
// We should end up skipping Error1 on the second call to B.
string skippedMessage1 =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteFailure", "Error1");
_logger.AssertLogContains(skippedMessage1);
// We shouldn't, however, see skip messages for the OnError targets
string skippedMessage2 =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteFailure", "Target2");
_logger.AssertLogDoesntContain(skippedMessage2);
string skippedMessage3 =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteSuccess", "Target3");
_logger.AssertLogDoesntContain(skippedMessage3);
string skippedMessage4 =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteSuccess", "Target4");
_logger.AssertLogDoesntContain(skippedMessage4);
_buildManager.EndBuild();
}
///
/// If two requests are made for the same project, and they call in with
/// just the right timing such that:
/// - request 1 builds for a while, reaches a P2P, and blocks
/// - request 2 starts building, skips for a while, reaches the above P2P, and
/// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
///
/// Then request 2 should end up exiting in the same fashion.
///
/// This simple test verifies that if there are two error targets in a row, AND
/// they're marked as ContinueOnError=ErrorAndContinue, then we won't bail, but
/// will continue executing (on the first request) or skipping (on the second)
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void VerifyMultipleRequestForSameProjectWithErrors_ErrorAndContinue()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
var projC = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
string contentsC = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
File.WriteAllText(projC, CleanupFileContents(contentsC));
_parameters.MaxNodeCount = 2;
_parameters.EnableNodeReuse = false;
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null,
new[] {"Build"}, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
// We should see both Error1 and Error2
_logger.AssertLogContains("Error 1");
_logger.AssertLogContains("Error 2");
// We should also end up skipping them both.
string skippedMessage1 =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteFailure", "Error1");
_logger.AssertLogContains(skippedMessage1);
string skippedMessage2 =
ResourceUtilities.FormatResourceString("TargetAlreadyCompleteFailure", "Error2");
_logger.AssertLogContains(skippedMessage2);
_buildManager.EndBuild();
}
///
/// If two requests are made for the same project, and they call in with
/// just the right timing such that:
/// - request 1 builds for a while, reaches a P2P, and blocks
/// - request 2 starts building, skips for a while, reaches the above P2P, and
/// blocks waiting for request 1's results
/// - request 1 resumes building, errors, and exits
/// - request 2 resumes building
///
/// Then request 2 should end up exiting in the same fashion.
///
/// This test verifies that if the errors are in AfterTargets, we still
/// exit as though the target that those targets run after has already run.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1240")]
#else
[Fact]
#endif
public void VerifyMultipleRequestForSameProjectWithErrors_AfterTargets()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
var projC = _env.CreateFile(".proj").Path;
string contentsA = @"
";
string contentsB = @"
";
string contentsC = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
File.WriteAllText(projC, CleanupFileContents(contentsC));
_parameters.MaxNodeCount = 2;
_parameters.EnableNodeReuse = false;
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null,
new[] {"Build"}, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Failure, result.OverallResult);
// We should never get to Error2, because we should never run its AfterTarget, after
// the AfterTarget with Error1 failed
_logger.AssertLogDoesntContain("Error 2");
_buildManager.EndBuild();
}
///
/// Related to the two tests above, if two requests are made for the same project, but
/// for different entry targets, and a target fails in the first request, if the second
/// request also runs that target, its skip-unsuccessful should behave in the same
/// way as if the target had actually errored.
///
[Fact]
public void VerifyMultipleRequestForSameProjectWithErrors_DifferentEntrypoints()
{
var projA = _env.CreateFile(".proj").Path;
var projB = _env.CreateFile(".proj").Path;
string contentsA = @"
Build
Build2
";
string contentsB = @"
";
File.WriteAllText(projA, CleanupFileContents(contentsA));
File.WriteAllText(projB, CleanupFileContents(contentsB));
_buildManager.BeginBuild(_parameters);
BuildRequestData data = new BuildRequestData(projA, new Dictionary(), null,
new[] {"Build"}, new HostServices());
BuildResult result = _buildManager.PendBuildRequest(data).Execute();
Assert.Equal(BuildResultCode.Success, result.OverallResult);
// We should never get to Error2, because it's only ever executed in the second
// request after Error1, which should skip-unsuccessful and exit
_logger.AssertLogDoesntContain("[Error2]");
_buildManager.EndBuild();
}
///
/// Verify that we can submit multiple simultaneous submissions with
/// legacy threading mode active and successfully build.
///
[Fact]
public void TestSimultaneousSubmissionsWithLegacyThreadingData()
{
string projectContent = @"
";
var projectPath1 = _env.CreateFile(".proj").Path;
File.WriteAllText(projectPath1, CleanupFileContents(projectContent));
Project project1 = new Project(projectPath1);
var projectPath2 = _env.CreateFile(".proj").Path;
File.WriteAllText(projectPath2, CleanupFileContents(projectContent));
Project project2 = new Project(projectPath2);
ConsoleLogger cl = new ConsoleLogger();
BuildParameters buildParameters = new BuildParameters(ProjectCollection.GlobalProjectCollection);
buildParameters.Loggers = new ILogger[] { cl };
buildParameters.LegacyThreadingSemantics = true;
BuildManager.DefaultBuildManager.BeginBuild(buildParameters);
AutoResetEvent project1DoneEvent = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
ProjectInstance pi = BuildManager.DefaultBuildManager.GetProjectInstanceForBuild(project1);
BuildRequestData requestData = new BuildRequestData(pi, new[] { "Build" });
BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
BuildResult br = submission.Execute();
project1DoneEvent.Set();
});
AutoResetEvent project2DoneEvent = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
ProjectInstance pi = BuildManager.DefaultBuildManager.GetProjectInstanceForBuild(project2);
BuildRequestData requestData = new BuildRequestData(pi, new[] { "Build" });
BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
BuildResult br = submission.Execute();
project2DoneEvent.Set();
});
project1DoneEvent.WaitOne();
project2DoneEvent.WaitOne();
BuildManager.DefaultBuildManager.EndBuild();
}
///
/// Verify that we can submit multiple simultaneous submissions with
/// legacy threading mode active and successfully build, and that one of those
/// submissions can P2P to the other.
///
[Fact]
public void TestSimultaneousSubmissionsWithLegacyThreadingData_P2P()
{
string projectContent1 = @"
";
var projectPath1 = _env.CreateFile(".proj").Path;
File.WriteAllText(projectPath1, CleanupFileContents(projectContent1));
Project project1 = new Project(projectPath1);
string projectContent2 = @"
";
var projectPath2 = _env.CreateFile(".proj").Path;
File.WriteAllText(projectPath2, CleanupFileContents(projectContent2));
Project project2 = new Project(projectPath2);
ConsoleLogger cl = new ConsoleLogger();
BuildParameters buildParameters = new BuildParameters(ProjectCollection.GlobalProjectCollection);
buildParameters.Loggers = new ILogger[] { cl };
buildParameters.LegacyThreadingSemantics = true;
BuildManager.DefaultBuildManager.BeginBuild(buildParameters);
AutoResetEvent project1DoneEvent = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
// need to kick off project 2 first so that it project 1 can get submitted before the P2P happens
ProjectInstance pi = BuildManager.DefaultBuildManager.GetProjectInstanceForBuild(project2);
BuildRequestData requestData = new BuildRequestData(pi, new[] { "MSDeployPublish" });
BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
BuildResult br = submission.Execute();
Assert.Equal(BuildResultCode.Success, br.OverallResult);
project1DoneEvent.Set();
});
AutoResetEvent project2DoneEvent = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
ProjectInstance pi = BuildManager.DefaultBuildManager.GetProjectInstanceForBuild(project1);
BuildRequestData requestData = new BuildRequestData(pi, new string[] { "CopyRunEnvironmentFiles" });
BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
BuildResult br = submission.Execute();
Assert.Equal(BuildResultCode.Success, br.OverallResult);
project2DoneEvent.Set();
});
project1DoneEvent.WaitOne();
project2DoneEvent.WaitOne();
BuildManager.DefaultBuildManager.EndBuild();
}
///
/// Verify that we can submit multiple simultaneous submissions with
/// legacy threading mode active and successfully build, and that one of those
/// submissions can P2P to the other.
///
/// A variation of the above test, where multiple nodes are available, so the
/// submissions aren't restricted to running strictly serially by the single in-proc
/// node.
///
#if MONO
[Fact(Skip = "https://github.com/Microsoft/msbuild/issues/1245")]
#else
[Fact]
#endif
public void TestSimultaneousSubmissionsWithLegacyThreadingData_P2P_MP()
{
string projectContent1 = @"
";
var projectPath1 = _env.CreateFile(".proj").Path;
File.WriteAllText(projectPath1, CleanupFileContents(projectContent1));
Project project1 = new Project(projectPath1);
string projectContent2 = @"
";
var projectPath2 = _env.CreateFile(".proj").Path;
File.WriteAllText(projectPath2, CleanupFileContents(projectContent2));
Project project2 = new Project(projectPath2);
ConsoleLogger cl = new ConsoleLogger();
BuildParameters buildParameters = new BuildParameters(ProjectCollection.GlobalProjectCollection);
buildParameters.Loggers = new ILogger[] { cl };
buildParameters.LegacyThreadingSemantics = true;
buildParameters.MaxNodeCount = 2;
buildParameters.EnableNodeReuse = false;
BuildManager.DefaultBuildManager.BeginBuild(buildParameters);
AutoResetEvent project1DoneEvent = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
// need to kick off project 2 first so that it project 1 can get submitted before the P2P happens
ProjectInstance pi = BuildManager.DefaultBuildManager.GetProjectInstanceForBuild(project2);
BuildRequestData requestData = new BuildRequestData(pi, new string[] { "MSDeployPublish" });
BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
BuildResult br = submission.Execute();
Assert.Equal(BuildResultCode.Success, br.OverallResult);
project1DoneEvent.Set();
});
AutoResetEvent project2DoneEvent = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(delegate
{
ProjectInstance pi = BuildManager.DefaultBuildManager.GetProjectInstanceForBuild(project1);
BuildRequestData requestData = new BuildRequestData(pi, new string[] { "CopyRunEnvironmentFiles" });
BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData);
BuildResult br = submission.Execute();
Assert.Equal(BuildResultCode.Success, br.OverallResult);
project2DoneEvent.Set();
});
project1DoneEvent.WaitOne();
project2DoneEvent.WaitOne();
BuildManager.DefaultBuildManager.EndBuild();
}
///
/// Ensures that properties and items are transferred from an out-of-proc project to an in-proc project.
///
///
/// This differs from transferring a project instance to an out-of-proc node because in this case the project
/// was loaded by MSBuild, not supplied directly by the user.
///
[Fact]
[Trait("Category", "mono-osx-failing")]
public void Regress265010()
{
string contents = CleanupFileContents(@"
BaseValue
NewValue
");
string fileName = _env.CreateFile(".proj").Path;
File.WriteAllText(fileName, contents);
_buildManager.BeginBuild(_parameters);
HostServices services = new HostServices();
services.SetNodeAffinity(fileName, NodeAffinity.OutOfProc);
BuildRequestData data = new BuildRequestData(fileName, new Dictionary(), MSBuildDefaultToolsVersion, new[] { "BaseTest" }, services);
_buildManager.PendBuildRequest(data).Execute();
_logger.AssertLogContains("[BaseValue]");
_logger.AssertLogContains("[BaseItem]");
_logger.ClearLog();
_parameters.ResetCaches = false;
services.SetNodeAffinity(fileName, NodeAffinity.InProc);
data = new BuildRequestData(fileName, new Dictionary(), MSBuildDefaultToolsVersion, new[] { "MovedTest" }, services);
_buildManager.PendBuildRequest(data).Execute();
_logger.AssertLogContains("[NewValue]");
_logger.AssertLogContains("[BaseItem;NewItem]");
_logger.AssertLogDoesntContain("[BaseValue]");
_buildManager.EndBuild();
}
///
/// Verifies that all warnings are treated as errors and that the overall build result is a failure.
///
[Fact]
public void WarningsAreTreatedAsErrorsAll()
{
string contents = CleanupFileContents(@"
");
_parameters.WarningsAsErrors = new HashSet();
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
BuildResult result1 = _buildManager.BuildRequest(new BuildRequestData(instance, new[] { "target1" }));
_buildManager.EndBuild();
Assert.Equal(0, _logger.WarningCount);
Assert.Equal(2, _logger.ErrorCount);
Assert.Equal(BuildResultCode.Failure, result1.OverallResult);
Assert.True(result1.HasResultsForTarget("target1"));
}
///
/// Verifies that only the specified warnings are treated as errors and that the overall build result is a failure.
///
[Fact]
public void WarningsAreTreatedAsErrorsSpecific()
{
string contents = CleanupFileContents(@"
");
_parameters.WarningsAsErrors = new HashSet { "ABC123" };
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
BuildResult result1 = _buildManager.BuildRequest(new BuildRequestData(instance, new string[] { "target1" }));
_buildManager.EndBuild();
Assert.Equal(2, _logger.WarningCount);
Assert.Equal(1, _logger.ErrorCount);
Assert.Equal(BuildResultCode.Failure, result1.OverallResult);
Assert.True(result1.HasResultsForTarget("target1"));
}
///
/// Verifies that when building targets which emit warnings, they still show as succeeding but the overall build result is a failure.
///
[Fact]
public void WarningsAreTreatedAsErrorsButTargetsStillSucceed()
{
string contents = CleanupFileContents(@"
");
_parameters.WarningsAsErrors = new HashSet { "ABC123" };
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
BuildResult buildResult = _buildManager.BuildRequest(new BuildRequestData(instance, new string[] { "target1", "target2" }));
_buildManager.EndBuild();
Assert.Equal(0, _logger.WarningCount);
Assert.Equal(1, _logger.ErrorCount);
Assert.Equal(BuildResultCode.Failure, buildResult.OverallResult);
Assert.True(buildResult.HasResultsForTarget("target1"));
Assert.True(buildResult.HasResultsForTarget("target2"));
// The two targets should still show as success because they don't know their warning was changed to an error
// Logging a warning as an error does not change execution, only the final result of the build
Assert.Equal(TargetResultCode.Success, buildResult.ResultsByTarget["target1"].ResultCode);
Assert.Equal(TargetResultCode.Success, buildResult.ResultsByTarget["target2"].ResultCode);
}
///
/// Helper for cache tests. Builds a project and verifies the right cache files are created.
///
private string BuildAndCheckCache(BuildManager localBuildManager, IEnumerable exceptCacheDirectories)
{
string contents = CleanupFileContents(@"
");
string fileName = Path.GetTempFileName();
File.WriteAllText(fileName, contents);
string cacheDirectory = FileUtilities.GetCacheDirectory();
BuildParameters parameters = new BuildParameters();
localBuildManager.BeginBuild(parameters);
try
{
var services = new HostServices();
BuildRequestData data = new BuildRequestData(fileName, new Dictionary(), MSBuildDefaultToolsVersion, new[] { "One", "Two", "Three" }, services);
var result = localBuildManager.PendBuildRequest(data).Execute();
Assert.Equal(result.OverallResult, BuildResultCode.Success); // "Test project failed to build correctly."
}
finally
{
localBuildManager.EndBuild();
}
// Ensure that we got the cache files we expected. There should be one set of results in there, once we exclude
// any of the specified directories from previous builds in the same test.
string directory = Directory.EnumerateDirectories(cacheDirectory).Except(exceptCacheDirectories).First();
// Within this directory should be a set of target results files, one for each of the targets we invoked.
var resultsFiles = Directory.EnumerateFiles(directory).Select(Path.GetFileName);
Assert.Equal(3, resultsFiles.Count());
Assert.True(resultsFiles.Contains("One.cache"));
Assert.True(resultsFiles.Contains("Two.cache"));
Assert.True(resultsFiles.Contains("Three.cache"));
// Return the cache directory created for this build.
return directory;
}
///
/// Extract a string dictionary from the property enumeration on a project started event.
///
private Dictionary ExtractProjectStartedPropertyList(IEnumerable properties)
{
// Gather a sorted list of all the properties.
return properties?.Cast()
.ToDictionary(prop => (string) prop.Key, prop => (string) prop.Value, StringComparer.OrdinalIgnoreCase);
}
///
/// Retrieves a BuildRequestData using the specified contents, default targets and an empty project collection.
///
private BuildRequestData GetBuildRequestData(string projectContents)
{
return GetBuildRequestData(projectContents, new string[] { });
}
///
/// Retrieves a BuildRequestData using the specified contents, targets and project collection.
///
private BuildRequestData GetBuildRequestData(string projectContents, string[] targets, string toolsVersion = null)
{
BuildRequestData data = new BuildRequestData(
CreateProjectInstance(projectContents, toolsVersion, _projectCollection, true), targets,
_projectCollection.HostServices);
return data;
}
///
/// Retrieve a ProjectInstance evaluated with the specified contents using the specified projectCollection
///
private ProjectInstance CreateProjectInstance(string contents, string toolsVersion, ProjectCollection projectCollection, bool deleteTempProject)
{
Project project = CreateProject(contents, toolsVersion, projectCollection, deleteTempProject);
return project.CreateProjectInstance();
}
///
/// Retrieve a Project with the specified contents using the specified projectCollection
///
private Project CreateProject(string contents, string toolsVersion, ProjectCollection projectCollection, bool deleteTempProject)
{
Project project = new Project(XmlReader.Create(new StringReader(contents)), null, toolsVersion, projectCollection)
{
FullPath = _env.CreateFile().Path
};
if (!deleteTempProject)
{
project.Save();
}
if (deleteTempProject)
{
File.Delete(project.FullPath);
}
return project;
}
///
/// Generate dummy projects
///
private ProjectInstance GenerateDummyProjects(string shutdownProjectDirectory, int parallelProjectCount, ProjectCollection projectCollection)
{
Directory.CreateDirectory(shutdownProjectDirectory);
// Generate the project. It will have the following format. Setting the AdditionalProperties
// causes the projects to be built to be separate configs, which allows us to build the same project
// a bunch of times in parallel.
//
//
//
//
// p={incremented value}
//
// ...
//
//
//
//
//
//
//
//
string rootProjectPath = Path.Combine(shutdownProjectDirectory, String.Format(CultureInfo.InvariantCulture, "RootProj_{0}.proj", Guid.NewGuid().ToString("N")));
ProjectRootElement rootProject = ProjectRootElement.Create(rootProjectPath, projectCollection);
ProjectTargetElement buildTarget = rootProject.AddTarget("Build");
ProjectTaskElement buildTask = buildTarget.AddTask("MSBuild");
buildTask.SetParameter("Projects", "@(ProjectReference)");
buildTask.SetParameter("BuildInParallel", "true");
buildTask.SetParameter("Targets", "ChildBuild");
rootProject.AddTarget("ChildBuild");
IDictionary metadata = new Dictionary(1);
for (int i = 0; i < parallelProjectCount; i++)
{
// Add the ProjectReference item for this actual config.
metadata["AdditionalProperties"] = String.Format(CultureInfo.InvariantCulture, "p={0}", i);
rootProject.AddItem("ProjectReference", rootProjectPath, metadata);
}
rootProject.Save();
return new ProjectInstance(rootProject);
}
[Fact]
[Trait("Category", "mono-osx-failing")] // out-of-proc nodes not working on mono yet
public void ShouldBuildMutatedProjectInstanceWhoseProjectWasPreviouslyBuiltAsAP2PDependency()
{
var mainProjectContents =
@"
";
var p2pProjectContents =
@"
InitialValue
";
using (var env = TestEnvironment.Create())
using (var collection = new ProjectCollection())
using (var manager = new BuildManager())
{
try
{
var testFiles = env.CreateTestProjectWithFiles(string.Empty, new[] { "p2p", "main" });
var p2pProjectPath = testFiles.CreatedFiles[0];
File.WriteAllText(p2pProjectPath, p2pProjectContents);
var mainRootElement = ProjectRootElement.Create(XmlReader.Create(new StringReader(string.Format(mainProjectContents, p2pProjectPath))), collection);
mainRootElement.FullPath = testFiles.CreatedFiles[1];
mainRootElement.Save();
// build p2p project as a real p2p dependency of some other project. This loads the p2p into msbuild's caches
var mainProject = new Project(mainRootElement, new Dictionary(), MSBuildConstants.CurrentToolsVersion, collection);
var mainInstance = mainProject.CreateProjectInstance(ProjectInstanceSettings.Immutable).DeepCopy(isImmutable: false);
Assert.Equal(0, mainInstance.GlobalProperties.Count);
var request = new BuildRequestData(mainInstance, new[] {"BuildOther"});
var parameters = new BuildParameters
{
DisableInProcNode = true,
EnableNodeReuse = false,
};
manager.BeginBuild(parameters);
var submission = manager.PendBuildRequest(request);
var results = submission.Execute();
Assert.Equal(BuildResultCode.Success, results.OverallResult);
Assert.Equal("InitialValue", results.ResultsByTarget["BuildOther"].Items.First().ItemSpec);
// build p2p directly via mutated ProjectInstances based of the same Project.
// This should rebuild and the result should reflect the in-memory changes and not reuse stale cache info
var p2pProject = new Project(p2pProjectPath, new Dictionary(), MSBuildConstants.CurrentToolsVersion, collection);
for (var i = 0; i < 2; i++)
{
var p2pInstance = p2pProject.CreateProjectInstance(ProjectInstanceSettings.Immutable).DeepCopy(isImmutable: false);
var newPropertyValue = $"NewValue_{i}";
p2pInstance.SetProperty("P", newPropertyValue);
request = new BuildRequestData(p2pInstance, new[] {"Foo"});
submission = manager.PendBuildRequest(request);
results = submission.Execute();
Assert.Equal(0, p2pInstance.GlobalProperties.Count);
Assert.Equal(BuildResultCode.Success, results.OverallResult);
Assert.Equal(newPropertyValue, results.ResultsByTarget["Foo"].Items.First().ItemSpec);
}
}
finally
{
manager.EndBuild();
}
}
}
[Fact]
[Trait("Category", "mono-osx-failing")] // out-of-proc nodes not working on mono yet
public void OutOfProcFileBasedP2PBuildSucceeds()
{
var mainProject =
@"
";
var p2pProject =
@"
";
var testFiles = _env.CreateTestProjectWithFiles(string.Empty, new[] {"main", "p2p"}, string.Empty);
var buildParameters = new BuildParameters(_projectCollection)
{
DisableInProcNode = true,
EnableNodeReuse = false,
Loggers = new ILogger[] {_logger}
};
_buildManager.BeginBuild(buildParameters);
try
{
var p2pProjectPath = testFiles.CreatedFiles[1];
var cleanedUpP2pContents = CleanupFileContents(p2pProject);
File.WriteAllText(p2pProjectPath, cleanedUpP2pContents);
var mainProjectPath = testFiles.CreatedFiles[0];
var cleanedUpMainContents = CleanupFileContents(string.Format(mainProject, p2pProjectPath));
File.WriteAllText(mainProjectPath, cleanedUpMainContents);
var buildRequestData = new BuildRequestData(
mainProjectPath,
new Dictionary(),
MSBuildConstants.CurrentToolsVersion,
new[] {"MainTarget"},
null
);
var submission = _buildManager.PendBuildRequest(buildRequestData);
var result = submission.Execute();
Assert.Equal(BuildResultCode.Success, result.OverallResult);
Assert.Equal("foo;bar",
string.Join(";", result.ResultsByTarget["MainTarget"].Items.Select(i => i.ItemSpec)));
}
finally
{
_buildManager.EndBuild();
}
}
/// When a ProjectInstance based BuildRequestData is built out of proc, the node should
/// not reload it from disk but instead fully utilize the entire translate project instance state
/// to do the build
[Theory]
[InlineData(false)]
[InlineData(true)]
[Trait("Category", "mono-osx-failing")] // out-of-proc nodes not working on mono yet
public void OutOfProcProjectInstanceBasedBuildDoesNotReloadFromDisk(bool shouldSerializeEntireState)
{
var mainProject =
@"
true
";
var importProject =
@"
";
var testFiles = _env.CreateTestProjectWithFiles(string.Empty, new[] {"main", "import"}, string.Empty);
try
{
var importPath = testFiles.CreatedFiles[1];
File.WriteAllText(importPath, CleanupFileContents(importProject));
var root = ProjectRootElement.Create(
XmlReader.Create(new StringReader(string.Format(mainProject, importPath))), _projectCollection);
root.FullPath = Path.GetTempFileName();
root.Save();
// build a project which runs a target from an imported file
var project = new Project(root, new Dictionary(), MSBuildConstants.CurrentToolsVersion,
_projectCollection);
var instance = project.CreateProjectInstance(ProjectInstanceSettings.Immutable).DeepCopy(false);
instance.TranslateEntireState = shouldSerializeEntireState;
var request = new BuildRequestData(instance, new[] {"Foo"});
var parameters = new BuildParameters(_projectCollection)
{
DisableInProcNode = true,
EnableNodeReuse = false,
Loggers = new ILogger[] {_logger}
};
_buildManager.BeginBuild(parameters);
var submission = _buildManager.PendBuildRequest(request);
var results = submission.Execute();
Assert.True(results.OverallResult == BuildResultCode.Success);
// reset caches to ensure nothing is reused
_buildManager.EndBuild();
_buildManager.ResetCaches();
// mutate the file on disk such that the import (containing the target to get executed)
// is no longer imported
project.SetProperty("ImportIt", "false");
project.Save();
// Build the initial project instance again.
// The project instance is not in sync with the file anymore, making it an in-memory build:
// the file does not contain the target Foo, but the project instance does
// Building the stale project instance should still succeed when the entire state is translated: MSBuild should use the
// in-memory state to build and not reload from disk.
_buildManager.BeginBuild(parameters);
request = new BuildRequestData(instance, new[] {"Foo"}, null,
BuildRequestDataFlags.ReplaceExistingProjectInstance);
submission = _buildManager.PendBuildRequest(request);
results = submission.Execute();
if (shouldSerializeEntireState)
{
Assert.Equal(BuildResultCode.Success, results.OverallResult);
}
else
{
Assert.Equal(BuildResultCode.Failure, results.OverallResult);
Assert.Contains("The target \"Foo\" does not exist in the project", _logger.FullLog,
StringComparison.OrdinalIgnoreCase);
}
}
finally
{
_buildManager.EndBuild();
}
}
[Fact]
[Trait("Category", "mono-osx-failing")] // out-of-proc nodes not working on mono yet
public void OutOfProcEvaluationIdsUnique()
{
var mainProject =
@"
";
var childProject =
@"
";
var testFiles = _env.CreateTestProjectWithFiles(string.Empty, new[] { "main", "child1", "child2" }, string.Empty);
var buildParameters = new BuildParameters(_projectCollection)
{
DisableInProcNode = true,
EnableNodeReuse = false,
Loggers = new ILogger[] { _logger }
};
_buildManager.BeginBuild(buildParameters);
try
{
var child1ProjectPath = testFiles.CreatedFiles[1];
var child2ProjectPath = testFiles.CreatedFiles[2];
var cleanedUpChildContents = CleanupFileContents(childProject);
File.WriteAllText(child1ProjectPath, cleanedUpChildContents);
File.WriteAllText(child2ProjectPath, cleanedUpChildContents);
var mainProjectPath = testFiles.CreatedFiles[0];
var cleanedUpMainContents = CleanupFileContents(string.Format(mainProject, child1ProjectPath, child2ProjectPath));
File.WriteAllText(mainProjectPath, cleanedUpMainContents);
var buildRequestData = new BuildRequestData(
mainProjectPath,
new Dictionary(),
MSBuildConstants.CurrentToolsVersion,
new[] { "MainTarget" },
null
);
var submission = _buildManager.PendBuildRequest(buildRequestData);
var result = submission.Execute();
Assert.Equal(BuildResultCode.Success, result.OverallResult);
Assert.True(_logger.AllBuildEvents.OfType().GroupBy(args => args.BuildEventContext.EvaluationId).All(g => g.Count() == 1));
}
finally
{
_buildManager.EndBuild();
}
}
///
/// Regression test for https://github.com/Microsoft/msbuild/issues/3047
///
[Fact]
[Trait("Category", "mono-osx-failing")] // out-of-proc nodes not working on mono yet
public void MultiProcReentrantProjectWithCallTargetDoesNotFail()
{
var a =
@"
".Cleanup();
var b =
@"
".Cleanup();
var c =
$@"
".Cleanup();
var delay =
$@"
".Cleanup();
var reentrant =
$@"
".Cleanup();
using (var env = TestEnvironment.Create(_output))
{
var entryFile = env.CreateFile(nameof(a), a).Path;
env.CreateFile(nameof(b), b);
env.CreateFile(nameof(c), c);
env.CreateFile(nameof(delay), delay);
env.CreateFile(nameof(reentrant), reentrant);
var mockLogger = new MockLogger(_output);
var buildParameters = new BuildParameters()
{
DisableInProcNode = true,
MaxNodeCount = Environment.ProcessorCount,
EnableNodeReuse = false,
Loggers = new List()
{
mockLogger
}
};
var buildRequestData = new BuildRequestData(entryFile, new Dictionary(), MSBuildDefaultToolsVersion, new[]{ "EntryTarget" }, null);
var result = _buildManager.Build(buildParameters, buildRequestData);
result.OverallResult.ShouldBe(BuildResultCode.Success);
}
}
}
}