From e8b20ed47d794f87f85d8906a38d94a42394ac0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rastislav=20Novotn=C3=BD?= Date: Tue, 20 Oct 2020 21:50:04 +0800 Subject: [PATCH] GH-2897: Add tests for MyGetProvider --- .../Fixtures/Build/MyGetFixture.cs | 34 ++++ .../Unit/Build/MyGet/MyGetProviderTests.cs | 179 ++++++++++++++++++ src/Cake.Common/Build/MyGet/MyGetProvider.cs | 56 ++++-- 3 files changed, 254 insertions(+), 15 deletions(-) create mode 100644 src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs create mode 100644 src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs diff --git a/src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs b/src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs new file mode 100644 index 0000000000..4619d39380 --- /dev/null +++ b/src/Cake.Common.Tests/Fixtures/Build/MyGetFixture.cs @@ -0,0 +1,34 @@ +using Cake.Common.Build.MyGet; +using Cake.Common.Tests.Fakes; +using Cake.Core; +using NSubstitute; + +namespace Cake.Common.Tests.Fixtures.Build +{ + internal sealed class MyGetFixture + { + public ICakeEnvironment Environment { get; set; } + + public FakeBuildSystemServiceMessageWriter Writer { get; set; } + + public MyGetFixture() + { + Environment = Substitute.For(); + Environment.GetEnvironmentVariable("BuildRunner").Returns((string)null); + Writer = new FakeBuildSystemServiceMessageWriter(); + } + + public void IsRunningOnMyGet(bool? capitalCase = default) + { + var buildRunnerValue = "MyGet"; + if (capitalCase.HasValue) + { + buildRunnerValue = capitalCase.Value ? "MYGET" : "myget"; + } + + Environment.GetEnvironmentVariable("BuildRunner").Returns(buildRunnerValue); + } + + public MyGetProvider CreateMyGetProvider() => new MyGetProvider(Environment, Writer); + } +} diff --git a/src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs b/src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs new file mode 100644 index 0000000000..c6e7c3cb16 --- /dev/null +++ b/src/Cake.Common.Tests/Unit/Build/MyGet/MyGetProviderTests.cs @@ -0,0 +1,179 @@ +using Cake.Common.Build.MyGet; +using Cake.Common.Tests.Fakes; +using Cake.Common.Tests.Fixtures.Build; +using Cake.Core; +using Cake.Testing; +using Xunit; + +namespace Cake.Common.Tests.Unit.Build.MyGet +{ + public sealed class MyGetProviderTests + { + public sealed class TheConstructor + { + [Fact] + public void Should_Throw_If_Environment_Is_Null() + { + // Given, When + var writer = new FakeBuildSystemServiceMessageWriter(); + var result = Record.Exception(() => new MyGetProvider(null, writer)); + + // Then + AssertEx.IsArgumentNullException(result, "environment"); + } + + [Fact] + public void Should_Throw_If_Writer_Is_Null() + { + // Given, When + var result = Record.Exception(() => new MyGetProvider(new FakeEnvironment(PlatformFamily.Unknown), null)); + + // Then + AssertEx.IsArgumentNullException(result, "writer"); + } + } + + public sealed class IsRunningOnMyGet + { + [Theory] + [InlineData(true)] + [InlineData(false)] + [InlineData(null)] + public void Should_Return_True_If_Running_On_MyGet(bool? capitalCase) + { + // Given + var fixture = new MyGetFixture(); + fixture.IsRunningOnMyGet(capitalCase); + var provider = fixture.CreateMyGetProvider(); + + // When + var result = provider.IsRunningOnMyGet; + + // Then + Assert.True(result); + } + + [Fact] + public void Should_Return_False_If_Not_Running_On_MyGet() + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + var result = provider.IsRunningOnMyGet; + + // Then + Assert.False(result); + } + } + + public sealed class BuildProblem + { + [Theory] + [InlineData("Test build problem", "Test build problem")] + [InlineData("", "")] + [InlineData(null, "")] + [InlineData("[Special characters:\r\n\"\'test|split\'\"]", "|[Special characters:|r|n\"|\'test||split|\'\"|]")] + public void Should_Log_Description(string description, string expectedOutput) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.BuildProblem(description); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[buildProblem description='{expectedOutput}']", entry); + } + } + + public sealed class SetParameter + { + [Theory] + [InlineData("Parameter", "Value", "Parameter", "Value")] + [InlineData("", "", "", "")] + [InlineData(null, null, "", "")] + [InlineData("Special [param] \'name\'", "test\n|\r||value||", "Special |[param|] |\'name|\'", "test|n|||r||||value||||")] + public void Should_Log_Parameter_Value(string name, string value, string expectedName, string expectedValue) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.SetParameter(name, value); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[setParameter name='{expectedName}' value='{expectedValue}']", entry); + } + } + + public sealed class WriteStatus + { + [Theory] + [InlineData("M", MyGetBuildStatus.Normal, "M", "NORMAL")] + [InlineData(null, MyGetBuildStatus.Warning, "", "WARNING")] + [InlineData("Message \n text", MyGetBuildStatus.Error, "Message |n text", "ERROR")] + [InlineData("[Failure]|status", MyGetBuildStatus.Failure, "|[Failure|]||status", "FAILURE")] + public void Should_Log_Status(string message, MyGetBuildStatus status, string expectedMessage, string expectedStatus) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.WriteStatus(message, status); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[message text='{expectedMessage}' status='{expectedStatus}']", entry); + } + + [Theory] + [InlineData("Hello, World!", MyGetBuildStatus.Normal, "", "Hello, World!", "NORMAL", "")] + [InlineData("My custom message", MyGetBuildStatus.Warning, "Hello, World!", "My custom message", "WARNING", "Hello, World!")] + [InlineData("Test", MyGetBuildStatus.Error, "r = (a - b) * c + (s1 & s2)", "Test", "ERROR", "r = (a - b) * c + (s1 & s2)")] + [InlineData("T", MyGetBuildStatus.Failure, "i = (b << 4) | c;\r\nr = a[i] / c;", "T", "FAILURE", "i = (b << 4) || c;|r|nr = a|[i|] / c;")] + public void Should_Log_Status_With_Error_Details(string message, MyGetBuildStatus status, string errorDetails, string expectedMessage, string expectedStatus, string expectedDetails) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.WriteStatus(message, status, errorDetails); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[message text='{expectedMessage}' status='{expectedStatus}' errorDetails='{expectedDetails}']", entry); + } + } + + public sealed class SetBuildNumber + { + [Theory] + [InlineData("2.3.1", "2.3.1")] + [InlineData("", "")] + [InlineData(null, "")] + [InlineData("99.4-beta", "99.4-beta")] + [InlineData("[net5.0\r\n\"\'beta|preview\'\"]", "|[net5.0|r|n\"|\'beta||preview|\'\"|]")] + public void Should_Log_Build_Number(string buildNumber, string expectedOutput) + { + // Given + var fixture = new MyGetFixture(); + var provider = fixture.CreateMyGetProvider(); + + // When + provider.SetBuildNumber(buildNumber); + + // Then + var entry = Assert.Single(fixture.Writer.Entries); + Assert.Equal($"##myget[buildNumber '{expectedOutput}']", entry); + } + } + } +} diff --git a/src/Cake.Common/Build/MyGet/MyGetProvider.cs b/src/Cake.Common/Build/MyGet/MyGetProvider.cs index e3718dee7f..8bf763944f 100644 --- a/src/Cake.Common/Build/MyGet/MyGetProvider.cs +++ b/src/Cake.Common/Build/MyGet/MyGetProvider.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Text; using Cake.Core; namespace Cake.Common.Build.MyGet @@ -19,22 +20,25 @@ public sealed class MyGetProvider : IMyGetProvider private const string MessagePrefix = "##myget["; private const string MessagePostfix = "]"; - private static readonly Dictionary _sanitizationTokens; + private static readonly Dictionary _sanitizationTokens; + private static readonly char[] _specialCharacters; private readonly ICakeEnvironment _environment; private readonly IBuildSystemServiceMessageWriter _writer; static MyGetProvider() { - _sanitizationTokens = new Dictionary + _sanitizationTokens = new Dictionary { - { "|", "||" }, - { "\'", "|\'" }, - { "\n", "|n" }, - { "\r", "|r" }, - { "[", "|['" }, - { "]", "|]" } + { '|', "||" }, + { '\'', "|\'" }, + { '\n', "|n" }, + { '\r', "|r" }, + { '[', "|[" }, + { ']', "|]" } }; + + _specialCharacters = _sanitizationTokens.Keys.ToArray(); } /// @@ -137,7 +141,7 @@ public void SetBuildNumber(string buildNumber) private void WriteServiceMessage(string messageName, string attributeValue) { - WriteServiceMessage(messageName, new Dictionary { { " ", attributeValue } }); + WriteServiceMessage(messageName, new Dictionary { { string.Empty, attributeValue } }); } private void WriteServiceMessage(string messageName, string attributeName, string attributeValue) @@ -153,11 +157,12 @@ private void WriteServiceMessage(string messageName, Dictionary values .Select(keypair => { - if (string.IsNullOrWhiteSpace(keypair.Key)) + var sanitizedValue = Sanitize(keypair.Value); + if (string.IsNullOrEmpty(keypair.Key)) { - return string.Format(CultureInfo.InvariantCulture, "'{0}'", Sanitize(keypair.Value)); + return string.Format(CultureInfo.InvariantCulture, "'{0}'", sanitizedValue); } - return string.Format(CultureInfo.InvariantCulture, "{0}='{1}'", keypair.Key, Sanitize(keypair.Value)); + return string.Format(CultureInfo.InvariantCulture, "{0}='{1}'", keypair.Key, sanitizedValue); }) .ToArray()); @@ -166,12 +171,33 @@ private void WriteServiceMessage(string messageName, Dictionary private static string Sanitize(string source) { - foreach (var charPair in _sanitizationTokens) + if (string.IsNullOrEmpty(source)) + { + return string.Empty; + } + + // When source does not contain special characters, then it is possible to skip building new string. + // This approach can possibly iterate through string 2 times, but special characters are exceptional. + if (source.IndexOfAny(_specialCharacters) < 0) + { + return source; + } + + var stringBuilder = new StringBuilder(source.Length * 2); + for (int index = 0; index < source.Length; index++) { - source = source.Replace(charPair.Key, charPair.Value); + char sourceChar = source[index]; + if (_sanitizationTokens.TryGetValue(sourceChar, out var replacement)) + { + stringBuilder.Append(replacement); + } + else + { + stringBuilder.Append(sourceChar); + } } - return source; + return stringBuilder.ToString(); } } } \ No newline at end of file