|
// 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.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Shared;
using Microsoft.Build.UnitTests;
using Microsoft.Build.UnitTests.Shared;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Build.BuildCheck.UnitTests;
public class EndToEndTests : IDisposable
{
private const string EditorConfigFileName = ".editorconfig";
private readonly TestEnvironment _env;
public EndToEndTests(ITestOutputHelper output)
{
_env = TestEnvironment.Create(output);
// this is needed to ensure the binary logger does not pollute the environment
_env.WithEnvironmentInvariant();
}
private static string AssemblyLocation { get; } = Path.Combine(Path.GetDirectoryName(typeof(EndToEndTests).Assembly.Location) ?? AppContext.BaseDirectory);
private static string TestAssetsRootPath { get; } = Path.Combine(AssemblyLocation, "TestAssets");
public void Dispose() => _env.Dispose();
[Theory]
[InlineData(true)]
[InlineData(false)]
public void PropertiesUsageAnalyzerTest(bool buildInOutOfProcessNode)
{
PrepareSampleProjectsAndConfig(
buildInOutOfProcessNode,
out TransientTestFile projectFile,
out _,
"PropsCheckTest.csproj");
string output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path} -check", out bool success);
_env.Output.WriteLine(output);
_env.Output.WriteLine("=========================");
success.ShouldBeTrue(output);
output.ShouldMatch(@"BC0201: .* Property: 'MyProp11'");
output.ShouldMatch(@"BC0202: .* Property: 'MyPropT2'");
output.ShouldMatch(@"BC0203: .* Property: 'MyProp13'");
// each finding should be found just once - but reported twice, due to summary
Regex.Matches(output, "BC0201: .* Property").Count.ShouldBe(2);
Regex.Matches(output, "BC0202: .* Property").Count.ShouldBe(2);
Regex.Matches(output, "BC0203 .* Property").Count.ShouldBe(2);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void WarningsCountExceedsLimitTest(bool buildInOutOfProcessNode, bool limitReportsCount)
{
PrepareSampleProjectsAndConfig(
buildInOutOfProcessNode,
out TransientTestFile projectFile,
out _,
"PropsCheckTestWithLimit.csproj");
if (limitReportsCount)
{
_env.SetEnvironmentVariable("MSBUILDDONOTLIMITBUILDCHECKRESULTSNUMBER", "0");
}
else
{
_env.SetEnvironmentVariable("MSBUILDDONOTLIMITBUILDCHECKRESULTSNUMBER", "1");
}
string output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path} -check", out bool success);
_env.Output.WriteLine(output);
_env.Output.WriteLine("=========================");
success.ShouldBeTrue(output);
// each finding should be found just once - but reported twice, due to summary
if (limitReportsCount)
{
output.ShouldMatch(@"has exceeded the maximum number of results allowed");
Regex.Matches(output, "BC0202: .* Property").Count.ShouldBe(2);
Regex.Matches(output, "BC0203: .* Property").Count.ShouldBe(38);
}
else
{
Regex.Matches(output, "BC0202: .* Property").Count.ShouldBe(2);
Regex.Matches(output, "BC0203: .* Property").Count.ShouldBe(42);
}
}
[Fact]
public void ConfigChangeReflectedOnReuse()
{
PrepareSampleProjectsAndConfig(
// we need out of proc build - to test node reuse
true,
out TransientTestFile projectFile,
out TransientTestFile editorconfigFile,
"PropsCheckTest.csproj");
// Build without BuildCheck - no findings should be reported
string output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path}", out bool success);
_env.Output.WriteLine(output);
_env.Output.WriteLine("=========================");
success.ShouldBeTrue(output);
output.ShouldNotContain("BC0201");
output.ShouldNotContain("BC0202");
output.ShouldNotContain("BC0203");
// Build with BuildCheck - findings should be reported
output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path} -check", out success);
_env.Output.WriteLine(output);
_env.Output.WriteLine("=========================");
success.ShouldBeTrue(output);
output.ShouldContain("warning BC0201");
output.ShouldContain("warning BC0202");
output.ShouldContain("warning BC0203");
// Flip config in editorconfig
string editorConfigChange = """
build_check.BC0201.Severity=error
build_check.BC0202.Severity=error
build_check.BC0203.Severity=error
""";
File.AppendAllText(editorconfigFile.Path, editorConfigChange);
// Build with BuildCheck - findings with new severity should be reported
output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path} -check", out success);
_env.Output.WriteLine(output);
_env.Output.WriteLine("=========================");
// build should fail due to error checks
success.ShouldBeFalse(output);
output.ShouldContain("error BC0201");
output.ShouldContain("error BC0202");
output.ShouldContain("error BC0203");
// Build without BuildCheck - no findings should be reported
output = RunnerUtilities.ExecBootstrapedMSBuild($"{projectFile.Path}", out success);
_env.Output.WriteLine(output);
_env.Output.WriteLine("=========================");
success.ShouldBeTrue(output);
output.ShouldNotContain("BC0201");
output.ShouldNotContain("BC0202");
output.ShouldNotContain("BC0203");
}
[Theory]
[InlineData(true, true)]
[InlineData(false, true)]
[InlineData(false, false)]
public void SampleCheckIntegrationTest_CheckOnBuild(bool buildInOutOfProcessNode, bool checkRequested)
{
PrepareSampleProjectsAndConfig(buildInOutOfProcessNode, out TransientTestFile projectFile, new List<(string, string)>() { ("BC0101", "warning") });
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore" +
(checkRequested ? " -check" : string.Empty), out bool success, false, _env.Output, timeoutMilliseconds: 120_000);
_env.Output.WriteLine(output);
success.ShouldBeTrue();
// The check warnings should appear - but only if check was requested.
if (checkRequested)
{
output.ShouldContain("BC0101");
output.ShouldContain("BC0102");
output.ShouldContain("BC0103");
output.ShouldContain("BC0104");
}
else
{
output.ShouldNotContain("BC0101");
output.ShouldNotContain("BC0102");
output.ShouldNotContain("BC0103");
output.ShouldNotContain("BC0104");
}
}
[Theory]
[InlineData(true, true, "warning")]
[InlineData(true, true, "error")]
[InlineData(true, true, "suggestion")]
[InlineData(false, true, "warning")]
[InlineData(false, true, "error")]
[InlineData(false, true, "suggestion")]
[InlineData(false, false, "warning")]
public void SampleCheckIntegrationTest_ReplayBinaryLogOfCheckedBuild(bool buildInOutOfProcessNode, bool checkRequested, string BC0101Severity)
{
PrepareSampleProjectsAndConfig(buildInOutOfProcessNode, out TransientTestFile projectFile, new List<(string, string)>() { ("BC0101", BC0101Severity) });
var projectDirectory = Path.GetDirectoryName(projectFile.Path);
string logFile = _env.ExpectFile(".binlog").Path;
_ = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore {(checkRequested ? "-check" : string.Empty)} -bl:{logFile}",
out bool success, false, _env.Output, timeoutMilliseconds: 120_000);
if (BC0101Severity != "error")
{
success.ShouldBeTrue();
}
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{logFile} -flp:logfile={Path.Combine(projectDirectory!, "logFile.log")};verbosity=diagnostic",
out success, false, _env.Output, timeoutMilliseconds: 120_000);
_env.Output.WriteLine(output);
if (BC0101Severity != "error")
{
success.ShouldBeTrue();
}
// The conflicting outputs warning appears - but only if check was requested
if (checkRequested)
{
output.ShouldContain(FormatExpectedDiagOutput("BC0101", BC0101Severity));
output.ShouldContain("BC0102");
output.ShouldContain("BC0103");
}
else
{
output.ShouldNotContain("BC0101");
output.ShouldNotContain("BC0102");
output.ShouldNotContain("BC0103");
}
string FormatExpectedDiagOutput(string code, string severity)
{
string msbuildSeverity = severity.Equals("suggestion") ? "message" : severity;
return $"{msbuildSeverity} {code}: https://aka.ms/buildcheck/codes#{code}";
}
}
[Theory]
[InlineData("warning", "warning BC0101", new string[] { "error BC0101" })]
[InlineData("error", "error BC0101", new string[] { "warning BC0101" })]
[InlineData("suggestion", "BC0101", new string[] { "error BC0101", "warning BC0101" })]
[InlineData("default", "warning BC0101", new string[] { "error BC0101" })]
[InlineData("none", null, new string[] { "BC0101" })]
public void EditorConfig_SeverityAppliedCorrectly(string BC0101Severity, string? expectedOutputValues, string[] unexpectedOutputValues)
{
PrepareSampleProjectsAndConfig(true, out TransientTestFile projectFile, new List<(string, string)>() { ("BC0101", BC0101Severity) });
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -check",
out bool success, false, _env.Output, timeoutMilliseconds: 120_000);
if (BC0101Severity != "error")
{
success.ShouldBeTrue();
}
if (!string.IsNullOrEmpty(expectedOutputValues))
{
output.ShouldContain(expectedOutputValues!);
}
foreach (string unexpectedOutputValue in unexpectedOutputValues)
{
output.ShouldNotContain(unexpectedOutputValue);
}
}
[Fact(Skip = "https://github.com/dotnet/msbuild/issues/10702")]
public void CheckHasAccessToAllConfigs()
{
using (var env = TestEnvironment.Create())
{
string checkCandidatePath = Path.Combine(TestAssetsRootPath, "CheckCandidate");
string message = ": An extra message for the analyzer";
string severity = "warning";
// Can't use Transitive environment due to the need to dogfood local nuget packages.
AddCustomDataSourceToNugetConfig(checkCandidatePath);
string editorConfigName = Path.Combine(checkCandidatePath, EditorConfigFileName);
File.WriteAllText(editorConfigName, ReadEditorConfig(
new List<(string, string)>() { ("X01234", severity) },
new List<(string, (string, string))>
{
("X01234",("setMessage", message))
},
checkCandidatePath));
string projectCheckBuildLog = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.Combine(checkCandidatePath, $"CheckCandidate.csproj")} /m:1 -nr:False -restore -check -verbosity:n", out bool success, timeoutMilliseconds: 1200_0000);
success.ShouldBeTrue();
projectCheckBuildLog.ShouldContain("warning X01234");
projectCheckBuildLog.ShouldContain(severity + message);
// Cleanup
File.Delete(editorConfigName);
}
}
[Theory]
[InlineData(true, true)]
[InlineData(false, true)]
[InlineData(false, false)]
public void SampleCheckIntegrationTest_CheckOnBinaryLogReplay(bool buildInOutOfProcessNode, bool checkRequested)
{
PrepareSampleProjectsAndConfig(buildInOutOfProcessNode, out TransientTestFile projectFile, new List<(string, string)>() { ("BC0101", "warning") });
string? projectDirectory = Path.GetDirectoryName(projectFile.Path);
string logFile = _env.ExpectFile(".binlog").Path;
_ = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -bl:{logFile}",
out bool success, false, _env.Output, timeoutMilliseconds: 120_000);
success.ShouldBeTrue();
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{logFile} -flp:logfile={Path.Combine(projectDirectory!, "logFile.log")};verbosity=diagnostic {(checkRequested ? "-check" : string.Empty)}",
out success, false, _env.Output, timeoutMilliseconds: 120_000);
_env.Output.WriteLine(output);
success.ShouldBeTrue();
// The conflicting outputs warning appears - but only if check was requested
if (checkRequested)
{
output.ShouldContain("BC0101");
output.ShouldContain("BC0102");
output.ShouldContain("BC0103");
}
else
{
output.ShouldNotContain("BC0101");
output.ShouldNotContain("BC0102");
output.ShouldNotContain("BC0103");
}
}
[Theory]
[InlineData(null, new[] { "Property is derived from environment variable: 'TestFromTarget'.", "Property is derived from environment variable: 'TestFromEvaluation'." })]
[InlineData(true, new[] { "Property is derived from environment variable: 'TestFromTarget' with value: 'FromTarget'.", "Property is derived from environment variable: 'TestFromEvaluation' with value: 'FromEvaluation'." })]
[InlineData(false, new[] { "Property is derived from environment variable: 'TestFromTarget'.", "Property is derived from environment variable: 'TestFromEvaluation'." })]
public void NoEnvironmentVariableProperty_Test(bool? customConfigEnabled, string[] expectedMessages)
{
List<(string RuleId, (string ConfigKey, string Value) CustomConfig)>? customConfigData = null;
if (customConfigEnabled.HasValue)
{
customConfigData = new List<(string, (string, string))>()
{
("BC0103", ("allow_displaying_environment_variable_value", customConfigEnabled.Value ? "true" : "false")),
};
}
PrepareSampleProjectsAndConfig(
buildInOutOfProcessNode: true,
out TransientTestFile projectFile,
new List<(string, string)>() { ("BC0103", "error") },
customConfigData);
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -check", out bool success, false, _env.Output);
foreach (string expectedMessage in expectedMessages)
{
output.ShouldContain(expectedMessage);
}
}
[Theory]
[InlineData(EvaluationCheckScope.ProjectFileOnly)]
[InlineData(EvaluationCheckScope.WorkTreeImports)]
[InlineData(EvaluationCheckScope.All)]
public void NoEnvironmentVariableProperty_Scoping(EvaluationCheckScope scope)
{
List<(string RuleId, (string ConfigKey, string Value) CustomConfig)>? customConfigData = null;
string editorconfigScope = scope switch
{
EvaluationCheckScope.ProjectFileOnly => "project_file",
EvaluationCheckScope.WorkTreeImports => "work_tree_imports",
EvaluationCheckScope.All => "all",
_ => throw new ArgumentOutOfRangeException(nameof(scope), scope, null),
};
customConfigData = new List<(string, (string, string))>()
{
("BC0103", ("scope", editorconfigScope)),
};
PrepareSampleProjectsAndConfig(
buildInOutOfProcessNode: true,
out TransientTestFile projectFile,
new List<(string, string)>() { ("BC0103", "error") },
customConfigData);
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -check", out bool success, false, _env.Output);
if (scope == EvaluationCheckScope.ProjectFileOnly)
{
output.ShouldNotContain("Property is derived from environment variable: 'TestImported'. Properties should be passed explicitly using the /p option.");
}
else
{
output.ShouldContain("Property is derived from environment variable: 'TestImported'. Properties should be passed explicitly using the /p option.");
}
}
[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(false, true)]
public void NoEnvironmentVariableProperty_DeferredProcessing(bool warnAsError, bool warnAsMessage)
{
PrepareSampleProjectsAndConfig(
buildInOutOfProcessNode: true,
out TransientTestFile projectFile,
new List<(string, string)>() { ("BC0103", "warning") });
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -restore -check" +
(warnAsError ? " /p:warn2err=BC0103" : "") + (warnAsMessage ? " /p:warn2msg=BC0103" : ""), out bool success,
false, _env.Output);
success.ShouldBe(!warnAsError);
if (warnAsMessage)
{
output.ShouldNotContain("warning BC0103");
output.ShouldNotContain("error BC0103");
}
else if (warnAsError)
{
output.ShouldNotContain("warning BC0103");
output.ShouldContain("error BC0103");
}
else
{
output.ShouldContain("warning BC0103");
output.ShouldNotContain("error BC0103");
}
}
[Theory(Skip = "https://github.com/dotnet/msbuild/issues/10702")]
[InlineData("CheckCandidate", new[] { "CustomRule1", "CustomRule2" })]
[InlineData("CheckCandidateWithMultipleChecksInjected", new[] { "CustomRule1", "CustomRule2", "CustomRule3" }, true)]
public void CustomCheckTest_NoEditorConfig(string checkCandidate, string[] expectedRegisteredRules, bool expectedRejectedChecks = false)
{
using (var env = TestEnvironment.Create())
{
var checkCandidatePath = Path.Combine(TestAssetsRootPath, checkCandidate);
AddCustomDataSourceToNugetConfig(checkCandidatePath);
string projectCheckBuildLog = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.Combine(checkCandidatePath, $"{checkCandidate}.csproj")} /m:1 -nr:False -restore -check -verbosity:n",
out bool successBuild);
foreach (string registeredRule in expectedRegisteredRules)
{
projectCheckBuildLog.ShouldContain(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CustomCheckSuccessfulAcquisition", registeredRule));
}
if (!expectedRejectedChecks)
{
successBuild.ShouldBeTrue(projectCheckBuildLog);
}
else
{
projectCheckBuildLog.ShouldContain(ResourceUtilities.FormatResourceStringStripCodeAndKeyword(
"CustomCheckBaseTypeNotAssignable",
"InvalidCheck",
"InvalidCustomCheck, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"));
}
}
}
[Theory]
[InlineData("CheckCandidate", "X01234", "error", "error X01234: http://samplelink.com/X01234")]
[InlineData("CheckCandidateWithMultipleChecksInjected", "X01234", "warning", "warning X01234: http://samplelink.com/X01234")]
public void CustomCheckTest_WithEditorConfig(string checkCandidate, string ruleId, string severity, string expectedMessage)
{
using (var env = TestEnvironment.Create())
{
string checkCandidatePath = Path.Combine(TestAssetsRootPath, checkCandidate);
// Can't use Transitive environment due to the need to dogfood local nuget packages.
AddCustomDataSourceToNugetConfig(checkCandidatePath);
string editorConfigName = Path.Combine(checkCandidatePath, EditorConfigFileName);
File.WriteAllText(editorConfigName, ReadEditorConfig(
new List<(string, string)>() { (ruleId, severity) },
ruleToCustomConfig: null,
checkCandidatePath));
string projectCheckBuildLog = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.Combine(checkCandidatePath, $"{checkCandidate}.csproj")} /m:1 -nr:False -restore -check -verbosity:n", out bool _);
projectCheckBuildLog.ShouldContain(expectedMessage);
// Cleanup
File.Delete(editorConfigName);
}
}
[Theory]
[InlineData("X01236", "ErrorOnInitializeCheck", "Something went wrong initializing")]
[InlineData("X01237", "ErrorOnRegisteredAction", "something went wrong when executing registered action")]
[InlineData("X01238", "ErrorWhenRegisteringActions", "something went wrong when registering actions")]
public void CustomChecksFailGracefully(string ruleId, string friendlyName, string expectedMessage)
{
using (var env = TestEnvironment.Create())
{
string checkCandidate = "CheckCandidateWithMultipleChecksInjected";
string checkCandidatePath = Path.Combine(TestAssetsRootPath, checkCandidate);
// Can't use Transitive environment due to the need to dogfood local nuget packages.
AddCustomDataSourceToNugetConfig(checkCandidatePath);
string editorConfigName = Path.Combine(checkCandidatePath, EditorConfigFileName);
File.WriteAllText(editorConfigName, ReadEditorConfig(
new List<(string, string)>() { (ruleId, "warning") },
ruleToCustomConfig: null,
checkCandidatePath));
string projectCheckBuildLog = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.Combine(checkCandidatePath, $"{checkCandidate}.csproj")} /m:1 -nr:False -restore -check -verbosity:n", out bool success);
success.ShouldBeTrue();
projectCheckBuildLog.ShouldContain(expectedMessage);
projectCheckBuildLog.ShouldNotContain("This check should have been disabled");
projectCheckBuildLog.ShouldContain($"Dismounting check '{friendlyName}'");
// Cleanup
File.Delete(editorConfigName);
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void DoesNotRunOnRestore(bool buildInOutOfProcessNode)
{
PrepareSampleProjectsAndConfig(buildInOutOfProcessNode, out TransientTestFile projectFile, new List<(string, string)>() { ("BC0101", "warning") });
string output = RunnerUtilities.ExecBootstrapedMSBuild(
$"{Path.GetFileName(projectFile.Path)} /m:1 -nr:False -t:restore -check",
out bool success);
success.ShouldBeTrue();
output.ShouldNotContain("BC0101");
output.ShouldNotContain("BC0102");
output.ShouldNotContain("BC0103");
}
private void AddCustomDataSourceToNugetConfig(string checkCandidatePath)
{
var nugetTemplatePath = Path.Combine(checkCandidatePath, "nugetTemplate.config");
var doc = new XmlDocument();
doc.LoadXml(File.ReadAllText(nugetTemplatePath));
if (doc.DocumentElement != null)
{
XmlNode? packageSourcesNode = doc.SelectSingleNode("//packageSources");
// The test packages are generated during the test project build and saved in CustomChecks folder.
string checksPackagesPath = Path.Combine(Directory.GetParent(AssemblyLocation)?.Parent?.FullName ?? string.Empty, "CustomChecks");
AddPackageSource(doc, packageSourcesNode, "Key", checksPackagesPath);
doc.Save(Path.Combine(checkCandidatePath, "nuget.config"));
}
}
private void AddPackageSource(XmlDocument doc, XmlNode? packageSourcesNode, string key, string value)
{
if (packageSourcesNode != null)
{
XmlElement addNode = doc.CreateElement("add");
PopulateXmlAttribute(doc, addNode, "key", key);
PopulateXmlAttribute(doc, addNode, "value", value);
packageSourcesNode.AppendChild(addNode);
}
}
private void PopulateXmlAttribute(XmlDocument doc, XmlNode node, string attributeName, string attributeValue)
{
node.ShouldNotBeNull($"The attribute {attributeName} can not be populated with {attributeValue}. Xml node is null.");
var attribute = doc.CreateAttribute(attributeName);
attribute.Value = attributeValue;
node.Attributes!.Append(attribute);
}
private void PrepareSampleProjectsAndConfig(
bool buildInOutOfProcessNode,
out TransientTestFile projectFile,
out TransientTestFile editorconfigFile,
string entryProjectAssetName,
IEnumerable<string>? supplementalAssetNames = null,
IEnumerable<(string RuleId, string Severity)>? ruleToSeverity = null,
IEnumerable<(string RuleId, (string ConfigKey, string Value) CustomConfig)>? ruleToCustomConfig = null)
{
string testAssetsFolderName = "SampleCheckIntegrationTest";
TransientTestFolder workFolder = _env.CreateFolder(createFolder: true);
TransientTestFile testFile = _env.CreateFile(workFolder, "somefile");
string contents = ReadAndAdjustProjectContent(entryProjectAssetName);
projectFile = _env.CreateFile(workFolder, entryProjectAssetName, contents);
foreach (string supplementalAssetName in supplementalAssetNames ?? Enumerable.Empty<string>())
{
string supplementalContent = ReadAndAdjustProjectContent(supplementalAssetName);
TransientTestFile supplementalFile = _env.CreateFile(workFolder, supplementalAssetName, supplementalContent);
}
editorconfigFile = _env.CreateFile(workFolder, ".editorconfig", ReadEditorConfig(ruleToSeverity, ruleToCustomConfig, testAssetsFolderName));
// OSX links /var into /private, which makes Path.GetTempPath() return "/var..." but Directory.GetCurrentDirectory return "/private/var...".
// This discrepancy breaks path equality checks in MSBuild checks if we pass to MSBuild full path to the initial project.
// See if there is a way of fixing it in the engine - tracked: https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=55702688.
_env.SetCurrentDirectory(Path.GetDirectoryName(projectFile.Path));
_env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", buildInOutOfProcessNode ? "1" : "0");
_env.SetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION", "1");
// Needed for testing check BC0103
_env.SetEnvironmentVariable("TestFromTarget", "FromTarget");
_env.SetEnvironmentVariable("TestFromEvaluation", "FromEvaluation");
_env.SetEnvironmentVariable("TestImported", "FromEnv");
string ReadAndAdjustProjectContent(string fileName) =>
File.ReadAllText(Path.Combine(TestAssetsRootPath, testAssetsFolderName, fileName))
.Replace("TestFilePath", testFile.Path)
.Replace("WorkFolderPath", workFolder.Path);
}
private void PrepareSampleProjectsAndConfig(
bool buildInOutOfProcessNode,
out TransientTestFile projectFile,
IEnumerable<(string RuleId, string Severity)>? ruleToSeverity,
IEnumerable<(string RuleId, (string ConfigKey, string Value) CustomConfig)>? ruleToCustomConfig = null)
=> PrepareSampleProjectsAndConfig(
buildInOutOfProcessNode,
out projectFile,
out _,
"Project1.csproj",
new[] { "Project2.csproj", "ImportedFile1.props" },
ruleToSeverity,
ruleToCustomConfig);
private string ReadEditorConfig(
IEnumerable<(string RuleId, string Severity)>? ruleToSeverity,
IEnumerable<(string RuleId, (string ConfigKey, string Value) CustomConfig)>? ruleToCustomConfig,
string testAssetsFolderName)
{
string configContent = File.ReadAllText(Path.Combine(TestAssetsRootPath, testAssetsFolderName, $"{EditorConfigFileName}test"));
PopulateRuleToSeverity(ruleToSeverity, ref configContent);
PopulateRuleToCustomConfig(ruleToCustomConfig, ref configContent);
return configContent;
}
private void PopulateRuleToSeverity(IEnumerable<(string RuleId, string Severity)>? ruleToSeverity, ref string configContent)
{
if (ruleToSeverity != null && ruleToSeverity.Any())
{
foreach (var rule in ruleToSeverity)
{
configContent = configContent.Replace($"build_check.{rule.RuleId}.Severity={rule.RuleId}Severity", $"build_check.{rule.RuleId}.Severity={rule.Severity}");
}
}
}
private void PopulateRuleToCustomConfig(IEnumerable<(string RuleId, (string ConfigKey, string Value) CustomConfig)>? ruleToCustomConfig, ref string configContent)
{
if (ruleToCustomConfig != null && ruleToCustomConfig.Any())
{
foreach (var rule in ruleToCustomConfig)
{
configContent = configContent.Replace($"build_check.{rule.RuleId}.CustomConfig=dummy", $"build_check.{rule.RuleId}.{rule.CustomConfig.ConfigKey}={rule.CustomConfig.Value}");
}
}
}
}
|