From bf9f7218ce50868cfbb1c317ea8077a5d8aa42cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kucha=C5=99?= <honza.kuchar@grifart.cz>
Date: Mon, 11 Mar 2019 11:31:35 +0100
Subject: [PATCH] print suppressed exceptions & their parents information into
 exception message, as this is supported by all debugging tools

---
 src/SuppressedExceptions.php       | 42 +++++++++++++++++++++++++++++-
 tests/nested.phpt                  | 32 +++++++++++++++++++++++
 tests/nested_exception-message.txt | 12 +++++++++
 tests/simple.phpt                  | 14 ++++++----
 tests/simple_exception-message.txt |  9 +++++++
 5 files changed, 103 insertions(+), 6 deletions(-)
 create mode 100644 tests/nested.phpt
 create mode 100644 tests/nested_exception-message.txt
 create mode 100644 tests/simple_exception-message.txt

diff --git a/src/SuppressedExceptions.php b/src/SuppressedExceptions.php
index 40fde3b..3fb078f 100644
--- a/src/SuppressedExceptions.php
+++ b/src/SuppressedExceptions.php
@@ -14,6 +14,7 @@ trait SuppressedExceptions /* implements WithSuppressedExceptions */
 	public function addSuppressed(\Throwable ...$exceptions): void
 	{
 		foreach($exceptions as $exception) {
+			$this->addTextVersionOfExceptionToMessage($exception);
 			$this->suppressedExceptions[] = $exception;
 		}
 	}
@@ -23,4 +24,43 @@ trait SuppressedExceptions /* implements WithSuppressedExceptions */
 		return $this->suppressedExceptions;
 	}
 
