diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f789c1..6e4a958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -- Backtrace limiting options, including a default and per-test +- Ability to limit backtrace records logged, with an override-able default +- Ability to log backtraces for statements run, either for all tests or a single test - `BacktraceCollection` class to contain backtrace logs - `Collection::map()` method -- Backtrace container property to the PDOStatement class -- Backtrace logging options to the Wye container ## [0.6.0] - 2018-01-28 ### Added diff --git a/src/Collections/BacktraceCollection.php b/src/Collections/BacktraceCollection.php index 0d42111..aaeb6d4 100644 --- a/src/Collections/BacktraceCollection.php +++ b/src/Collections/BacktraceCollection.php @@ -4,5 +4,56 @@ class BacktraceCollection extends Collection implements BacktraceCollectionInterface { + /** + * Return a new collection of strings formatted generally into the + * print_debug_backtrace() format. + * + * @return CollectionInterface + */ + public function getTrace() + { + return $this->map(function ($item, $key) { + // Convert arguments into something more readable + if (!empty($item['args'])) { + $args = array_map(function ($item) { + switch (true) { + case $item === null: + return 'null'; + case $item === true: + return 'true'; + case $item === false: + return 'false'; + case is_array($item): + if (!count($item)) { + return 'array(0)'; + } elseif (array_keys($item) !== range(0, count($item) -1)) { + return 'associative-array(' . count($item) . ')'; + } + return 'sequential-array(' . count($item) . ')'; + case is_object($item): + return get_class($item); + case is_string($item): + return "\"{$item}\""; + case is_numeric($item): + return $item; + } + return "\"{$item}\""; + }, $item['args']); + } else { + $args = []; + } + + return sprintf( + '#%d %s%s%s(%s) called at [%s:%d]', + $key, + !empty($item['class']) ? $item['class'] : null, + !empty($item['type']) ? $item['type'] : null, + !empty($item['function']) ? $item['function'] : null, + implode(', ', $args), + !empty($item['file']) ? $item['file'] : null, + !empty($item['line']) ? $item['line'] : null + ); + }); + } } diff --git a/src/Wye.php b/src/Wye.php index 4e62fc0..aa9bcb5 100644 --- a/src/Wye.php +++ b/src/Wye.php @@ -228,6 +228,17 @@ public static function executeStatement( //Increment number of queries run static::incrementNumQueries(); + + if (static::shouldLogBacktrace()) { + $statement->setBacktrace( + static::makeBacktraceCollection( + debug_backtrace( + DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, + static::resolveBacktraceLimit() + ) + ) + ); + } } diff --git a/tests/Feature/StoreBacktracesForStatementsTest.php b/tests/Feature/StoreBacktracesForStatementsTest.php new file mode 100644 index 0000000..54e5531 --- /dev/null +++ b/tests/Feature/StoreBacktracesForStatementsTest.php @@ -0,0 +1,132 @@ +execute(); + + $this->assertNull($stmt->getBacktrace()); + } + + public function testOnForAllTestsStoresBacktrace() + { + Wye::logBacktraceForAllTests(); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt2 = Wye::makeStatement('SELECT * FROM table_name', []); + + $stmt->execute(); + $stmt2->execute(); + + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt->getBacktrace()); + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt2->getBacktrace()); + } + + public function testOnForTestStoresBacktrace() + { + Wye::logBacktraceForTest(); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt2 = Wye::makeStatement('SELECT * FROM table_name', []); + + $stmt->execute(); + $stmt2->execute(); + + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt->getBacktrace()); + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt2->getBacktrace()); + } + + public function testOnForAllTestsSpansResets() + { + Wye::logBacktraceForAllTests(); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt->execute(); + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt->getBacktrace()); + + Wye::reset(); + + $stmt2 = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt2->execute(); + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt2->getBacktrace()); + } + + public function testOnForOneTestDoesNotSpanResets() + { + Wye::logBacktraceForTest(); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt->execute(); + $this->assertInstanceOf(BacktraceCollectionInterface::class, $stmt->getBacktrace()); + + Wye::reset(); + + $stmt2 = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt2->execute(); + $this->assertNull($stmt2->getBacktrace()); + } + + public function testBacktraceDefaultLimitAppliedIfNeeded() + { + Wye::logBacktraceForTest(); + Wye::setBacktraceDefaultLimit(3); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt->execute(); + + $this->assertCount(3, $stmt->getBacktrace()); + } + + public function testPerTestBacktraceLimitApplied() + { + Wye::logBacktraceForTest(); + Wye::setBacktraceDefaultLimit(3); + Wye::setBacktraceLimit(2); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt->execute(); + + $this->assertCount(2, $stmt->getBacktrace()); + } + + public function testBacktraceDefaultLimitSpansResets() + { + Wye::logBacktraceForAllTests(); + Wye::setBacktraceDefaultLimit(3); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt->execute(); + $this->assertCount(3, $stmt->getBacktrace()); + + Wye::reset(); + + $stmt2 = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt2->execute(); + $this->assertCount(3, $stmt2->getBacktrace()); + } + + public function testBacktraceLimitDoesNotSpanResets() + { + Wye::logBacktraceForAllTests(); + Wye::setBacktraceDefaultLimit(3); + Wye::setBacktraceLimit(2); + + $stmt = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt->execute(); + $this->assertCount(2, $stmt->getBacktrace()); + + Wye::reset(); + + $stmt2 = Wye::makeStatement('SELECT * FROM table_name', []); + $stmt2->execute(); + $this->assertCount(3, $stmt2->getBacktrace()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 8ee1a9e..4176c1e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,5 +11,6 @@ public function setUp() { Wye::reset(); Wye::resetBacktraceForAllTests(); + Wye::resetBacktraceDefaultLimit(); } } diff --git a/tests/Unit/Collections/BacktraceCollection/GetTraceTest.php b/tests/Unit/Collections/BacktraceCollection/GetTraceTest.php new file mode 100644 index 0000000..5327118 --- /dev/null +++ b/tests/Unit/Collections/BacktraceCollection/GetTraceTest.php @@ -0,0 +1,105 @@ +getTrace(); + + $this->assertNotSame($new, $collection); + $this->assertInstanceOf(CollectionInterface::class, $new); + } + + public function testFormatsItemsCorrectly() + { + $all = [ + 'class' => 'a', + 'type' => 'b', + 'function' => 'c', + 'file' => 'd', + 'line' => '1' + ]; + + $keys = [ + 'class', + 'type', + 'function', + 'file', + 'line', + ]; + + $data[] = $all; + + foreach ($keys as $key) { + $data[] = array_merge($all, [$key => null]); + } + + $collection = Wye::makeBacktraceCollection($data); + + $expected = [ + '#0 abc() called at [d:1]', + '#1 bc() called at [d:1]', + '#2 ac() called at [d:1]', + '#3 ab() called at [d:1]', + '#4 abc() called at [:1]', + '#5 abc() called at [d:0]', + ]; + + $this->assertSame($expected, $collection->getTrace()->all()); + } + + public function testFormatsArgsCorrectly() + { + $values = [ + [true], + [false], + [null], + [[]], + [[1, 2]], + [['test' => 1]], + [Wye::makeBacktraceCollection()], + ['123'], + ['1.2'], + ['-8'], + ['cool'], + [123], + [1.2], + [-8], + ['test', 'multiple', 'values'], + ]; + + foreach ($values as $value) { + $data[] = ['args' => $value]; + } + + $expected = [ + '#0 (true) called at [:0]', + '#1 (false) called at [:0]', + '#2 (null) called at [:0]', + '#3 (array(0)) called at [:0]', + '#4 (sequential-array(2)) called at [:0]', + '#5 (associative-array(1)) called at [:0]', + '#6 (' . BacktraceCollection::class . ') called at [:0]', + '#7 ("123") called at [:0]', + '#8 ("1.2") called at [:0]', + '#9 ("-8") called at [:0]', + '#10 ("cool") called at [:0]', + '#11 (123) called at [:0]', + '#12 (1.2) called at [:0]', + '#13 (-8) called at [:0]', + '#14 ("test", "multiple", "values") called at [:0]', + ]; + + $collection = Wye::makeBacktraceCollection($data); + + $this->assertSame($expected, $collection->getTrace()->all()); + } +}