File: ConsoleOutputAlignerTests.cs
Web Access
Project: ..\..\..\src\Build.UnitTests\Microsoft.Build.Engine.UnitTests.csproj (Microsoft.Build.Engine.UnitTests)
// 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.Text;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Framework;
using Shouldly;
using Xunit;
 
namespace Microsoft.Build.UnitTests
{
    public class ConsoleOutputAlignerTests
    {
        [Theory]
        [InlineData("a", true)]
        [InlineData("a", false)]
        [InlineData("12345", true)]
        [InlineData("12345", false)]
        public void IndentBiggerThanBuffer_IndentedAndNotAligned(string input, bool aligned)
        {
            string indent = "    ";
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: aligned, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: indent.Length);
 
            output.ShouldBe(indent + input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("a")]
        [InlineData("12345")]
        public void NoAlignNoIndent_NotAlignedEvenIfBiggerThanBuffer(string input)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: false, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData(1)]
        [InlineData(1000)]
        public void NoBufferWidthNoIndent_NotAligned(int sizeOfMessage)
        {
            string input = new string('.', sizeOfMessage);
            var aligner = new ConsoleOutputAligner(bufferWidth: -1, alignMessages: false, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("a")]
        [InlineData("12345")]
        public void WithoutBufferWidthWithoutIndentWithAlign_NotIndentedAndNotAligned(string input)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: -1, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("a")]
        [InlineData("12345")]
        public void NoAlignPrefixAlreadyWritten_NotChanged(string input)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: 10, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("", "123")]
        [InlineData(" ", "12")]
        [InlineData("  ", "1")]
        public void SmallerThanBuffer_NotAligned(string indent, string input)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: indent.Length);
 
            output.ShouldBe(indent + input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("", "1234", "123", "4")]
        [InlineData(" ", "123", " 12", " 3")]
        [InlineData("  ", "12", "  1", "  2")]
        public void BiggerThanBuffer_AlignedWithIndent(string indent, string input, string expected1stLine, string expected2ndLine)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: indent.Length);
 
            output.ShouldBe(expected1stLine + Environment.NewLine + expected2ndLine + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("", "12345678", "123\n" +
                                    "456\n" +
                                    "78\n")]
        [InlineData(" ", "12345678", " 12\n" +
                                     " 34\n" +
                                     " 56\n" +
                                     " 78\n")]
        [InlineData("  ", "1234", "  1\n" +
                                  "  2\n" +
                                  "  3\n" +
                                  "  4\n")]
        public void XTimesBiggerThanBuffer_AlignedToMultipleLines(string indent, string input, string expected)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: indent.Length);
 
            output.ShouldBe(expected.Replace("\n", Environment.NewLine));
        }
 
 
        [Theory]
        [InlineData("", "1234", "123", "4")]
        [InlineData(" ", "123", "12", " 3")]
        [InlineData("  ", "12", "1", "  2")]
        public void BiggerThanBufferWithPrefixAlreadyWritten_AlignedWithIndentFromSecondLine(string indent, string input, string expected1stLine, string expected2ndLine)
        {
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: indent.Length);
 
            output.ShouldBe(expected1stLine + Environment.NewLine + expected2ndLine + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("a\nb")]
        [InlineData("12345\n54321")]
        [InlineData("\t12345\n\t54321")]
        public void MultiLineWithoutAlign_NotChanged(string input)
        {
            input = input.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: false, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        /// <summary>
        /// Although consoles interprets \r as return carrier to the begging of the line, we treat \r as NewLine, as it is most consistent with how file viewers interpret it and
        ///    because logs are rarely read directly from console but more often from log files.
        /// Consequently \n\r shall be interpreted not as sequence but two control characters with equivalent of \n\n.
        /// </summary>
        [Theory]
        [InlineData("a\n\rb", "a\n\n  b")]
        [InlineData("a\rb", "a\n  b")]
        [InlineData("\n\ra", "\n\n  a")]
        [InlineData("\ra", "\n  a")]
        [InlineData("a\nb\n\r", "a\n  b\n\n")]
        [InlineData("a\nb\r", "a\n  b\n")]
        public void NonStandardNewLines_AlignAsExpected(string input, string expected)
        {
            expected = expected.Replace("\n", Environment.NewLine) + Environment.NewLine;
 
            var aligner = new ConsoleOutputAligner(bufferWidth: 10, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: 2);
 
            output.ShouldBe(expected);
        }
 
        [Theory]
        [InlineData("a\nb")]
        [InlineData("123456789\n987654321")]
        [InlineData("\t1\n9\t1")]
        public void ShortMultiLineWithAlign_NoChange(string input)
        {
            input = input.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: 10, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("a\nb")]
        [InlineData("a\r\nb")]
        [InlineData("a\nb\r\n")]
        [InlineData("a\nb\n")]
        [InlineData("a\n\nb")]
        [InlineData("a\r\n\nb")]
        [InlineData("a\n\r\nb")]
        [InlineData("a\r\n\r\nb")]
        [InlineData("\r\na\nb")]
        [InlineData("\na\nb")]
        [InlineData("\na\r\nb\nc")]
        [InlineData("\r\na\nb\r\nc")]
        public void ShortMultiLineWithMixedNewLines_NewLinesReplacedByActualEnvironmentNewLines(string input)
        {
            string expected = input.Replace("\r", "").Replace("\n", Environment.NewLine) + Environment.NewLine;
            var aligner = new ConsoleOutputAligner(bufferWidth: 10, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: 0);
 
            output.ShouldBe(expected);
        }
 
        [Theory]
        [InlineData("", "a\n12345", "a\n123\n45\n")]
        [InlineData("", "12345\na\n54321", "123\n45\na\n543\n21\n")]
        [InlineData(" ", "12345\na\n54321", "12\n 34\n 5\n a\n 54\n 32\n 1\n")]
        public void MultiLineWithPrefixAlreadyWritten(string prefix, string input, string expected)
        {
            input = input.Replace("\n", Environment.NewLine);
            expected = expected.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: prefix.Length);
 
            output.ShouldBe(expected);
        }
 
        [Theory]
        [InlineData(" ", "a\n12345", " a\n 12\n 34\n 5\n")]
        [InlineData(" ", "12345\na\n54321", " 12\n 34\n 5\n a\n 54\n 32\n 1\n")]
        public void MultiLineWithoutPrefixAlreadyWritten(string prefix, string input, string expected)
        {
            input = input.Replace("\n", Environment.NewLine);
            expected = expected.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: 4, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: false, prefixWidth: prefix.Length);
 
            output.ShouldBe(expected);
        }
 
        [Theory]
        [InlineData("\t")]
        [InlineData("a\nb\tc\nd")]
        public void ShortTextWithTabs_NoChange(string input)
        {
            input = input.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: 50, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: true, prefixWidth: 0);
 
            output.ShouldBe(input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("", "\t", 7, false)]
        [InlineData("", "12345678\t", 15, false)]
        [InlineData(" ", "2345678\t", 15, false)]
        [InlineData(" ", "2345678\t", 15, true)]
        public void LastTabOverLimit_NoChange(string prefix, string input, int bufferWidthWithoutNewLine, bool prefixAlreadyWritten)
        {
            input = input.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: bufferWidthWithoutNewLine + 1, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: prefixAlreadyWritten, prefixWidth: prefix.Length);
 
            output.ShouldBe((prefixAlreadyWritten ? string.Empty : prefix) + input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("", "\t", 8, false)]
        [InlineData("", "12345678\t", 16, false)]
        [InlineData(" ", "2345678\t", 16, false)]
        [InlineData(" ", "2345678\t", 16, true)]
        public void LastTabAtLimit_NoChange(string prefix, string input, int bufferWidthWithoutNewLine, bool prefixAlreadyWritten)
        {
            input = input.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: bufferWidthWithoutNewLine + 1, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: prefixAlreadyWritten, prefixWidth: prefix.Length);
 
            output.ShouldBe((prefixAlreadyWritten ? string.Empty : prefix) + input + Environment.NewLine);
        }
 
        [Theory]
        [InlineData("", "\t", 8, false)]
        [InlineData("", "12345678\t", 16, false)]
        [InlineData(" ", "2345678\t", 16, false)]
        [InlineData(" ", "2345678\t", 16, true)]
        public void TabsMakesItJustOverLimit_IndentAndAlign(string prefix, string input, int bufferWidthWithoutNewLine, bool prefixAlreadyWritten)
        {
            input = input.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: bufferWidthWithoutNewLine + 1, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input + "x", prefixAlreadyWritten: prefixAlreadyWritten, prefixWidth: prefix.Length);
 
            string expected = (prefixAlreadyWritten ? string.Empty : prefix) + input + Environment.NewLine +
                              prefix + "x" + Environment.NewLine;
 
            output.ShouldBe(expected);
        }
 
        [Theory]
        // +----+----+---+---+---+---+---+---+
        // | 1  | 2  | 3 | 4 | 5 | 6 | 7 | 8 |
        // +----+----+---+---+---+---+---+---+
        // | \t | .  | . | . | . | . | . | . |
        // +----+----+---+---+---+---+---+---+
        // | 1  |    |   |   |   |   |   |   |
        // +----+----+---+---+---+---+---+---+
        // | a  | \t | . | . | . | . | . | . |
        // +----+----+---+---+---+---+---+---+
        // | b  |    |   |   |   |   |   |   |
        // +----+----+---+---+---+---+---+---+
        [InlineData("", "\t1\na\tb", "\t\n1\na\t\nb\n", 8, false)]
        // +---+---+---+----+---+---+---+---+----+
        // | 1 | 2 | 3 | 4  | 5 | 6 | 7 | 8 | 9  |
        // +---+---+---+----+---+---+---+---+----+
        // | 1 | 2 | 3 | 4  | 5 | 6 | 7 | 8 | \t |
        // +---+---+---+----+---+---+---+---+----+
        // | a | b | c |    |   |   |   |   |    |
        // +---+---+---+----+---+---+---+---+----+
        // | d | e | f | \t | . | . | . | . | g  |
        // +---+---+---+----+---+---+---+---+----+
        [InlineData("", "12345678\tabc\ndef\tg", "12345678\t\nabc\ndef\tg\n", 9, false)]
        // +----+---+---+----+---+---+---+---+----+----+----+----+----+----+----+----+----+
        // | 1  | 2 | 3 | 4  | 5 | 6 | 7 | 8 | 9  | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
        // +----+---+---+----+---+---+---+---+----+----+----+----+----+----+----+----+----+
        // | \t | . | . | .  | . | . | . | . | \t | .  | .  | .  | .  | .  | .  | .  | a  |
        // +----+---+---+----+---+---+---+---+----+----+----+----+----+----+----+----+----+
        // | b  | c |   |    |   |   |   |   |    |    |    |    |    |    |    |    |    |
        // +----+---+---+----+---+---+---+---+----+----+----+----+----+----+----+----+----+
        // | d  | e | f | \t | . | . | . | . | \t | .  | .  | .  | .  | .  | .  | .  | g  |
        // +----+---+---+----+---+---+---+---+----+----+----+----+----+----+----+----+----+
        // | h  | i |   |    |   |   |   |   |    |    |    |    |    |    |    |    |    |
        // +----+---+---+----+---+---+---+---+----+----+----+----+----+----+----+----+----+
        [InlineData("", "\t\tabc\ndef\t\tghi", "\t\ta\nbc\ndef\t\tg\nhi\n", 17, false)]
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | 1 | 2 | 3  | 4 | 5  | 6 | 7 | 8 | 9  | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | a | \t | . | .  | . | . | . | \t | .  | .  | .  | .  | .  | .  | .  | b  |
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | c |    |   |    |   |   |   |    |    |    |    |    |    |    |    |    |
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | d | e  | f | \t | . | . | . | \t | .  | .  | .  | .  | .  | .  | .  | g  |
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | h | i  | 5 | 6  | 7 | 8 | 9  | 0 | 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  |
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | 9 |    |   |    |   |   |   |    |    |    |    |    |    |    |    |    |
        // +---+---+----+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        [InlineData(" ", "a\t\tbc\ndef\t\tghi567890123456789", " a\t\tb\n c\n def\t\tg\n hi56789012345678\n 9\n", 17, false)]
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | 1 | 2  | 3 | 4 | 5  | 6 | 7 | 8 | 9  | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | a | \t | . | . | .  | . | . | . | \t | .  | .  | .  | .  | .  | .  | .  | b  |
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | c  |   |   |    |   |   |   |    |    |    |    |    |    |    |    |    |
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | d  | e | f | \t | . | . | . | \t | .  | .  | .  | .  | .  | .  | .  | g  |
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | h  | i | 5 | 6  | 7 | 8 | 9 | 0  | 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  |
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        // | _ | 9  |   |   |    |   |   |   |    |    |    |    |    |    |    |    |    |
        // +---+----+---+---+----+---+---+---+----+----+----+----+----+----+----+----+----+
        [InlineData(" ", "a\t\tbc\ndef\t\tghi567890123456789", "a\t\tb\n c\n def\t\tg\n hi56789012345678\n 9\n", 17, true)]
        public void MultiLinesOverLimit_IndentAndAlign(string prefix, string input, string expected, int bufferWidthWithoutNewLine, bool prefixAlreadyWritten)
        {
            input = input.Replace("\n", Environment.NewLine);
            expected = expected.Replace("\n", Environment.NewLine);
            var aligner = new ConsoleOutputAligner(bufferWidth: bufferWidthWithoutNewLine + 1, alignMessages: true, stringBuilderProvider: new TestStringBuilderProvider());
 
            string output = aligner.AlignConsoleOutput(message: input, prefixAlreadyWritten: prefixAlreadyWritten, prefixWidth: prefix.Length);
 
            output.ShouldBe(expected);
        }
 
        private sealed class TestStringBuilderProvider : IStringBuilderProvider
        {
            public StringBuilder Acquire(int capacity) => new StringBuilder(capacity);
            public string GetStringAndRelease(StringBuilder builder) => builder.ToString();
        }
    }
}