-}
\ No newline at end of file
+	private function addTextVersionOfExceptionToMessage(\Throwable $throwable): void
+	{
+		if ($this->suppressedExceptions === []) {
+			$this->message .= "\nSuppressed exceptions:\n";
+		}
+
+		$moveRight = function(string $textToMoveRight, int $offset): string {
+			$replaceWith = "\n" . \str_repeat(' ', $offset);
+			return str_replace(
+				["\r\n","\n","\r"],
+				$replaceWith,
+				$textToMoveRight
+			);
+		};
+
+		$renderSingle = function(\Throwable $throwable): string {
+			$message = $throwable->getMessage();
+			$type = \get_class($throwable);
+			$message =
+				$message === ''
+					? $type
+					: "{$message} ({$type})";
+
+			$fileRelativePath = str_replace(\getcwd() . DIRECTORY_SEPARATOR, '', $throwable->getFile());
+			return "{$fileRelativePath}:{$throwable->getLine()} - {$message}";
+		};
+
+		$renderTree = function(\Throwable $throwable) use ($moveRight, $renderSingle): string {
+			$string = $renderSingle($throwable);
+			$previous = $throwable;
+			while (($previous = $previous->getPrevious()) !== NULL) {
+				$string .= "\n  previous: {$moveRight($renderSingle($previous), 12)}";
+			}
+			return $string;
+		};
+
+		$this->message .= "- {$moveRight($renderTree($throwable), 2)}\n";
+	}
+
+}
diff --git a/tests/nested.phpt b/tests/nested.phpt
new file mode 100644
index 0000000..8bb5382
--- /dev/null
+++ b/tests/nested.phpt
@@ -0,0 +1,32 @@
+<?php declare(strict_types=1);
+
+namespace Grifart\SuppressedExceptions\__tests;
+
+use Grifart\SuppressedExceptions\SuppressedExceptions;
+use Grifart\SuppressedExceptions\WithSuppressedExceptions;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Tester\Assert;
+
+require __DIR__ . '/bootstrap.php';
+
+class TestingSuppressedExceptionsException extends \RuntimeException implements WithSuppressedExceptions
+{
+	use SuppressedExceptions;
+
+}
+
+$previous = new TestingSuppressedExceptionsException('previous', -1, new RuntimeException());
+$exception1 = new TestingSuppressedExceptionsException('message', 42, $previous);
+$exception1->addSuppressed(new RuntimeException('message', 0, $previous));
+
+$exception2 = new TestingSuppressedExceptionsException('message2', 42);
+$exception2->addSuppressed($previous);
+$exception2->addSuppressed($exception1);
+
+Assert::exception(
+	function() use ($exception2) {
+		throw $exception2;
+	},
+	TestingSuppressedExceptionsException::class,
+	\file_get_contents(__DIR__ . '/nested_exception-message.txt')
+);
diff --git a/tests/nested_exception-message.txt b/tests/nested_exception-message.txt
new file mode 100644
index 0000000..af012b4
--- /dev/null
+++ b/tests/nested_exception-message.txt
@@ -0,0 +1,12 @@
+message2
+Suppressed exceptions:
+- nested.phpt:18 - previous (Grifart\SuppressedExceptions\__tests\TestingSuppressedExceptionsException)
+    previous: nested.phpt:18 - Symfony\Component\Console\Exception\RuntimeException
+- nested.phpt:19 - message
+  Suppressed exceptions:
+  - nested.phpt:20 - message (Symfony\Component\Console\Exception\RuntimeException)
+      previous: nested.phpt:18 - previous (Grifart\SuppressedExceptions\__tests\TestingSuppressedExceptionsException)
+      previous: nested.phpt:18 - Symfony\Component\Console\Exception\RuntimeException
+   (Grifart\SuppressedExceptions\__tests\TestingSuppressedExceptionsException)
+    previous: nested.phpt:18 - previous (Grifart\SuppressedExceptions\__tests\TestingSuppressedExceptionsException)
+    previous: nested.phpt:18 - Symfony\Component\Console\Exception\RuntimeException
diff --git a/tests/simple.phpt b/tests/simple.phpt
index 48c9459..2573cc2 100644
--- a/tests/simple.phpt
+++ b/tests/simple.phpt
@@ -19,15 +19,19 @@ $previous = new TestingSuppressedExceptionsException('previous', -1, new Runtime
 $exception = new TestingSuppressedExceptionsException('message', 42, $previous);
 
 $exception->addSuppressed($suppressed1 = new \RuntimeException());
-$exception->addSuppressed($suppressed2 = new \LogicException());
+$exception->addSuppressed($suppressed2 = new \LogicException('This is message'));
 $exception->addSuppressed($suppressed3 = new \Error());
-$exception->addSuppressed($suppressed4 = new \Exception());
+$exception->addSuppressed($suppressed4 = new \Exception('With previous', 0, $previous));
 $exception->addSuppressed($suppressed5 = new TestingSuppressedExceptionsException());
 
 // test that can be thrown
-Assert::exception(function() use ($exception) {
-	throw $exception;
-}, TestingSuppressedExceptionsException::class);
+Assert::exception(
+	function() use ($exception) {
+		throw $exception;
+	},
+	TestingSuppressedExceptionsException::class,
+	\file_get_contents(__DIR__ . '/simple_exception-message.txt')
+);
 
 // previous
 Assert::same($previous, $exception->getPrevious());
diff --git a/tests/simple_exception-message.txt b/tests/simple_exception-message.txt
new file mode 100644
index 0000000..e82674a
--- /dev/null
+++ b/tests/simple_exception-message.txt
@@ -0,0 +1,9 @@
+message
+Suppressed exceptions:
+- simple.phpt:21 - RuntimeException
+- simple.phpt:22 - This is message (LogicException)
+- simple.phpt:23 - Error
+- simple.phpt:24 - With previous (Exception)
+    previous: simple.phpt:18 - previous (Grifart\SuppressedExceptions\__tests\TestingSuppressedExceptionsException)
+    previous: simple.phpt:18 - Symfony\Component\Console\Exception\RuntimeException
+- simple.phpt:25 - Grifart\SuppressedExceptions\__tests\TestingSuppressedExceptionsException
-- 
GitLab