From 6adc384fe6ad1722bca03432040a48609f26f185 Mon Sep 17 00:00:00 2001 From: Socolin Date: Sun, 17 Dec 2023 16:36:31 -0500 Subject: [PATCH 1/2] Only un-escape unicode character encoding color codes #1124 --- .../NUnitEngine/NUnitTestEvent.cs | 4 +- .../NUnitTestEventSuiteFinished.cs | 6 +-- .../NUnitEngine/NUnitTestEventTestCase.cs | 8 ++-- .../NUnitEngine/UnicodeEscapeHelper.cs | 44 ++++++++++++++++++- .../UnicodeEscapeHelperTests.cs | 11 +++-- 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs index f95da627..a2c8e4b8 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs @@ -127,7 +127,7 @@ protected NUnitTestEvent(XmlNode node) : base(node) public string MethodName => Node.GetAttribute("methodname"); public string ClassName => Node.GetAttribute("classname"); - public string Output => Node.SelectSingleNode("output")?.InnerText.UnEscapeUnicodeCharacters(); + public string Output => Node.SelectSingleNode("output")?.InnerText.UnEscapeUnicodeColorCodesCharacters(); public CheckedTime StartTime() @@ -165,7 +165,7 @@ public IEnumerable NUnitAttachments foreach (XmlNode attachment in Node.SelectNodes("attachments/attachment")) { var path = attachment.SelectSingleNode("filePath")?.InnerText ?? string.Empty; - var description = attachment.SelectSingleNode("description")?.InnerText.UnEscapeUnicodeCharacters(); + var description = attachment.SelectSingleNode("description")?.InnerText.UnEscapeUnicodeColorCodesCharacters(); nUnitAttachments.Add(new NUnitAttachment(path, description)); } return nUnitAttachments; diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventSuiteFinished.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventSuiteFinished.cs index 6f0a900f..dbc941c5 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventSuiteFinished.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventSuiteFinished.cs @@ -28,10 +28,10 @@ public NUnitTestEventSuiteFinished(XmlNode node) : base(node) var failureNode = Node.SelectSingleNode("failure"); if (failureNode != null) { - FailureMessage = failureNode.SelectSingleNode("message")?.InnerText.UnEscapeUnicodeCharacters(); - StackTrace = failureNode.SelectSingleNode("stack-trace")?.InnerText.UnEscapeUnicodeCharacters(); + FailureMessage = failureNode.SelectSingleNode("message")?.InnerText.UnEscapeUnicodeColorCodesCharacters(); + StackTrace = failureNode.SelectSingleNode("stack-trace")?.InnerText.UnEscapeUnicodeColorCodesCharacters(); } - ReasonMessage = Node.SelectSingleNode("reason/message")?.InnerText.UnEscapeUnicodeCharacters(); + ReasonMessage = Node.SelectSingleNode("reason/message")?.InnerText.UnEscapeUnicodeColorCodesCharacters(); } public string ReasonMessage { get; } diff --git a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestCase.cs b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestCase.cs index 4b079cf5..c5f7438b 100644 --- a/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestCase.cs +++ b/src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestCase.cs @@ -48,11 +48,11 @@ public NUnitTestEventTestCase(XmlNode node) if (failureNode != null) { Failure = new NUnitFailure( - failureNode.SelectSingleNode("message")?.InnerText.UnEscapeUnicodeCharacters(), - failureNode.SelectSingleNode("stack-trace")?.InnerText.UnEscapeUnicodeCharacters()); + failureNode.SelectSingleNode("message")?.InnerText.UnEscapeUnicodeColorCodesCharacters(), + failureNode.SelectSingleNode("stack-trace")?.InnerText.UnEscapeUnicodeColorCodesCharacters()); } - ReasonMessage = Node.SelectSingleNode("reason/message")?.InnerText.UnEscapeUnicodeCharacters(); + ReasonMessage = Node.SelectSingleNode("reason/message")?.InnerText.UnEscapeUnicodeColorCodesCharacters(); } public string ReasonMessage { get; } @@ -73,7 +73,7 @@ public string StackTrace int i = 1; foreach (XmlNode assertionStacktraceNode in Node.SelectNodes("assertions/assertion/stack-trace")) { - stackTrace += $"{i++}) " + assertionStacktraceNode.InnerText.UnEscapeUnicodeCharacters(); + stackTrace += $"{i++}) " + assertionStacktraceNode.InnerText.UnEscapeUnicodeColorCodesCharacters(); stackTrace += "\n"; } diff --git a/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs b/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs index 5949e930..e967b2de 100644 --- a/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs +++ b/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs @@ -6,7 +6,9 @@ namespace NUnit.VisualStudio.TestAdapter.NUnitEngine; internal static class UnicodeEscapeHelper { - public static string UnEscapeUnicodeCharacters(this string text) + private const int EscapeAsciiValue = 0x1B; + + public static string UnEscapeUnicodeColorCodesCharacters(this string text) { if (text == null) return null; @@ -43,13 +45,51 @@ private static bool TryUnEscapeOneCharacter(string text, int position, out char if (position + unicodeEscapeSample.Length >= text.Length) return false; - extraCharacterRead = unicodeEscapeSample.Length; if (!int.TryParse(text.Substring(position + 2, unicodeEscapeSample.Length - 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var escapeValue)) return false; + // Here we only want to escape color escape character when used in a context of a ANSI color code + // See https://github.com/nunit/nunit3-vs-adapter/issues/1124 for more information. + if (escapeValue != EscapeAsciiValue) + return false; + + if (!IsAnsiColorCodeSequence(text, position + extraCharacterRead + 1)) + return false; + escapedChar = (char)escapeValue; return true; } + + private static bool IsAnsiColorCodeSequence(string text, int position) + { + var start = false; + while (position < text.Length) + { + var c = text[position++]; + // Look for the begining [ + if (c == '[' && !start) + { + start = true; + continue; + } + + // Found the 'm' at the end + if (c == 'm' && start) + return true; + + // [ was not found + if (!start) + return false; + + // Ignore all number and ; + var isDigit = c is >= '0' and <= '9'; + if (!isDigit && c != ';') + return false; + } + + // At the end without the ending 'm' + return false; + } } \ No newline at end of file diff --git a/src/NUnitTestAdapterTests/NUnitEngineTests/UnicodeEscapeHelperTests.cs b/src/NUnitTestAdapterTests/NUnitEngineTests/UnicodeEscapeHelperTests.cs index e13ea6b6..f8d37df6 100644 --- a/src/NUnitTestAdapterTests/NUnitEngineTests/UnicodeEscapeHelperTests.cs +++ b/src/NUnitTestAdapterTests/NUnitEngineTests/UnicodeEscapeHelperTests.cs @@ -5,14 +5,17 @@ namespace NUnit.VisualStudio.TestAdapter.Tests.NUnitEngineTests; public class UnicodeEscapeHelperTests { - [TestCase("\\u001b", "\u001b")] + [TestCase("\\u001b", "\\u001b")] [TestCase("\\u001", "\\u001")] [TestCase("\\u01", "\\u01")] [TestCase("\\u1", "\\u1")] - [TestCase("\\u001b6", "\u001b6")] + [TestCase("\\u001b6", "\\u001b6")] + [TestCase("\\u001b[0m", "\u001b[0m")] + [TestCase("\\u001b[36m", "\u001b[36m")] + [TestCase("\\u001b[48;5;122mTest", "\u001b[48;5;122mTest")] [TestCase("some-text", "some-text")] - public void UnEscapeUnicodeCharacters_ShouldReplaceBackslashU(string value, string expected) + public void UnEscapeUnicodeColorCodesCharactersShouldReplaceBackslashU(string value, string expected) { - Assert.That(value.UnEscapeUnicodeCharacters(), Is.EqualTo(expected)); + Assert.That(value.UnEscapeUnicodeColorCodesCharacters(), Is.EqualTo(expected)); } } \ No newline at end of file From faaaf7f4fb63f966ce8d3a0b81856d29f06ad3b5 Mon Sep 17 00:00:00 2001 From: Socolin Date: Sat, 27 Jul 2024 14:21:25 -0400 Subject: [PATCH 2/2] Fix indentation --- .../NUnitEngine/UnicodeEscapeHelper.cs | 124 +++++++++--------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs b/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs index e967b2de..616813bf 100644 --- a/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs +++ b/src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs @@ -10,86 +10,86 @@ internal static class UnicodeEscapeHelper public static string UnEscapeUnicodeColorCodesCharacters(this string text) { - if (text == null) - return null; + if (text == null) + return null; - // Small optimization, if there are no "\u", then there is no need to rewrite the string - var firstEscapeIndex = text.IndexOf("\\u", StringComparison.Ordinal); - if (firstEscapeIndex == -1) - return text; + // Small optimization, if there are no "\u", then there is no need to rewrite the string + var firstEscapeIndex = text.IndexOf("\\u", StringComparison.Ordinal); + if (firstEscapeIndex == -1) + return text; - var stringBuilder = new StringBuilder(text.Substring(0, firstEscapeIndex)); - for (var position = firstEscapeIndex; position < text.Length; position++) + var stringBuilder = new StringBuilder(text.Substring(0, firstEscapeIndex)); + for (var position = firstEscapeIndex; position < text.Length; position++) + { + char c = text[position]; + if (c == '\\' && TryUnEscapeOneCharacter(text, position, out var escapedChar, out var extraCharacterRead)) { - char c = text[position]; - if (c == '\\' && TryUnEscapeOneCharacter(text, position, out var escapedChar, out var extraCharacterRead)) - { - stringBuilder.Append(escapedChar); - position += extraCharacterRead; - } - else - { - stringBuilder.Append(c); - } + stringBuilder.Append(escapedChar); + position += extraCharacterRead; + } + else + { + stringBuilder.Append(c); } - - return stringBuilder.ToString(); } + return stringBuilder.ToString(); + } + private static bool TryUnEscapeOneCharacter(string text, int position, out char escapedChar, out int extraCharacterRead) { - const string unicodeEscapeSample = "u0000"; + const string unicodeEscapeSample = "u0000"; - extraCharacterRead = 0; - escapedChar = '\0'; - if (position + unicodeEscapeSample.Length >= text.Length) - return false; + extraCharacterRead = 0; + escapedChar = '\0'; + if (position + unicodeEscapeSample.Length >= text.Length) + return false; - extraCharacterRead = unicodeEscapeSample.Length; - if (!int.TryParse(text.Substring(position + 2, unicodeEscapeSample.Length - 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var escapeValue)) - return false; + extraCharacterRead = unicodeEscapeSample.Length; + if (!int.TryParse(text.Substring(position + 2, unicodeEscapeSample.Length - 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var escapeValue)) + return false; - // Here we only want to escape color escape character when used in a context of a ANSI color code - // See https://github.com/nunit/nunit3-vs-adapter/issues/1124 for more information. - if (escapeValue != EscapeAsciiValue) - return false; + // Here we only want to escape color escape character when used in a context of a ANSI color code + // See https://github.com/nunit/nunit3-vs-adapter/issues/1124 for more information. + if (escapeValue != EscapeAsciiValue) + return false; - if (!IsAnsiColorCodeSequence(text, position + extraCharacterRead + 1)) - return false; + if (!IsAnsiColorCodeSequence(text, position + extraCharacterRead + 1)) + return false; - escapedChar = (char)escapeValue; + escapedChar = (char)escapeValue; - return true; - } + return true; + } - private static bool IsAnsiColorCodeSequence(string text, int position) + private static bool IsAnsiColorCodeSequence(string text, int position) + { + var start = false; + while (position < text.Length) { - var start = false; - while (position < text.Length) + var c = text[position++]; + // Look for the begining [ + if (c == '[' && !start) { - var c = text[position++]; - // Look for the begining [ - if (c == '[' && !start) - { - start = true; - continue; - } - - // Found the 'm' at the end - if (c == 'm' && start) - return true; - - // [ was not found - if (!start) - return false; - - // Ignore all number and ; - var isDigit = c is >= '0' and <= '9'; - if (!isDigit && c != ';') - return false; + start = true; + continue; } - // At the end without the ending 'm' - return false; + // Found the 'm' at the end + if (c == 'm' && start) + return true; + + // [ was not found + if (!start) + return false; + + // Ignore all number and ; + var isDigit = c is >= '0' and <= '9'; + if (!isDigit && c != ';') + return false; } + + // At the end without the ending 'm' + return false; + } } \ No newline at end of file