From 1c3b240ce7417223672c62862a6ff7e884e6997a Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Tue, 7 May 2024 18:57:46 +0200 Subject: [PATCH] Add e2e tests for custom analyzer rules (#10076) --- .../Evaluation/Expander_Tests.cs | 4 +- .../BuildCheckConnectorLogger.cs | 42 ++++---- src/Build/Evaluation/Expander.cs | 6 +- src/Build/Evaluation/IntrinsicFunctions.cs | 2 +- src/BuildCheck.UnitTests/EndToEndTests.cs | 96 ++++++++++++++++++- ...icrosoft.Build.BuildCheck.UnitTests.csproj | 4 + .../AnalysisCandidate.csproj | 18 ++++ .../AnalysisCandidate/nugetTemplate.config | 6 ++ ...didateWithMultipleAnalyzersInjected.csproj | 19 ++++ .../nugetTemplate.config | 6 ++ .../TestAssets/CustomAnalyzer/Analyzer1.cs | 38 ++++++++ .../TestAssets/CustomAnalyzer/Analyzer2.cs | 38 ++++++++ .../CustomAnalyzer/CustomAnalyzer.csproj | 29 ++++++ .../CustomAnalyzer/CustomAnalyzer.props | 6 ++ .../TestAssets/CustomAnalyzer2/Analyzer3.cs | 38 ++++++++ .../CustomAnalyzer2/CustomAnalyzer2.csproj | 28 ++++++ .../CustomAnalyzer2/CustomAnalyzer2.props | 6 ++ src/UnitTests.Shared/RunnerUtilities.cs | 28 ++++-- .../Company.AnalyzerTemplate.csproj | 4 +- .../Company.AnalyzerTemplate.props | 2 +- 20 files changed, 378 insertions(+), 42 deletions(-) create mode 100644 src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/AnalysisCandidate.csproj create mode 100644 src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/nugetTemplate.config create mode 100644 src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/AnalysisCandidateWithMultipleAnalyzersInjected.csproj create mode 100644 src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/nugetTemplate.config create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer1.cs create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer2.cs create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.csproj create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.props create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/Analyzer3.cs create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.csproj create mode 100644 src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.props diff --git a/src/Build.UnitTests/Evaluation/Expander_Tests.cs b/src/Build.UnitTests/Evaluation/Expander_Tests.cs index 90f06cf86c7..f47f94fd217 100644 --- a/src/Build.UnitTests/Evaluation/Expander_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Expander_Tests.cs @@ -5071,7 +5071,7 @@ private static bool ICUModeAvailable() } [Fact] - public void PropertyFunctionRegisterAnalyzer() + public void PropertyFunctionRegisterBuildCheck() { using (var env = TestEnvironment.Create()) { @@ -5084,7 +5084,7 @@ public void PropertyFunctionRegisterAnalyzer() var dummyAssemblyFile = env.CreateFile(env.CreateFolder(), "test.dll"); var result = new Expander(new PropertyDictionary(), FileSystems.Default) - .ExpandIntoStringLeaveEscaped($"$([MSBuild]::RegisterAnalyzer({dummyAssemblyFile.Path}))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance, loggingContext); + .ExpandIntoStringLeaveEscaped($"$([MSBuild]::RegisterBuildCheck({dummyAssemblyFile.Path}))", ExpanderOptions.ExpandProperties, MockElementLocation.Instance, loggingContext); result.ShouldBe(Boolean.TrueString); _ = logger.AllBuildEvents.Select(be => be.ShouldBeOfType()); diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index 14f16e691f0..867a50b5b04 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -9,7 +9,6 @@ using Microsoft.Build.BuildCheck.Utilities; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; -using static Microsoft.Build.BuildCheck.Infrastructure.BuildCheckManagerProvider; namespace Microsoft.Build.BuildCheck.Infrastructure; @@ -67,6 +66,14 @@ private void HandleProjectEvaluationStartedEvent(ProjectEvaluationStartedEventAr } } + private void HandleBuildCheckTracingEvent(BuildCheckTracingEventArgs eventArgs) + { + if (!eventArgs.IsAggregatedGlobalReport) + { + _stats.Merge(eventArgs.TracingData, (span1, span2) => span1 + span2); + } + } + private bool IsMetaProjFile(string? projectFile) => !string.IsNullOrEmpty(projectFile) && projectFile!.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase); private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) @@ -81,19 +88,12 @@ private void EventSource_AnyEventRaised(object sender, BuildEventArgs e) private void EventSource_BuildFinished(object sender, BuildFinishedEventArgs e) { - BuildEventContext buildEventContext = e.BuildEventContext - ?? new BuildEventContext( - BuildEventContext.InvalidNodeId, - BuildEventContext.InvalidTargetId, - BuildEventContext.InvalidProjectContextId, - BuildEventContext.InvalidTaskId); - - LoggingContext loggingContext = _loggingContextFactory.CreateLoggingContext(buildEventContext); + LoggingContext loggingContext = _loggingContextFactory.CreateLoggingContext(GetBuildEventContext(e)); _stats.Merge(_buildCheckManager.CreateAnalyzerTracingStats(), (span1, span2) => span1 + span2); LogAnalyzerStats(loggingContext); } - + private void LogAnalyzerStats(LoggingContext loggingContext) { Dictionary infraStats = new Dictionary(); @@ -131,18 +131,18 @@ private string BuildCsvString(string title, Dictionary rowData private Dictionary> GetBuildEventHandlers() => new() { - { typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs) e) }, - { typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs) e) }, + { typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs)e) }, + { typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs)e) }, { typeof(ProjectStartedEventArgs), (BuildEventArgs e) => _buildCheckManager.StartProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) }, { typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => _buildCheckManager.EndProjectRequest(BuildCheckDataSource.EventArgs, e.BuildEventContext!) }, - { typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => - { - if(!((BuildCheckTracingEventArgs)e).IsAggregatedGlobalReport) - { - _stats.Merge(((BuildCheckTracingEventArgs)e).TracingData, (span1, span2) => span1 + span2); - } - } - }, - { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData(), e.BuildEventContext!) }, + { typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => HandleBuildCheckTracingEvent((BuildCheckTracingEventArgs)e) }, + { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => _buildCheckManager.ProcessAnalyzerAcquisition(((BuildCheckAcquisitionEventArgs)e).ToAnalyzerAcquisitionData(), GetBuildEventContext(e)) }, }; + + private BuildEventContext GetBuildEventContext(BuildEventArgs e) => e.BuildEventContext + ?? new BuildEventContext( + BuildEventContext.InvalidNodeId, + BuildEventContext.InvalidTargetId, + BuildEventContext.InvalidProjectContextId, + BuildEventContext.InvalidTaskId); } diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 84d485b7c59..e67785390a4 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -3917,12 +3917,12 @@ private bool TryExecuteWellKnownFunction(out object returnVal, object objectInst } else if (_receiverType == typeof(IntrinsicFunctions)) { - if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.RegisterAnalyzer), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.RegisterBuildCheck), StringComparison.OrdinalIgnoreCase)) { - ErrorUtilities.VerifyThrow(_loggingContext != null, $"The logging context is missed. {nameof(IntrinsicFunctions.RegisterAnalyzer)} can not be invoked."); + ErrorUtilities.VerifyThrow(_loggingContext != null, $"The logging context is missed. {nameof(IntrinsicFunctions.RegisterBuildCheck)} can not be invoked."); if (TryGetArg(args, out string arg0)) { - returnVal = IntrinsicFunctions.RegisterAnalyzer(arg0, _loggingContext); + returnVal = IntrinsicFunctions.RegisterBuildCheck(arg0, _loggingContext); return true; } } diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 944478d4be6..17e67ce123a 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -697,7 +697,7 @@ public static string GetMSBuildExtensionsPath() public static bool IsRunningFromVisualStudio() => BuildEnvironmentHelper.Instance.Mode == BuildEnvironmentMode.VisualStudio; - public static bool RegisterAnalyzer(string pathToAssembly, LoggingContext loggingContext) + public static bool RegisterBuildCheck(string pathToAssembly, LoggingContext loggingContext) { pathToAssembly = FileUtilities.GetFullPathNoThrow(pathToAssembly); if (File.Exists(pathToAssembly)) diff --git a/src/BuildCheck.UnitTests/EndToEndTests.cs b/src/BuildCheck.UnitTests/EndToEndTests.cs index 9351612060f..cc2aa1ae612 100644 --- a/src/BuildCheck.UnitTests/EndToEndTests.cs +++ b/src/BuildCheck.UnitTests/EndToEndTests.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Xml; using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.Shared; +using Newtonsoft.Json.Linq; using Shouldly; using Xunit; using Xunit.Abstractions; @@ -19,6 +17,7 @@ namespace Microsoft.Build.BuildCheck.UnitTests; public class EndToEndTests : IDisposable { private readonly TestEnvironment _env; + public EndToEndTests(ITestOutputHelper output) { _env = TestEnvironment.Create(output); @@ -27,6 +26,8 @@ public EndToEndTests(ITestOutputHelper output) _env.WithEnvironmentInvariant(); } + private static string TestAssetsRootPath { get; } = Path.Combine(Path.GetDirectoryName(typeof(EndToEndTests).Assembly.Location) ?? AppContext.BaseDirectory, "TestAssets"); + public void Dispose() => _env.Dispose(); [Theory] @@ -91,7 +92,6 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool ana // var cache = new SimpleProjectRootElementCache(); // ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile.Path, /*unused*/null, /*unused*/null, cache, false /*Not explicitly loaded - unused*/); - TransientTestFile config = _env.CreateFile(workFolder, "editorconfig.json", /*lang=json,strict*/ """ @@ -134,4 +134,90 @@ public void SampleAnalyzerIntegrationTest(bool buildInOutOfProcessNode, bool ana output.ShouldNotContain("BC0101"); } } + + [Theory] + [InlineData(new[] { "CustomAnalyzer" }, "AnalysisCandidate", new[] { "CustomRule1", "CustomRule2" })] + [InlineData(new[] { "CustomAnalyzer", "CustomAnalyzer2" }, "AnalysisCandidateWithMultipleAnalyzersInjected", new[] { "CustomRule1", "CustomRule2", "CustomRule3" })] + public void CustomAnalyzerTest(string[] customAnalyzerNames, string analysisCandidate, string[] expectedRegisteredRules) + { + using (var env = TestEnvironment.Create()) + { + var candidatesNugetFullPaths = BuildAnalyzerRules(env, customAnalyzerNames); + + candidatesNugetFullPaths.ShouldNotBeEmpty("Nuget package with custom analyzer was not generated or detected."); + + var analysisCandidatePath = Path.Combine(TestAssetsRootPath, analysisCandidate); + AddCustomDataSourceToNugetConfig(analysisCandidatePath, candidatesNugetFullPaths); + + string projectAnalysisBuildLog = RunnerUtilities.ExecBootstrapedMSBuild( + $"{Path.Combine(analysisCandidatePath, $"{analysisCandidate}.csproj")} /m:1 -nr:False -restore /p:OutputPath={env.CreateFolder().Path} -analyze -verbosity:d", + out bool successBuild); + successBuild.ShouldBeTrue(); + + foreach (string expectedRegisteredRule in expectedRegisteredRules) + { + projectAnalysisBuildLog.ShouldContain($"Custom analyzer rule: {expectedRegisteredRule} has been registered successfully."); + } + } + } + + private IList BuildAnalyzerRules(TestEnvironment env, string[] customAnalyzerNames) + { + var candidatesNugetFullPaths = new List(); + + foreach (var customAnalyzerName in customAnalyzerNames) + { + var candidateAnalysisProjectPath = Path.Combine(TestAssetsRootPath, customAnalyzerName, $"{customAnalyzerName}.csproj"); + var nugetPackResults = RunnerUtilities.ExecBootstrapedMSBuild( + $"{candidateAnalysisProjectPath} /m:1 -nr:False -restore /p:OutputPath={env.CreateFolder().Path} -getTargetResult:Build", out bool success, attachProcessId: false); + + success.ShouldBeTrue(); + + string? candidatesNugetPackageFullPath = (string?)(JObject.Parse(nugetPackResults)?["TargetResults"]?["Build"]?["Items"]?[0]?["RelativeDir"] ?? string.Empty); + + candidatesNugetPackageFullPath.ShouldNotBeNull(); + candidatesNugetFullPaths.Add(candidatesNugetPackageFullPath); + } + + return candidatesNugetFullPaths; + } + + private void AddCustomDataSourceToNugetConfig(string analysisCandidatePath, IList candidatesNugetPackageFullPaths) + { + var nugetTemplatePath = Path.Combine(analysisCandidatePath, "nugetTemplate.config"); + + var doc = new XmlDocument(); + doc.LoadXml(File.ReadAllText(nugetTemplatePath)); + if (doc.DocumentElement != null) + { + XmlNode? packageSourcesNode = doc.SelectSingleNode("//packageSources"); + for (int i = 0; i < candidatesNugetPackageFullPaths.Count; i++) + { + AddPackageSource(doc, packageSourcesNode, $"Key{i}", Path.GetDirectoryName(candidatesNugetPackageFullPaths[i]) ?? string.Empty); + } + + doc.Save(Path.Combine(analysisCandidatePath, "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); + } } diff --git a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj index 3aa9eaff7d1..ada169a4b49 100644 --- a/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj +++ b/src/BuildCheck.UnitTests/Microsoft.Build.BuildCheck.UnitTests.csproj @@ -33,5 +33,9 @@ PreserveNewest + + PreserveNewest + + diff --git a/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/AnalysisCandidate.csproj b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/AnalysisCandidate.csproj new file mode 100644 index 00000000000..52f65afffee --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/AnalysisCandidate.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + + + + + + + + + PreserveNewest + + + + diff --git a/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/nugetTemplate.config b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/nugetTemplate.config new file mode 100644 index 00000000000..1097d29bafd --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidate/nugetTemplate.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/AnalysisCandidateWithMultipleAnalyzersInjected.csproj b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/AnalysisCandidateWithMultipleAnalyzersInjected.csproj new file mode 100644 index 00000000000..9e71d7ff38f --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/AnalysisCandidateWithMultipleAnalyzersInjected.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + + + + + + + + + + PreserveNewest + + + + diff --git a/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/nugetTemplate.config b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/nugetTemplate.config new file mode 100644 index 00000000000..1097d29bafd --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/AnalysisCandidateWithMultipleAnalyzersInjected/nugetTemplate.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer1.cs b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer1.cs new file mode 100644 index 00000000000..5cd1d3317c0 --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer1.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental.BuildCheck; + +namespace CustomAnalyzer +{ + public sealed class Analyzer1 : BuildAnalyzer + { + public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule( + "X01234", + "Title", + "Description", + "Message format: {0}", + new BuildAnalyzerConfiguration()); + + public override string FriendlyName => "CustomRule1"; + + public override IReadOnlyList SupportedRules { get; } = new List() { SupportedRule }; + + public override void Initialize(ConfigurationContext configurationContext) + { + // configurationContext to be used only if analyzer needs external configuration data. + } + + public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) + { + registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); + } + + private void EvaluatedPropertiesAction(BuildCheckDataContext context) + { + context.ReportResult(BuildCheckResult.Create( + SupportedRule, + ElementLocation.EmptyLocation, + "Argument for the message format")); + } + } +} diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer2.cs b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer2.cs new file mode 100644 index 00000000000..714a82ae95a --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/Analyzer2.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental.BuildCheck; + +namespace CustomAnalyzer +{ + public sealed class Analyzer2 : BuildAnalyzer + { + public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule( + "X01235", + "Title", + "Description", + "Message format: {0}", + new BuildAnalyzerConfiguration()); + + public override string FriendlyName => "CustomRule2"; + + public override IReadOnlyList SupportedRules { get; } = new List() { SupportedRule }; + + public override void Initialize(ConfigurationContext configurationContext) + { + // configurationContext to be used only if analyzer needs external configuration data. + } + + public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) + { + registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); + } + + private void EvaluatedPropertiesAction(BuildCheckDataContext context) + { + context.ReportResult(BuildCheckResult.Create( + SupportedRule, + ElementLocation.EmptyLocation, + "Argument for the message format")); + } + } +} diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.csproj b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.csproj new file mode 100644 index 00000000000..f780e9eb213 --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + True + false + + NU5101;NU5128;MSB3277 + + + + + + + + + + $(MSBuildProjectDirectory)\..\..\Microsoft.Build.dll + + + + + + + + + + + diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.props b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.props new file mode 100644 index 00000000000..31a9526dd62 --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer/CustomAnalyzer.props @@ -0,0 +1,6 @@ + + + + $([MSBuild]::RegisterBuildCheck($(MSBuildThisFileDirectory)CustomAnalyzer.dll)) + + diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/Analyzer3.cs b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/Analyzer3.cs new file mode 100644 index 00000000000..c0272937c87 --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/Analyzer3.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Microsoft.Build.Construction; +using Microsoft.Build.Experimental.BuildCheck; + +namespace CustomAnalyzer2 +{ + public sealed class Analyzer3 : BuildAnalyzer + { + public static BuildAnalyzerRule SupportedRule = new BuildAnalyzerRule( + "X01235", + "Title", + "Description", + "Message format: {0}", + new BuildAnalyzerConfiguration()); + + public override string FriendlyName => "CustomRule3"; + + public override IReadOnlyList SupportedRules { get; } = new List() { SupportedRule }; + + public override void Initialize(ConfigurationContext configurationContext) + { + // configurationContext to be used only if analyzer needs external configuration data. + } + + public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) + { + registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction); + } + + private void EvaluatedPropertiesAction(BuildCheckDataContext context) + { + context.ReportResult(BuildCheckResult.Create( + SupportedRule, + ElementLocation.EmptyLocation, + "Argument for the message format")); + } + } +} diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.csproj b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.csproj new file mode 100644 index 00000000000..17007b03785 --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + True + + NU5101;NU5128;MSB3277 + + + + + + + + + + $(MSBuildProjectDirectory)\..\..\Microsoft.Build.dll + + + + + + + + + + + diff --git a/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.props b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.props new file mode 100644 index 00000000000..869000fa12f --- /dev/null +++ b/src/BuildCheck.UnitTests/TestAssets/CustomAnalyzer2/CustomAnalyzer2.props @@ -0,0 +1,6 @@ + + + + $([MSBuild]::RegisterBuildCheck($(MSBuildThisFileDirectory)CustomAnalyzer2.dll)) + + diff --git a/src/UnitTests.Shared/RunnerUtilities.cs b/src/UnitTests.Shared/RunnerUtilities.cs index a61f1a9fb4c..6b8354dc0da 100644 --- a/src/UnitTests.Shared/RunnerUtilities.cs +++ b/src/UnitTests.Shared/RunnerUtilities.cs @@ -52,7 +52,12 @@ public static string ExecMSBuild(string pathToMsBuildExe, string msbuildParamete return RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper); } - public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper outputHelper = null) + public static string ExecBootstrapedMSBuild( + string msbuildParameters, + out bool successfulExit, + bool shellExecute = false, + ITestOutputHelper outputHelper = null, + bool attachProcessId = true) { BootstrapLocationAttribute attribute = Assembly.GetExecutingAssembly().GetCustomAttribute() ?? throw new InvalidOperationException("This test assembly does not have the BootstrapLocationAttribute"); @@ -64,7 +69,7 @@ public static string ExecBootstrapedMSBuild(string msbuildParameters, out bool s #else string pathToExecutable = Path.Combine(binaryFolder, "MSBuild.exe"); #endif - return RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper); + return RunProcessAndGetOutput(pathToExecutable, msbuildParameters, out successfulExit, shellExecute, outputHelper, attachProcessId); } private static void AdjustForShellExecution(ref string pathToExecutable, ref string arguments) @@ -84,9 +89,15 @@ private static void AdjustForShellExecution(ref string pathToExecutable, ref str } /// - /// Run the process and get stdout and stderr + /// Run the process and get stdout and stderr. /// - public static string RunProcessAndGetOutput(string process, string parameters, out bool successfulExit, bool shellExecute = false, ITestOutputHelper outputHelper = null) + public static string RunProcessAndGetOutput( + string process, + string parameters, + out bool successfulExit, + bool shellExecute = false, + ITestOutputHelper outputHelper = null, + bool attachProcessId = true) { if (shellExecute) { @@ -148,10 +159,13 @@ public static string RunProcessAndGetOutput(string process, string parameters, o successfulExit = p.ExitCode == 0; } - WriteOutput("Process ID is " + pid + "\r\n"); - WriteOutput("=============="); + if (attachProcessId) + { + output += "Process ID is " + pid + "\r\n"; + WriteOutput("Process ID is " + pid + "\r\n"); + WriteOutput("=============="); + } - output += "Process ID is " + pid + "\r\n"; return output; void WriteOutput(string data) diff --git a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj index 0a1b8f974fc..33d8c992326 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj +++ b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.csproj @@ -36,10 +36,10 @@ - + - + diff --git a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.props b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.props index 3b752b831cc..5a606b3cac6 100644 --- a/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.props +++ b/template_feed/Microsoft.AnalyzerTemplate/Company.AnalyzerTemplate.props @@ -1,7 +1,7 @@ - $([MSBuild]::RegisterAnalyzer($(MSBuildThisFileDirectory)..\lib\Company.AnalyzerTemplate.dll)) + $([MSBuild]::RegisterBuildCheck($(MSBuildThisFileDirectory)Company.AnalyzerTemplate.dll))