diff --git a/src/MSBuildProjectCreator.UnitTests/BuildEngineTests.cs b/src/MSBuildProjectCreator.UnitTests/BuildEngineTests.cs new file mode 100644 index 0000000..844aaf1 --- /dev/null +++ b/src/MSBuildProjectCreator.UnitTests/BuildEngineTests.cs @@ -0,0 +1,103 @@ +// Copyright (c) Jeff Kluge. All rights reserved. +// +// Licensed under the MIT license. + +using Microsoft.Build.Framework; +using Shouldly; +using System; +using Xunit; + +namespace Microsoft.Build.Utilities.ProjectCreation.UnitTests +{ + public class BuildEngineTests : MSBuildTestBase + { + [Fact] + public void ConsoleLog() + { + BuildEngine buildEngine = GetBuildEngineWithEvents(i => + { + i.LogErrorEvent(new BuildErrorEventArgs(null, "A6DAB901460D483FBDF3A0980B14C46F", "48F7F352E2914991827100BCEB69331F", 3, 4, 0, 0, "D988473FF8634A16A0CD8FE94FF20D53", null, null)); + i.LogWarningEvent(new BuildWarningEventArgs(null, "AE1B25881A694A70B2EA299C04625596", "07006F38A63E420AAB4124EBE58081BC", 1, 2, 0, 0, "3A3DD4A40DA44BA5BBB123E105EE1F71", null, null)); + i.LogMessageEvent(new BuildMessageEventArgs("61BD637C7D704D4B98C25805E3111152", null, null, MessageImportance.High)); + i.LogMessageEvent(new BuildMessageEventArgs("B02496FA4D3348A6997DC918EBF7455B", null, null, MessageImportance.Normal)); + i.LogMessageEvent(new BuildMessageEventArgs("2C254C4346A347AE94AE5E7FB6C03B0C", null, null, MessageImportance.Low)); + }); + + buildEngine.GetConsoleLog() + .ShouldBe( + @"48F7F352E2914991827100BCEB69331F(3,4): error A6DAB901460D483FBDF3A0980B14C46F: D988473FF8634A16A0CD8FE94FF20D53 +07006F38A63E420AAB4124EBE58081BC(1,2): warning AE1B25881A694A70B2EA299C04625596: 3A3DD4A40DA44BA5BBB123E105EE1F71 +61BD637C7D704D4B98C25805E3111152 +B02496FA4D3348A6997DC918EBF7455B +", + StringCompareShould.IgnoreLineEndings); + } + + [Theory] + [InlineData("6E1BF8E271E345BC892FA348132C02A9", "28C465226FCF47498C3C728FA8DEC0AB")] + public void Errors(string expectedMessage, string expectedCode) + { + BuildErrorEventArgs args = GetBuildEngineWithEvents(buildEngine => buildEngine.LogErrorEvent(new BuildErrorEventArgs(null, expectedCode, null, 0, 0, 0, 0, expectedMessage, null, null))) + .ErrorEvents + .ShouldHaveSingleItem(); + + args.Message.ShouldBe(expectedMessage); + + args.Code.ShouldBe(expectedCode); + } + + [Theory] + [InlineData("High")] + [InlineData("Normal")] + [InlineData("Low")] + public void MessagesByImportance(string value) + { + const string expectedMessage = "D2C36BFAE11847CE95B4135775E0156F"; + + MessageImportance importance = (MessageImportance)Enum.Parse(typeof(MessageImportance), value); + + BuildEngine buildEngine = GetBuildEngineWithEvents(i => i.LogMessageEvent(new BuildMessageEventArgs(expectedMessage, null, null, importance))); + + string actualItem = buildEngine.Messages.ShouldHaveSingleItem(); + + actualItem.ShouldBe(expectedMessage); + + switch (importance) + { + case MessageImportance.High: + buildEngine.Messages.High.ShouldHaveSingleItem().ShouldBe(actualItem); + break; + + case MessageImportance.Normal: + buildEngine.Messages.Normal.ShouldHaveSingleItem().ShouldBe(actualItem); + break; + + case MessageImportance.Low: + buildEngine.Messages.Low.ShouldHaveSingleItem().ShouldBe(actualItem); + break; + } + } + + [Theory] + [InlineData("87647890B61644A1B8CCE28EA7F2CD67", "1684AC30BB494C86AAFE67CD081547F9")] + public void Warnings(string expectedMessage, string expectedCode) + { + BuildWarningEventArgs args = GetBuildEngineWithEvents(buildEngine => buildEngine.LogWarningEvent(new BuildWarningEventArgs(null, expectedCode, null, 0, 0, 0, 0, expectedMessage, null, null))) + .WarningEvents + .ShouldHaveSingleItem(); + + args.Message.ShouldBe(expectedMessage); + + args.Code.ShouldBe(expectedCode); + } + + private BuildEngine GetBuildEngineWithEvents(Action action) + { + BuildEngine buildEngine = BuildEngine.Create(); + + action(buildEngine); + + return buildEngine; + } + } +} \ No newline at end of file diff --git a/src/MSBuildProjectCreator.UnitTests/BuildOutputTests.cs b/src/MSBuildProjectCreator.UnitTests/BuildOutputTests.cs index d9c98f3..445c346 100644 --- a/src/MSBuildProjectCreator.UnitTests/BuildOutputTests.cs +++ b/src/MSBuildProjectCreator.UnitTests/BuildOutputTests.cs @@ -50,16 +50,18 @@ public void Errors(string expectedMessage, string expectedCode) } [Theory] - [InlineData(MessageImportance.High)] - [InlineData(MessageImportance.Normal)] - [InlineData(MessageImportance.Low)] - public void MessagesByImportance(MessageImportance importance) + [InlineData("High")] + [InlineData("Normal")] + [InlineData("Low")] + public void MessagesByImportance(string value) { const string expectedMessage = "A7E9F67E46A64181B25DC136A786F480"; + MessageImportance importance = (MessageImportance)Enum.Parse(typeof(MessageImportance), value); + BuildOutput buildOutput = GetProjectLoggerWithEvents(eventSource => { eventSource.OnMessageRaised(expectedMessage, importance); }); - var actualItem = buildOutput.Messages.ShouldHaveSingleItem(); + string actualItem = buildOutput.Messages.ShouldHaveSingleItem(); actualItem.ShouldBe(expectedMessage); diff --git a/src/MSBuildProjectCreator/BuildEngine.cs b/src/MSBuildProjectCreator/BuildEngine.cs new file mode 100644 index 0000000..deb4f4f --- /dev/null +++ b/src/MSBuildProjectCreator/BuildEngine.cs @@ -0,0 +1,59 @@ +// Copyright (c) Jeff Kluge. All rights reserved. +// +// Licensed under the MIT license. + +using Microsoft.Build.Framework; +using System; +using System.Collections; + +namespace Microsoft.Build.Utilities.ProjectCreation +{ + /// + /// Represents an implementation of that allows for capturing logged events in tasks. + /// + public sealed class BuildEngine : BuildEventArgsCollection, IBuildEngine + { + private BuildEngine() + { + } + + /// + public int ColumnNumberOfTaskNode => 0; + + /// + public bool ContinueOnError => false; + + /// + public int LineNumberOfTaskNode => 0; + + /// + public string ProjectFileOfTaskNode => null; + + /// + /// Creates an instance of the class. + /// + /// A instance. + public static BuildEngine Create() + { + return new BuildEngine(); + } + + /// + public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + { + throw new NotSupportedException(); + } + + /// + public void LogCustomEvent(CustomBuildEventArgs e) => Add(e); + + /// + public void LogErrorEvent(BuildErrorEventArgs e) => Add(e); + + /// + public void LogMessageEvent(BuildMessageEventArgs e) => Add(e); + + /// + public void LogWarningEvent(BuildWarningEventArgs e) => Add(e); + } +} \ No newline at end of file diff --git a/src/MSBuildProjectCreator/BuildEventArgsCollection.cs b/src/MSBuildProjectCreator/BuildEventArgsCollection.cs new file mode 100644 index 0000000..167277c --- /dev/null +++ b/src/MSBuildProjectCreator/BuildEventArgsCollection.cs @@ -0,0 +1,185 @@ +// Copyright (c) Jeff Kluge. All rights reserved. +// +// Licensed under the MIT license. + +using Microsoft.Build.Framework; +using Microsoft.Build.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Build.Utilities.ProjectCreation +{ + /// + /// Represents a collection of objects. + /// + public abstract class BuildEventArgsCollection : IDisposable + { + /// + /// Stores the errors that were logged. + /// + private readonly List _errorEvents = new List(); + + /// + /// Stores the messages that were logged. + /// + private readonly List _messageEvents = new List(50); + + /// + /// Stores the warnings that were logged. + /// + private readonly List _warningEvents = new List(); + + /// + /// Stores all build events. + /// + private ConcurrentQueue _allEvents = new ConcurrentQueue(); + + /// + /// Initializes a new instance of the class. + /// + protected BuildEventArgsCollection() + { + MessageEvents = new BuildMessageEventArgsCollection(_messageEvents); + Messages = new BuildMessageCollection(this); + } + + /// + /// Gets all events that were logged. + /// + public IReadOnlyCollection AllEvents => _allEvents; + + /// + /// Gets the error events that were logged. + /// + public IReadOnlyCollection ErrorEvents => _errorEvents; + + /// + /// Gets the error messages that were logged. + /// + public IReadOnlyCollection Errors => _errorEvents.Select(i => i.Message).ToList(); + + /// + /// Gets the messages that were logged. + /// + public BuildMessageEventArgsCollection MessageEvents { get; } + + /// + /// Gets a object that gets the messages from the build. + /// + public BuildMessageCollection Messages { get; } + + /// + /// Gets the warning events that were logged. + /// + public IReadOnlyCollection WarningEvents => _warningEvents; + + /// + /// Gets the warning messages that were logged. + /// + public IReadOnlyCollection Warnings => _warningEvents.Select(i => i.Message).ToList(); + + /// + public virtual void Dispose() + { + _errorEvents.Clear(); + _messageEvents.Clear(); + _warningEvents.Clear(); + _allEvents = null; + } + + /// + /// Gets the current build output in the format of a console log. + /// + /// The logger verbosity to use. + /// The build output in the format of a console log. + public string GetConsoleLog(LoggerVerbosity verbosity = LoggerVerbosity.Normal) + { + StringBuilder sb = new StringBuilder(AllEvents.Count * 300); + + ConsoleLogger logger = new ConsoleLogger(verbosity, message => sb.Append(message), color => { }, () => { }); + + foreach (BuildEventArgs buildEventArgs in AllEvents) + { + switch (buildEventArgs) + { + case BuildMessageEventArgs buildMessageEventArgs: + logger.MessageHandler(logger, buildMessageEventArgs); + break; + + case BuildErrorEventArgs buildErrorEventArgs: + logger.ErrorHandler(logger, buildErrorEventArgs); + break; + + case BuildWarningEventArgs buildWarningEventArgs: + logger.WarningHandler(logger, buildWarningEventArgs); + break; + + case BuildStartedEventArgs buildStartedEventArgs: + logger.BuildStartedHandler(logger, buildStartedEventArgs); + break; + + case BuildFinishedEventArgs buildFinishedEventArgs: + logger.BuildFinishedHandler(logger, buildFinishedEventArgs); + break; + + case ProjectStartedEventArgs projectStartedEventArgs: + logger.ProjectStartedHandler(logger, projectStartedEventArgs); + break; + + case ProjectFinishedEventArgs projectFinishedEventArgs: + logger.ProjectFinishedHandler(logger, projectFinishedEventArgs); + break; + + case TargetStartedEventArgs targetStartedEventArgs: + logger.TargetStartedHandler(logger, targetStartedEventArgs); + break; + + case TargetFinishedEventArgs targetFinishedEventArgs: + logger.TargetFinishedHandler(logger, targetFinishedEventArgs); + break; + + case TaskStartedEventArgs taskStartedEventArgs: + logger.TaskStartedHandler(logger, taskStartedEventArgs); + break; + + case TaskFinishedEventArgs taskFinishedEventArgs: + logger.TaskFinishedHandler(logger, taskFinishedEventArgs); + break; + + case CustomBuildEventArgs customBuildEventArgs: + logger.CustomEventHandler(logger, customBuildEventArgs); + break; + } + } + + return sb.ToString(); + } + + /// + /// Adds a build event. + /// + /// A object to add. + protected void Add(BuildEventArgs buildEventArgs) + { + _allEvents.Enqueue(buildEventArgs); + + switch (buildEventArgs) + { + case BuildMessageEventArgs buildMessageEventArgs: + _messageEvents.Add(buildMessageEventArgs); + break; + + case BuildWarningEventArgs buildWarningEventArgs: + _warningEvents.Add(buildWarningEventArgs); + break; + + case BuildErrorEventArgs buildErrorEventArgs: + _errorEvents.Add(buildErrorEventArgs); + break; + } + } + } +} \ No newline at end of file diff --git a/src/MSBuildProjectCreator/BuildOutputMessages.cs b/src/MSBuildProjectCreator/BuildMessageCollection.cs similarity index 78% rename from src/MSBuildProjectCreator/BuildOutputMessages.cs rename to src/MSBuildProjectCreator/BuildMessageCollection.cs index 71e2253..05851e8 100644 --- a/src/MSBuildProjectCreator/BuildOutputMessages.cs +++ b/src/MSBuildProjectCreator/BuildMessageCollection.cs @@ -11,17 +11,17 @@ namespace Microsoft.Build.Utilities.ProjectCreation { /// - /// Represents the messages that were logged during a build. + /// Represents the messages that were logged. /// - public sealed class BuildOutputMessages : IReadOnlyCollection + public sealed class BuildMessageCollection : IReadOnlyCollection { - private readonly BuildOutput _buildOutput; + private readonly BuildEventArgsCollection _buildOutput; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The object that has message events. - internal BuildOutputMessages(BuildOutput buildOutput) + /// The object that has message events. + internal BuildMessageCollection(BuildEventArgsCollection buildOutput) { _buildOutput = buildOutput ?? throw new ArgumentNullException(nameof(buildOutput)); } diff --git a/src/MSBuildProjectCreator/BuildOutputMessageEvents.cs b/src/MSBuildProjectCreator/BuildMessageEventArgsCollection.cs similarity index 87% rename from src/MSBuildProjectCreator/BuildOutputMessageEvents.cs rename to src/MSBuildProjectCreator/BuildMessageEventArgsCollection.cs index f05cd7c..8b12a07 100644 --- a/src/MSBuildProjectCreator/BuildOutputMessageEvents.cs +++ b/src/MSBuildProjectCreator/BuildMessageEventArgsCollection.cs @@ -11,17 +11,17 @@ namespace Microsoft.Build.Utilities.ProjectCreation { /// - /// Represents the that were logged during a build. + /// Represents the that were logged. /// - public sealed class BuildOutputMessageEvents : IReadOnlyCollection + public sealed class BuildMessageEventArgsCollection : IReadOnlyCollection { private readonly IReadOnlyCollection _messageEvents; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// A containing the logged message events. - internal BuildOutputMessageEvents(IReadOnlyCollection messageEvents) + internal BuildMessageEventArgsCollection(IReadOnlyCollection messageEvents) { _messageEvents = messageEvents ?? throw new ArgumentNullException(nameof(messageEvents)); } diff --git a/src/MSBuildProjectCreator/BuildOutput.cs b/src/MSBuildProjectCreator/BuildOutput.cs index 1e603d5..262a4c9 100644 --- a/src/MSBuildProjectCreator/BuildOutput.cs +++ b/src/MSBuildProjectCreator/BuildOutput.cs @@ -3,45 +3,22 @@ // Licensed under the MIT license. using Microsoft.Build.Framework; -using Microsoft.Build.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Microsoft.Build.Utilities.ProjectCreation { /// /// Stores and makes available the logged output of MSBuild when building a project. /// - public sealed class BuildOutput : ILogger, IDisposable + public sealed class BuildOutput : BuildEventArgsCollection, ILogger { - /// - /// Stores the errors that were logged. - /// - private readonly List _errorEvents = new List(); - - /// - /// Stores the messages that were logged. - /// - private readonly List _messageEvents = new List(50); - /// /// Stores the results by project. /// private readonly ConcurrentDictionary _resultsByProject = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// Stores the warnings that were logged. - /// - private readonly List _warningEvents = new List(); - - /// - /// Stores all build events. - /// - private ConcurrentQueue _allEvents = new ConcurrentQueue(); - /// /// Stores the that were logged when the build finished. /// @@ -49,35 +26,8 @@ public sealed class BuildOutput : ILogger, IDisposable private BuildOutput() { - MessageEvents = new BuildOutputMessageEvents(_messageEvents); - Messages = new BuildOutputMessages(this); } - /// - /// Gets all events that were logged. - /// - public IReadOnlyCollection AllEvents => _allEvents; - - /// - /// Gets the error events that were logged. - /// - public IReadOnlyCollection ErrorEvents => _errorEvents; - - /// - /// Gets the error messages that were logged. - /// - public IReadOnlyCollection Errors => _errorEvents.Select(i => i.Message).ToList(); - - /// - /// Gets the messages that were logged. - /// - public BuildOutputMessageEvents MessageEvents { get; } - - /// - /// Gets a object that gets the messages from the build. - /// - public BuildOutputMessages Messages { get; } - /// public string Parameters { get; set; } @@ -94,16 +44,6 @@ private BuildOutput() /// public LoggerVerbosity Verbosity { get; set; } - /// - /// Gets the warning events that were logged. - /// - public IReadOnlyCollection WarningEvents => _warningEvents; - - /// - /// Gets the warning messages that were logged. - /// - public IReadOnlyCollection Warnings => _warningEvents.Select(i => i.Message).ToList(); - /// /// Creates an instance of the class. /// @@ -114,91 +54,18 @@ public static BuildOutput Create() } /// - public void Dispose() + public override void Dispose() { _buildFinished = null; - _errorEvents.Clear(); - _messageEvents.Clear(); - _warningEvents.Clear(); - _allEvents = null; - } - - /// - /// Gets the current build output in the format of a console log. - /// - /// The logger verbosity to use. - /// The build output in the format of a console log. - public string GetConsoleLog(LoggerVerbosity verbosity = LoggerVerbosity.Normal) - { - StringBuilder sb = new StringBuilder(_allEvents.Count * 300); - - ConsoleLogger logger = new ConsoleLogger(verbosity, message => sb.Append(message), color => { }, () => { }); - - foreach (BuildEventArgs buildEventArgs in _allEvents) - { - switch (buildEventArgs) - { - case BuildMessageEventArgs buildMessageEventArgs: - logger.MessageHandler(logger, buildMessageEventArgs); - break; - - case BuildErrorEventArgs buildErrorEventArgs: - logger.ErrorHandler(logger, buildErrorEventArgs); - break; - case BuildWarningEventArgs buildWarningEventArgs: - logger.WarningHandler(logger, buildWarningEventArgs); - break; - - case BuildStartedEventArgs buildStartedEventArgs: - logger.BuildStartedHandler(logger, buildStartedEventArgs); - break; - - case BuildFinishedEventArgs buildFinishedEventArgs: - logger.BuildFinishedHandler(logger, buildFinishedEventArgs); - break; - - case ProjectStartedEventArgs projectStartedEventArgs: - logger.ProjectStartedHandler(logger, projectStartedEventArgs); - break; - - case ProjectFinishedEventArgs projectFinishedEventArgs: - logger.ProjectFinishedHandler(logger, projectFinishedEventArgs); - break; - - case TargetStartedEventArgs targetStartedEventArgs: - logger.TargetStartedHandler(logger, targetStartedEventArgs); - break; - - case TargetFinishedEventArgs targetFinishedEventArgs: - logger.TargetFinishedHandler(logger, targetFinishedEventArgs); - break; - - case TaskStartedEventArgs taskStartedEventArgs: - logger.TaskStartedHandler(logger, taskStartedEventArgs); - break; - - case TaskFinishedEventArgs taskFinishedEventArgs: - logger.TaskFinishedHandler(logger, taskFinishedEventArgs); - break; - - case CustomBuildEventArgs customBuildEventArgs: - logger.CustomEventHandler(logger, customBuildEventArgs); - break; - } - } - - return sb.ToString(); + base.Dispose(); } /// public void Initialize(IEventSource eventSource) { eventSource.BuildFinished += OnBuildFinished; - eventSource.ErrorRaised += OnErrorRaised; - eventSource.MessageRaised += OnMessageRaised; eventSource.ProjectFinished += OnProjectFinished; - eventSource.WarningRaised += OnWarningRaised; eventSource.AnyEventRaised += OnAnyEventRaised; } @@ -209,17 +76,11 @@ public void Shutdown() private void OnAnyEventRaised(object sender, BuildEventArgs e) { - _allEvents.Enqueue(e); + Add(e); } private void OnBuildFinished(object sender, BuildFinishedEventArgs args) => _buildFinished = args; - private void OnErrorRaised(object sender, BuildErrorEventArgs args) => _errorEvents.Add(args); - - private void OnMessageRaised(object sender, BuildMessageEventArgs args) => _messageEvents.Add(args); - private void OnProjectFinished(object sender, ProjectFinishedEventArgs e) => _resultsByProject.AddOrUpdate(e.ProjectFile, e.Succeeded, (projectFile, succeeded) => succeeded && e.Succeeded); - - private void OnWarningRaised(object sender, BuildWarningEventArgs args) => _warningEvents.Add(args); } } \ No newline at end of file