Add sample LiveLogger unit tests

This commit is contained in:
Ladi Prosek 2023-03-31 10:00:07 +02:00
Родитель 8c385b0066
Коммит aa5cf68300
4 изменённых файлов: 319 добавлений и 3 удалений

Просмотреть файл

@ -0,0 +1,201 @@
// 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;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging.LiveLogger;
using Shouldly;
using Xunit;
namespace Microsoft.Build.UnitTests
{
public class LiveLogger_Tests : IEventSource, IDisposable
{
private const int _nodeCount = 8;
private const int _terminalWidth = 80;
private const string _eventSender = "Test";
private const string _projectFile = @"C:\src\project.proj";
private readonly MockTerminal _mockTerminal;
private readonly LiveLogger _liveLogger;
private readonly DateTime _buildStartTime = new DateTime(2023, 3, 30, 16, 30, 0);
private readonly DateTime _buildFinishTime = new DateTime(2023, 3, 30, 16, 30, 5);
public LiveLogger_Tests()
{
_mockTerminal = new MockTerminal(_terminalWidth);
_liveLogger = new LiveLogger(_mockTerminal);
_liveLogger.Initialize(this, _nodeCount);
}
#region IEventSource implementation
public event BuildMessageEventHandler? MessageRaised;
public event BuildErrorEventHandler? ErrorRaised;
public event BuildWarningEventHandler? WarningRaised;
public event BuildStartedEventHandler? BuildStarted;
public event BuildFinishedEventHandler? BuildFinished;
public event ProjectStartedEventHandler? ProjectStarted;
public event ProjectFinishedEventHandler? ProjectFinished;
public event TargetStartedEventHandler? TargetStarted;
public event TargetFinishedEventHandler? TargetFinished;
public event TaskStartedEventHandler? TaskStarted;
public event TaskFinishedEventHandler? TaskFinished;
public event CustomBuildEventHandler? CustomEventRaised;
public event BuildStatusEventHandler? StatusEventRaised;
public event AnyEventHandler? AnyEventRaised;
#endregion
#region IDisposable implementation
public void Dispose()
{
_liveLogger.Shutdown();
}
#endregion
#region Event args helpers
private BuildEventContext MakeBuildEventContext()
{
return new BuildEventContext(1, 1, 1, 1);
}
private BuildStartedEventArgs MakeBuildStartedEventArgs()
{
return new BuildStartedEventArgs(null, null, _buildStartTime);
}
private BuildFinishedEventArgs MakeBuildFinishedEventArgs(bool succeeded)
{
return new BuildFinishedEventArgs(null, null, succeeded, _buildFinishTime);
}
private ProjectStartedEventArgs MakeProjectStartedEventArgs(string projectFile, string targetNames = "Build")
{
return new ProjectStartedEventArgs("", "", projectFile, targetNames, new Dictionary<string, string>(), new List<DictionaryEntry>())
{
BuildEventContext = MakeBuildEventContext(),
};
}
private ProjectFinishedEventArgs MakeProjectFinishedEventArgs(string projectFile, bool succeeded)
{
return new ProjectFinishedEventArgs(null, null, projectFile, succeeded)
{
BuildEventContext = MakeBuildEventContext(),
};
}
private TargetStartedEventArgs MakeTargetStartedEventArgs(string projectFile, string targetName)
{
return new TargetStartedEventArgs("", "", targetName, projectFile, targetFile: projectFile)
{
BuildEventContext = MakeBuildEventContext(),
};
}
private TargetFinishedEventArgs MakeTargetFinishedEventArgs(string projectFile, string targetName, bool succeeded)
{
return new TargetFinishedEventArgs("", "", targetName, projectFile, targetFile: projectFile, succeeded)
{
BuildEventContext = MakeBuildEventContext(),
};
}
private TaskStartedEventArgs MakeTaskStartedEventArgs(string projectFile, string taskName)
{
return new TaskStartedEventArgs("", "", projectFile, taskFile: projectFile, taskName)
{
BuildEventContext = MakeBuildEventContext(),
};
}
private TaskFinishedEventArgs MakeTaskFinishedEventArgs(string projectFile, string taskName, bool succeeded)
{
return new TaskFinishedEventArgs("", "", projectFile, taskFile: projectFile, taskName, succeeded)
{
BuildEventContext = MakeBuildEventContext(),
};
}
private BuildWarningEventArgs MakeWarningEventArgs(string warning)
{
return new BuildWarningEventArgs("", "", "", 0, 0, 0, 0, warning, null, null)
{
BuildEventContext = MakeBuildEventContext(),
};
}
private BuildErrorEventArgs MakeErrorEventArgs(string error)
{
return new BuildErrorEventArgs("", "", "", 0, 0, 0, 0, error, null, null)
{
BuildEventContext = MakeBuildEventContext(),
};
}
#endregion
#region Build summary tests
private void InvokeLoggerCallbacksForSimpleProject(bool succeeded, Action additionalCallbacks)
{
BuildStarted?.Invoke(_eventSender, MakeBuildStartedEventArgs());
ProjectStarted?.Invoke(_eventSender, MakeProjectStartedEventArgs(_projectFile));
TargetStarted?.Invoke(_eventSender, MakeTargetStartedEventArgs(_projectFile, "Build"));
TaskStarted?.Invoke(_eventSender, MakeTaskStartedEventArgs(_projectFile, "Task"));
additionalCallbacks();
TaskFinished?.Invoke(_eventSender, MakeTaskFinishedEventArgs(_projectFile, "Task", succeeded));
TargetFinished?.Invoke(_eventSender, MakeTargetFinishedEventArgs(_projectFile, "Build", succeeded));
ProjectFinished?.Invoke(_eventSender, MakeProjectFinishedEventArgs(_projectFile, succeeded));
BuildFinished?.Invoke(_eventSender, MakeBuildFinishedEventArgs(succeeded));
}
[Fact]
public void PrintsBuildSummary_Succeeded()
{
InvokeLoggerCallbacksForSimpleProject(succeeded: true, () => { });
_mockTerminal.GetLastLine().ShouldBe("Build succeeded in 5.0s");
}
[Fact]
public void PrintBuildSummary_SucceededWithWarnings()
{
InvokeLoggerCallbacksForSimpleProject(succeeded: true, () =>
{
WarningRaised?.Invoke(_eventSender, MakeWarningEventArgs("Warning!"));
});
_mockTerminal.GetLastLine().ShouldBe("Build succeeded with warnings in 5.0s");
}
[Fact]
public void PrintBuildSummary_Failed()
{
InvokeLoggerCallbacksForSimpleProject(succeeded: false, () => { });
_mockTerminal.GetLastLine().ShouldBe("Build failed in 5.0s");
}
[Fact]
public void PrintBuildSummary_FailedWithErrors()
{
InvokeLoggerCallbacksForSimpleProject(succeeded: false, () =>
{
ErrorRaised?.Invoke(_eventSender, MakeErrorEventArgs("Error!"));
});
_mockTerminal.GetLastLine().ShouldBe("Build failed with errors in 5.0s");
}
#endregion
}
}

