// 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); } } } }