diff --git a/src/Collector/Console/ConsoleAppInfoCollector.php b/src/Collector/Console/ConsoleAppInfoCollector.php index 3211ee285..5aa75a225 100644 --- a/src/Collector/Console/ConsoleAppInfoCollector.php +++ b/src/Collector/Console/ConsoleAppInfoCollector.php @@ -43,7 +43,7 @@ public function getCollected(): array public function collect(object $event): void { - if (!is_object($event) || !$this->isActive()) { + if (!$this->isActive()) { return; } diff --git a/src/Collector/ExceptionCollector.php b/src/Collector/ExceptionCollector.php index f0ae8bfaa..32b42fbdb 100644 --- a/src/Collector/ExceptionCollector.php +++ b/src/Collector/ExceptionCollector.php @@ -19,6 +19,9 @@ public function __construct(private TimelineCollector $timelineCollector) public function getCollected(): array { + if (!$this->isActive()) { + return []; + } if ($this->exception === null) { return []; } @@ -45,6 +48,9 @@ public function collect(ApplicationError $error): void public function getSummary(): array { + if (!$this->isActive()) { + return []; + } return [ 'exception' => $this->exception === null ? [] : [ 'class' => $this->exception::class, diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index 5e96aac43..1aef6d593 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -33,29 +33,7 @@ public function __construct(private TimelineCollector $timelineCollector) { } - public function getCollected(): array - { - return array_merge(...array_values($this->requests)); - } - - public function getSummary(): array - { - return [ - 'http' => [ - 'count' => array_sum(array_map(static fn (array $requests) => count($requests), $this->requests)), - 'totalTime' => array_sum( - array_merge( - ...array_map( - static fn (array $entry) => array_column($entry, 'totalTime'), - array_values($this->requests) - ) - ) - ), - ], - ]; - } - - public function collect(RequestInterface $request, float|string $startTime, string $line, ?string $uniqueId): void + public function collect(RequestInterface $request, float $startTime, string $line, ?string $uniqueId): void { if (!$this->isActive()) { return; @@ -73,7 +51,7 @@ public function collect(RequestInterface $request, float|string $startTime, stri $this->timelineCollector->collect($this, $uniqueId); } - public function collectTotalTime(?ResponseInterface $response, float|string $startTime, ?string $uniqueId): void + public function collectTotalTime(?ResponseInterface $response, float $endTime, ?string $uniqueId): void { if (!$this->isActive()) { return; @@ -88,7 +66,35 @@ public function collectTotalTime(?ResponseInterface $response, float|string $sta $entry['responseStatus'] = $response->getStatusCode(); Message::rewindBody($response); } - $entry['endTime'] = $startTime; + $entry['endTime'] = $endTime; $entry['totalTime'] = $entry['endTime'] - $entry['startTime']; } + + public function getCollected(): array + { + if (!$this->isActive()) { + return []; + } + return array_merge(...array_values($this->requests)); + } + + public function getSummary(): array + { + if (!$this->isActive()) { + return []; + } + return [ + 'http' => [ + 'count' => array_sum(array_map(static fn (array $requests) => count($requests), $this->requests)), + 'totalTime' => array_sum( + array_merge( + ...array_map( + static fn (array $entry) => array_column($entry, 'totalTime'), + array_values($this->requests) + ) + ) + ), + ], + ]; + } } diff --git a/src/Collector/VarDumperCollector.php b/src/Collector/VarDumperCollector.php index 8eb9c3e4a..8042f1520 100644 --- a/src/Collector/VarDumperCollector.php +++ b/src/Collector/VarDumperCollector.php @@ -29,9 +29,7 @@ public function getCollected(): array return []; } - return [ - 'var-dumper' => $this->vars, - ]; + return $this->vars; } public function getSummary(): array diff --git a/src/Collector/Web/WebAppInfoCollector.php b/src/Collector/Web/WebAppInfoCollector.php index 6f389b161..b22b711cd 100644 --- a/src/Collector/Web/WebAppInfoCollector.php +++ b/src/Collector/Web/WebAppInfoCollector.php @@ -12,8 +12,6 @@ use Yiisoft\Yii\Http\Event\AfterRequest; use Yiisoft\Yii\Http\Event\BeforeRequest; -use function is_object; - final class WebAppInfoCollector implements SummaryCollectorInterface { use CollectorTrait; @@ -44,7 +42,7 @@ public function getCollected(): array public function collect(object $event): void { - if (!is_object($event) || !$this->isActive()) { + if (!$this->isActive()) { return; } diff --git a/src/Dumper.php b/src/Dumper.php index 52414990e..72d579b96 100644 --- a/src/Dumper.php +++ b/src/Dumper.php @@ -97,7 +97,7 @@ private function dumpNested($variable, int $depth, int $objectCollapseLevel): mi return $this->dumpNestedInternal($variable, $depth, 0, $objectCollapseLevel); } - private function getObjectProperties($var): array + private function getObjectProperties(object $var): array { if (\__PHP_Incomplete_Class::class !== $var::class && method_exists($var, '__debugInfo')) { $var = $var->__debugInfo(); @@ -158,7 +158,6 @@ private function dumpNestedInternal($var, int $depth, int $level, int $objectCol $objectCollapseLevel ); } - break; case 'resource': case 'resource (closed)': diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index ea39b7c1d..22809ba71 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -136,24 +136,25 @@ private function collectSummaryData(): array */ private function gc(): void { - $summaryFiles = glob($this->path . '/**/**/sumamry.json', GLOB_NOSORT); - if ((is_countable($summaryFiles) ? count($summaryFiles) : 0) >= $this->historySize + 1) { - uasort($summaryFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a)); - $excessFiles = array_slice($summaryFiles, $this->historySize); - foreach ($excessFiles as $file) { - $path1 = dirname($file); - $path2 = dirname($file, 2); - $path3 = dirname($file, 3); - $resource = substr($path1, strlen($path3)); - - - FileHelper::removeDirectory($this->path . $resource); - - // Clean empty group directories - $group = substr($path2, strlen($path3)); - if (FileHelper::isEmptyDirectory($this->path . $group)) { - FileHelper::removeDirectory($this->path . $group); - } + $summaryFiles = glob($this->path . '/**/**/summary.json', GLOB_NOSORT); + if (empty($summaryFiles) || count($summaryFiles) <= $this->historySize) { + return; + } + + uasort($summaryFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a)); + $excessFiles = array_slice($summaryFiles, $this->historySize); + foreach ($excessFiles as $file) { + $path1 = dirname($file); + $path2 = dirname($file, 2); + $path3 = dirname($file, 3); + $resource = substr($path1, strlen($path3)); + + FileHelper::removeDirectory($this->path . $resource); + + // Clean empty group directories + $group = substr($path2, strlen($path3)); + if (FileHelper::isEmptyDirectory($this->path . $group)) { + FileHelper::removeDirectory($this->path . $group); } } } diff --git a/tests/Shared/AbstractCollectorTestCase.php b/tests/Shared/AbstractCollectorTestCase.php index 80ac2cd62..25dd6c63a 100644 --- a/tests/Shared/AbstractCollectorTestCase.php +++ b/tests/Shared/AbstractCollectorTestCase.php @@ -40,6 +40,18 @@ public function testEmptyCollector(): void } } + public function testInactiveCollector(): void + { + $collector = $this->getCollector(); + + $this->collectTestData($collector); + + $this->assertEquals([], $collector->getCollected()); + if ($collector instanceof SummaryCollectorInterface) { + $this->assertEquals([], $collector->getSummary()); + } + } + abstract protected function getCollector(): CollectorInterface; abstract protected function collectTestData(CollectorInterface $collector): void; diff --git a/tests/Support/Application/config/.merge-plan.php b/tests/Support/Application/config/.merge-plan.php index 957a4471c..f60be9931 100644 --- a/tests/Support/Application/config/.merge-plan.php +++ b/tests/Support/Application/config/.merge-plan.php @@ -4,6 +4,8 @@ return [ '/'=>[ + 'params' => [ + ] ], ]; diff --git a/tests/Support/Application/config/param1.php b/tests/Support/Application/config/param1.php new file mode 100644 index 000000000..c9d956327 --- /dev/null +++ b/tests/Support/Application/config/param1.php @@ -0,0 +1,13 @@ + [ + 'params' => [ + 'yiitest/yii-debug' => [ + 'param1.php', + ], + ], + ], +]; diff --git a/tests/Support/Stub/ThreeProperties.php b/tests/Support/Stub/ThreeProperties.php new file mode 100644 index 000000000..4dd455b62 --- /dev/null +++ b/tests/Support/Stub/ThreeProperties.php @@ -0,0 +1,12 @@ +collect(new ApplicationStartup(null)); + $command = $this->createMock(Command::class); + $input = new ArrayInput([]); + $output = new NullOutput(); + $collector->collect(new ConsoleCommandEvent(null, $input, $output)); + $collector->collect(new ConsoleErrorEvent($input, $output, new \Exception())); + $collector->collect(new ConsoleTerminateEvent($command, $input, $output, 2)); + DIRECTORY_SEPARATOR === '\\' ? sleep(1) : usleep(123_000); $collector->collect(new ApplicationShutdown(0)); @@ -40,4 +52,20 @@ protected function checkCollectedData(array $data): void $this->assertGreaterThan(0.122, $data['applicationProcessingTime']); } + + protected function checkSummaryData(array $data): void + { + parent::checkSummaryData($data); + + $this->assertArrayHasKey('console', $data); + $this->assertArrayHasKey('php', $data['console']); + $this->assertArrayHasKey('version', $data['console']['php']); + $this->assertArrayHasKey('request', $data['console']); + $this->assertArrayHasKey('startTime', $data['console']['request']); + $this->assertArrayHasKey('processingTime', $data['console']['request']); + $this->assertArrayHasKey('memory', $data['console']); + $this->assertArrayHasKey('peakUsage', $data['console']['memory']); + + $this->assertEquals(PHP_VERSION, $data['console']['php']['version']); + } } diff --git a/tests/Unit/Collector/ContainerInterfaceProxyTest.php b/tests/Unit/Collector/ContainerInterfaceProxyTest.php index b38e7bdee..cf27b2a69 100644 --- a/tests/Unit/Collector/ContainerInterfaceProxyTest.php +++ b/tests/Unit/Collector/ContainerInterfaceProxyTest.php @@ -143,6 +143,10 @@ public function testGetWithoutConfig(): void public function testGetAndHasWithWrongId(): void { + $containerProxy = new ContainerInterfaceProxy($this->getContainer(), $this->getConfig()); + + $this->assertFalse($containerProxy->has(CollectorInterface::class)); + $this->expectException(ContainerExceptionInterface::class); $this->expectExceptionMessage( sprintf( @@ -151,11 +155,18 @@ public function testGetAndHasWithWrongId(): void CollectorInterface::class ) ); + $containerProxy->get(CollectorInterface::class); + } + public function testGetContainerItself(): void + { $containerProxy = new ContainerInterfaceProxy($this->getContainer(), $this->getConfig()); - $containerProxy->has(CollectorInterface::class); - $containerProxy->get(CollectorInterface::class); + $this->assertTrue($containerProxy->has(ContainerInterface::class)); + + $container = $containerProxy->get(ContainerInterface::class); + $this->assertNotNull($container); + $this->assertInstanceOf(ContainerInterface::class, $container); } public function testGetAndHasWithNotService(): void diff --git a/tests/Unit/Collector/EventDispatcherInterfaceProxyTest.php b/tests/Unit/Collector/EventDispatcherInterfaceProxyTest.php new file mode 100644 index 000000000..be091d9f2 --- /dev/null +++ b/tests/Unit/Collector/EventDispatcherInterfaceProxyTest.php @@ -0,0 +1,35 @@ +startup(); + + $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); + $eventDispatcherMock + ->expects($this->once()) + ->method('dispatch') + ->with($event) + ->willReturn($event); + $eventDispatcher = new EventDispatcherInterfaceProxy($eventDispatcherMock, $collector); + + $newEvent = $eventDispatcher->dispatch($event); + + $this->assertSame($event, $newEvent); + $this->assertCount(1, $collector->getCollected()); + } +} diff --git a/tests/Unit/Collector/ExceptionCollectorTest.php b/tests/Unit/Collector/ExceptionCollectorTest.php new file mode 100644 index 000000000..6ed1de5cf --- /dev/null +++ b/tests/Unit/Collector/ExceptionCollectorTest.php @@ -0,0 +1,81 @@ +collect(new ApplicationError($exception)); + } + + protected function getCollector(): CollectorInterface + { + return new ExceptionCollector(new TimelineCollector()); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); + $this->assertCount(2, $data); + foreach ($data as $exception) { + $this->assertArrayHasKey('class', $exception); + $this->assertArrayHasKey('message', $exception); + $this->assertArrayHasKey('file', $exception); + $this->assertArrayHasKey('line', $exception); + $this->assertArrayHasKey('code', $exception); + $this->assertArrayHasKey('trace', $exception); + $this->assertArrayHasKey('traceAsString', $exception); + } + + $exception = $data[0]; + $this->assertEquals(Exception::class, $exception['class']); + $this->assertEquals('test', $exception['message']); + $this->assertEquals(777, $exception['code']); + + $exception = $data[1]; + $this->assertEquals(Exception::class, $exception['class']); + $this->assertEquals('previous', $exception['message']); + $this->assertEquals(666, $exception['code']); + } + + protected function checkSummaryData(array $data): void + { + parent::checkSummaryData($data); + $this->assertCount(1, $data); + $this->assertArrayHasKey('exception', $data); + + $exception = $data['exception']; + $this->assertArrayHasKey('class', $exception); + $this->assertArrayHasKey('message', $exception); + $this->assertArrayHasKey('file', $exception); + $this->assertArrayHasKey('line', $exception); + $this->assertArrayHasKey('code', $exception); + + $this->assertEquals(Exception::class, $exception['class']); + $this->assertEquals('test', $exception['message']); + $this->assertEquals(777, $exception['code']); + } + + public function testNoExceptionCollected() + { + $collector = new ExceptionCollector(new TimelineCollector()); + + $collector->startup(); + + $this->assertEquals([], $collector->getCollected()); + } +} diff --git a/tests/Unit/Collector/HttpClientCollectorTest.php b/tests/Unit/Collector/HttpClientCollectorTest.php new file mode 100644 index 000000000..52c0c5b92 --- /dev/null +++ b/tests/Unit/Collector/HttpClientCollectorTest.php @@ -0,0 +1,108 @@ +collect( + new Request('GET', 'http://example.com'), + startTime: 10.10, + line: 'file1:123', + uniqueId: 'test1', + ); + $collector->collect( + new Request('POST', 'http://yiiframework.com'), + startTime: 12.10, + line: 'file2:555', + uniqueId: 'test2' + ); + $collector->collect( + new Request('GET', 'http://yiiframework.com'), + startTime: 15.00, + line: 'file2:666', + uniqueId: 'test3' + ); + + $collector->collectTotalTime( + new Response(200, [], 'test'), + endTime: 13.10, + uniqueId: 'test1' + ); + $collector->collectTotalTime( + new Response(200, [], 'test'), + endTime: 12.20, + uniqueId: 'test2' + ); + $collector->collectTotalTime( + new Response(200, [], 'test'), + endTime: 20.00, + uniqueId: 'test4' + ); + } + + protected function getCollector(): CollectorInterface + { + return new HttpClientCollector(new TimelineCollector()); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); + + $this->assertCount(3, $data); + + $entry = $data[0]; + $this->assertEquals(10.10, $entry['startTime']); + $this->assertEquals(13.10, $entry['endTime']); + $this->assertEquals(3.0, $entry['totalTime']); + $this->assertEquals('GET', $entry['method']); + $this->assertEquals('http://example.com', $entry['uri']); + $this->assertEquals(['Host' => ['example.com']], $entry['headers']); + $this->assertEquals('file1:123', $entry['line']); + + $entry = $data[1]; + $this->assertEquals(12.10, $entry['startTime']); + $this->assertEquals(12.20, $entry['endTime']); + $this->assertEquals(0.1, round($entry['totalTime'], 1)); + $this->assertEquals('POST', $entry['method']); + $this->assertEquals('http://yiiframework.com', $entry['uri']); + $this->assertEquals(['Host' => ['yiiframework.com']], $entry['headers']); + $this->assertEquals('file2:555', $entry['line']); + + $entry = $data[2]; + $this->assertEquals(15.0, $entry['startTime']); + $this->assertEquals(15.0, $entry['endTime']); + $this->assertEquals(0.0, round($entry['totalTime'], 1)); + $this->assertEquals('GET', $entry['method']); + $this->assertEquals('http://yiiframework.com', $entry['uri']); + $this->assertEquals(['Host' => ['yiiframework.com']], $entry['headers']); + $this->assertEquals('file2:666', $entry['line']); + } + + protected function checkSummaryData(array $data): void + { + parent::checkSummaryData($data); + $this->assertCount(1, $data); + $this->assertArrayHasKey('http', $data); + $this->assertCount(2, $data['http']); + $this->assertArrayHasKey('count', $data['http']); + $this->assertArrayHasKey('totalTime', $data['http']); + + $this->assertEquals(3, $data['http']['count']); + $this->assertEquals(3.1, round($data['http']['totalTime'], 1)); + } +} diff --git a/tests/Unit/Collector/HttpClientInterfaceProxyTest.php b/tests/Unit/Collector/HttpClientInterfaceProxyTest.php new file mode 100644 index 000000000..188b2c9fd --- /dev/null +++ b/tests/Unit/Collector/HttpClientInterfaceProxyTest.php @@ -0,0 +1,37 @@ +createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('sendRequest') + ->with($request) + ->willReturn($response); + $collector = new HttpClientCollector(new TimelineCollector()); + $collector->startup(); + + $proxy = new HttpClientInterfaceProxy($client, $collector); + + $newResponse = $proxy->sendRequest($request); + + $this->assertSame($newResponse, $response); + $this->assertCount(1, $collector->getCollected()); + } +} diff --git a/tests/Unit/Collector/LoggerProxyTest.php b/tests/Unit/Collector/LoggerProxyTest.php index a5a09eefd..ba9c2f660 100644 --- a/tests/Unit/Collector/LoggerProxyTest.php +++ b/tests/Unit/Collector/LoggerProxyTest.php @@ -44,16 +44,15 @@ public function testMethodLog($method, string $level, string $message, array $co $proxy->log($level, $message, $context); } - public function logMethodsProvider(): array + public static function logMethodsProvider(): iterable { - return [ - ['alert', LogLevel::ALERT, 'message', []], - ['critical', LogLevel::CRITICAL, 'message', []], - ['debug', LogLevel::DEBUG, 'message', []], - ['emergency', LogLevel::EMERGENCY, 'message', []], - ['error', LogLevel::ERROR, 'message', ['context']], - ['info', LogLevel::INFO, 'message', ['context']], - ['warning', LogLevel::WARNING, 'message', ['context']], - ]; + yield 'alert' => ['alert', LogLevel::ALERT, 'message', []]; + yield 'critical' => ['critical', LogLevel::CRITICAL, 'message', []]; + yield 'debug' => ['debug', LogLevel::DEBUG, 'message', []]; + yield 'emergency' => ['emergency', LogLevel::EMERGENCY, 'message', []]; + yield 'notice' => ['notice', LogLevel::NOTICE, 'message', []]; + yield 'error' => ['error', LogLevel::ERROR, 'message', ['context']]; + yield 'info' => ['info', LogLevel::INFO, 'message', ['context']]; + yield 'warning' => ['warning', LogLevel::WARNING, 'message', ['context']]; } } diff --git a/tests/Unit/Collector/TimelineCollectorTest.php b/tests/Unit/Collector/TimelineCollectorTest.php new file mode 100644 index 000000000..13829d105 --- /dev/null +++ b/tests/Unit/Collector/TimelineCollectorTest.php @@ -0,0 +1,44 @@ +collect(new LogCollector($collector), '123'); + $collector->collect(new LogCollector($collector), '345', 'context2', __FILE__ . ':' . 123); + } + + protected function getCollector(): CollectorInterface + { + return new TimelineCollector(); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); + + $this->assertNotEmpty($data); + $this->assertCount(2, $data); + $this->assertCount(4, $data[0]); + $this->assertSame(LogCollector::class, $data[0][2]); + $this->assertSame('123', $data[0][1]); + $this->assertSame([], $data[0][3]); + + $this->assertCount(4, $data[1]); + $this->assertSame(LogCollector::class, $data[1][2]); + $this->assertSame('345', $data[1][1]); + $this->assertSame(['context2', __FILE__ . ':' . 123], $data[1][3]); + } +} diff --git a/tests/Unit/Collector/VarDumperCollectorTest.php b/tests/Unit/Collector/VarDumperCollectorTest.php new file mode 100644 index 000000000..e07c6c33b --- /dev/null +++ b/tests/Unit/Collector/VarDumperCollectorTest.php @@ -0,0 +1,44 @@ +collect('test', 'file:123'); + } + + protected function getCollector(): CollectorInterface + { + return new VarDumperCollector(new TimelineCollector()); + } + + protected function checkCollectedData(array $data): void + { + parent::checkCollectedData($data); + $this->assertCount(1, $data); + $this->assertCount(2, $data[0]); + $this->assertSame('test', $data[0]['variable']); + $this->assertSame('file:123', $data[0]['line']); + } + + protected function checkSummaryData(array $data): void + { + parent::checkSummaryData($data); + $this->assertCount(1, $data); + $this->assertArrayHasKey('var-dumper', $data); + $this->assertArrayHasKey('total', $data['var-dumper']); + $this->assertEquals(1, $data['var-dumper']['total']); + } +} diff --git a/tests/Unit/Command/DebugContainerCommandTest.php b/tests/Unit/Command/DebugContainerCommandTest.php index dc039de5d..699bd8fe2 100644 --- a/tests/Unit/Command/DebugContainerCommandTest.php +++ b/tests/Unit/Command/DebugContainerCommandTest.php @@ -29,11 +29,16 @@ public function testCommand() $storage->expects($this->never())->method('clear'); $debugger = new Debugger($idGenerator, $storage, []); - $command = new DebugContainerCommand($container, $debugger); + $config = $container->get(ConfigInterface::class); + // trigger config build + $config->get('params'); + $command = new DebugContainerCommand($container, $debugger); $commandTester = new CommandTester($command); $commandTester->execute([]); + + $this->assertEquals(0, $commandTester->getStatusCode()); } private function createContainer(): ContainerInterface diff --git a/tests/Unit/DebuggerTest.php b/tests/Unit/DebuggerTest.php index 31d3941a8..96a9adf7b 100644 --- a/tests/Unit/DebuggerTest.php +++ b/tests/Unit/DebuggerTest.php @@ -88,9 +88,10 @@ public function testIgnoreByEnv(): void $storage = $this->getMockBuilder(StorageInterface::class)->getMock(); $storage->expects($this->never())->method('flush'); - $_ENV['YII_DEBUG_IGNORE'] = 'true'; + putenv('YII_DEBUG_IGNORE=true'); $debugger = new Debugger($idGenerator, $storage, [$collector], []); - $debugger->startup(new ApplicationStartup('')); + $debugger->startup(new ApplicationStartup('command')); + putenv('YII_DEBUG_IGNORE=false'); $debugger->shutdown(); } diff --git a/tests/Unit/DumperTest.php b/tests/Unit/DumperTest.php index 0f5b6d81f..1ae7fd72d 100644 --- a/tests/Unit/DumperTest.php +++ b/tests/Unit/DumperTest.php @@ -8,6 +8,7 @@ use stdClass; use Yiisoft\Yii\Debug as D; use Yiisoft\Yii\Debug\Dumper; +use Yiisoft\Yii\Debug\Tests\Support\Stub\ThreeProperties; final class DumperTest extends TestCase { @@ -60,6 +61,57 @@ public function testAsJson($variable, string $result): void $this->assertEqualsWithoutLE($result, $output); } + public function testDeepNestedArray(): void + { + $variable = [[[[[['test']]]]]]; + $output = Dumper::create($variable)->asJson(2); + $result = '[["array [...]"]]'; + $this->assertEqualsWithoutLE($result, $output); + } + + public function testDeepNestedObject(): void + { + $object = new ThreeProperties(); + $object->first = $object; + $variable = [[$object]]; + + $output = Dumper::create($variable)->asJson(2); + $result = sprintf( + '[["%s#%d (...)"]]', + str_replace('\\', '\\\\', ThreeProperties::class), + spl_object_id($object), + ); + $this->assertEqualsWithoutLE($result, $output); + } + + public function testObjectVisibilityProperties(): void + { + $variable = new ThreeProperties(); + + $output = Dumper::create($variable)->asJson(2); + $result = sprintf( + '{"%s#%d":{"public $first":"first","protected $second":"second","private $third":"third"}}', + str_replace('\\', '\\\\', ThreeProperties::class), + spl_object_id($variable), + ); + $this->assertEqualsWithoutLE($result, $output); + } + + public function testFormatJson(): void + { + $variable = [['test']]; + + $output = Dumper::create($variable)->asJson(2, true); + $result = <<assertEqualsWithoutLE($result, $output); + } + public static function jsonDataProvider(): iterable { $emptyObject = new stdClass(); @@ -276,6 +328,15 @@ public static function jsonDataProvider(): iterable '"{closed resource}"', ]; + $socketResource = \socket_create(\AF_INET, \SOCK_STREAM, \SOL_TCP); + $socketResourceId = spl_object_id($socketResource); + yield 'socket resource' => [ + $socketResource, + << [ diff --git a/tests/Unit/Helper/BacktraceIgnoreMatcherTest.php b/tests/Unit/Helper/BacktraceIgnoreMatcherTest.php new file mode 100644 index 000000000..98231d0f2 --- /dev/null +++ b/tests/Unit/Helper/BacktraceIgnoreMatcherTest.php @@ -0,0 +1,81 @@ +assertFalse(BacktraceIgnoreMatcher::isIgnoredByClass($backtrace, [self::class])); + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByClass($backtrace, [stdClass::class])); + + $backtrace[3] = $backtrace[0]; + + $this->assertTrue(BacktraceIgnoreMatcher::isIgnoredByClass($backtrace, [self::class])); + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByClass($backtrace, [stdClass::class])); + } + + public function testFileIgnorance(): void + { + $backtrace = debug_backtrace(); + $reflection = new \ReflectionClass(TestCase::class); + $file = $reflection->getFileName(); + + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByFile($backtrace, [preg_quote($file)])); + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByFile($backtrace, [preg_quote(__FILE__)])); + + $backtrace[2] = $backtrace[0]; + + $this->assertTrue(BacktraceIgnoreMatcher::isIgnoredByFile($backtrace, [preg_quote($file)])); + $this->assertTrue( + BacktraceIgnoreMatcher::isIgnoredByFile( + $backtrace, + [preg_quote(dirname($file) . DIRECTORY_SEPARATOR) . '*'] + ) + ); + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByFile($backtrace, [preg_quote(__FILE__)])); + } + + public function testStringMatches(): void + { + $this->assertTrue( + BacktraceIgnoreMatcher::doesStringMatchPattern( + 'dev/123/456', + ['dev/123/456'] + ) + ); + $this->assertTrue( + BacktraceIgnoreMatcher::doesStringMatchPattern( + 'dev/123/456', + ['456'] + ) + ); + $this->assertTrue( + BacktraceIgnoreMatcher::doesStringMatchPattern( + 'dev/123/456', + ['dev/.*/456'] + ) + ); + $this->assertTrue( + BacktraceIgnoreMatcher::doesStringMatchPattern( + 'dev/123/456', + ['dev*/456', 'dev/123/*'] + ) + ); + } + + public function testEmptyBacktrace(): void + { + $this->assertFalse(BacktraceIgnoreMatcher::doesStringMatchPattern('dev/123/456', [])); + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByFile([], ['dev/123/456'])); + $this->assertFalse(BacktraceIgnoreMatcher::isIgnoredByClass([], ['dev/123/456'])); + } +} diff --git a/tests/Unit/Storage/AbstractStorageTest.php b/tests/Unit/Storage/AbstractStorageTest.php index 3ef6695fd..95af83cb4 100644 --- a/tests/Unit/Storage/AbstractStorageTest.php +++ b/tests/Unit/Storage/AbstractStorageTest.php @@ -5,8 +5,11 @@ namespace Yiisoft\Yii\Debug\Tests\Unit\Storage; use PHPUnit\Framework\TestCase; +use stdClass; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; use Yiisoft\Yii\Debug\DebuggerIdGenerator; +use Yiisoft\Yii\Debug\Dumper; use Yiisoft\Yii\Debug\Storage\MemoryStorage; use Yiisoft\Yii\Debug\Storage\StorageInterface; @@ -33,15 +36,19 @@ public function testRead(array $data): void { $idGenerator = new DebuggerIdGenerator(); $storage = $this->getStorage($idGenerator); - $collector = $this->createFakeCollector($data); - $storage->addCollector($collector); + $storage->addCollector($this->createFakeCollector($data)); + $storage->addCollector($this->createFakeSummaryCollector($data)); $expectedData = $storage->getData(); + $encodedExpectedData = \json_decode(Dumper::create($expectedData)->asJson(), true, 512, JSON_THROW_ON_ERROR); + if (!$storage instanceof MemoryStorage) { $storage->flush(); } - $data = $storage->read(StorageInterface::TYPE_DATA); - $this->assertEquals([$idGenerator->getId() => $expectedData], $data); + + $result = $storage->read(StorageInterface::TYPE_DATA); + $encodedResult = \json_decode(Dumper::create($result)->asJson(), true, 512, JSON_THROW_ON_ERROR); + $this->assertEquals([$idGenerator->getId() => $encodedExpectedData], $encodedResult); } /** @@ -60,17 +67,16 @@ public function testFlush(array $data): void abstract public function getStorage(DebuggerIdGenerator $idGenerator): StorageInterface; - public function dataProvider(): array + public static function dataProvider(): iterable { - return [ - [[1, 2, 3]], - [['string']], - [[[['', 0, false]]]], - [['test']], - [[false]], - [[null]], - [[0]], - ]; + yield [[1, 2, 3]]; + yield [['string']]; + yield [[[['', 0, false]]]]; + yield [['test']]; + yield [[false]]; + yield [[null]]; + yield [[0]]; + yield [[new stdClass()]]; } protected function createFakeCollector(array $data) @@ -85,4 +91,21 @@ protected function createFakeCollector(array $data) return $collector; } + + protected function createFakeSummaryCollector(array $data) + { + $collector = $this->getMockBuilder(SummaryCollectorInterface::class)->getMock(); + $collector + ->method('getCollected') + ->willReturn($data); + $collector + ->method('getName') + ->willReturn('SummaryMock_Collector'); + + $collector + ->method('getSummary') + ->willReturn(['summary' => 'summary data']); + + return $collector; + } } diff --git a/tests/Unit/Storage/FileStorageTest.php b/tests/Unit/Storage/FileStorageTest.php index 3306a31d2..8bf5a2f37 100644 --- a/tests/Unit/Storage/FileStorageTest.php +++ b/tests/Unit/Storage/FileStorageTest.php @@ -35,6 +35,33 @@ public function testFlushWithGC(array $data): void $this->assertLessThanOrEqual(5, count($storage->read(StorageInterface::TYPE_SUMMARY, null))); } + /** + * @dataProvider dataProvider() + */ + public function testHistorySize(array $data): void + { + $idGenerator = new DebuggerIdGenerator(); + $idGenerator->reset(); + $storage = $this->getStorage($idGenerator); + $storage->setHistorySize(2); + $collector = $this->createFakeCollector($data); + + $storage->addCollector($collector); + $storage->flush(); + $idGenerator->reset(); + + $storage->addCollector($collector); + $storage->flush(); + $idGenerator->reset(); + + $storage->addCollector($collector); + $storage->flush(); + $idGenerator->reset(); + + $read = $storage->read(StorageInterface::TYPE_SUMMARY, null); + $this->assertCount(2, $read); + } + /** * @dataProvider dataProvider() */ @@ -50,7 +77,7 @@ public function testClear(array $data): void $this->assertDirectoryDoesNotExist($this->path); } - public function getStorage(DebuggerIdGenerator $idGenerator): StorageInterface + public function getStorage(DebuggerIdGenerator $idGenerator): FileStorage { return new FileStorage( $this->path, diff --git a/tests/Unit/Storage/MemoryStorageTest.php b/tests/Unit/Storage/MemoryStorageTest.php index e211c8688..98f71de4d 100644 --- a/tests/Unit/Storage/MemoryStorageTest.php +++ b/tests/Unit/Storage/MemoryStorageTest.php @@ -14,4 +14,26 @@ public function getStorage(DebuggerIdGenerator $idGenerator): StorageInterface { return new MemoryStorage($idGenerator); } + + public function testSummaryCount() + { + $idGenerator = new DebuggerIdGenerator(); + $storage = $this->getStorage($idGenerator); + + $storage->addCollector($collector1 = $this->createFakeSummaryCollector(['test' => 'test'])); + $storage->addCollector($collector2 = $this->createFakeCollector(['test' => 'test'])); + + $result = $storage->read(StorageInterface::TYPE_SUMMARY, null); + $this->assertCount(1, $result); + + $this->assertEquals( + [ + $idGenerator->getId() => [ + 'id' => $idGenerator->getId(), + 'collectors' => [$collector1->getName(), $collector2->getName()], + ], + ], + $result + ); + } }