Просмотреть файл

@ -45,6 +45,9 @@
<Compile Include="..\UnitTests.Shared\EnvironmentProvider.cs" />
<Compile Include="..\UnitTests.Shared\RunnerUtilities.cs" />
<Compile Remove="LiveLogger_Tests.cs" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
<Compile Remove="MockTerminal.cs" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
<EmbeddedResource Include="..\MSBuild\MSBuild\Microsoft.Build.Core.xsd">
<Link>Microsoft.Build.Core.xsd</Link>
<SubType>

Просмотреть файл

@ -0,0 +1,113 @@
// 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.Text;
using Microsoft.Build.Logging.LiveLogger;
namespace Microsoft.Build.UnitTests
{
/// <summary>
/// A test implementation of <see cref="ITerminal"/>.
/// </summary>
internal sealed class MockTerminal : ITerminal
{
private readonly int _width;
/// <summary>
/// Contains output lines written to the terminal.
/// </summary>
private List<string> _outputLines = new();
private StringBuilder _bufferedOutput = new();
private bool _isBuffering = false;
public MockTerminal(int width)
{
_width = width;
_outputLines.Add("");
}
/// <summary>
/// Gets the last line written to the terminal.
/// </summary>
/// <remarks>
/// If the last character was \n, it returns characters between the second to last \n and last \n.
/// If the last character was not \n, it returns characters between the last \n and the end of the output.
/// </remarks>
public string GetLastLine()
{
string lastLine = _outputLines[^1];
if (lastLine.Length == 0 && _outputLines.Count > 1)
{
lastLine = _outputLines[^2];
}
return lastLine;
}
/// <summary>
/// Adds a string to <see cref="_outputLines"/>.
/// </summary>
private void AddOutput(string text)
{
if (_isBuffering)
{
_bufferedOutput.Append(text);
}
else
{
string[] lines = text.Split('\n');
_outputLines[^1] += lines[0];
for (int i = 1; i < lines.Length; i++)
{
_outputLines.Add("");
_outputLines[^1] += lines[i];
}
}
}
#region ITerminal implementation
public void BeginUpdate()
{
if (_isBuffering)
{
throw new InvalidOperationException();
}
_isBuffering = true;
}
public void EndUpdate()
{
if (!_isBuffering)
{
throw new InvalidOperationException();
}
_isBuffering = false;
AddOutput(_bufferedOutput.ToString());
_bufferedOutput.Clear();
}
public void Write(string text) => AddOutput(text);
public void WriteColor(TerminalColor color, string text) => AddOutput(text);
public void WriteColorLine(TerminalColor color, string text) { AddOutput(text); AddOutput("\n"); }
public void WriteLine(string text) { AddOutput(text); AddOutput("\n"); }
public void WriteLine(ReadOnlySpan<char> text) { AddOutput(text.ToString()); AddOutput("\n"); }
public void WriteLineFitToWidth(ReadOnlySpan<char> input)
{
AddOutput(input.Slice(0, Math.Min(input.Length, _width - 1)).ToString());
AddOutput("\n");
}
#endregion
#region IDisposable implementation
public void Dispose()
{ }
#endregion
}
}

Просмотреть файл

@ -172,7 +172,8 @@
<Compile Include="DistributedLoggerRecord.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
<Compile Include="LiveLogger\*.cs" />
<!-- LiveLogger is supported only in Core builds -->
<Compile Include="LiveLogger\*.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
<Compile Include="InitializationException.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
@ -189,8 +190,6 @@
<Compile Include="XMake.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
<!-- LiveLogger is supported only in Core builds -->
<Compile Remove="LiveLogger\**\*.*" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
<!-- This is to enable CodeMarkers in MSBuild.exe -->
<!-- Win32 RC Files -->
<RCResourceFile Include="native.rc" />