Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only un-escape unicode character encoding color codes Fix #1124 #1147

Merged
merged 3 commits into from
Sep 4, 2024
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
4 changes: 2 additions & 2 deletions src/NUnitTestAdapter/NUnitEngine/NUnitTestEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -165,7 +165,7 @@ public IEnumerable<NUnitAttachment> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
8 changes: 4 additions & 4 deletions src/NUnitTestAdapter/NUnitEngine/NUnitTestEventTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -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";
}

Expand Down
102 changes: 71 additions & 31 deletions src/NUnitTestAdapter/NUnitEngine/UnicodeEscapeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,90 @@ 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;
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;

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)
{
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request: Would it make sense to add a test for a few more invalid inputs, such as a leading [ but missing trailing m ?

[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));
}
}