From 010beffae042a039daa273c3bae41643ad3c04ef Mon Sep 17 00:00:00 2001 From: Phil Scott Date: Mon, 10 Feb 2025 20:57:38 -0500 Subject: [PATCH] Normalizes paths when writing exceptions to the console for tests. The verified output for Should_Write_GenericException was outputting ProjectDirectory}Data\Exceptions.cs on my Windows machine, but the Verified version wasProjectDirectory}Data/Exceptions.cs Wasn't causing a build issue because we run those on Ubuntu, but locally it was giving me an error. --- .../TestConsoleExtensions.Exceptions.cs | 59 ------------------- .../Utilities/TestConsoleExtensions.cs | 41 +++++++++++++ 2 files changed, 41 insertions(+), 59 deletions(-) delete mode 100644 src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.Exceptions.cs create mode 100644 src/Tests/Spectre.Console.Tests/Utilities/TestConsoleExtensions.cs diff --git a/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.Exceptions.cs b/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.Exceptions.cs deleted file mode 100644 index 582ddf18..00000000 --- a/src/Spectre.Console.Testing/Extensions/TestConsoleExtensions.Exceptions.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Spectre.Console.Testing; - -/// -/// Provides extension methods for working with in a testing context, -/// including stack trace normalization for consistent and deterministic test output. -/// -public static partial class TestConsoleExtensions -{ - private static readonly Regex _lineNumberRegex = new Regex(":\\d+", RegexOptions.Singleline); - private static readonly Regex _filenameRegex = new Regex("\\sin\\s.*cs:nn", RegexOptions.Multiline); - - /// - /// Writes the given exception to the and returns a normalized string - /// representation of the exception, with file paths and line numbers sanitized. - /// - /// The to write to. - /// The exception to write and normalize. - /// Optional formatting options for exception output. - /// A normalized string of the exception's output, safe for snapshot testing. - /// - /// Thrown if the console's output buffer is not empty before writing the exception. - /// - public static string WriteNormalizedException(this TestConsole console, Exception ex, ExceptionFormats formats = ExceptionFormats.Default) - { - if (!string.IsNullOrWhiteSpace(console.Output)) - { - throw new InvalidOperationException("Output buffer is not empty."); - } - - console.WriteException(ex, formats); - return string.Join("\n", NormalizeStackTrace(console.Output) - .NormalizeLineEndings() - .Split(new char[] { '\n' }) - .Select(line => line.TrimEnd())); - } - - /// - /// Normalizes a stack trace string by replacing line numbers with ":nn" - /// and converting full file paths to a fixed placeholder path ("/xyz/filename.cs"). - /// - /// The stack trace text to normalize. - /// A sanitized stack trace suitable for stable testing output. - public static string NormalizeStackTrace(string text) - { - text = _lineNumberRegex.Replace(text, match => - { - return ":nn"; - }); - - return _filenameRegex.Replace(text, match => - { - var value = match.Value; - var index = value.LastIndexOfAny(new[] { '\\', '/' }); - var filename = value.Substring(index + 1, value.Length - index - 1); - - return $" in /xyz/{filename}"; - }); - } -} diff --git a/src/Tests/Spectre.Console.Tests/Utilities/TestConsoleExtensions.cs b/src/Tests/Spectre.Console.Tests/Utilities/TestConsoleExtensions.cs new file mode 100644 index 00000000..8617ebca --- /dev/null +++ b/src/Tests/Spectre.Console.Tests/Utilities/TestConsoleExtensions.cs @@ -0,0 +1,41 @@ +namespace Spectre.Console.Tests; + +public static class TestConsoleExtensions +{ + private static readonly Regex _lineNumberRegex = new Regex(":\\d+", RegexOptions.Singleline); + private static readonly Regex _filenameRegex = new Regex("\\sin\\s.*cs:nn", RegexOptions.Multiline); + private static readonly Regex _pathSeparatorRegex = new Regex(@"[/\\]+"); + + public static string WriteNormalizedException(this TestConsole console, Exception ex, ExceptionFormats formats = ExceptionFormats.Default) + { + if (!string.IsNullOrWhiteSpace(console.Output)) + { + throw new InvalidOperationException("Output buffer is not empty."); + } + + console.WriteException(ex, formats); + return string.Join("\n", NormalizeStackTrace(console.Output) + .NormalizeLineEndings() + .Split(new char[] { '\n' }) + .Select(line => line.TrimEnd())); + } + + public static string NormalizeStackTrace(string text) + { + // First normalize line numbers + text = _lineNumberRegex.Replace(text, ":nn"); + + // Then normalize paths and filenames + text = _filenameRegex.Replace(text, match => + { + var value = match.Value; + var index = value.LastIndexOfAny(new[] { '\\', '/' }); + var filename = value.Substring(index + 1, value.Length - index - 1); + + return $" in /xyz/{filename}"; + }); + + // Finally normalize any remaining path separators + return _pathSeparatorRegex.Replace(text, "/"); + } +}