diff --git a/csharp/Platform.Exceptions.Tests/ExceptionExtensionsTests.cs b/csharp/Platform.Exceptions.Tests/ExceptionExtensionsTests.cs new file mode 100644 index 0000000..af5cec4 --- /dev/null +++ b/csharp/Platform.Exceptions.Tests/ExceptionExtensionsTests.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Exceptions/ExceptionExtensions.cs b/csharp/Platform.Exceptions/ExceptionExtensions.cs index 0694252..e2541f0 100644 --- a/csharp/Platform.Exceptions/ExceptionExtensions.cs +++ b/csharp/Platform.Exceptions/ExceptionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text; namespace Platform.Exceptions @@ -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(); + 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); } diff --git a/experiments/RecursionTest.cs b/experiments/RecursionTest.cs new file mode 100644 index 0000000..76a4b6c --- /dev/null +++ b/experiments/RecursionTest.cs @@ -0,0 +1,126 @@ +using System; +using System.Text; +using System.Collections.Generic; + +namespace Platform.Exceptions.Tests +{ + /// + /// Test program to compare recursive vs iterative implementations of BuildExceptionString + /// + 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); + } +} \ No newline at end of file diff --git a/experiments/RecursionTest/Program.cs b/experiments/RecursionTest/Program.cs new file mode 100644 index 0000000..b751396 --- /dev/null +++ b/experiments/RecursionTest/Program.cs @@ -0,0 +1,165 @@ +using System; +using System.Text; +using System.Collections.Generic; + +namespace Platform.Exceptions.Tests +{ + /// + /// Test program to compare recursive vs iterative implementations of BuildExceptionString + /// + 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) + { + // Let's trace through the exact recursive pattern step by step + // For each exception, we need: + // 1. Message + // 2. Separator + // 3. If there's an inner exception: "Inner exception: " + recurse + // 4. Separator + // 5. Stack trace + + // Use a simple iteration from outer to inner + var current = exception; + var level = 0; + + while (current != null) + { + // Step 1: Message with indent + Indent(sb, level); + sb.AppendLine(current.Message); + + // Step 2: Separator with indent + Indent(sb, level); + sb.AppendLine(ExceptionContentsSeparator); + + // Step 3: Check for inner exception + if (current.InnerException != null) + { + Indent(sb, level); + sb.AppendLine("Inner exception: "); + + // Move to inner exception for next iteration + current = current.InnerException; + level++; + } + else + { + // Step 4: Final separator for innermost + Indent(sb, level); + sb.AppendLine(ExceptionContentsSeparator); + + // Step 5: Stack trace for innermost + Indent(sb, level); + 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(); + 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--) + { + Indent(sb, i); + sb.AppendLine(ExceptionContentsSeparator); + Indent(sb, i); + sb.AppendLine(exceptions[i].StackTrace); + } + } + + private static void Indent(StringBuilder sb, int level) => sb.Append('\t', level); + } +} diff --git a/experiments/RecursionTest/RecursionTest.csproj b/experiments/RecursionTest/RecursionTest.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/experiments/RecursionTest/RecursionTest.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/experiments/RecursionTest/output.txt b/experiments/RecursionTest/output.txt new file mode 100644 index 0000000..1b1ae03 --- /dev/null +++ b/experiments/RecursionTest/output.txt @@ -0,0 +1,42 @@ +/tmp/gh-issue-solver-1757842410291/experiments/RecursionTest/Program.cs(57,30): warning CS0168: The variable 'ex' is declared but never used [/tmp/gh-issue-solver-1757842410291/experiments/RecursionTest/RecursionTest.csproj] +/tmp/gh-issue-solver-1757842410291/experiments/RecursionTest/Program.cs(90,30): warning CS0168: The variable 'ex' is declared but never used [/tmp/gh-issue-solver-1757842410291/experiments/RecursionTest/RecursionTest.csproj] +Testing recursive vs iterative implementations: + +RECURSIVE VERSION: +Outer exception +--- +Inner exception: + Middle exception + --- + Inner exception: + Innermost exception + --- + --- + + --- + +--- + + + +ITERATIVE VERSION: +Outer exception +--- +Inner exception: +--- + + Middle exception + --- + Inner exception: + --- + + Innermost exception + --- + --- + + + +Results are equal: False +DIFFERENCE FOUND! +Recursive length: 129 +Iterative length: 129 diff --git a/experiments/RecursionTest/output2.txt b/experiments/RecursionTest/output2.txt new file mode 100644 index 0000000..88703f1 --- /dev/null +++ b/experiments/RecursionTest/output2.txt @@ -0,0 +1,37 @@ +Testing recursive vs iterative implementations: + +RECURSIVE VERSION: +Outer exception +--- +Inner exception: + Middle exception + --- + Inner exception: + Innermost exception + --- + --- + + --- + +--- + + + +ITERATIVE VERSION: +Outer exception +--- +Inner exception: + Middle exception + --- + Inner exception: + Innermost exception + --- + --- + + --- + +--- + + + +Results are equal: True