Skip to content
Open
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
76 changes: 76 additions & 0 deletions csharp/Platform.Exceptions.Tests/ExceptionExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System;
using Xunit;

namespace Platform.Exceptions.Tests
{
public static class ExceptionExtensionsTests
{
[Fact]
public static void ToStringWithAllInnerExceptions_WithSingleException_FormatsProperly()
{
// Arrange
var exception = new ArgumentException("Test message");

// Act
var result = exception.ToStringWithAllInnerExceptions();

// Assert
Assert.Contains("Test message", result);
Assert.Contains(ExceptionExtensions.ExceptionContentsSeparator, result);
}

[Fact]
public static void ToStringWithAllInnerExceptions_WithNestedExceptions_FormatsProperly()
{
// Arrange
var innermost = new ArgumentException("Innermost exception");
var middle = new InvalidOperationException("Middle exception", innermost);
var outer = new Exception("Outer exception", middle);

// Act
var result = outer.ToStringWithAllInnerExceptions();

// Assert
Assert.Contains("Outer exception", result);
Assert.Contains("Middle exception", result);
Assert.Contains("Innermost exception", result);
Assert.Contains("Inner exception: ", result);
}

[Fact]
public static void ToStringWithAllInnerExceptions_WithNullException_HandlesGracefully()
{
// This test ensures our iterative implementation handles edge cases
Exception nullException = null;

// This should not be called on null, but let's test with a valid exception with null inner
var exception = new ArgumentException("Test");

// Act & Assert (should not throw)
var result = exception.ToStringWithAllInnerExceptions();
Assert.NotNull(result);
Assert.Contains("Test", result);
}

[Fact]
public static void ToStringWithAllInnerExceptions_WithDeeplyNestedExceptions_WorksWithoutStackOverflow()
{
// Arrange - Create a deep chain of exceptions to test non-recursive behavior
Exception current = new ArgumentException("Level 0");

// Create 100 levels of nested exceptions
for (int i = 1; i < 100; i++)
{
current = new InvalidOperationException($"Level {i}", current);
}

// Act & Assert (should not throw stack overflow)
var result = current.ToStringWithAllInnerExceptions();

Assert.NotNull(result);
Assert.Contains("Level 0", result);
Assert.Contains("Level 99", result);
Assert.NotEqual(ExceptionExtensions.ExceptionStringBuildingFailed, result);
}
}
}
66 changes: 54 additions & 12 deletions csharp/Platform.Exceptions/ExceptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Platform.Exceptions
Expand Down Expand Up @@ -50,20 +51,61 @@ public static string ToStringWithAllInnerExceptions(this Exception exception)
}
private static void BuildExceptionString(this StringBuilder sb, Exception exception, int level)
{
sb.Indent(level);
sb.AppendLine(exception.Message);
sb.Indent(level);
sb.AppendLine(ExceptionContentsSeparator);
if (exception.InnerException != null)
// Iterative implementation without recursion to avoid stack overflow issues
var current = exception;
var currentLevel = level;

while (current != null)
{
sb.Indent(level);
sb.AppendLine("Inner exception: ");
sb.BuildExceptionString(exception.InnerException, level + 1);
// Step 1: Message with indent
sb.Indent(currentLevel);
sb.AppendLine(current.Message);

// Step 2: Separator with indent
sb.Indent(currentLevel);
sb.AppendLine(ExceptionContentsSeparator);

// Step 3: Check for inner exception
if (current.InnerException != null)
{
sb.Indent(currentLevel);
sb.AppendLine("Inner exception: ");

// Move to inner exception for next iteration
current = current.InnerException;
currentLevel++;
}
else
{
// Step 4: Final separator for innermost
sb.Indent(currentLevel);
sb.AppendLine(ExceptionContentsSeparator);

// Step 5: Stack trace for innermost
sb.Indent(currentLevel);
sb.AppendLine(current.StackTrace);
break;
}
}

// Now we need to add the trailing separators and stack traces for all outer exceptions
// Working backwards from the chain
var exceptions = new List<Exception>();
current = exception;
while (current != null)
{
exceptions.Add(current);
current = current.InnerException;
}

// Add the closing parts for each exception (except the innermost which we already handled)
for (int i = exceptions.Count - 2; i >= 0; i--)
{
sb.Indent(level + i);
sb.AppendLine(ExceptionContentsSeparator);
sb.Indent(level + i);
sb.AppendLine(exceptions[i].StackTrace);
}
sb.Indent(level);
sb.AppendLine(ExceptionContentsSeparator);
sb.Indent(level);
sb.AppendLine(exception.StackTrace);
}
private static void Indent(this StringBuilder sb, int level) => sb.Append('\t', level);
}
Expand Down
126 changes: 126 additions & 0 deletions experiments/RecursionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Text;
using System.Collections.Generic;

