mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-09 16:16:13 -04:00
184 lines
6.7 KiB
C#
184 lines
6.7 KiB
C#
namespace LibationUiBase.Tests;
|
||
|
||
[TestClass]
|
||
public class ExceptionDisplayTests
|
||
{
|
||
/// <summary>Outer exception with a fixed <see cref="Exception.StackTrace"/> so golden-string tests are stable.</summary>
|
||
private sealed class ExceptionWithFixedStack : Exception
|
||
{
|
||
private string _fixedStack { get; }
|
||
public ExceptionWithFixedStack(string message, Exception? innerException, string fixedStack)
|
||
: base(message, innerException) => _fixedStack = fixedStack;
|
||
public override string? StackTrace => _fixedStack;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Golden output for <see cref="ExceptionDisplay.FormatMessageAndStackTrace"/> when there are fourteen
|
||
/// <see cref="Exception.InnerException"/> links (fifteen messages: outer L0 … deepest L14): first ten inners,
|
||
/// two omitted (L11–L12), then the two deepest (L13–L14), then the outer stack trace.
|
||
/// </summary>
|
||
[TestMethod]
|
||
public void _FullText_14Levels()
|
||
{
|
||
const string stack = "<<GOLDEN-TEST-STACK-TRACE>>";
|
||
var messages = Enumerable.Range(0, 15).Select(i => $"L{i}").ToArray();
|
||
var ex = CreateChainWithFixedStack(stack, messages);
|
||
|
||
const string expected = """
|
||
L0
|
||
|
||
Inner exception: L1
|
||
|
||
Inner exception: L2
|
||
|
||
Inner exception: L3
|
||
|
||
Inner exception: L4
|
||
|
||
Inner exception: L5
|
||
|
||
Inner exception: L6
|
||
|
||
Inner exception: L7
|
||
|
||
Inner exception: L8
|
||
|
||
Inner exception: L9
|
||
|
||
Inner exception: L10
|
||
|
||
2 inner exceptions omitted.
|
||
|
||
Inner exception: L13
|
||
|
||
Inner exception: L14
|
||
|
||
<<GOLDEN-TEST-STACK-TRACE>>
|
||
""";
|
||
Assert.AreEqual(expected.ReplaceLineEndings("\n"), Format(ex));
|
||
}
|
||
|
||
/// <inheritdoc cref="CreateChain"/>
|
||
/// <remarks>Same chain shape as <see cref="CreateChain"/>, but the outer exception reports <paramref name="stackTrace"/> from <see cref="Exception.StackTrace"/>.</remarks>
|
||
private static Exception CreateChainWithFixedStack(string stackTrace, params string[] messages)
|
||
{
|
||
if (messages.Length == 0)
|
||
throw new ArgumentException("At least one message is required.", nameof(messages));
|
||
|
||
var innermost = new Exception(messages[^1]);
|
||
for (var i = messages.Length - 2; i >= 1; i--)
|
||
innermost = new Exception(messages[i], innermost);
|
||
return new ExceptionWithFixedStack(messages[0], innermost, stackTrace);
|
||
}
|
||
|
||
/// <summary>messages[0] = outer; each following string is the next <see cref="Exception.InnerException"/> message.</summary>
|
||
private static Exception CreateChain(params string[] messages)
|
||
{
|
||
if (messages.Length == 0)
|
||
throw new ArgumentException("At least one message is required.", nameof(messages));
|
||
|
||
var innermost = new Exception(messages[^1]);
|
||
for (var i = messages.Length - 2; i >= 0; i--)
|
||
innermost = new Exception(messages[i], innermost);
|
||
return innermost;
|
||
}
|
||
|
||
private static string Format(Exception ex) => ExceptionDisplay.FormatMessageAndStackTrace(ex).ReplaceLineEndings("\n");
|
||
|
||
[TestMethod]
|
||
public void NoInnerException_ContainsOuterMessageAndStack()
|
||
{
|
||
var ex = new Exception("outer only");
|
||
var text = Format(ex);
|
||
|
||
Assert.StartsWith("outer only\n", text);
|
||
Assert.IsFalse(text.Contains("Inner exception:", StringComparison.Ordinal));
|
||
if (ex.StackTrace is not null)
|
||
Assert.IsTrue(text.Contains(ex.StackTrace, StringComparison.Ordinal));
|
||
}
|
||
|
||
[TestMethod]
|
||
public void OneInner_ShowsInnerLine()
|
||
{
|
||
var ex = CreateChain("outer", "inner-a");
|
||
var text = Format(ex);
|
||
|
||
var expectedHead = """
|
||
outer
|
||
|
||
Inner exception: inner-a
|
||
|
||
|
||
""".ReplaceLineEndings("\n");
|
||
Assert.AreEqual(expectedHead + ex.StackTrace, text);
|
||
}
|
||
|
||
[TestMethod]
|
||
public void TenInners_ShowsAllTen()
|
||
{
|
||
var messages = new[] { "m0", "m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9", "m10" };
|
||
var ex = CreateChain(messages);
|
||
var text = Format(ex);
|
||
|
||
for (var i = 1; i <= 10; i++)
|
||
Assert.IsTrue(text.Contains($"Inner exception: m{i}\n", StringComparison.Ordinal), $"missing inner m{i}");
|
||
Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
[TestMethod]
|
||
public void ElevenInners_FirstTenThenDeepestOnly_NoOmitLine()
|
||
{
|
||
var messages = Enumerable.Range(0, 12).Select(i => $"n{i}").ToArray();
|
||
var ex = CreateChain(messages);
|
||
var text = Format(ex);
|
||
|
||
for (var i = 1; i <= 10; i++)
|
||
Assert.IsTrue(text.Contains($"Inner exception: n{i}\n", StringComparison.Ordinal));
|
||
Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase));
|
||
Assert.IsTrue(text.Contains("Inner exception: n11\n", StringComparison.Ordinal));
|
||
}
|
||
|
||
[TestMethod]
|
||
public void TwelveInners_FirstTenThenLastTwo_NoOmitLine()
|
||
{
|
||
var messages = Enumerable.Range(0, 13).Select(i => $"p{i}").ToArray();
|
||
var ex = CreateChain(messages);
|
||
var text = Format(ex);
|
||
|
||
for (var i = 1; i <= 10; i++)
|
||
Assert.IsTrue(text.Contains($"Inner exception: p{i}\n", StringComparison.Ordinal));
|
||
Assert.IsFalse(text.Contains("omitted", StringComparison.OrdinalIgnoreCase));
|
||
Assert.IsTrue(text.Contains("Inner exception: p11\n", StringComparison.Ordinal));
|
||
Assert.IsTrue(text.Contains("Inner exception: p12\n", StringComparison.Ordinal));
|
||
}
|
||
|
||
[TestMethod]
|
||
public void ThirteenInners_FirstTen_OmitOne_ThenLastTwo()
|
||
{
|
||
var messages = Enumerable.Range(0, 14).Select(i => $"q{i}").ToArray();
|
||
var ex = CreateChain(messages);
|
||
var text = Format(ex);
|
||
|
||
for (var i = 1; i <= 10; i++)
|
||
Assert.IsTrue(text.Contains($"Inner exception: q{i}\n", StringComparison.Ordinal));
|
||
Assert.IsTrue(text.Contains("1 inner exception omitted.\n", StringComparison.Ordinal));
|
||
Assert.IsFalse(text.Contains("Inner exception: q11\n", StringComparison.Ordinal), "omitted inner should not appear");
|
||
Assert.IsTrue(text.Contains("Inner exception: q12\n", StringComparison.Ordinal));
|
||
Assert.IsTrue(text.Contains("Inner exception: q13\n", StringComparison.Ordinal));
|
||
}
|
||
|
||
[TestMethod]
|
||
public void ManyInners_OmitCountPlural()
|
||
{
|
||
var depth = 20;
|
||
var messages = Enumerable.Range(0, depth + 1).Select(i => $"x{i}").ToArray();
|
||
var ex = CreateChain(messages);
|
||
var text = Format(ex);
|
||
|
||
var omitted = depth - 10 - 2;
|
||
Assert.IsTrue(text.Contains($"{omitted} inner exceptions omitted.\n", StringComparison.Ordinal));
|
||
Assert.IsTrue(text.Contains($"Inner exception: x{depth - 1}\n", StringComparison.Ordinal));
|
||
Assert.IsTrue(text.Contains($"Inner exception: x{depth}\n", StringComparison.Ordinal));
|
||
}
|
||
}
|