|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#if FEATURE_XML_SCHEMA_VALIDATION
using System;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Threading;
using System.Xml;
using Microsoft.Build.CommandLine;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Xunit;
#nullable disable
namespace Microsoft.Build.UnitTests
{
public class ProjectSchemaValidationHandlerTest
{
/***********************************************************************
*
* Test: ProjectSchemaValidationHandlerTest.VerifyProjectSchema
*
* This calls VerifyProjectSchema to validate a project file passed, where
* the project contents are invalid
*
**********************************************************************/
[Fact]
public void VerifyInvalidProjectSchema()
{
string[] msbuildTempXsdFilenames = Array.Empty<string>();
string projectFilename = null;
string oldValueForMSBuildOldOM = null;
string oldValueForMSBuildLoadMicrosoftTargetsReadOnly = Environment.GetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly");
try
{
oldValueForMSBuildOldOM = Environment.GetEnvironmentVariable("MSBuildOldOM");
Environment.SetEnvironmentVariable("MSBuildOldOM", "");
// Create schema files in the temp folder
msbuildTempXsdFilenames = PrepareSchemaFiles();
projectFilename = CreateTempFileOnDisk(@"
<Project xmlns=`msbuildnamespace`>
<PropertyGroup>
<MyInvalidProperty/>
</PropertyGroup>
<Target Name=`Build` />
</Project>
");
string quotedProjectFilename = "\"" + projectFilename + "\"";
Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFilename + " /validate:\"" + msbuildTempXsdFilenames[0] + "\""));
}
finally
{
if (projectFilename != null)
{
File.Delete(projectFilename);
}
CleanupSchemaFiles(msbuildTempXsdFilenames);
Environment.SetEnvironmentVariable("MSBuildOldOM", oldValueForMSBuildOldOM);
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", oldValueForMSBuildLoadMicrosoftTargetsReadOnly);
}
}
/// <summary>
/// Checks that an exception is thrown when the schema being validated
/// against is itself invalid
/// </summary>
[Fact]
public void VerifyInvalidSchemaItself1()
{
string invalidSchemaFile = null;
string projectFilename = null;
string oldValueForMSBuildOldOM = null;
string oldValueForMSBuildLoadMicrosoftTargetsReadOnly = Environment.GetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly");
try
{
oldValueForMSBuildOldOM = Environment.GetEnvironmentVariable("MSBuildOldOM");
Environment.SetEnvironmentVariable("MSBuildOldOM", "");
// Create schema files in the temp folder
invalidSchemaFile = FileUtilities.GetTemporaryFileName();
File.WriteAllText(invalidSchemaFile, "<this_is_invalid_schema_content/>");
projectFilename = CreateTempFileOnDisk(@"
<Project xmlns=`msbuildnamespace`>
<Target Name=`Build` />
</Project>
");
string quotedProjectFile = "\"" + projectFilename + "\"";
Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + invalidSchemaFile + "\""));
}
finally
{
if (projectFilename != null)
{
File.Delete(projectFilename);
}
if (invalidSchemaFile != null)
{
File.Delete(invalidSchemaFile);
}
Environment.SetEnvironmentVariable("MSBuildOldOM", oldValueForMSBuildOldOM);
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", oldValueForMSBuildLoadMicrosoftTargetsReadOnly);
}
}
/// <summary>
/// Checks that an exception is thrown when the schema being validated
/// against is itself invalid
/// </summary>
[Fact]
public void VerifyInvalidSchemaItself2()
{
string invalidSchemaFile = null;
string projectFilename = null;
string oldValueForMSBuildOldOM = null;
string oldValueForMSBuildLoadMicrosoftTargetsReadOnly = Environment.GetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly");
try
{
oldValueForMSBuildOldOM = Environment.GetEnvironmentVariable("MSBuildOldOM");
Environment.SetEnvironmentVariable("MSBuildOldOM", "");
// Create schema files in the temp folder
invalidSchemaFile = FileUtilities.GetTemporaryFileName();
File.WriteAllText(invalidSchemaFile, @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xs:schema targetNamespace=""http://schemas.microsoft.com/developer/msbuild/2003"" xmlns:msb=""http://schemas.microsoft.com/developer/msbuild/2003"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"" elementFormDefault=""qualified"">
<xs:element name=""Project"">
<xs:complexType>
<xs:sequence>
<xs:group ref=""x"" minOccurs=""0"" maxOccurs=""unbounded""/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
");
projectFilename = CreateTempFileOnDisk(@"
<Project xmlns=`msbuildnamespace`>
<Target Name=`Build` />
</Project>
");
string quotedProjectFile = "\"" + projectFilename + "\"";
Assert.Equal(MSBuildApp.ExitType.InitializationError, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + invalidSchemaFile + "\""));
}
finally
{
if (invalidSchemaFile != null)
{
File.Delete(invalidSchemaFile);
}
if (projectFilename != null)
{
File.Delete(projectFilename);
}
Environment.SetEnvironmentVariable("MSBuildOldOM", oldValueForMSBuildOldOM);
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", oldValueForMSBuildLoadMicrosoftTargetsReadOnly);
}
}
/***********************************************************************
*
* Test: ProjectSchemaValidationHandlerTest.VerifyProjectSchema
*
* This calls VerifyProjectSchema to validate a project XML
* specified in a string, where the project passed is valid
*
**********************************************************************/
[Fact]
public void VerifyValidProjectSchema()
{
string oldValueForMSBuildLoadMicrosoftTargetsReadOnly = Environment.GetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly");
string[] msbuildTempXsdFilenames = Array.Empty<string>();
string projectFilename = CreateTempFileOnDisk(@"
<Project xmlns=`msbuildnamespace`>
<Target Name=`Build` />
</Project>
");
string oldValueForMSBuildOldOM = null;
try
{
oldValueForMSBuildOldOM = Environment.GetEnvironmentVariable("MSBuildOldOM");
Environment.SetEnvironmentVariable("MSBuildOldOM", "");
// Create schema files in the temp folder
msbuildTempXsdFilenames = PrepareSchemaFiles();
string quotedProjectFile = "\"" + projectFilename + "\"";
Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + msbuildTempXsdFilenames[0] + "\""));
// ProjectSchemaValidationHandler.VerifyProjectSchema
// (
// projectFilename,
// msbuildTempXsdFilenames[0],
// @"c:\"
// );
}
finally
{
File.Delete(projectFilename);
CleanupSchemaFiles(msbuildTempXsdFilenames);
Environment.SetEnvironmentVariable("MSBuildOldOM", oldValueForMSBuildOldOM);
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", oldValueForMSBuildLoadMicrosoftTargetsReadOnly);
}
}
/// <summary>
/// The test has a valid project file, importing an invalid project file.
/// We should not validate imported files against the schema in V1, so this
/// should not be caught by the schema
/// </summary>
[Fact]
public void VerifyInvalidImportNotCaughtBySchema()
{
string oldValueForMSBuildLoadMicrosoftTargetsReadOnly = Environment.GetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly");
string[] msbuildTempXsdFilenames = Array.Empty<string>();
string importedProjectFilename = CreateTempFileOnDisk(@"
<Project xmlns=`msbuildnamespace`>
<PropertyGroup><UnknownProperty/></PropertyGroup>
<Target Name=`Build` />
</Project>
");
string projectFilename = CreateTempFileOnDisk(@"
<Project xmlns=`msbuildnamespace`>
<Import Project=`{0}` />
</Project>
", importedProjectFilename);
string oldValueForMSBuildOldOM = null;
try
{
oldValueForMSBuildOldOM = Environment.GetEnvironmentVariable("MSBuildOldOM");
Environment.SetEnvironmentVariable("MSBuildOldOM", "");
// Create schema files in the temp folder
msbuildTempXsdFilenames = PrepareSchemaFiles();
string quotedProjectFile = "\"" + projectFilename + "\"";
Assert.Equal(MSBuildApp.ExitType.Success, MSBuildApp.Execute(@"c:\foo\msbuild.exe " + quotedProjectFile + " /validate:\"" + msbuildTempXsdFilenames[0] + "\""));
// ProjectSchemaValidationHandler.VerifyProjectSchema
// (
// projectFilename,
// msbuildTempXsdFilenames[0],
// @"c:\"
// );
}
finally
{
CleanupSchemaFiles(msbuildTempXsdFilenames);
File.Delete(projectFilename);
File.Delete(importedProjectFilename);
Environment.SetEnvironmentVariable("MSBuildOldOM", oldValueForMSBuildOldOM);
Environment.SetEnvironmentVariable("MSBuildLoadMicrosoftTargetsReadOnly", oldValueForMSBuildLoadMicrosoftTargetsReadOnly);
}
}
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
#region Helper Functions
/// <summary>
/// MSBuild schemas are embedded as a resource into Microsoft.Build.Engine.UnitTests.dll.
/// Extract the stream from the resource and write the XSDs out to a temporary file,
/// so that our schema validator can access it.
/// </summary>
private string[] PrepareSchemaFiles()
{
string msbuildXsdRootDirectory = Path.GetTempPath();
Directory.CreateDirectory(msbuildXsdRootDirectory);
Stream msbuildXsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Build.CommandLine.UnitTests.Microsoft.Build.xsd");
using StreamReader msbuildXsdStreamReader = new StreamReader(msbuildXsdStream);
string msbuildXsdContents = msbuildXsdStreamReader.ReadToEnd();
string msbuildTempXsdFilename = Path.Combine(msbuildXsdRootDirectory, "Microsoft.Build.xsd");
File.WriteAllText(msbuildTempXsdFilename, msbuildXsdContents);
string msbuildXsdSubDirectory = Path.Combine(msbuildXsdRootDirectory, "MSBuild");
Directory.CreateDirectory(msbuildXsdSubDirectory);
msbuildXsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Build.CommandLine.UnitTests.Microsoft.Build.Core.xsd");
using StreamReader msbuildXsdStreamReader2 = new StreamReader(msbuildXsdStream);
msbuildXsdContents = msbuildXsdStreamReader2.ReadToEnd();
string msbuildTempXsdFilename2 = Path.Combine(msbuildXsdSubDirectory, "Microsoft.Build.Core.xsd");
File.WriteAllText(msbuildTempXsdFilename2, msbuildXsdContents);
msbuildXsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Build.CommandLine.UnitTests.Microsoft.Build.CommonTypes.xsd");
using StreamReader msbuildXsdStreamReader3 = new StreamReader(msbuildXsdStream);
msbuildXsdContents = msbuildXsdStreamReader3.ReadToEnd();
string msbuildTempXsdFilename3 = Path.Combine(msbuildXsdSubDirectory, "Microsoft.Build.CommonTypes.xsd");
File.WriteAllText(msbuildTempXsdFilename3, msbuildXsdContents);
return new string[] { msbuildTempXsdFilename, msbuildTempXsdFilename2, msbuildTempXsdFilename3 };
}
/// <summary>
/// Gets rid of the temporary files created to hold the schemas for the duration
/// of these unit tests.
/// </summary>
private void CleanupSchemaFiles(string[] msbuildTempXsdFilenames)
{
foreach (string file in msbuildTempXsdFilenames)
{
if (File.Exists(file))
{
File.Delete(file);
}
}
string msbuildXsdSubDirectory = Path.Combine(Path.GetTempPath(), "MSBuild");
if (Directory.Exists(msbuildXsdSubDirectory))
{
for (int i = 0; i < 5; i++)
{
try
{
FileUtilities.DeleteWithoutTrailingBackslash(msbuildXsdSubDirectory, true /* recursive */);
break;
}
catch (Exception)
{
Thread.Sleep(1000);
// Eat exceptions from the delete
}
}
}
}
/// <summary>
/// Create an MSBuild project file on disk and return the full path to it.
/// </summary>
/// <remarks>Stolen from ObjectModelHelpers because we use relatively little
/// of the ObjectModelHelpers functionality, so as to avoid having to include in
/// this project everything that ObjectModelHelpers depends on</remarks>
internal static string CreateTempFileOnDisk(string fileContents, params object[] args)
{
return CreateTempFileOnDiskNoFormat(String.Format(fileContents, args));
}
/// <summary>
/// Create an MSBuild project file on disk and return the full path to it.
/// </summary>
/// <remarks>Stolen from ObjectModelHelpers because we use relatively little
/// of the ObjectModelHelpers functionality, so as to avoid having to include in
/// this project everything that ObjectModelHelpers depends on</remarks>
internal static string CreateTempFileOnDiskNoFormat(string fileContents)
{
string projectFilePath = FileUtilities.GetTemporaryFileName();
File.WriteAllText(projectFilePath, CleanupFileContents(fileContents));
return projectFilePath;
}
/// <summary>
/// Does certain replacements in a string representing the project file contents.
/// This makes it easier to write unit tests because the author doesn't have
/// to worry about escaping double-quotes, etc.
/// </summary>
/// <remarks>Stolen from ObjectModelHelpers because we use relatively little
/// of the ObjectModelHelpers functionality, so as to avoid having to include in
/// this project everything that ObjectModelHelpers depends on</remarks>
private static string CleanupFileContents(string projectFileContents)
{
// Replace reverse-single-quotes with double-quotes.
projectFileContents = projectFileContents.Replace("`", "\"");
// Place the correct MSBuild namespace into the <Project> tag.
projectFileContents = projectFileContents.Replace("msbuildnamespace", msbuildNamespace);
projectFileContents = projectFileContents.Replace("msbuilddefaulttoolsversion", msbuildDefaultToolsVersion);
return projectFileContents;
}
private const string msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003";
private const string msbuildDefaultToolsVersion = "4.0";
#endregion // Helper Functions
#pragma warning restore format
}
}
#endif
|