Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/MSBuildProjectCreator.UnitTests/BuildEngineTests.cs
Original file line number Diff line number Diff line change
@@ -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<IBuildEngine> action)
{
BuildEngine buildEngine = BuildEngine.Create();

action(buildEngine);

return buildEngine;
}
}
}
12 changes: 7 additions & 5 deletions src/MSBuildProjectCreator.UnitTests/BuildOutputTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
59 changes: 59 additions & 0 deletions src/MSBuildProjectCreator/BuildEngine.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an implementation of <see cref="IBuildEngine"/> that allows for capturing logged events in tasks.
/// </summary>
public sealed class BuildEngine : BuildEventArgsCollection, IBuildEngine
{
private BuildEngine()
{
}

/// <inheritdoc cref="IBuildEngine.ColumnNumberOfTaskNode"/>
public int ColumnNumberOfTaskNode => 0;

/// <inheritdoc cref="IBuildEngine.ContinueOnError"/>
public bool ContinueOnError => false;

/// <inheritdoc cref="IBuildEngine.LineNumberOfTaskNode"/>
public int LineNumberOfTaskNode => 0;

/// <inheritdoc cref="IBuildEngine.ProjectFileOfTaskNode"/>
public string ProjectFileOfTaskNode => null;

/// <summary>
/// Creates an instance of the <see cref="BuildEngine"/> class.
/// </summary>
/// <returns>A <see cref="BuildEngine"/> instance.</returns>
public static BuildEngine Create()
{
return new BuildEngine();
}

/// <inheritdoc cref="IBuildEngine.BuildProjectFile"/>
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
{
throw new NotSupportedException();
}

/// <inheritdoc cref="IBuildEngine.LogCustomEvent"/>
public void LogCustomEvent(CustomBuildEventArgs e) => Add(e);

/// <inheritdoc cref="IBuildEngine.LogErrorEvent"/>
public void LogErrorEvent(BuildErrorEventArgs e) => Add(e);

/// <inheritdoc cref="IBuildEngine.LogMessageEvent"/>
public void LogMessageEvent(BuildMessageEventArgs e) => Add(e);

/// <inheritdoc cref="IBuildEngine.LogWarningEvent"/>
public void LogWarningEvent(BuildWarningEventArgs e) => Add(e);
}
}
185 changes: 185 additions & 0 deletions src/MSBuildProjectCreator/BuildEventArgsCollection.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents a collection of <see cref="BuildEventArgs"/> objects.
/// </summary>
public abstract class BuildEventArgsCollection : IDisposable
{
/// <summary>
/// Stores the errors that were logged.
/// </summary>
private readonly List<BuildErrorEventArgs> _errorEvents = new List<BuildErrorEventArgs>();

/// <summary>
/// Stores the messages that were logged.
/// </summary>
private readonly List<BuildMessageEventArgs> _messageEvents = new List<BuildMessageEventArgs>(50);

/// <summary>
/// Stores the warnings that were logged.
/// </summary>
private readonly List<BuildWarningEventArgs> _warningEvents = new List<BuildWarningEventArgs>();

/// <summary>
/// Stores all build events.
/// </summary>
private ConcurrentQueue<BuildEventArgs> _allEvents = new ConcurrentQueue<BuildEventArgs>();

/// <summary>
/// Initializes a new instance of the <see cref="BuildEventArgsCollection"/> class.
/// </summary>
protected BuildEventArgsCollection()
{
MessageEvents = new BuildMessageEventArgsCollection(_messageEvents);
Messages = new BuildMessageCollection(this);
}

/// <summary>
/// Gets all events that were logged.
/// </summary>
public IReadOnlyCollection<BuildEventArgs> AllEvents => _allEvents;

/// <summary>
/// Gets the error events that were logged.
/// </summary>
public IReadOnlyCollection<BuildErrorEventArgs> ErrorEvents => _errorEvents;

/// <summary>
/// Gets the error messages that were logged.
/// </summary>
public IReadOnlyCollection<string> Errors => _errorEvents.Select(i => i.Message).ToList();

/// <summary>
/// Gets the messages that were logged.
/// </summary>
public BuildMessageEventArgsCollection MessageEvents { get; }

/// <summary>
/// Gets a <see cref="BuildMessageCollection"/> object that gets the messages from the build.
/// </summary>
public BuildMessageCollection Messages { get; }

/// <summary>
/// Gets the warning events that were logged.
/// </summary>
public IReadOnlyCollection<BuildWarningEventArgs> WarningEvents => _warningEvents;

/// <summary>
/// Gets the warning messages that were logged.
/// </summary>
public IReadOnlyCollection<string> Warnings => _warningEvents.Select(i => i.Message).ToList();

/// <inheritdoc cref="IDisposable.Dispose"/>
public virtual void Dispose()
{
_errorEvents.Clear();
_messageEvents.Clear();
_warningEvents.Clear();
_allEvents = null;
}

/// <summary>
/// Gets the current build output in the format of a console log.
/// </summary>
/// <param name="verbosity">The logger verbosity to use.</param>
/// <returns>The build output in the format of a console log.</returns>
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();
}

/// <summary>
/// Adds a build event.
/// </summary>
/// <param name="buildEventArgs">A <see cref="BuildEventArgs"/> object to add.</param>
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;
}
}
}
}
Loading