mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2026-01-02 01:03:32 +01:00
Add support for rendering exceptions
This commit is contained in:
committed by
Patrik Svensson
parent
971f9032ba
commit
3c3afe7439
@@ -24,3 +24,6 @@ dotnet_diagnostic.CA2000.severity = none
|
||||
|
||||
# SA1118: Parameter should not span multiple lines
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
|
||||
# CA1031: Do not catch general exception types
|
||||
dotnet_diagnostic.CA1031.severity = none
|
||||
|
||||
23
src/Spectre.Console.Tests/Data/Exceptions.cs
Normal file
23
src/Spectre.Console.Tests/Data/Exceptions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Tests.Data
|
||||
{
|
||||
public static class TestExceptions
|
||||
{
|
||||
[SuppressMessage("Usage", "CA1801:Review unused parameters", Justification = "<Pending>")]
|
||||
public static bool MethodThatThrows(int? number) => throw new InvalidOperationException("Throwing!");
|
||||
|
||||
public static void ThrowWithInnerException()
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodThatThrows(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Something threw!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,34 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
private static readonly Regex _lineNumberRegex = new Regex(":\\d+", RegexOptions.Singleline);
|
||||
private static readonly Regex _filenameRegex = new Regex("\\sin\\s.*cs:nn", RegexOptions.Multiline);
|
||||
|
||||
public static string NormalizeLineEndings(this string text)
|
||||
{
|
||||
return text?.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase)
|
||||
?.Replace("\r", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string NormalizeStackTrace(this 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}";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Spectre.Console.Tests/Tools/MarkupConsoleFixture.cs
Normal file
44
src/Spectre.Console.Tests/Tools/MarkupConsoleFixture.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Tests.Tools
|
||||
{
|
||||
public sealed class MarkupConsoleFixture : IDisposable, IAnsiConsole
|
||||
{
|
||||
private readonly StringWriter _writer;
|
||||
private readonly IAnsiConsole _console;
|
||||
|
||||
public string Output => _writer.ToString().TrimEnd('\n');
|
||||
|
||||
public Capabilities Capabilities => _console.Capabilities;
|
||||
public Encoding Encoding => _console.Encoding;
|
||||
public int Width { get; }
|
||||
public int Height => _console.Height;
|
||||
|
||||
public MarkupConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
|
||||
{
|
||||
_writer = new StringWriter();
|
||||
_console = AnsiConsole.Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = ansi,
|
||||
ColorSystem = (ColorSystemSupport)system,
|
||||
Out = _writer,
|
||||
LinkIdentityGenerator = new TestLinkIdentityGenerator(),
|
||||
});
|
||||
|
||||
Width = width;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_writer?.Dispose();
|
||||
}
|
||||
|
||||
public void Write(Segment segment)
|
||||
{
|
||||
_console.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
@@ -50,5 +51,16 @@ namespace Spectre.Console.Tests
|
||||
|
||||
Writer.Write(segment.Text);
|
||||
}
|
||||
|
||||
public string[] WriteExceptionAndGetLines(Exception ex, ExceptionFormats formats = ExceptionFormats.None)
|
||||
{
|
||||
this.WriteException(ex, formats);
|
||||
|
||||
return Output.NormalizeStackTrace()
|
||||
.NormalizeLineEndings()
|
||||
.Split(new char[] { '\n' })
|
||||
.Select(line => line.TrimEnd())
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
src/Spectre.Console.Tests/Unit/ExceptionTests.cs
Normal file
99
src/Spectre.Console.Tests/Unit/ExceptionTests.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Tests.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class ExceptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Write_Exception()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(4);
|
||||
result[0].ShouldBe("System.InvalidOperationException: Throwing!");
|
||||
result[1].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[2].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception>b__0_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[3].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Exception_With_Shortened_Types()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex, ExceptionFormats.ShortenTypes);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(4);
|
||||
result[0].ShouldBe("InvalidOperationException: Throwing!");
|
||||
result[1].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[2].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception_With_Shortened_Types>b__1_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[3].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Exception_With_Shortened_Methods()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex, ExceptionFormats.ShortenMethods);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(4);
|
||||
result[0].ShouldBe("System.InvalidOperationException: Throwing!");
|
||||
result[1].ShouldBe(" at MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[2].ShouldBe(" at <Should_Write_Exception_With_Shortened_Methods>b__2_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[3].ShouldBe(" at GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Exception_With_Inner_Exception()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.ThrowWithInnerException());
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(7);
|
||||
result[0].ShouldBe("System.InvalidOperationException: Something threw!");
|
||||
result[1].ShouldBe(" System.InvalidOperationException: Throwing!");
|
||||
result[2].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[3].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.ThrowWithInnerException() in /xyz/Exceptions.cs:nn");
|
||||
result[4].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.ThrowWithInnerException() in /xyz/Exceptions.cs:nn");
|
||||
result[5].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception_With_Inner_Exception>b__3_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[6].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
public static Exception GetException(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Exception harness failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
@@ -298,5 +300,33 @@ namespace Spectre.Console.Tests.Unit
|
||||
console.Lines[3].ShouldBe("│ └─────────────┘ │");
|
||||
console.Lines[4].ShouldBe("└─────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Wrap_Content_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 84);
|
||||
var rows = new List<IRenderable>();
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn().PadLeft(2).PadRight(0));
|
||||
grid.AddColumn(new GridColumn().PadLeft(1).PadRight(0));
|
||||
grid.AddRow("at", "[grey]System.Runtime.CompilerServices.TaskAwaiter.[/][yellow]HandleNonSuccessAndDebuggerNotification[/]([blue]Task[/] task)");
|
||||
rows.Add(grid);
|
||||
|
||||
var panel = new Panel(grid)
|
||||
.Expand().RoundedBorder()
|
||||
.SetBorderStyle(Style.WithForeground(Color.Grey))
|
||||
.SetHeader("Short paths ", Style.WithForeground(Color.Grey));
|
||||
|
||||
// When
|
||||
console.Render(panel);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(4);
|
||||
console.Lines[0].ShouldBe("╭─Short paths ─────────────────────────────────────────────────────────────────────╮");
|
||||
console.Lines[1].ShouldBe("│ at System.Runtime.CompilerServices.TaskAwaiter. │");
|
||||
console.Lines[2].ShouldBe("│ HandleNonSuccessAndDebuggerNotification(Task task) │");
|
||||
console.Lines[3].ShouldBe("╰──────────────────────────────────────────────────────────────────────────────────╯");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user