From 376d349613c6b213313936b3ea37b57ec3ea2222 Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Fri, 30 Jun 2023 18:34:17 +0300 Subject: [PATCH 1/6] Fix tests (#218) --- tests/Unit/DumperTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/DumperTest.php b/tests/Unit/DumperTest.php index dd0522a7..0f5b6d81 100644 --- a/tests/Unit/DumperTest.php +++ b/tests/Unit/DumperTest.php @@ -106,7 +106,7 @@ public static function jsonDataProvider(): iterable yield 'function' => [ $functionObject, << [ $staticFunctionObject, << [ From 7bf0e1fd525fd7600ce099f0b512cbb261ac5278 Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Sun, 2 Jul 2023 07:14:37 +0300 Subject: [PATCH 2/6] Debug commands (#216) --- composer-require-checker.json | 3 + composer.json | 13 +- config/params.php | 8 +- src/Command/DebugContainerCommand.php | 192 ++++++++++++++++++ src/Command/DebugEventsCommand.php | 126 ++++++++++++ ...ResetCommand.php => DebugResetCommand.php} | 13 +- src/Debugger.php | 10 +- .../Application/config/.merge-plan.php | 9 + .../Command/DebugContainerCommandTest.php | 53 +++++ tests/Unit/Command/DebugEventsCommandTest.php | 53 +++++ tests/Unit/Command/ResetCommandTest.php | 8 +- tests/Unit/DebuggerTest.php | 2 +- 12 files changed, 469 insertions(+), 21 deletions(-) create mode 100644 src/Command/DebugContainerCommand.php create mode 100644 src/Command/DebugEventsCommand.php rename src/Command/{ResetCommand.php => DebugResetCommand.php} (67%) create mode 100644 tests/Support/Application/config/.merge-plan.php create mode 100644 tests/Unit/Command/DebugContainerCommandTest.php create mode 100644 tests/Unit/Command/DebugEventsCommandTest.php diff --git a/composer-require-checker.json b/composer-require-checker.json index 626f2302..5571a66f 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -1,5 +1,8 @@ { "symbol-whitelist": [ + "Yiisoft\\Definitions\\ArrayDefinition", + "Yiisoft\\Definitions\\CallableDefinition", + "Yiisoft\\Definitions\\ValueDefinition", "Yiisoft\\Middleware\\Dispatcher\\Event\\BeforeMiddleware", "Yiisoft\\Yii\\Console\\Event\\ApplicationShutdown", "Yiisoft\\Yii\\Console\\Event\\ApplicationStartup", diff --git a/composer.json b/composer.json index d68c1e75..103a792a 100644 --- a/composer.json +++ b/composer.json @@ -29,8 +29,10 @@ "psr/http-message": "^1.0", "psr/log": "^2.0|^3.0", "symfony/console": "^5.4|^6.0", + "symfony/var-dumper": "^5.4", "yiisoft/aliases": "^3.0", "yiisoft/arrays": "^2.0|^3.0", + "yiisoft/config": "^1.3", "yiisoft/di": "^1.0", "yiisoft/files": "^2.0", "yiisoft/json": "^1.0", @@ -41,9 +43,6 @@ }, "require-dev": { "ext-curl": "*", - "yiisoft/middleware-dispatcher": "^3.0|^4.0|^5.0", - "yiisoft/yii-console": "^2.0", - "yiisoft/yii-http": "^1.0", "maglnet/composer-require-checker": "^4.2", "nyholm/psr7": "^1.3", "phpunit/phpunit": "^9.4", @@ -53,7 +52,10 @@ "vimeo/psalm": "^4.30|^5.7", "yiisoft/error-handler": "^3.0", "yiisoft/event-dispatcher": "^1.0", - "yiisoft/log": "^2.0" + "yiisoft/log": "^2.0", + "yiisoft/middleware-dispatcher": "^3.0|^4.0|^5.0", + "yiisoft/yii-console": "^2.0", + "yiisoft/yii-http": "^1.0" }, "autoload": { "psr-4": { @@ -93,7 +95,8 @@ "sort-packages": true, "allow-plugins": { "infection/extension-installer": true, - "composer/package-versions-deprecated": true + "composer/package-versions-deprecated": true, + "yiisoft/config": false } }, "scripts": { diff --git a/config/params.php b/config/params.php index bcf0a4b9..b2f92802 100644 --- a/config/params.php +++ b/config/params.php @@ -24,7 +24,9 @@ use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; -use Yiisoft\Yii\Debug\Command\ResetCommand; +use Yiisoft\Yii\Debug\Command\DebugContainerCommand; +use Yiisoft\Yii\Debug\Command\DebugEventsCommand; +use Yiisoft\Yii\Debug\Command\DebugResetCommand; /** * @var $params array @@ -87,7 +89,9 @@ ], 'yiisoft/yii-console' => [ 'commands' => [ - 'debug/reset' => ResetCommand::class, + DebugResetCommand::COMMAND_NAME => DebugResetCommand::class, + DebugContainerCommand::COMMAND_NAME => DebugContainerCommand::class, + DebugEventsCommand::COMMAND_NAME => DebugEventsCommand::class, ], ], ]; diff --git a/src/Command/DebugContainerCommand.php b/src/Command/DebugContainerCommand.php new file mode 100644 index 00000000..15baa001 --- /dev/null +++ b/src/Command/DebugContainerCommand.php @@ -0,0 +1,192 @@ +setDescription('Show information about container') + ->addArgument('id', InputArgument::IS_ARRAY, 'Service ID') + ->addOption('groups', null, InputOption::VALUE_NONE, 'Show groups') + ->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Show group'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->debugger->stop(); + $config = $this->container->get(ConfigInterface::class); + + $io = new SymfonyStyle($input, $output); + + if ($input->hasArgument('id') && !empty($ids = $input->getArgument('id'))) { + $build = $this->getConfigBuild($config); + foreach ($ids as $id) { + $definition = null; + foreach ($build as $definitions) { + if (array_key_exists($id, $definitions)) { + $definition = $definitions[$id]; + } + } + if ($definition === null) { + $io->error( + sprintf( + 'Service "%s" not found.', + $id, + ) + ); + continue; + } + $io->title($id); + + $normalizedDefinition = DefinitionNormalizer::normalize($definition, $id); + if ($normalizedDefinition instanceof ArrayDefinition) { + $definitionList = ['ID' => $id]; + if (class_exists($normalizedDefinition->getClass())) { + $definitionList[] = ['Class' => $normalizedDefinition->getClass()]; + } + if (!empty($normalizedDefinition->getConstructorArguments())) { + $definitionList[] = [ + 'Constructor' => $this->export( + $normalizedDefinition->getConstructorArguments() + ), + ]; + } + if (!empty($normalizedDefinition->getMethodsAndProperties())) { + $definitionList[] = [ + 'Methods' => $this->export( + $normalizedDefinition->getMethodsAndProperties() + ), + ]; + } + if (isset($definition['tags'])) { + $definitionList[] = ['Tags' => $this->export($definition['tags'])]; + } + + $io->definitionList(...$definitionList); + + continue; + } + if ($normalizedDefinition instanceof CallableDefinition || $normalizedDefinition instanceof ValueDefinition) { + $io->text( + $this->export($definition) + ); + continue; + } + + $output->writeln([ + $id, + VarDumper::create($normalizedDefinition)->asString(), + ]); + } + + return ExitCode::OK; + } + + if ($input->hasOption('groups') && $input->getOption('groups')) { + $build = $this->getConfigBuild($config); + $groups = array_keys($build); + ksort($groups); + + $io->table(['Group'], array_map(fn ($group) => [$group], $groups)); + + return ExitCode::OK; + } + if ($input->hasOption('group') && !empty($group = $input->getOption('group'))) { + $data = $config->get($group); + ksort($data); + + $rows = $this->getGroupServices($data); + + $table = new Table($output); + $table + ->setHeaderTitle($group) + ->setHeaders(['Service', 'Definition']) + ->setRows($rows); + $table->render(); + + return ExitCode::OK; + } + + $build = $this->getConfigBuild($config); + + foreach ($build as $group => $data) { + $rows = $this->getGroupServices($data); + + $table = new Table($output); + $table + ->setHeaderTitle($group) + ->setHeaders(['Group', 'Services']) + ->setRows($rows); + $table->render(); + } + + return ExitCode::OK; + } + + private function getConfigBuild(mixed $config): array + { + $reflection = new \ReflectionClass($config); + $buildReflection = $reflection->getProperty('build'); + $buildReflection->setAccessible(true); + return $buildReflection->getValue($config); + } + + protected function getGroupServices(array $data): array + { + $rows = []; + foreach ($data as $id => $definition) { + $class = ''; + if (is_string($definition)) { + $class = $definition; + } + if (is_array($definition)) { + $class = $definition['class'] ?? $id; + } + if (is_object($definition)) { + $class = $definition::class; + } + + $rows[] = [ + $id, + $class, + ]; + } + return $rows; + } + + protected function export(mixed $value): string + { + return VarDumper::create($value)->asString(); + } +} diff --git a/src/Command/DebugEventsCommand.php b/src/Command/DebugEventsCommand.php new file mode 100644 index 00000000..1aba6e10 --- /dev/null +++ b/src/Command/DebugEventsCommand.php @@ -0,0 +1,126 @@ +setDescription('Show information about events and listeners') + ->addArgument('id', InputArgument::IS_ARRAY, 'Service ID') + ->addOption('groups', null, InputOption::VALUE_NONE, 'Show groups') + ->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Show group'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->debugger->stop(); + $config = $this->container->get(ConfigInterface::class); + + $io = new SymfonyStyle($input, $output); + + if ($input->hasOption('groups') && $input->getOption('groups')) { + $build = $this->getConfigBuild($config); + $groups = array_keys($build); + ksort($groups); + + $io->table(['Group'], array_map(fn ($group) => [$group], $groups)); + + return ExitCode::OK; + } + if ($input->hasOption('group') && !empty($group = $input->getOption('group'))) { + $data = $config->get($group); + ksort($data); + $table = new Table($output); + + foreach ($data as $event => $listeners) { + $io->title($event); + foreach ($listeners as $listener) { + if (is_callable($listener) && !is_array($listener)) { + SymfonyVarDumper::dump($this->export($listener)); + } else { + SymfonyVarDumper::dump($listener); + } + } + $table->render(); + $io->newLine(); + } + return ExitCode::OK; + } + + $data = []; + if ($config->has('events')) { + $data = array_merge($data, $config->get('events')); + } + if ($config->has('events-console')) { + $data = array_merge($data, $config->get('events-console')); + } + $rows = []; + foreach ($data as $event => $listeners) { + $rows[] = [ + $event, + is_countable($listeners) ? count($listeners) : 0, + implode( + "\n", + array_map(function ($listener) { + if (is_array($listener)) { + return sprintf( + '%s::%s', + $listener[0], + $listener[1] + ); + } + return $this->export($listener); + }, $listeners) + ), + ]; + } + $table = new Table($output); + $table + ->setHeaders(['Event', 'Count', 'Listeners']) + ->setRows($rows); + $table->render(); + + return ExitCode::OK; + } + + private function getConfigBuild(mixed $config): array + { + $reflection = new \ReflectionClass($config); + $buildReflection = $reflection->getProperty('build'); + $buildReflection->setAccessible(true); + return $buildReflection->getValue($config); + } + + protected function export(mixed $value): string + { + return VarDumper::create($value)->asString(); + } +} diff --git a/src/Command/ResetCommand.php b/src/Command/DebugResetCommand.php similarity index 67% rename from src/Command/ResetCommand.php rename to src/Command/DebugResetCommand.php index 625400a0..b42585bc 100644 --- a/src/Command/ResetCommand.php +++ b/src/Command/DebugResetCommand.php @@ -8,14 +8,18 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Yiisoft\Yii\Console\ExitCode; +use Yiisoft\Yii\Debug\Debugger; use Yiisoft\Yii\Debug\Storage\StorageInterface; -final class ResetCommand extends Command +final class DebugResetCommand extends Command { - protected static $defaultName = 'debug/reset'; + public const COMMAND_NAME = 'debug:reset'; + protected static $defaultName = self::COMMAND_NAME; - public function __construct(private StorageInterface $storage) - { + public function __construct( + private StorageInterface $storage, + private Debugger $debugger, + ) { parent::__construct(); } @@ -28,6 +32,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { + $this->debugger->stop(); $this->storage->clear(); return ExitCode::OK; diff --git a/src/Debugger.php b/src/Debugger.php index bc3bebbb..276e18ae 100644 --- a/src/Debugger.php +++ b/src/Debugger.php @@ -80,14 +80,10 @@ public function stop(): void return; } - try { - $this->target->clear(); - } finally { - foreach ($this->collectors as $collector) { - $collector->shutdown(); - } - $this->active = false; + foreach ($this->collectors as $collector) { + $collector->shutdown(); } + $this->active = false; } private function isRequestIgnored(ServerRequestInterface $request): bool diff --git a/tests/Support/Application/config/.merge-plan.php b/tests/Support/Application/config/.merge-plan.php new file mode 100644 index 00000000..957a4471 --- /dev/null +++ b/tests/Support/Application/config/.merge-plan.php @@ -0,0 +1,9 @@ +[ + + ], +]; diff --git a/tests/Unit/Command/DebugContainerCommandTest.php b/tests/Unit/Command/DebugContainerCommandTest.php new file mode 100644 index 00000000..dc039de5 --- /dev/null +++ b/tests/Unit/Command/DebugContainerCommandTest.php @@ -0,0 +1,53 @@ +createContainer(); + $idGenerator = new DebuggerIdGenerator(); + $storage = $this->createMock(StorageInterface::class); + $storage->expects($this->never())->method('clear'); + $debugger = new Debugger($idGenerator, $storage, []); + + $command = new DebugContainerCommand($container, $debugger); + + $commandTester = new CommandTester($command); + + $commandTester->execute([]); + } + + private function createContainer(): ContainerInterface + { + $config = ContainerConfig::create() + ->withDefinitions([ + LoggerInterface::class => NullLogger::class, + ConfigInterface::class => [ + 'class' => Config::class, + '__construct()' => [ + new ConfigPaths(dirname(__DIR__, 2) . '/Support/Application/config'), + ], + ], + ]); + return new Container($config); + } +} diff --git a/tests/Unit/Command/DebugEventsCommandTest.php b/tests/Unit/Command/DebugEventsCommandTest.php new file mode 100644 index 00000000..7862c8b1 --- /dev/null +++ b/tests/Unit/Command/DebugEventsCommandTest.php @@ -0,0 +1,53 @@ +createContainer(); + $idGenerator = new DebuggerIdGenerator(); + $storage = $this->createMock(StorageInterface::class); + $storage->expects($this->never())->method('clear'); + $debugger = new Debugger($idGenerator, $storage, []); + + $command = new DebugEventsCommand($container, $debugger); + + $commandTester = new CommandTester($command); + + $commandTester->execute([]); + } + + private function createContainer(): ContainerInterface + { + $config = ContainerConfig::create() + ->withDefinitions([ + LoggerInterface::class => NullLogger::class, + ConfigInterface::class => [ + 'class' => Config::class, + '__construct()' => [ + new ConfigPaths(dirname(__DIR__, 2) . '/Support/Application/config'), + ], + ], + ]); + return new Container($config); + } +} diff --git a/tests/Unit/Command/ResetCommandTest.php b/tests/Unit/Command/ResetCommandTest.php index 22d377df..1aa8bafc 100644 --- a/tests/Unit/Command/ResetCommandTest.php +++ b/tests/Unit/Command/ResetCommandTest.php @@ -6,17 +6,21 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandTester; -use Yiisoft\Yii\Debug\Command\ResetCommand; +use Yiisoft\Yii\Debug\Command\DebugResetCommand; +use Yiisoft\Yii\Debug\Debugger; +use Yiisoft\Yii\Debug\DebuggerIdGenerator; use Yiisoft\Yii\Debug\Storage\StorageInterface; final class ResetCommandTest extends TestCase { public function testCommand() { + $idGenerator = new DebuggerIdGenerator(); $storage = $this->createMock(StorageInterface::class); $storage->expects($this->once())->method('clear'); + $debugger = new Debugger($idGenerator, $storage, []); - $command = new ResetCommand($storage); + $command = new DebugResetCommand($storage, $debugger); $commandTester = new CommandTester($command); diff --git a/tests/Unit/DebuggerTest.php b/tests/Unit/DebuggerTest.php index bdc5c537..31d3941a 100644 --- a/tests/Unit/DebuggerTest.php +++ b/tests/Unit/DebuggerTest.php @@ -196,7 +196,7 @@ public function testStopSkipped(): void $collector = $this->getMockBuilder(CollectorInterface::class)->getMock(); $collector->expects($this->once())->method('shutdown'); $storage = $this->getMockBuilder(StorageInterface::class)->getMock(); - $storage->expects($this->once())->method('clear'); + $storage->expects($this->never())->method('clear'); $storage->expects($this->never())->method('flush'); $debugger = new Debugger($idGenerator, $storage, [$collector]); From e16d64d4a8dc3f0d6a64cf1c9476aa4a59d8e73f Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Sun, 9 Jul 2023 22:32:04 +0300 Subject: [PATCH 3/6] Fix psalm errors (#220) --- tests/Unit/Storage/FileStorageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Storage/FileStorageTest.php b/tests/Unit/Storage/FileStorageTest.php index ffb28949..3306a31d 100644 --- a/tests/Unit/Storage/FileStorageTest.php +++ b/tests/Unit/Storage/FileStorageTest.php @@ -32,7 +32,7 @@ public function testFlushWithGC(array $data): void $storage->addCollector($collector); $storage->flush(); - $this->assertLessThanOrEqual(5, count($storage->read(StorageInterface::TYPE_SUMMARY))); + $this->assertLessThanOrEqual(5, count($storage->read(StorageInterface::TYPE_SUMMARY, null))); } /** From 7557311a95fead8d1544f21a0b326f4b32939f9d Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Sun, 8 Oct 2023 12:49:57 +0300 Subject: [PATCH 4/6] Update strings package (#226) * Update strings package * Fix check * Fix tests * Fix tests * Add example doc --- composer.json | 2 +- config/di.php | 8 +++++++- src/Collector/Stream/FilesystemStreamProxy.php | 2 ++ src/Collector/Stream/HttpStreamProxy.php | 2 ++ src/Helper/BacktraceIgnoreMatcher.php | 10 +++++----- tests/Unit/Collector/FilesystemStreamCollectorTest.php | 10 +++++----- tests/Unit/Collector/HttpStreamCollectorTest.php | 4 ++-- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 103a792a..e500baf8 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "yiisoft/json": "^1.0", "yiisoft/profiler": "^3.0", "yiisoft/proxy": "^1.0.1", - "yiisoft/strings": "^2.0", + "yiisoft/strings": "^2.2", "yiisoft/var-dumper": "^1.0" }, "require-dev": { diff --git a/config/di.php b/config/di.php index 67eee2a3..e8f4ff37 100644 --- a/config/di.php +++ b/config/di.php @@ -58,7 +58,13 @@ }, FilesystemStreamCollector::class => [ '__construct()' => [ - 'ignoredPathPatterns' => [], + 'ignoredPathPatterns' => [ + /** + * Examples: + * - templates/ + * - src/Directory/To/Ignore + */ + ], 'ignoredClasses' => [ ClosureExporter::class, UseStatementParser::class, diff --git a/src/Collector/Stream/FilesystemStreamProxy.php b/src/Collector/Stream/FilesystemStreamProxy.php index 46030354..f419f72a 100644 --- a/src/Collector/Stream/FilesystemStreamProxy.php +++ b/src/Collector/Stream/FilesystemStreamProxy.php @@ -4,6 +4,7 @@ namespace Yiisoft\Yii\Debug\Collector\Stream; +use Yiisoft\Strings\CombinedRegexp; use Yiisoft\Yii\Debug\Helper\BacktraceIgnoreMatcher; use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapper; use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapperInterface; @@ -67,6 +68,7 @@ public static function register(): void */ class_exists(BacktraceIgnoreMatcher::class); class_exists(StreamWrapper::class); + class_exists(CombinedRegexp::class); stream_wrapper_unregister('file'); stream_wrapper_register('file', self::class, STREAM_IS_URL); self::$registered = true; diff --git a/src/Collector/Stream/HttpStreamProxy.php b/src/Collector/Stream/HttpStreamProxy.php index 4a6050cc..39212a80 100644 --- a/src/Collector/Stream/HttpStreamProxy.php +++ b/src/Collector/Stream/HttpStreamProxy.php @@ -4,6 +4,7 @@ namespace Yiisoft\Yii\Debug\Collector\Stream; +use Yiisoft\Strings\CombinedRegexp; use Yiisoft\Yii\Debug\Helper\BacktraceIgnoreMatcher; use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapper; use Yiisoft\Yii\Debug\Helper\StreamWrapper\StreamWrapperInterface; @@ -68,6 +69,7 @@ public static function register(): void */ class_exists(BacktraceIgnoreMatcher::class); class_exists(StreamWrapper::class); + class_exists(CombinedRegexp::class); stream_wrapper_unregister('http'); stream_wrapper_register('http', self::class, STREAM_IS_URL); stream_wrapper_unregister('https'); diff --git a/src/Helper/BacktraceIgnoreMatcher.php b/src/Helper/BacktraceIgnoreMatcher.php index 0130ae71..11f0f8c4 100644 --- a/src/Helper/BacktraceIgnoreMatcher.php +++ b/src/Helper/BacktraceIgnoreMatcher.php @@ -4,6 +4,8 @@ namespace Yiisoft\Yii\Debug\Helper; +use Yiisoft\Strings\CombinedRegexp; + /** * All backtrace parameters should contain at least 4 elements in the following order: * 0 – Called method @@ -30,11 +32,9 @@ public static function isIgnoredByClass(array $backtrace, array $classes): bool public static function doesStringMatchPattern(string $string, array $patterns): bool { - foreach ($patterns as $ignoredPathPattern) { - if (preg_match($ignoredPathPattern, $string) > 0) { - return true; - } + if (empty($patterns)) { + return false; } - return false; + return (new CombinedRegexp($patterns))->matches($string); } } diff --git a/tests/Unit/Collector/FilesystemStreamCollectorTest.php b/tests/Unit/Collector/FilesystemStreamCollectorTest.php index 94166a74..31079772 100644 --- a/tests/Unit/Collector/FilesystemStreamCollectorTest.php +++ b/tests/Unit/Collector/FilesystemStreamCollectorTest.php @@ -92,7 +92,7 @@ public function dataSkipCollectOnMatchIgnoreReferences(): iterable yield 'mkdir ignored by path' => [ $path, $mkdirBefore, - ['/' . basename(__FILE__, '.php') . '/'], + [basename(__FILE__, '.php')], [], $mkdirOperation, $mkdirAfter, @@ -139,7 +139,7 @@ public function dataSkipCollectOnMatchIgnoreReferences(): iterable yield 'rename ignored by path' => [ $path, $renameBefore, - ['/' . basename(__FILE__, '.php') . '/'], + [ basename(__FILE__, '.php') ], [], $renameOperation, $renameAfter, @@ -185,7 +185,7 @@ public function dataSkipCollectOnMatchIgnoreReferences(): iterable yield 'rmdir ignored by path' => [ $path, $rmdirBefore, - ['/' . basename(__FILE__, '.php') . '/'], + [basename(__FILE__, '.php')], [], $rmdirOperation, $rmdirAfter, @@ -232,7 +232,7 @@ public function dataSkipCollectOnMatchIgnoreReferences(): iterable yield 'unlink ignored by path' => [ $path, $unlinkBefore, - ['/' . basename(__FILE__, '.php') . '/'], + [basename(__FILE__, '.php')], [], $unlinkOperation, $unlinkAfter, @@ -291,7 +291,7 @@ public function dataSkipCollectOnMatchIgnoreReferences(): iterable yield 'file stream ignored by path' => [ $path, $fileStreamBefore, - ['/' . basename(__FILE__, '.php') . '/'], + [basename(__FILE__, '.php')], [], $fileStreamOperation, $fileStreamAfter, diff --git a/tests/Unit/Collector/HttpStreamCollectorTest.php b/tests/Unit/Collector/HttpStreamCollectorTest.php index 01700f82..e29323a8 100644 --- a/tests/Unit/Collector/HttpStreamCollectorTest.php +++ b/tests/Unit/Collector/HttpStreamCollectorTest.php @@ -108,7 +108,7 @@ function (string $url, array $collected) { yield 'file stream ignored by path' => [ $url, $httpStreamBefore, - ['/' . basename(__FILE__, '.php') . '/'], + [basename(__FILE__, '.php')], [], [], $httpStreamOperation, @@ -130,7 +130,7 @@ function (string $url, array $collected) { $httpStreamBefore, [], [], - ['/example/'], + ['example'], $httpStreamOperation, $httpStreamAfter, [], From 71ac2a8a507aa8284a3a8405959579f76d2e1edc Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Sun, 8 Oct 2023 14:43:18 +0300 Subject: [PATCH 5/6] Add var dumper collector (#223) * Add var dumper collector * Include bootstrap config * Fix composer-require-checker * Timeline collector (#224) * Add timeline collector * Change timeline signature * Use microtime instead of time * Remove redundant property * Apply fixes from StyleCI * Fix tests & psalm errors --------- Co-authored-by: StyleCI Bot * Update var-dumper * Remove Yiisoft\\VarDumper\\HandlerInterface from composer-require-checker.json --------- Co-authored-by: StyleCI Bot --- composer.json | 3 +- config/bootstrap.php | 29 +++++++++++ config/params.php | 4 ++ src/Collector/Console/CommandCollector.php | 7 +++ .../Console/ConsoleAppInfoCollector.php | 7 +++ src/Collector/EventCollector.php | 12 ++--- src/Collector/ExceptionCollector.php | 5 ++ src/Collector/HttpClientCollector.php | 5 ++ src/Collector/LogCollector.php | 5 ++ src/Collector/ServiceCollector.php | 5 ++ src/Collector/TimelineCollector.php | 34 +++++++++++++ src/Collector/VarDumperCollector.php | 49 +++++++++++++++++++ .../VarDumperHandlerInterfaceProxy.php | 42 ++++++++++++++++ src/Collector/Web/MiddlewareCollector.php | 6 +++ src/Collector/Web/RequestCollector.php | 27 +++------- src/Collector/Web/WebAppInfoCollector.php | 10 +++- tests/Unit/Collector/CommandCollectorTest.php | 3 +- .../Collector/ConsoleAppInfoCollectorTest.php | 4 +- .../Collector/ContainerInterfaceProxyTest.php | 20 +++++--- .../Collector/ContainerProxyConfigTest.php | 18 ++++--- tests/Unit/Collector/EventCollectorTest.php | 3 +- .../Collector/EventDispatcherProxyTest.php | 31 ------------ tests/Unit/Collector/LogCollectorTest.php | 3 +- .../Collector/MiddlewareCollectorTest.php | 3 +- tests/Unit/Collector/RequestCollectorTest.php | 3 +- tests/Unit/Collector/ServiceCollectorTest.php | 3 +- .../Collector/WebAppInfoCollectorTest.php | 3 +- 27 files changed, 263 insertions(+), 81 deletions(-) create mode 100644 config/bootstrap.php create mode 100644 src/Collector/TimelineCollector.php create mode 100644 src/Collector/VarDumperCollector.php create mode 100644 src/Collector/VarDumperHandlerInterfaceProxy.php delete mode 100644 tests/Unit/Collector/EventDispatcherProxyTest.php diff --git a/composer.json b/composer.json index e500baf8..aebfa63b 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "yiisoft/profiler": "^3.0", "yiisoft/proxy": "^1.0.1", "yiisoft/strings": "^2.2", - "yiisoft/var-dumper": "^1.0" + "yiisoft/var-dumper": "^1.7" }, "require-dev": { "ext-curl": "*", @@ -77,6 +77,7 @@ }, "config-plugin": { "params": "params.php", + "bootstrap": "bootstrap.php", "di": "di.php", "di-console": "di-console.php", "di-web": "di-web.php", diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 00000000..a78e5b21 --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,29 @@ +has(VarDumperCollector::class)) { + return; + } + + VarDumper::setDefaultHandler( + new VarDumperHandlerInterfaceProxy( + VarDumper::getDefaultHandler(), + $container->get(VarDumperCollector::class), + ), + ); + }, +]; diff --git a/config/params.php b/config/params.php index b2f92802..f6d4d2cd 100644 --- a/config/params.php +++ b/config/params.php @@ -21,6 +21,8 @@ use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\Stream\FilesystemStreamCollector; use Yiisoft\Yii\Debug\Collector\Stream\HttpStreamCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; +use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; @@ -43,6 +45,8 @@ FilesystemStreamCollector::class, HttpStreamCollector::class, ExceptionCollector::class, + VarDumperCollector::class, + TimelineCollector::class, ], 'collectors.web' => [ WebAppInfoCollector::class, diff --git a/src/Collector/Console/CommandCollector.php b/src/Collector/Console/CommandCollector.php index 1ec359e4..b97e64f1 100644 --- a/src/Collector/Console/CommandCollector.php +++ b/src/Collector/Console/CommandCollector.php @@ -13,6 +13,7 @@ use Yiisoft\Yii\Console\Output\ConsoleBufferedOutput; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class CommandCollector implements SummaryCollectorInterface { @@ -24,6 +25,10 @@ final class CommandCollector implements SummaryCollectorInterface private const UNDEFINED_EXIT_CODE = -1; private array $commands = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { return $this->commands; @@ -35,6 +40,8 @@ public function collect(ConsoleEvent|ConsoleErrorEvent|ConsoleTerminateEvent $ev return; } + $this->timelineCollector->collect($this, spl_object_id($event)); + $command = $event->getCommand(); if ($event instanceof ConsoleErrorEvent) { diff --git a/src/Collector/Console/ConsoleAppInfoCollector.php b/src/Collector/Console/ConsoleAppInfoCollector.php index 68bac302..3211ee28 100644 --- a/src/Collector/Console/ConsoleAppInfoCollector.php +++ b/src/Collector/Console/ConsoleAppInfoCollector.php @@ -11,6 +11,7 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class ConsoleAppInfoCollector implements SummaryCollectorInterface { @@ -21,6 +22,10 @@ final class ConsoleAppInfoCollector implements SummaryCollectorInterface private float $requestProcessingTimeStarted = 0; private float $requestProcessingTimeStopped = 0; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -58,6 +63,8 @@ public function collect(object $event): void } elseif ($event instanceof ApplicationShutdown) { $this->applicationProcessingTimeStopped = microtime(true); } + + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/src/Collector/EventCollector.php b/src/Collector/EventCollector.php index ecc89318..d04985ca 100644 --- a/src/Collector/EventCollector.php +++ b/src/Collector/EventCollector.php @@ -8,12 +8,16 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup as ConsoleApplicationStartup; use Yiisoft\Yii\Http\Event\ApplicationStartup as HttpApplicationStartup; -class EventCollector implements SummaryCollectorInterface +final class EventCollector implements SummaryCollectorInterface { use CollectorTrait; private array $events = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -32,11 +36,6 @@ public function collect(object $event, string $line): void return; } - $this->collectEvent($event, $line); - } - - private function collectEvent(object $event, $line): void - { $this->events[] = [ 'name' => $event::class, 'event' => $event, @@ -44,6 +43,7 @@ private function collectEvent(object $event, $line): void 'line' => $line, 'time' => microtime(true), ]; + $this->timelineCollector->collect($this, spl_object_id($event), $event::class); } public function getSummary(): array diff --git a/src/Collector/ExceptionCollector.php b/src/Collector/ExceptionCollector.php index 8f3e7cff..f0ae8bfa 100644 --- a/src/Collector/ExceptionCollector.php +++ b/src/Collector/ExceptionCollector.php @@ -13,6 +13,10 @@ final class ExceptionCollector implements SummaryCollectorInterface private ?Throwable $exception = null; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if ($this->exception === null) { @@ -36,6 +40,7 @@ public function collect(ApplicationError $error): void } $this->exception = $error->getThrowable(); + $this->timelineCollector->collect($this, $error::class); } public function getSummary(): array diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index 0304ef2f..5e96aac4 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -29,6 +29,10 @@ final class HttpClientCollector implements SummaryCollectorInterface */ private array $requests = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { return array_merge(...array_values($this->requests)); @@ -66,6 +70,7 @@ public function collect(RequestInterface $request, float|string $startTime, stri 'headers' => $request->getHeaders(), 'line' => $line, ]; + $this->timelineCollector->collect($this, $uniqueId); } public function collectTotalTime(?ResponseInterface $response, float|string $startTime, ?string $uniqueId): void diff --git a/src/Collector/LogCollector.php b/src/Collector/LogCollector.php index 5d963b47..291f4487 100644 --- a/src/Collector/LogCollector.php +++ b/src/Collector/LogCollector.php @@ -10,6 +10,10 @@ class LogCollector implements SummaryCollectorInterface private array $messages = []; + public function __construct(private TimelineCollector $timelineCollector, ) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -31,6 +35,7 @@ public function collect(string $level, $message, array $context, string $line): 'context' => $context, 'line' => $line, ]; + $this->timelineCollector->collect($this, count($this->messages)); } private function reset(): void diff --git a/src/Collector/ServiceCollector.php b/src/Collector/ServiceCollector.php index 56988856..e281bc16 100644 --- a/src/Collector/ServiceCollector.php +++ b/src/Collector/ServiceCollector.php @@ -10,6 +10,10 @@ final class ServiceCollector implements SummaryCollectorInterface private array $items = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -44,6 +48,7 @@ public function collect( 'timeStart' => $timeStart, 'timeEnd' => $timeEnd, ]; + $this->timelineCollector->collect($this, count($this->items)); } public function getSummary(): array diff --git a/src/Collector/TimelineCollector.php b/src/Collector/TimelineCollector.php new file mode 100644 index 00000000..068ec070 --- /dev/null +++ b/src/Collector/TimelineCollector.php @@ -0,0 +1,34 @@ +isActive()) { + return []; + } + return $this->events; + } + + public function collect(CollectorInterface $collector, string|int $reference, ...$data): void + { + if (!$this->isActive()) { + return; + } + + $this->events[] = [microtime(true), $reference, $collector::class, array_values($data)]; + } + + private function reset(): void + { + $this->events = []; + } +} diff --git a/src/Collector/VarDumperCollector.php b/src/Collector/VarDumperCollector.php new file mode 100644 index 00000000..8eb9c3e4 --- /dev/null +++ b/src/Collector/VarDumperCollector.php @@ -0,0 +1,49 @@ +vars[] = [ + 'variable' => $variable, + 'line' => $line, + ]; + $this->timelineCollector->collect($this, count($this->vars)); + } + + public function getCollected(): array + { + if (!$this->isActive()) { + return []; + } + + return [ + 'var-dumper' => $this->vars, + ]; + } + + public function getSummary(): array + { + if (!$this->isActive()) { + return []; + } + + return [ + 'var-dumper' => [ + 'total' => count($this->vars), + ], + ]; + } +} diff --git a/src/Collector/VarDumperHandlerInterfaceProxy.php b/src/Collector/VarDumperHandlerInterfaceProxy.php new file mode 100644 index 00000000..c76f2cd0 --- /dev/null +++ b/src/Collector/VarDumperHandlerInterfaceProxy.php @@ -0,0 +1,42 @@ +collector->collect( + $variable, + $callStack === null ? '' : $callStack['file'] . ':' . $callStack['line'] + ); + $this->decorated->handle($variable, $depth, $highlight); + } +} diff --git a/src/Collector/Web/MiddlewareCollector.php b/src/Collector/Web/MiddlewareCollector.php index 70bd4e84..7dda19dd 100644 --- a/src/Collector/Web/MiddlewareCollector.php +++ b/src/Collector/Web/MiddlewareCollector.php @@ -9,6 +9,7 @@ use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class MiddlewareCollector implements SummaryCollectorInterface { @@ -17,6 +18,10 @@ final class MiddlewareCollector implements SummaryCollectorInterface private array $beforeStack = []; private array $afterStack = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -73,6 +78,7 @@ public function collect(BeforeMiddleware|AfterMiddleware $event): void 'response' => $event->getResponse(), ]; } + $this->timelineCollector->collect($this, spl_object_id($event)); } private function reset(): void diff --git a/src/Collector/Web/RequestCollector.php b/src/Collector/Web/RequestCollector.php index 472ceff4..3dcb9ab4 100644 --- a/src/Collector/Web/RequestCollector.php +++ b/src/Collector/Web/RequestCollector.php @@ -5,13 +5,13 @@ namespace Yiisoft\Yii\Debug\Collector\Web; use GuzzleHttp\Psr7\Message; -use JsonException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Yiisoft\Yii\Http\Event\AfterRequest; -use Yiisoft\Yii\Http\Event\BeforeRequest; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; +use Yiisoft\Yii\Http\Event\AfterRequest; +use Yiisoft\Yii\Http\Event\BeforeRequest; use function is_object; @@ -29,26 +29,15 @@ final class RequestCollector implements SummaryCollectorInterface private ?ServerRequestInterface $request = null; private ?ResponseInterface $response = null; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { return []; } - $content = null; - if ($this->response !== null) { - $stream = $this->response->getBody(); - if ($stream->isReadable() && $stream->isSeekable()) { - $position = $stream->tell(); - $stream->rewind(); - $content = $stream->getContents(); - try { - $content = json_decode($content, associative: true, flags: JSON_THROW_ON_ERROR); - } catch (JsonException) { - // pass - } - $stream->seek($position); - } - } $requestRaw = null; if ($this->request instanceof ServerRequestInterface) { @@ -74,7 +63,6 @@ public function getCollected(): array 'requestRaw' => $requestRaw, 'response' => $this->response, 'responseRaw' => $responseRaw, - 'content' => $content, ]; } @@ -101,6 +89,7 @@ public function collect(object $event): void $this->response = $response; $this->responseStatusCode = $response !== null ? $response->getStatusCode() : 500; } + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/src/Collector/Web/WebAppInfoCollector.php b/src/Collector/Web/WebAppInfoCollector.php index 7213608f..6f389b16 100644 --- a/src/Collector/Web/WebAppInfoCollector.php +++ b/src/Collector/Web/WebAppInfoCollector.php @@ -5,11 +5,12 @@ namespace Yiisoft\Yii\Debug\Collector\Web; use Yiisoft\Yii\Console\Event\ApplicationStartup; +use Yiisoft\Yii\Debug\Collector\CollectorTrait; +use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Http\Event\AfterEmit; use Yiisoft\Yii\Http\Event\AfterRequest; use Yiisoft\Yii\Http\Event\BeforeRequest; -use Yiisoft\Yii\Debug\Collector\CollectorTrait; -use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; use function is_object; @@ -22,6 +23,10 @@ final class WebAppInfoCollector implements SummaryCollectorInterface private float $requestProcessingTimeStarted = 0; private float $requestProcessingTimeStopped = 0; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -52,6 +57,7 @@ public function collect(object $event): void } elseif ($event instanceof AfterEmit) { $this->applicationProcessingTimeStopped = microtime(true); } + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/tests/Unit/Collector/CommandCollectorTest.php b/tests/Unit/Collector/CommandCollectorTest.php index 33c71cd8..5d99fd78 100644 --- a/tests/Unit/Collector/CommandCollectorTest.php +++ b/tests/Unit/Collector/CommandCollectorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Yii\Console\Output\ConsoleBufferedOutput; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\Console\CommandCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class CommandCollectorTest extends AbstractCollectorTestCase @@ -57,7 +58,7 @@ public function testCollectWithInactiveCollector(): void protected function getCollector(): CollectorInterface { - return new CommandCollector(); + return new CommandCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php b/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php index 6ef9bbf1..cf632672 100644 --- a/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php +++ b/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php @@ -8,8 +8,8 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\Console\ConsoleAppInfoCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; - use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use function sleep; @@ -31,7 +31,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new ConsoleAppInfoCollector(); + return new ConsoleAppInfoCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ContainerInterfaceProxyTest.php b/tests/Unit/Collector/ContainerInterfaceProxyTest.php index 3d41a5df..b38e7bde 100644 --- a/tests/Unit/Collector/ContainerInterfaceProxyTest.php +++ b/tests/Unit/Collector/ContainerInterfaceProxyTest.php @@ -18,13 +18,14 @@ use Yiisoft\EventDispatcher\Provider\Provider; use Yiisoft\Files\FileHelper; use Yiisoft\Yii\Debug\Collector\CollectorInterface; -use Yiisoft\Yii\Debug\Collector\EventCollector; -use Yiisoft\Yii\Debug\Collector\LogCollector; -use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; class ContainerInterfaceProxyTest extends TestCase { @@ -72,7 +73,7 @@ public function testGetAndHasWithCallableServices(): void ], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -89,7 +90,7 @@ public function testGetAndHasWithCallableServices(): void public function testGetWithArrayConfigWithStringKeys(): void { $dispatcherMock = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); - $serviceCollector = new ServiceCollector(); + $serviceCollector = $this->createServiceCollector(); $serviceCollector->startup(); // activate collector $config = new ContainerProxyConfig( @@ -127,7 +128,7 @@ public function testGetWithoutConfig(): void EventDispatcherInterface::class, ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -183,7 +184,7 @@ private function getConfig(): ContainerProxyConfig ], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -201,4 +202,9 @@ private function getContainer(): Container ]); return new Container($config); } + + protected function createServiceCollector(): ServiceCollector + { + return new ServiceCollector(new TimelineCollector()); + } } diff --git a/tests/Unit/Collector/ContainerProxyConfigTest.php b/tests/Unit/Collector/ContainerProxyConfigTest.php index c6921f90..203d09c7 100644 --- a/tests/Unit/Collector/ContainerProxyConfigTest.php +++ b/tests/Unit/Collector/ContainerProxyConfigTest.php @@ -7,12 +7,13 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; -use Yiisoft\Yii\Debug\Collector\EventCollector; -use Yiisoft\Yii\Debug\Collector\LogCollector; -use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class ContainerProxyConfigTest extends TestCase { @@ -23,13 +24,13 @@ public function testImmutability(): void $dispatcherMock = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); $this->assertNotSame($config, $config->activate()); - $this->assertNotSame($config, $config->withCollector(new ServiceCollector())); + $this->assertNotSame($config, $config->withCollector($this->createServiceCollector())); $this->assertNotSame($config, $config->withLogLevel(1)); $this->assertNotSame($config, $config->withProxyCachePath('@tests/runtime')); $this->assertNotSame( $config, $config->withDispatcher( - new EventDispatcherInterfaceProxy($dispatcherMock, new EventCollector()) + new EventDispatcherInterfaceProxy($dispatcherMock, new EventCollector(new TimelineCollector())) ) ); $this->assertNotSame( @@ -51,7 +52,7 @@ public function testGetters(): void LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), '@tests/runtime', 1 ); @@ -79,4 +80,9 @@ public function testGetters(): void $this->assertFalse($config->hasDecoratedServiceArrayConfigWithStringKeys(LoggerInterface::class)); $this->assertFalse($config->hasDecoratedServiceCallableConfig(LoggerInterface::class)); } + + protected function createServiceCollector(): ServiceCollector + { + return new ServiceCollector(new TimelineCollector()); + } } diff --git a/tests/Unit/Collector/EventCollectorTest.php b/tests/Unit/Collector/EventCollectorTest.php index 4d75ef80..c32d99be 100644 --- a/tests/Unit/Collector/EventCollectorTest.php +++ b/tests/Unit/Collector/EventCollectorTest.php @@ -6,6 +6,7 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\EventCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Debug\Tests\Unit\Support\DummyEvent; @@ -21,7 +22,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new EventCollector(); + return new EventCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/EventDispatcherProxyTest.php b/tests/Unit/Collector/EventDispatcherProxyTest.php deleted file mode 100644 index 2d0e07ff..00000000 --- a/tests/Unit/Collector/EventDispatcherProxyTest.php +++ /dev/null @@ -1,31 +0,0 @@ -createMock(EventDispatcherInterface::class); - $collector = $this->createMock(EventCollector::class); - - $eventDispatcher->method('dispatch')->willReturn($event); - $collector - ->expects($this->once()) - ->method('collect') - ->with($event, __FILE__ . ':29'); - - $proxy = new EventDispatcherInterfaceProxy($eventDispatcher, $collector); - - $proxy->dispatch($event); - } -} diff --git a/tests/Unit/Collector/LogCollectorTest.php b/tests/Unit/Collector/LogCollectorTest.php index b7e7dbc2..3221d94c 100644 --- a/tests/Unit/Collector/LogCollectorTest.php +++ b/tests/Unit/Collector/LogCollectorTest.php @@ -7,6 +7,7 @@ use Psr\Log\LogLevel; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\LogCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class LogCollectorTest extends AbstractCollectorTestCase @@ -21,6 +22,6 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new LogCollector(); + return new LogCollector(new TimelineCollector()); } } diff --git a/tests/Unit/Collector/MiddlewareCollectorTest.php b/tests/Unit/Collector/MiddlewareCollectorTest.php index aa86e86c..81fd4efe 100644 --- a/tests/Unit/Collector/MiddlewareCollectorTest.php +++ b/tests/Unit/Collector/MiddlewareCollectorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware; use Yiisoft\Middleware\Dispatcher\MiddlewareFactory; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Debug\Tests\Unit\Support\DummyMiddleware; @@ -34,7 +35,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new MiddlewareCollector(); + return new MiddlewareCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/RequestCollectorTest.php b/tests/Unit/Collector/RequestCollectorTest.php index 5105a374..90a7bb7e 100644 --- a/tests/Unit/Collector/RequestCollectorTest.php +++ b/tests/Unit/Collector/RequestCollectorTest.php @@ -9,6 +9,7 @@ use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Http\Event\AfterRequest; @@ -59,7 +60,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new RequestCollector(); + return new RequestCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ServiceCollectorTest.php b/tests/Unit/Collector/ServiceCollectorTest.php index 1d1805e4..9fd2d0ae 100644 --- a/tests/Unit/Collector/ServiceCollectorTest.php +++ b/tests/Unit/Collector/ServiceCollectorTest.php @@ -7,6 +7,7 @@ use stdClass; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class ServiceCollectorTest extends AbstractCollectorTestCase @@ -22,6 +23,6 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new ServiceCollector(); + return new ServiceCollector(new TimelineCollector()); } } diff --git a/tests/Unit/Collector/WebAppInfoCollectorTest.php b/tests/Unit/Collector/WebAppInfoCollectorTest.php index 763a35f9..63ce78ae 100644 --- a/tests/Unit/Collector/WebAppInfoCollectorTest.php +++ b/tests/Unit/Collector/WebAppInfoCollectorTest.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Http\Event\AfterRequest; @@ -33,7 +34,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new WebAppInfoCollector(); + return new WebAppInfoCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void From 8d79cc4baf852a682ceb1690010b000916a42423 Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Sat, 14 Oct 2023 16:26:20 +0300 Subject: [PATCH 6/6] Add more tests (#227) * Add more tests * Fix test * Cover BacktraceIgnoreMatcher * Cover VarDumperCollector * Cover ExceptionCollector * Cover HttpClientCollector * Cover HttpClientCollectorProxy * Apply fixes from StyleCI * Fix test * Cover more cases * Cover more cases * Apply fixes from StyleCI * Apply Rector changes (CI) * Add checks * Cover line * Try fix * fix ci * rollback * fix * fix * Cover more lines * Enhance command test * Apply fixes from StyleCI * Apply Rector changes (CI) --------- Co-authored-by: StyleCI Bot Co-authored-by: xepozz Co-authored-by: Sergei Predvoditelev --- .../Console/ConsoleAppInfoCollector.php | 2 +- src/Collector/ExceptionCollector.php | 6 + src/Collector/HttpClientCollector.php | 56 +++++---- src/Collector/VarDumperCollector.php | 4 +- src/Collector/Web/WebAppInfoCollector.php | 4 +- src/Dumper.php | 3 +- src/Storage/FileStorage.php | 37 +++--- tests/Shared/AbstractCollectorTestCase.php | 12 ++ .../Application/config/.merge-plan.php | 2 + tests/Support/Application/config/param1.php | 13 +++ tests/Support/Stub/ThreeProperties.php | 12 ++ tests/Unit/Collector/.gitignore | 1 - .../Collector/ConsoleAppInfoCollectorTest.php | 32 +++++- .../Collector/ContainerInterfaceProxyTest.php | 15 ++- .../EventDispatcherInterfaceProxyTest.php | 35 ++++++ .../Unit/Collector/ExceptionCollectorTest.php | 81 +++++++++++++ .../Collector/HttpClientCollectorTest.php | 108 ++++++++++++++++++ .../HttpClientInterfaceProxyTest.php | 37 ++++++ tests/Unit/Collector/LoggerProxyTest.php | 19 ++- .../Unit/Collector/TimelineCollectorTest.php | 44 +++++++ .../Unit/Collector/VarDumperCollectorTest.php | 44 +++++++ .../Command/DebugContainerCommandTest.php | 7 +- tests/Unit/DebuggerTest.php | 5 +- tests/Unit/DumperTest.php | 61 ++++++++++ .../Helper/BacktraceIgnoreMatcherTest.php | 81 +++++++++++++ tests/Unit/Storage/AbstractStorageTest.php | 51 ++++++--- tests/Unit/Storage/FileStorageTest.php | 29 ++++- tests/Unit/Storage/MemoryStorageTest.php | 22 ++++ 28 files changed, 738 insertions(+), 85 deletions(-) create mode 100644 tests/Support/Application/config/param1.php create mode 100644 tests/Support/Stub/ThreeProperties.php delete mode 100644 tests/Unit/Collector/.gitignore create mode 100644 tests/Unit/Collector/EventDispatcherInterfaceProxyTest.php create mode 100644 tests/Unit/Collector/ExceptionCollectorTest.php create mode 100644 tests/Unit/Collector/HttpClientCollectorTest.php create mode 100644 tests/Unit/Collector/HttpClientInterfaceProxyTest.php create mode 100644 tests/Unit/Collector/TimelineCollectorTest.php create mode 100644 tests/Unit/Collector/VarDumperCollectorTest.php create mode 100644 tests/Unit/Helper/BacktraceIgnoreMatcherTest.php diff --git a/src/Collector/Console/ConsoleAppInfoCollector.php b/src/Collector/Console/ConsoleAppInfoCollector.php index 3211ee28..5aa75a22 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 f0ae8bfa..32b42fbd 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 5e96aac4..1aef6d59 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 8eb9c3e4..8042f152 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 6f389b16..b22b711c 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 52414990..72d579b9 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 ea39b7c1..22809ba7 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 80ac2cd6..25dd6c63 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 957a4471..f60be993 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 00000000..c9d95632 --- /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 00000000..4dd455b6 --- /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 b38e7bde..cf27b2a6 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 00000000..be091d9f --- /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 00000000..6ed1de5c --- /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 00000000..52c0c5b9 --- /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 00000000..188b2c9f --- /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 a5a09eef..ba9c2f66 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 00000000..13829d10 --- /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 00000000..e07c6c33 --- /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 dc039de5..699bd8fe 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 31d3941a..96a9adf7 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 0f5b6d81..1ae7fd72 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 00000000..98231d0f --- /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 3ef6695f..95af83cb 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 3306a31d..8bf5a2f3 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 e211c868..98f71de4 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 + ); + } }