|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
#nullable disable
namespace Microsoft.Build.UnitTests.OM.Definition
{
/// <summary>
/// Tests for ProjectCollection
/// </summary>
public class ProjectCollection_Tests : IDisposable
{
/// <summary>
/// Gets or sets the test context.
/// </summary>
public ITestOutputHelper TestOutput { get; set; }
public ProjectCollection_Tests(ITestOutputHelper outputHelper)
{
TestOutput = outputHelper;
ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
}
/// <summary>
/// Clear out the global project collection
/// </summary>
public void Dispose()
{
ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
Assert.Equal(0, ProjectCollection.GlobalProjectCollection.Count);
IDictionary<string, string> globalProperties = ProjectCollection.GlobalProjectCollection.GlobalProperties;
foreach (string propertyName in globalProperties.Keys)
{
ProjectCollection.GlobalProjectCollection.RemoveGlobalProperty(propertyName);
}
Assert.Empty(ProjectCollection.GlobalProjectCollection.GlobalProperties);
}
/// <summary>
/// Add a single project from disk and verify it's put in the global project collection
/// </summary>
[Fact]
public void AddProjectFromDisk()
{
string path = null;
try
{
path = FileUtilities.GetTemporaryFileName();
ProjectRootElement xml = ProjectRootElement.Create(path);
xml.Save();
Project project = new Project(path);
Project project2 = ProjectCollection.GlobalProjectCollection.LoadProject(path);
Assert.True(ReferenceEquals(project, project2));
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// When an unnamed project is saved, it gets a name, and should be entered into
/// the appropriate project collection.
/// </summary>
[Fact]
public void AddProjectOnSave()
{
string path = null;
try
{
Project project = new Project();
Assert.Equal(0, ProjectCollection.GlobalProjectCollection.Count);
path = FileUtilities.GetTemporaryFileName();
project.Save(path);
Project project2 = ProjectCollection.GlobalProjectCollection.LoadProject(path);
Assert.True(ReferenceEquals(project, project2));
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// When an unnamed project is saved, it gets a name, and should be entered into
/// the appropriate project collection.
/// </summary>
[Fact]
public void AddProjectOnSave_SpecifiedProjectCollection()
{
string path = null;
try
{
using ProjectCollection collection = new ProjectCollection();
Project project = new Project(collection);
path = FileUtilities.GetTemporaryFileName();
project.Save(path);
Project project2 = collection.LoadProject(path);
Assert.True(ReferenceEquals(project, project2));
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// When an unnamed project is given a name, it should be entered into its
/// project collection.
/// </summary>
[Fact]
public void AddProjectOnSetName()
{
var project = new Project { FullPath = "c:\\x" };
Project project2 = ProjectCollection.GlobalProjectCollection.LoadProject("c:\\x");
Assert.True(ReferenceEquals(project, project2));
}
/// <summary>
/// Loading a project from a file inherits the project collection's global properties
/// </summary>
[Fact]
public void GlobalPropertyInheritLoadFromFile()
{
string path = null;
try
{
path = CreateProjectFile();
using var collection = new ProjectCollection();
collection.SetGlobalProperty("p", "v");
Project project = collection.LoadProject(path);
Assert.Equal("v", project.GlobalProperties["p"]);
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// Loading a project from a file inherits the project collection's global properties
/// </summary>
#if FEATURE_INSTALLED_MSBUILD
[Fact]
#else
[Fact(Skip = "https://github.com/dotnet/msbuild/issues/276")]
#endif
public void GlobalPropertyInheritLoadFromFile2()
{
string path = null;
try
{
path = CreateProjectFile();
using ProjectCollection collection = new ProjectCollection();
collection.SetGlobalProperty("p", "v");
Project project = collection.LoadProject(path, "4.0");
Assert.Equal("v", project.GlobalProperties["p"]);
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// Loading a project from a file inherits the project collection's global properties
/// </summary>
#if FEATURE_INSTALLED_MSBUILD
[Fact]
#else
[Fact(Skip = "https://github.com/dotnet/msbuild/issues/276")]
#endif
public void GlobalPropertyInheritLoadFromFile3()
{
string path = null;
try
{
path = CreateProjectFile();
using ProjectCollection collection = new ProjectCollection();
collection.SetGlobalProperty("p", "v");
Project project = collection.LoadProject(path, null, "4.0");
Assert.Equal("v", project.GlobalProperties["p"]);
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// Loading a project from a reader inherits the project collection's global properties
/// </summary>
[Fact]
public void GlobalPropertyInheritLoadFromXml1()
{
using XmlReader reader = CreateProjectXmlReader();
using var collection = new ProjectCollection();
collection.SetGlobalProperty("p", "v");
Project project = collection.LoadProject(reader);
Assert.Equal("v", project.GlobalProperties["p"]);
}
/// <summary>
/// Loading a project from a reader inherits the project collection's global properties
/// </summary>
[Fact]
public void GlobalPropertyInheritLoadFromXml2()
{
using XmlReader reader = CreateProjectXmlReader();
using var collection = new ProjectCollection();
collection.SetGlobalProperty("p", "v");
Project project = collection.LoadProject(reader, ObjectModelHelpers.MSBuildDefaultToolsVersion);
Assert.Equal("v", project.GlobalProperties["p"]);
}
/// <summary>
/// Creating a project inherits the project collection's global properties
/// </summary>
[Fact]
public void GlobalPropertyInheritProjectConstructor()
{
using var collection = new ProjectCollection();
collection.SetGlobalProperty("p", "v");
var project = new Project(collection);
Assert.Equal("v", project.GlobalProperties["p"]);
}
/// <summary>
/// Load project should load a project, if it wasn't already loaded.
/// </summary>
[Fact]
public void GetLoadedProjectNonExistent()
{
string path = null;
try
{
path = FileUtilities.GetTemporaryFileName();
ProjectRootElement xml = ProjectRootElement.Create();
xml.Save(path);
Assert.Equal(0, ProjectCollection.GlobalProjectCollection.Count);
ProjectCollection.GlobalProjectCollection.LoadProject(path);
Assert.Equal(1, ProjectCollection.GlobalProjectCollection.Count);
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// Verify that one project collection doesn't contain the projects of another
/// </summary>
[Fact]
public void GetLoadedProjectWrongCollection()
{
var project1 = new Project { FullPath = "c:\\1" };
using var collection = new ProjectCollection();
var project2 = new Project(collection) { FullPath = "c:\\1" };
Assert.True(ReferenceEquals(project2, collection.LoadProject("c:\\1")));
Assert.False(ReferenceEquals(project1, collection.LoadProject("c:\\1")));
}
/// <summary>
/// Verify that one project collection doesn't contain the ProjectRootElements of another
/// -- because they don't share a ProjectRootElementCache
/// </summary>
[Fact]
public void GetLoadedProjectRootElementWrongCollection()
{
string path = null;
try
{
path = FileUtilities.GetTemporaryFileName();
ProjectRootElement.Create(path).Save();
using ProjectCollection collection1 = new ProjectCollection();
Project project1 = collection1.LoadProject(path);
Project project1b = collection1.LoadProject(path);
Assert.True(ReferenceEquals(project1.Xml, project1b.Xml));
using ProjectCollection collection2 = new ProjectCollection();
Project project2 = collection2.LoadProject(path);
Assert.False(ReferenceEquals(project1.Xml, project2.Xml));
}
finally
{
if (path != null)
{
File.Delete(path);
}
}
}
/// <summary>
/// Attempt to have two equivalent projects in a project collection fails.
/// </summary>
[Fact]
public void ErrorTwoProjectsEquivalentOneCollection()
{
_ = new Project { FullPath = "c:\\x" };
Assert.Throws<InvalidOperationException>(() =>
{
_ = new Project { FullPath = "c:\\x" };
});
}
/// <summary>
/// Validates that when loading two projects with nominally different global properties, but that match when we take
/// into account the ProjectCollection's global properties, we get the pre-existing project if one exists.
/// </summary>
[Fact]
public void TwoProjectsEquivalentWhenOneInheritsFromProjectCollection()
{
var project = new Project { FullPath = "c:\\1" };
// Set a global property on the project collection -- this should be passed on to all
// loaded projects.
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("Configuration", "Debug");
Assert.Equal("Debug", project.GlobalProperties["Configuration"]);
// now create a global properties dictionary to pass to a new project
var globals = new Dictionary<string, string> { { "Configuration", "Debug" } };
ProjectCollection.GlobalProjectCollection.LoadProject("c:\\1", globals, null);
Assert.Equal(1, ProjectCollection.GlobalProjectCollection.Count);
}
/// <summary>
/// Two projects may have the same path but different global properties.
/// </summary>
[Fact]
public void TwoProjectsDistinguishedByGlobalPropertiesOnly()
{
ProjectRootElement xml = ProjectRootElement.Create();
string projectDirectory = NativeMethodsShared.IsWindows ? "c:\\1" : "/l";
var globalProperties1 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "p", "v1" } };
var project1 =
new Project(xml, globalProperties1, ObjectModelHelpers.MSBuildDefaultToolsVersion)
{
FullPath = projectDirectory
};
var globalProperties2 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "p", "v2" } };
var project2 =
new Project(xml, globalProperties2, ObjectModelHelpers.MSBuildDefaultToolsVersion)
{
FullPath = projectDirectory
};
Assert.True(ReferenceEquals(project1, ProjectCollection.GlobalProjectCollection.LoadProject(projectDirectory, globalProperties1, ObjectModelHelpers.MSBuildDefaultToolsVersion)));
Assert.True(ReferenceEquals(project2, ProjectCollection.GlobalProjectCollection.LoadProject(projectDirectory, globalProperties2, ObjectModelHelpers.MSBuildDefaultToolsVersion)));
List<Project> projects = Helpers.MakeList(ProjectCollection.GlobalProjectCollection.LoadedProjects);
Assert.Equal(2, projects.Count);
Assert.Equal(2, ProjectCollection.GlobalProjectCollection.Count);
Assert.Contains(project1, projects);
Assert.Contains(project2, projects);
}
/// <summary>
/// Validates that we can correctly load two of the same project file with different global properties, even when
/// those global properties are applied to the project by the project collection (and then overridden in one case).
/// </summary>
[Fact]
public void TwoProjectsDistinguishedByGlobalPropertiesOnly_ProjectOverridesProjectCollection()
{
var project = new Project { FullPath = "c:\\1" };
// Set a global property on the project collection -- this should be passed on to all
// loaded projects.
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("Configuration", "Debug");
Assert.Equal("Debug", project.GlobalProperties["Configuration"]);
// Differentiate this project from the one below
project.SetGlobalProperty("MyProperty", "MyValue");
// now create a global properties dictionary to pass to a new project
var project2Globals =
new Dictionary<string, string> { { "Configuration", "Release" }, { "Platform", "Win32" } };
Project project2 = ProjectCollection.GlobalProjectCollection.LoadProject("c:\\1", project2Globals, null);
Assert.Equal("Release", project2.GlobalProperties["Configuration"]);
// Setting a global property on the project collection overrides all contained projects,
// whether they were initially loaded with the global project collection's value or not.
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("Platform", "X64");
Assert.Equal("X64", project.GlobalProperties["Platform"]);
Assert.Equal("X64", project2.GlobalProperties["Platform"]);
// But setting a global property on the project directly should override that.
project2.SetGlobalProperty("Platform", "Itanium");
Assert.Equal("Itanium", project2.GlobalProperties["Platform"]);
// Now set global properties such that the two projects have an identical set.
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("Configuration", "Debug2");
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("Platform", "X86");
bool exceptionCaught = false;
try
{
// This will make it identical, so we should get a throw here.
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("MyProperty", "MyValue2");
}
catch (InvalidOperationException)
{
exceptionCaught = true;
}
Assert.True(exceptionCaught); // "Should have caused the two projects to be identical, causing an exception to be thrown"
}
/// <summary>
/// Two projects may have the same path but different tools version.
/// </summary>
[Fact]
public void TwoProjectsDistinguishedByToolsVersionOnly()
{
if (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version35) == null)
{
// "Requires 3.5 to be installed"
return;
}
if (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version20) == null)
{
// ".NET Framework 2.0 is required to be installed for this test, but it is not installed."
return;
}
ProjectRootElement xml = ProjectRootElement.Create();
var project1 = new Project(xml, null, "2.0") { FullPath = "c:\\1" };
var project2 = new Project(xml, null, ObjectModelHelpers.MSBuildDefaultToolsVersion) { FullPath = "c:\\1" };
Assert.True(ReferenceEquals(project1, ProjectCollection.GlobalProjectCollection.LoadProject("c:\\1", null, "2.0")));
Assert.True(ReferenceEquals(project2, ProjectCollection.GlobalProjectCollection.LoadProject("c:\\1", null, ObjectModelHelpers.MSBuildDefaultToolsVersion)));
}
/// <summary>
/// If the ToolsVersion in the project file is bogus, we'll default to the current ToolsVersion and successfully
/// load it. Make sure we can RE-load it, too, and successfully pick up the correct copy of the loaded project.
/// </summary>
[Fact]
public void ReloadProjectWithInvalidToolsVersionInFile()
{
const string content = @"
<Project ToolsVersion='bogus'>
<Target Name='t'/>
</Project>
";
using ProjectFromString projectFromString = new(content);
Project project = projectFromString.Project;
project.FullPath = "c:\\123.proj";
Project project2 = ProjectCollection.GlobalProjectCollection.LoadProject("c:\\123.proj", null, null);
Assert.True(ReferenceEquals(project, project2));
}
/// <summary>
/// Make sure we can reload a project that has a ToolsVersion that doesn't match what it ends up getting
/// forced to by default (current).
/// </summary>
[Fact]
public void ReloadProjectWithProjectToolsVersionDifferentFromEffectiveToolsVersion()
{
const string content = @"
<Project ToolsVersion='4.0'>
<Target Name='t'/>
</Project>
";
using ProjectFromString projectFromString = new(content);
Project project = projectFromString.Project;
project.FullPath = "c:\\123.proj";
Project project2 = ProjectCollection.GlobalProjectCollection.LoadProject("c:\\123.proj", null, null);
Assert.True(ReferenceEquals(project, project2));
}
/// <summary>
/// Collection stores projects distinguished by path, global properties, and tools version.
/// Changing global properties should update the collection.
/// </summary>
[Fact]
public void ChangingGlobalPropertiesUpdatesCollection()
{
using ProjectCollection collection = new ProjectCollection();
var project = new Project(collection) { FullPath = "c:\\x" };
project.SetGlobalProperty("p", "v1"); // should update collection
var globalProperties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "p", "v1" } };
Project newProject = collection.LoadProject("c:\\x", globalProperties, null);
Assert.True(ReferenceEquals(project, newProject));
}
/// <summary>
/// Changing global properties on collection should update the collection's defaults,
/// and any projects even if they have defined the same global properties
/// </summary>
[Fact]
public void SettingGlobalPropertiesOnCollectionUpdatesProjects()
{
using var collection = new ProjectCollection();
var project1 = new Project(collection) { FullPath = "c:\\y" };
Assert.Empty(project1.GlobalProperties);
collection.SetGlobalProperty("g1", "v1");
collection.SetGlobalProperty("g2", "v2");
collection.SetGlobalProperty("g2", "v2"); // try dupe
Assert.Equal(2, project1.GlobalProperties.Count);
collection.RemoveGlobalProperty("g2");
var project2 = new Project(collection) { FullPath = "c:\\x" };
Assert.Single(project1.GlobalProperties);
Assert.Equal("v1", project2.GlobalProperties["g1"]);
Assert.Single(project2.GlobalProperties);
Assert.Equal("v1", project2.GlobalProperties["g1"]);
}
/// <summary>
/// Changing global properties on collection should update the collection's defaults,
/// and any projects even if they have defined the same global properties
/// </summary>
[Fact]
public void SettingGlobalPropertiesOnCollectionUpdatesProjects2()
{
using var collection = new ProjectCollection();
var project1 = new Project(collection) { FullPath = "c:\\y" };
// load into collection
project1.SetGlobalProperty("g1", "v0");
Helpers.ClearDirtyFlag(project1.Xml);
collection.SetGlobalProperty("g1", "v1");
collection.SetGlobalProperty("g2", "v2");
Assert.Equal(2, project1.GlobalProperties.Count);
Assert.Equal("v1", project1.GlobalProperties["g1"]);
Assert.Equal("v2", project1.GlobalProperties["g2"]); // Got overwritten
Assert.True(project1.IsDirty);
}
/// <summary>
/// Changing global properties on collection should update the collection's defaults,
/// and all projects as well
/// </summary>
[Fact]
public void RemovingGlobalPropertiesOnCollectionUpdatesProjects()
{
using var collection = new ProjectCollection();
var project1 = new Project(collection) { FullPath = "c:\\y" };
Assert.Empty(project1.GlobalProperties);
Helpers.ClearDirtyFlag(project1.Xml);
collection.SetGlobalProperty("g1", "v1"); // should make both dirty
collection.SetGlobalProperty("g2", "v2"); // should make both dirty
Assert.True(project1.IsDirty);
var project2 = new Project(collection) { FullPath = "c:\\x" };
Assert.True(project2.IsDirty);
Assert.Equal(2, project1.GlobalProperties.Count);
Assert.Equal("v1", project2.GlobalProperties["g1"]);
Assert.Equal(2, project2.GlobalProperties.Count);
Assert.Equal("v1", project2.GlobalProperties["g1"]);
Helpers.ClearDirtyFlag(project1.Xml);
Helpers.ClearDirtyFlag(project2.Xml);
collection.RemoveGlobalProperty("g2"); // should make both dirty
Assert.True(project1.IsDirty);
Assert.True(project2.IsDirty);
Assert.Single(project1.GlobalProperties);
Assert.Single(project2.GlobalProperties);
collection.RemoveGlobalProperty("g1");
Assert.Empty(project1.GlobalProperties);
Assert.Empty(project2.GlobalProperties);
}
/// <summary>
/// Changing global properties on collection should update the collection's defaults,
/// and all projects as well
/// </summary>
[Fact]
public void RemovingGlobalPropertiesOnCollectionUpdatesProjects2()
{
using var collection = new ProjectCollection();
collection.SetGlobalProperty("g1", "v1");
var project1 = new Project(collection) { FullPath = "c:\\y" };
project1.SetGlobalProperty("g1", "v0"); // mask collection property
Helpers.ClearDirtyFlag(project1.Xml);
collection.RemoveGlobalProperty("g1"); // should modify the project
Assert.Empty(project1.GlobalProperties);
Assert.True(project1.IsDirty);
}
/// <summary>
/// Unloading a project should remove it from the project collection
/// </summary>
[Fact]
public void UnloadProject()
{
var project = new Project { FullPath = "c:\\x" };
Assert.Equal(1, ProjectCollection.GlobalProjectCollection.Count);
ProjectCollection.GlobalProjectCollection.UnloadProject(project); // should not throw
Assert.Equal(0, ProjectCollection.GlobalProjectCollection.Count);
Assert.Empty(Helpers.MakeList(ProjectCollection.GlobalProjectCollection.LoadedProjects));
}
/// <summary>
/// Unloading project XML should remove it from the weak cache.
/// </summary>
[Fact]
public void UnloadProjectXml()
{
var project = new Project { FullPath = "c:\\x" };
ProjectRootElement xml = project.Xml;
// Unload the evaluation project, and then the XML.
ProjectCollection.GlobalProjectCollection.UnloadProject(project);
ProjectCollection.GlobalProjectCollection.UnloadProject(xml);
try
{
// If the ProjectRootElement was unloaded from the cache, then
// an attempt to load it by the pretend filename should fail,
// so it makes a good test to see that the UnloadProject method worked.
ProjectCollection.GlobalProjectCollection.LoadProject(xml.FullPath);
Assert.Fail("An InvalidProjectFileException was expected.");
}
catch (InvalidProjectFileException)
{
}
}
/// <summary>
/// Unloading project XML while it is in use should result in an exception.
/// </summary>
[Fact]
public void UnloadProjectXmlWhileInDirectUse()
{
Assert.Throws<InvalidOperationException>(() =>
{
var project = new Project { FullPath = "c:\\x" };
// Attempt to unload the xml before unloading the project evaluation.
ProjectCollection.GlobalProjectCollection.UnloadProject(project.Xml);
});
}
/// <summary>
/// Unloading project XML while it is in use should result in an exception.
/// </summary>
[Fact]
public void UnloadProjectXmlWhileInImportUse()
{
Assert.Throws<InvalidOperationException>(() =>
{
var mainProject = new Project { FullPath = "c:\\main" };
var importProject = new Project { FullPath = "c:\\import" };
ProjectRootElement importedXml = importProject.Xml;
// Import into main project
mainProject.Xml.PrependChild(mainProject.Xml.CreateImportElement(importProject.FullPath));
mainProject.ReevaluateIfNecessary();
// Unload the import evaluation, but not the main project that still has a reference to it.
ProjectCollection.GlobalProjectCollection.UnloadProject(importProject);
// Attempt to unload the import xml before unloading the project that still references it.
try
{
ProjectCollection.GlobalProjectCollection.UnloadProject(importedXml);
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.Message);
throw;
}
});
}
/// <summary>
/// Renaming a project should correctly update the project collection's set of loaded projects.
/// </summary>
[Fact]
public void RenameProject()
{
var project = new Project { FullPath = "c:\\x" };
project.FullPath = "c:\\y";
Assert.Equal(1, ProjectCollection.GlobalProjectCollection.Count);
Assert.True(ReferenceEquals(project, Helpers.MakeList(ProjectCollection.GlobalProjectCollection.LoadedProjects)[0]));
ProjectCollection.GlobalProjectCollection.UnloadProject(project); // should not throw
Assert.Equal(0, ProjectCollection.GlobalProjectCollection.Count);
}
/// <summary>
/// Validates that we don't somehow lose the ProjectCollection global properties when renaming the project.
/// </summary>
[Fact]
public void RenameProjectAndVerifyStillContainsProjectCollectionGlobalProperties()
{
var project = new Project { FullPath = "c:\\1" };
// Set a global property on the project collection -- this should be passed on to all
// loaded projects.
ProjectCollection.GlobalProjectCollection.SetGlobalProperty("Configuration", "Debug");
Assert.Equal("Debug", project.GlobalProperties["Configuration"]);
project.FullPath = "c:\\2";
Assert.Equal("Debug", project.GlobalProperties["Configuration"]);
}
/// <summary>
/// Saving a project to a new name should correctly update the project collection's set of loaded projects.
/// Reported by F#.
/// </summary>
[Fact]
public void SaveToNewNameAndUnload()
{
string file1 = null;
string file2 = null;
try
{
file1 = FileUtilities.GetTemporaryFileName();
file2 = FileUtilities.GetTemporaryFileName();
Project project = new Project();
project.Save(file1);
using ProjectCollection collection = new ProjectCollection();
Project project2 = collection.LoadProject(file1);
project2.Save(file2);
collection.UnloadProject(project2);
}
finally
{
if (file1 != null)
{
File.Delete(file1);
}
if (file2 != null)
{
File.Delete(file2);
}
}
}
/// <summary>
/// Saving a project to a new name after loading, unloading, and reloading, should work.
/// Reported by F#.
/// </summary>
[Fact]
public void LoadUnloadReloadSaveToNewName()
{
string file1 = null;
string file2 = null;
try
{
file1 = FileUtilities.GetTemporaryFileName();
file2 = FileUtilities.GetTemporaryFileName();
var project = new Project();
project.Save(file1);
project.ProjectCollection.UnloadProject(project);
using var collection = new ProjectCollection();
Project project2 = collection.LoadProject(file1);
collection.UnloadProject(project2);
Project project3 = collection.LoadProject(file1);
project3.Save(file2); // should not crash
collection.UnloadProject(project3);
}
finally
{
if (file1 != null)
{
File.Delete(file1);
}
if (file2 != null)
{
File.Delete(file2);
}
}
}
/// <summary>
/// Saving a project to a new name after loading, unloading, and reloading, should work.
/// Reported by F#.
/// </summary>
[Fact]
public void LoadUnloadAllReloadSaveToNewName()
{
string file1 = null;
string file2 = null;
try
{
file1 = FileUtilities.GetTemporaryFileName();
file2 = FileUtilities.GetTemporaryFileName();
var project = new Project();
project.Save(file1);
project.ProjectCollection.UnloadProject(project);
using var collection = new ProjectCollection();
collection.LoadProject(file1);
collection.UnloadAllProjects();
Project project3 = collection.LoadProject(file1);
project3.Save(file2); // should not crash
collection.UnloadProject(project3);
}
finally
{
if (file1 != null)
{
File.Delete(file1);
}
if (file2 != null)
{
File.Delete(file2);
}
}
}
/// <summary>
/// Add a toolset
/// </summary>
[Fact]
public void AddToolset()
{
using var collection = new ProjectCollection();
collection.RemoveAllToolsets();
var toolset = new Toolset("x", "c:\\y", collection, null);
collection.AddToolset(toolset);
Assert.Equal(toolset, collection.GetToolset("x"));
Assert.True(collection.ContainsToolset("x"));
List<Toolset> toolsets = Helpers.MakeList(collection.Toolsets);
Assert.Single(toolsets);
Assert.Equal(toolset, toolsets[0]);
}
/// <summary>
/// Add two toolsets
/// </summary>
[Fact]
public void AddTwoToolsets()
{
using var collection = new ProjectCollection();
collection.RemoveAllToolsets();
var toolset1 = new Toolset("x", "c:\\y", collection, null);
var toolset2 = new Toolset("y", "c:\\z", collection, null);
collection.AddToolset(toolset1);
collection.AddToolset(toolset2);
Assert.Equal(toolset1, collection.GetToolset("x"));
Assert.Equal(toolset2, collection.GetToolset("y"));
List<Toolset> toolsets = Helpers.MakeList(collection.Toolsets);
Assert.Equal(2, toolsets.Count);
Assert.Contains(toolset1, toolsets);
Assert.Contains(toolset2, toolsets);
}
/// <summary>
/// Add a toolset that overrides another
/// </summary>
[Fact]
public void ReplaceToolset()
{
using var collection = new ProjectCollection();
collection.RemoveAllToolsets();
var toolset1 = new Toolset("x", "c:\\y", collection, null);
var toolset2 = new Toolset("x", "c:\\z", collection, null);
collection.AddToolset(toolset1);
collection.AddToolset(toolset2);
Assert.Equal(toolset2, collection.GetToolset("x"));
List<Toolset> toolsets = Helpers.MakeList(collection.Toolsets);
Assert.Single(toolsets);
Assert.Equal(toolset2, toolsets[0]);
}
/// <summary>
/// Attempt to add a null toolset
/// </summary>
[Fact]
public void AddNullToolset()
{
Assert.Throws<ArgumentNullException>(() =>
{
ProjectCollection.GlobalProjectCollection.AddToolset(null);
});
}
/// <summary>
/// Remove a toolset
/// </summary>
[Fact]
public void RemoveToolset()
{
using var collection = new ProjectCollection();
var toolset1 = new Toolset("x", "c:\\y", collection, null);
var toolset2 = new Toolset("y", "c:\\z", collection, null);
int initial = Helpers.MakeList(collection.Toolsets).Count;
collection.AddToolset(toolset1);
collection.AddToolset(toolset2);
Assert.True(collection.RemoveToolset("x"));
Assert.False(collection.ContainsToolset("x"));
Assert.Equal(1, Helpers.MakeList(collection.Toolsets).Count - initial);
}
/// <summary>
/// Remove a nonexistent toolset
/// </summary>
[Fact]
public void RemoveNonexistentToolset()
{
using var collection = new ProjectCollection();
Assert.False(collection.RemoveToolset("nonexistent"));
}
/// <summary>
/// Attempt to remove a null tools version
/// </summary>
[Fact]
public void RemoveNullToolsVersion()
{
Assert.Throws<ArgumentNullException>(() =>
{
ProjectCollection.GlobalProjectCollection.RemoveToolset(null);
});
}
/// <summary>
/// Attempt to remove an empty string toolsversion
/// </summary>
[Fact]
public void RemoveEmptyToolsVersion()
{
Assert.Throws<ArgumentException>(() =>
{
ProjectCollection.GlobalProjectCollection.RemoveToolset(String.Empty);
});
}
/// <summary>
/// Current default from registry is 2.0 if 2.0 is installed
/// </summary>
[Fact]
public void DefaultToolsVersion()
{
if (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version20) == null)
{
// "Requires 2.0 to be installed"
return;
}
using var collection = new ProjectCollection();
Assert.Equal(ObjectModelHelpers.MSBuildDefaultToolsVersion, collection.DefaultToolsVersion);
}
/// <summary>
/// Current default from registry is 4.0 if 2.0 is not installed
/// </summary>
#if FEATURE_INSTALLED_MSBUILD
[Fact]
#else
[Fact(Skip = "https://github.com/dotnet/msbuild/issues/276")]
#endif
public void DefaultToolsVersion2()
{
if (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version20) != null)
{
// "Requires 2.0 to NOT be installed"
return;
}
using var collection = new ProjectCollection(null, null, ToolsetDefinitionLocations.Registry);
Assert.Equal(ObjectModelHelpers.MSBuildDefaultToolsVersion, collection.DefaultToolsVersion);
}
/// <summary>
/// Error setting default tools version to empty
/// </summary>
[Fact]
public void SetDefaultToolsVersionEmpty()
{
Assert.Throws<ArgumentException>(() =>
{
ProjectCollection.GlobalProjectCollection.DefaultToolsVersion = String.Empty;
});
}
/// <summary>
/// Error setting default tools version to a toolset that does not exist
/// </summary>
[Fact]
public void SetDefaultToolsVersionNonexistentToolset()
{
Assert.Throws<InvalidOperationException>(() =>
{
ProjectCollection.GlobalProjectCollection.DefaultToolsVersion = "nonexistent";
});
}
/// <summary>
/// Set default tools version; subsequent projects should use it
/// </summary>
[Fact]
public void SetDefaultToolsVersion()
{
using var collection = new ProjectCollection();
collection.AddToolset(new Toolset("x", @"c:\y", collection, null));
collection.DefaultToolsVersion = "x";
Assert.Equal("x", collection.DefaultToolsVersion);
string content = ObjectModelHelpers.CleanupFileContents(@"
<Project xmlns='msbuildnamespace' >
<Target Name='t'/>
</Project>
");
using ProjectFromString projectFromString = new(content, null, null, collection);
Project project = projectFromString.Project;
// ... and after all that, we end up defaulting to the current ToolsVersion instead. There's a way
// to turn this behavior (new in Dev12) off, but it requires setting an environment variable and
// clearing some internal state to make sure that the update environment variable is picked up, so
// there's not a good way of doing it from these deliberately public OM only tests.
Assert.Equal(project.ToolsVersion, ObjectModelHelpers.MSBuildDefaultToolsVersion);
}
/// <summary>
/// Changes to the ProjectCollection object should raise a ProjectCollectionChanged event.
/// </summary>
[Fact]
public void ProjectCollectionChangedEvent()
{
using var collection = new ProjectCollection();
bool dirtyRaised = false;
ProjectCollectionChangedState expectedChange = ProjectCollectionChangedState.Loggers;
collection.ProjectCollectionChanged +=
(sender, e) =>
{
Assert.Same(collection, sender);
Assert.Equal(expectedChange, e.Changed);
dirtyRaised = true;
};
Assert.False(dirtyRaised);
expectedChange = ProjectCollectionChangedState.DisableMarkDirty;
dirtyRaised = false;
collection.DisableMarkDirty = true; // LEAVE THIS TRUE for rest of the test, to verify it doesn't suppress these events
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.IsBuildEnabled;
dirtyRaised = false;
collection.IsBuildEnabled = !collection.IsBuildEnabled;
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.OnlyLogCriticalEvents;
dirtyRaised = false;
collection.OnlyLogCriticalEvents = !collection.OnlyLogCriticalEvents;
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.SkipEvaluation;
dirtyRaised = false;
collection.SkipEvaluation = !collection.SkipEvaluation;
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.GlobalProperties;
dirtyRaised = false;
collection.SetGlobalProperty("a", "b");
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.GlobalProperties;
dirtyRaised = false;
collection.RemoveGlobalProperty("a");
Assert.True(dirtyRaised);
// Verify HostServices changes raise the event.
expectedChange = ProjectCollectionChangedState.HostServices;
dirtyRaised = false;
collection.HostServices = new HostServices();
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.Loggers;
dirtyRaised = false;
collection.RegisterLogger(new MockLogger());
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.Loggers;
dirtyRaised = false;
collection.RegisterLoggers(new Build.Framework.ILogger[] { new MockLogger(), new MockLogger() });
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.Loggers;
dirtyRaised = false;
collection.UnregisterAllLoggers();
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.Toolsets;
dirtyRaised = false;
collection.AddToolset(new Toolset("testTools", Path.GetTempPath(), collection, Path.GetTempPath()));
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.DefaultToolsVersion;
dirtyRaised = false;
collection.DefaultToolsVersion = "testTools";
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.Toolsets;
dirtyRaised = false;
collection.RemoveToolset("testTools");
Assert.True(dirtyRaised);
expectedChange = ProjectCollectionChangedState.Toolsets;
dirtyRaised = false;
collection.RemoveAllToolsets();
Assert.True(dirtyRaised);
}
/// <summary>
/// Changes to the ProjectCollection object should raise a ProjectCollectionChanged event.
/// </summary>
[Fact]
public void ProjectCollectionChangedEvent2()
{
// Verify if the project, project collection and the value we are setting in the project collection are all the same
// then the projects value for the property should not change and no event should be fired.
using var collection = new ProjectCollection();
using XmlReader reader = CreateProjectXmlReader();
Project project = collection.LoadProject(reader, ObjectModelHelpers.MSBuildDefaultToolsVersion);
project.SetProperty("a", "1");
collection.SetGlobalProperty("a", "1");
VerifyProjectCollectionEvents(collection, false, "1");
// Verify if the project, project collection and the value we are setting in the project collection are all the same
// then the projects value for the property should not change and no event should be fired.
using var collection2 = new ProjectCollection();
using var reader2 = CreateProjectXmlReader();
project = collection2.LoadProject(reader2, ObjectModelHelpers.MSBuildDefaultToolsVersion);
project.SetProperty("a", "%28x86%29");
collection2.SetGlobalProperty("a", "%28x86%29");
VerifyProjectCollectionEvents(collection2, false, "%28x86%29");
// Verify if the project, project collection have the same value but a new value is set in the project collection
// then the projects value for the property should be change and an event should be fired.
using var collection3 = new ProjectCollection();
using var reader3 = CreateProjectXmlReader();
project = collection3.LoadProject(reader3, ObjectModelHelpers.MSBuildDefaultToolsVersion);
project.SetProperty("a", "1");
collection3.SetGlobalProperty("a", "1");
VerifyProjectCollectionEvents(collection3, true, "2");
// Verify if the project, project collection have the same value but a new value is set in the project collection
// then the projects value for the property should be change and an event should be fired.
using var collection4 = new ProjectCollection();
using var reader4 = CreateProjectXmlReader();
project = collection4.LoadProject(reader4, ObjectModelHelpers.MSBuildDefaultToolsVersion);
project.SetProperty("a", "1");
collection4.SetGlobalProperty("a", "(x86)");
VerifyProjectCollectionEvents(collection4, true, "%28x86%29");
// Verify if the project has one value and project collection and the property we are setting on the project collection have the same value
// then the projects value for the property should be change but no event should be fired
using var collection5 = new ProjectCollection();
using var reader5 = CreateProjectXmlReader();
project = collection5.LoadProject(reader5, ObjectModelHelpers.MSBuildDefaultToolsVersion);
project.SetProperty("a", "2");
collection5.SetGlobalProperty("a", "1");
VerifyProjectCollectionEvents(collection5, false, "1");
// Verify if the project and the property being set have one value but the project collection has another
// then the projects value for the property should not change and event should be fired
using var collection6 = new ProjectCollection();
using var reader6 = CreateProjectXmlReader();
project = collection6.LoadProject(reader6, ObjectModelHelpers.MSBuildDefaultToolsVersion);
project.SetProperty("a", "1");
collection6.SetGlobalProperty("a", "2");
VerifyProjectCollectionEvents(collection6, true, "1");
// item is added to project collection for the first time. Make sure it is added to the project and an event is fired.
using var collection7 = new ProjectCollection();
using var reader7 = CreateProjectXmlReader();
project = collection7.LoadProject(reader7, ObjectModelHelpers.MSBuildDefaultToolsVersion);
VerifyProjectCollectionEvents(collection7, true, "1");
}
/// <summary>
/// Changes to project XML should raise an ProjectXmlChanged event.
/// </summary>
[Fact]
public void ProjectXmlChangedEvent()
{
using var collection = new ProjectCollection();
ProjectRootElement pre = null;
bool dirtyRaised = false;
collection.ProjectXmlChanged +=
(sender, e) =>
{
Assert.Same(collection, sender);
Assert.Same(pre, e.ProjectXml);
TestOutput.WriteLine(e.Reason ?? String.Empty);
dirtyRaised = true;
};
Assert.False(dirtyRaised);
// Ensure that the event is raised even when DisableMarkDirty is set.
collection.DisableMarkDirty = true;
// Create a new PRE but don't change the template.
dirtyRaised = false;
pre = ProjectRootElement.Create(collection);
Assert.False(dirtyRaised);
// Change PRE prior to setting a filename and thus associating the PRE with the ProjectCollection.
dirtyRaised = false;
pre.AppendChild(pre.CreatePropertyGroupElement());
Assert.False(dirtyRaised);
// Associate with the ProjectCollection
dirtyRaised = false;
pre.FullPath = FileUtilities.GetTemporaryFile();
Assert.True(dirtyRaised);
// Now try dirtying again and see that the event is raised this time.
dirtyRaised = false;
pre.AppendChild(pre.CreatePropertyGroupElement());
Assert.True(dirtyRaised);
// Make sure that project collection global properties don't raise this event.
dirtyRaised = false;
collection.SetGlobalProperty("a", "b");
Assert.False(dirtyRaised);
// Change GlobalProperties on a project to see that doesn't propagate as an XML change.
dirtyRaised = false;
var project = new Project(pre);
project.SetGlobalProperty("q", "s");
Assert.False(dirtyRaised);
// Change XML via the Project to verify the event is raised.
dirtyRaised = false;
project.SetProperty("z", "y");
Assert.True(dirtyRaised);
}
/// <summary>
/// Changes to a Project evaluation object should raise a ProjectChanged event.
/// </summary>
[Fact]
public void ProjectChangedEvent()
{
using var collection = new ProjectCollection();
Project project = null;
bool dirtyRaised = false;
collection.ProjectChanged +=
(sender, e) =>
{
Assert.Same(collection, sender);
Assert.Same(project, e.Project);
dirtyRaised = true;
};
Assert.False(dirtyRaised);
ProjectRootElement pre = ProjectRootElement.Create(collection);
project = new Project(pre, null, null, collection);
// all these should still pass with disableMarkDirty set
collection.DisableMarkDirty = true;
project.DisableMarkDirty = true;
dirtyRaised = false;
pre.AppendChild(pre.CreatePropertyGroupElement());
Assert.False(dirtyRaised); // "Dirtying the XML directly should not result in a ProjectChanged event."
// No events should be raised before we associate a filename with the PRE
dirtyRaised = false;
project.SetGlobalProperty("someGlobal", "someValue");
Assert.False(dirtyRaised);
dirtyRaised = false;
project.SetProperty("someProp", "someValue");
Assert.False(dirtyRaised);
pre.FullPath = FileUtilities.GetTemporaryFile();
dirtyRaised = false;
project.SetGlobalProperty("someGlobal", "someValue2");
Assert.True(dirtyRaised);
dirtyRaised = false;
project.RemoveGlobalProperty("someGlobal");
Assert.True(dirtyRaised);
dirtyRaised = false;
collection.SetGlobalProperty("somePCglobal", "someValue");
Assert.True(dirtyRaised);
dirtyRaised = false;
project.SetProperty("someProp", "someValue2");
Assert.True(dirtyRaised);
}
/// <summary>
/// Verifies that the <see cref="ProjectCollection.Version"/> is correct.
/// </summary>
/// <remarks>
/// This test was written because on OS X and Ubuntu, the version is showing "0.0.0.0"
/// while Windows was working just fine.
/// </remarks>
[Fact]
public void ProjectCollectionVersionIsCorrect()
{
Version expectedVersion = new Version(this.GetType().GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version);
ProjectCollection.Version.Major.ShouldBe(expectedVersion.Major);
ProjectCollection.Version.Minor.ShouldBe(expectedVersion.Minor);
}
/// <summary>
/// Create an empty project file and return the path
/// </summary>
private static string CreateProjectFile()
{
ProjectRootElement xml = ProjectRootElement.Create();
string path = FileUtilities.GetTemporaryFileName();
xml.Save(path);
return path;
}
/// <summary>
/// Create an XmlReader around an empty project file content
/// </summary>
private static XmlReader CreateProjectXmlReader()
{
ProjectRootElement xml = ProjectRootElement.Create();
XmlReader reader = XmlReader.Create(new StringReader(xml.RawXml));
return reader;
}
/// <summary>
/// Verify that when a property is set on the project collection that the correct events are fired.
/// </summary>
private static void VerifyProjectCollectionEvents(ProjectCollection collection, bool expectEventRaised, string propertyValue)
{
bool raisedEvent = false;
ProjectCollectionChangedState expectedChange = ProjectCollectionChangedState.Loggers;
collection.ProjectCollectionChanged +=
(sender, e) =>
{
Assert.Same(collection, sender);
Assert.Equal(expectedChange, e.Changed);
raisedEvent = true;
};
expectedChange = ProjectCollectionChangedState.GlobalProperties;
collection.SetGlobalProperty("a", propertyValue);
Assert.Equal(raisedEvent, expectEventRaised);
ProjectPropertyInstance property = collection.GetGlobalProperty("a");
Assert.NotNull(property);
Assert.Equal(ProjectCollection.Unescape(propertyValue), property.EvaluatedValue);
}
}
}
|