namespace Platform.Exceptions.Tests
{
/// <summary>
/// Test program to compare recursive vs iterative implementations of BuildExceptionString
/// </summary>
public class RecursionTest
{
public static readonly string ExceptionContentsSeparator = "---";

static void Main()
{
// Create nested exceptions for testing
var innermost = new ArgumentException("Innermost exception");
var middle = new InvalidOperationException("Middle exception", innermost);
var outer = new Exception("Outer exception", middle);

Console.WriteLine("Testing recursive vs iterative implementations:");
Console.WriteLine();

// Test recursive version (current implementation)
var recursiveResult = ToStringWithAllInnerExceptionsRecursive(outer);
Console.WriteLine("RECURSIVE VERSION:");
Console.WriteLine(recursiveResult);
Console.WriteLine();

// Test iterative version
var iterativeResult = ToStringWithAllInnerExceptionsIterative(outer);
Console.WriteLine("ITERATIVE VERSION:");
Console.WriteLine(iterativeResult);
Console.WriteLine();

// Compare results
bool areEqual = recursiveResult == iterativeResult;
Console.WriteLine($"Results are equal: {areEqual}");

if (!areEqual)
{
Console.WriteLine("DIFFERENCE FOUND!");
Console.WriteLine($"Recursive length: {recursiveResult.Length}");
Console.WriteLine($"Iterative length: {iterativeResult.Length}");
}
}

// Current recursive implementation
public static string ToStringWithAllInnerExceptionsRecursive(Exception exception)
{
try
{
var sb = new StringBuilder();
BuildExceptionStringRecursive(sb, exception, 0);
return sb.ToString();
}
catch (Exception ex)
{
return "Unable to format exception.";
}
}

private static void BuildExceptionStringRecursive(StringBuilder sb, Exception exception, int level)
{
Indent(sb, level);
sb.AppendLine(exception.Message);
Indent(sb, level);
sb.AppendLine(ExceptionContentsSeparator);
if (exception.InnerException != null)
{
Indent(sb, level);
sb.AppendLine("Inner exception: ");
BuildExceptionStringRecursive(sb, exception.InnerException, level + 1);
}
Indent(sb, level);
sb.AppendLine(ExceptionContentsSeparator);
Indent(sb, level);
sb.AppendLine(exception.StackTrace);
}

// New iterative implementation
public static string ToStringWithAllInnerExceptionsIterative(Exception exception)
{
try
{
var sb = new StringBuilder();
BuildExceptionStringIterative(sb, exception);
return sb.ToString();
}
catch (Exception ex)
{
return "Unable to format exception.";
}
}

private static void BuildExceptionStringIterative(StringBuilder sb, Exception exception)
{
var stack = new Stack<(Exception ex, int level)>();
stack.Push((exception, 0));

while (stack.Count > 0)
{
var (current, level) = stack.Pop();

Indent(sb, level);
sb.AppendLine(current.Message);
Indent(sb, level);
sb.AppendLine(ExceptionContentsSeparator);

if (current.InnerException != null)
{
Indent(sb, level);
sb.AppendLine("Inner exception: ");
stack.Push((current.InnerException, level + 1));
}

Indent(sb, level);
sb.AppendLine(ExceptionContentsSeparator);
Indent(sb, level);
sb.AppendLine(current.StackTrace);
}
}

private static void Indent(StringBuilder sb, int level) => sb.Append('\t', level);
}
}
Loading
Loading