From c2fac08914e730dad83959cdbf5ce6ae338356bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 11:04:25 +0200 Subject: [PATCH 01/12] update tests --- tests/StreamFormatterTest.php | 337 ++++++++++++++++++++++++---------- 1 file changed, 244 insertions(+), 93 deletions(-) diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index 580aa61..04b7710 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -27,7 +27,7 @@ use stdClass; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\OutputInterface; use UnexpectedValueException; use function file_put_contents; @@ -429,15 +429,18 @@ public function testFormat(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->with() - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher, $message): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame($message, $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -499,14 +502,18 @@ public function testFormat2(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', 220), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher, $message): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame($message, $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -568,14 +575,18 @@ public function testFormat3(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', 220), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame('test message true test-app', $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -637,14 +648,18 @@ public function testFormat4(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', 220), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame('test message ["abc","xyz"] test-app', $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -707,14 +722,18 @@ public function testFormat5(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', 220), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame('test message test test test-app', $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -784,14 +803,18 @@ public function testFormat6(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', 220), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame("test message test\ntest test-app", $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -862,14 +885,21 @@ public function testFormat7(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', 220), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher, $exception): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame( + "test message test\ntest <[object] (RuntimeException(code: " . $exception->getCode() . '): error at ' . $exception->getFile() . ':' . $exception->getLine() . ')>', + $messages, + ), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -941,14 +971,18 @@ public function testFormat8(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame("test message test\ntest test-app", $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -1022,14 +1056,18 @@ public function testFormat9(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame("test message test\ntest test-app", $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -1103,14 +1141,21 @@ public function testFormat10(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame( + "test message context.one test\ntest test-app extra.Exception", + $messages, + ), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -1180,14 +1225,21 @@ public function testFormat11(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame( + "test message NULL test\ntest test-app test-app", + $messages, + ), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -1261,14 +1313,18 @@ public function testFormat12(): void $output->expects(self::exactly(2)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected); - $output->expects(self::exactly(5)) + $matcher = self::exactly(5); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher, $formattedMessage): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame($formattedMessage, $messages), + }; + }, ); $table = $this->getMockBuilder(Table::class) @@ -1367,6 +1423,85 @@ public function testFormat13(): void | six | stdClass | {"a":"test-channel","b":"test message"} | +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +'; + + $output = new BufferedOutput(); + $table = new Table($output); + + $formatter = new StreamFormatter( + $output, + $table, + '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + $tableStyle, + null, + true, + ); + + $record = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: ['one' => null, 'five' => "test\ntest", 'six' => $stdClass], + extra: ['app' => 'test-app'], + ); + + $lineFormatter = $this->getMockBuilder(LineFormatter::class) + ->disableOriginalConstructor() + ->getMock(); + $lineFormatter->expects(self::once()) + ->method('format') + ->with($record) + ->willReturn($formattedMessage); + + $formatter->setFormatter($lineFormatter); + + $formatted = $formatter->format($record); + + self::assertSame( + str_replace("\r\n", "\n", $expected), + str_replace("\r\n", "\n", $formatted), + ); + } + + /** + * @throws Exception + * @throws RuntimeException + */ + public function testFormat14(): void + { + $message = ' test message '; + $channel = 'test-channel'; + $tableStyle = 'default'; + $datetime = new DateTimeImmutable('now'); + $formattedMessage = 'this is a formatted message'; + $stdClass = new stdClass(); + $stdClass->a = $channel; + $stdClass->b = $message; + + $expected = '============================================================================================================================================================================================================================================================================== + +this is a formatted message + ++----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| General Info | +| Time | ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' | +| Level | ERROR | ++----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Extra | ++----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| app | test-app | ++----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Context | ++----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| one | NULL | +| five | test | +| | test | +| six | stdClass | {"a":"test-channel","b":" test message "} | ++----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + '; $output = new BufferedOutput(); @@ -1454,14 +1589,30 @@ public function testFormatBatch(): void $output->expects(self::exactly(6)) ->method('fetch') ->willReturnOnConsecutiveCalls('', $expected1, '', $expected2, '', $expected3); - $output->expects(self::exactly(15)) + $matcher = self::exactly(15); + $output->expects($matcher) ->method('writeln') - ->willReturnMap( - [ - [str_repeat('=', StreamFormatter::FULL_WIDTH), Output::OUTPUT_NORMAL, null], - ['', Output::OUTPUT_NORMAL, null], - [$message, Output::OUTPUT_NORMAL, null], - ], + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher, $message): void { + match ($matcher->numberOfInvocations()) { + 1, 6, 11 => self::assertSame( + str_repeat('=', StreamFormatter::FULL_WIDTH), + $messages, + (string) $matcher->numberOfInvocations(), + ), + 2, 4, 5, 7, 9, 10, 12, 14, 15 => self::assertSame( + '', + $messages, + (string) $matcher->numberOfInvocations(), + ), + default => self::assertSame( + $message, + $messages, + (string) $matcher->numberOfInvocations(), + ), + }; + }, ); $table = $this->getMockBuilder(Table::class) From 259e942177b01715d76752655bf4e5cd176438f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 13:59:29 +0200 Subject: [PATCH 02/12] update tests --- src/StreamFormatter.php | 3 +- tests/StreamFormatterTest.php | 1501 +++++++++++++++++++++++++++++++-- 2 files changed, 1445 insertions(+), 59 deletions(-) diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index 9dfc247..fc946dc 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -29,7 +29,6 @@ use function count; use function is_array; use function is_bool; -use function is_iterable; use function is_scalar; use function is_string; use function mb_strpos; @@ -173,7 +172,7 @@ public function format(LogRecord $record): string ); foreach (['extra', 'context'] as $element) { - if (empty($vars[$element]) || !is_iterable($vars[$element])) { + if ($vars[$element] === []) { continue; } diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index 04b7710..5460015 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -26,11 +26,15 @@ use RuntimeException; use stdClass; use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; +use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use UnexpectedValueException; +use function assert; use function file_put_contents; +use function in_array; use function str_repeat; use function str_replace; @@ -420,6 +424,7 @@ public function testFormat(): void $message = 'test message'; $channel = 'test-channel'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $expected = 'rendered-content'; @@ -463,8 +468,64 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(3)) - ->method('addRow'); + $matcher = self::exactly(3); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -473,7 +534,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: [], extra: [], @@ -493,6 +554,7 @@ public function testFormat2(): void $message = 'test message'; $channel = 'test-channel'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $expected = 'rendered-content'; @@ -536,8 +598,95 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(15)) - ->method('addRow'); + $matcher = self::exactly(15); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 14 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -546,7 +695,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], @@ -566,6 +715,7 @@ public function testFormat3(): void $message = 'test message'; $channel = 'test-channel'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $expected = 'rendered-content'; @@ -609,8 +759,95 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(15)) - ->method('addRow'); + $matcher = self::exactly(15); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 14 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -619,7 +856,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], @@ -639,6 +876,7 @@ public function testFormat4(): void $message = 'test message'; $channel = 'test-channel'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $expected = 'rendered-content'; @@ -682,8 +920,95 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(15)) - ->method('addRow'); + $matcher = self::exactly(15); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 14 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -692,7 +1017,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], @@ -713,6 +1038,7 @@ public function testFormat5(): void $channel = 'test-channel'; $datetime = new DateTimeImmutable('now'); $tableStyle = 'default'; + $level = Level::Error; $expected = 'rendered-content'; @@ -756,8 +1082,95 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(16)) - ->method('addRow'); + $matcher = self::exactly(16); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 14 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -773,7 +1186,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app'], @@ -794,6 +1207,7 @@ public function testFormat6(): void $channel = 'test-channel'; $datetime = new DateTimeImmutable('now'); $tableStyle = 'default'; + $level = Level::Error; $expected = 'rendered-content'; @@ -837,8 +1251,95 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(16)) - ->method('addRow'); + $matcher = self::exactly(16); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 14 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -854,7 +1355,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app'], @@ -876,6 +1377,7 @@ public function testFormat7(): void $tableStyle = 'default'; $datetime = new DateTimeImmutable('now'); $exception = new RuntimeException('error'); + $level = Level::Error; $expected = 'rendered-content'; @@ -922,8 +1424,99 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(22)) - ->method('addRow'); + $matcher = self::exactly(22); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 14, 16], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 15 => self::assertCount( + 1, + $row, + (string) $matcher->numberOfInvocations(), + ), + 8, 20 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 15) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -939,7 +1532,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app', 'Exception' => $exception], @@ -960,8 +1553,8 @@ public function testFormat8(): void $channel = 'test-channel'; $tableStyle = 'default'; $datetime = new DateTimeImmutable('now'); - - $exception = new RuntimeException('error'); + $exception = new RuntimeException('error'); + $level = Level::Error; $expected = 'rendered-content'; @@ -1005,8 +1598,99 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(22)) - ->method('addRow'); + $matcher = self::exactly(22); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 14, 16], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 15 => self::assertCount( + 1, + $row, + (string) $matcher->numberOfInvocations(), + ), + 8, 20 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 15) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -1022,7 +1706,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app', 'Exception' => $exception], @@ -1043,6 +1727,7 @@ public function testFormat9(): void $channel = 'test-channel'; $tableStyle = 'default'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $expected = 'rendered-content'; @@ -1090,24 +1775,119 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(34)) - ->method('addRow'); - $table->expects(self::once()) - ->method('render'); + $matcher = self::exactly(34); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 26, 28], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); - $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.five% %extra.app%', - $tableStyle, - null, - true, - ); + return $table; + } - $record = new LogRecord( - datetime: $datetime, + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 27 => self::assertCount( + 1, + $row, + (string) $matcher->numberOfInvocations(), + ), + 8, 14, 20, 32 => self::assertCount( + 3, + $row, + (string) $matcher->numberOfInvocations(), + ), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 27) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); + $table->expects(self::once()) + ->method('render'); + + $formatter = new StreamFormatter( + $output, + $table, + '%message% %context.five% %extra.app%', + $tableStyle, + null, + true, + ); + + $record = new LogRecord( + datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app', 'Exception' => $exception3], @@ -1128,6 +1908,7 @@ public function testFormat10(): void $channel = 'test-channel'; $tableStyle = 'default'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $exception1 = new RuntimeException('error'); $exception2 = new UnexpectedValueException('error', 4711, $exception1); @@ -1178,8 +1959,103 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(34)) - ->method('addRow'); + $matcher = self::exactly(34); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 26, 28], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 27 => self::assertCount( + 1, + $row, + (string) $matcher->numberOfInvocations(), + ), + 8, 14, 20, 32 => self::assertCount( + 3, + $row, + (string) $matcher->numberOfInvocations(), + ), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 27) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -1195,7 +2071,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app', 'Exception' => $exception3], @@ -1216,6 +2092,7 @@ public function testFormat11(): void $channel = 'test-channel'; $tableStyle = 'default'; $datetime = new DateTimeImmutable('now'); + $level = Level::Error; $expected = 'rendered-content'; @@ -1262,8 +2139,94 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(12)) - ->method('addRow'); + $matcher = self::exactly(12); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -1279,7 +2242,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'five' => "test\ntest"], extra: ['app' => 'test-app'], @@ -1304,6 +2267,7 @@ public function testFormat12(): void $stdClass = new stdClass(); $stdClass->a = $channel; $stdClass->b = $message; + $level = Level::Error; $expected = 'rendered-content'; @@ -1347,8 +2311,95 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(13)) - ->method('addRow'); + $matcher = self::exactly(13); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 13 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + return $table; + }, + ); $table->expects(self::once()) ->method('render'); @@ -1364,7 +2415,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'five' => "test\ntest", 'six' => $stdClass], extra: ['app' => 'test-app'], @@ -1399,6 +2450,7 @@ public function testFormat13(): void $stdClass = new stdClass(); $stdClass->a = $channel; $stdClass->b = $message; + $level = Level::Error; $expected = '============================================================================================================================================================================================================================================================================== @@ -1440,7 +2492,7 @@ public function testFormat13(): void $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'five' => "test\ntest", 'six' => $stdClass], extra: ['app' => 'test-app'], @@ -1478,6 +2530,7 @@ public function testFormat14(): void $stdClass = new stdClass(); $stdClass->a = $channel; $stdClass->b = $message; + $level = Level::Error; $expected = '============================================================================================================================================================================================================================================================================== @@ -1519,7 +2572,7 @@ public function testFormat14(): void $record = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level, message: $message, context: ['one' => null, 'five' => "test\ntest", 'six' => $stdClass], extra: ['app' => 'test-app'], @@ -1553,6 +2606,9 @@ public function testFormatBatch(): void $channel = 'test-channel'; $tableStyle = StreamFormatter::BOX_STYLE; $datetime = new DateTimeImmutable('now'); + $level1 = Level::Error; + $level2 = Level::Error; + $level3 = Level::Error; $expected1 = 'rendered-content-1'; $expected2 = 'rendered-content-2'; @@ -1561,7 +2617,7 @@ public function testFormatBatch(): void $record1 = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level1, message: $message, context: [], extra: [], @@ -1569,7 +2625,7 @@ public function testFormatBatch(): void $record2 = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level2, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], @@ -1577,7 +2633,7 @@ public function testFormatBatch(): void $record3 = new LogRecord( datetime: $datetime, channel: $channel, - level: Level::Error, + level: $level3, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app'], @@ -1635,8 +2691,236 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $table->expects(self::exactly(34)) - ->method('addRow'); + $matcher = self::exactly(34); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level1, $level2, $level3): Table { + if ( + in_array( + $matcher->numberOfInvocations(), + [7, 9, 11, 13, 22, 24, 26, 28], + true, + ) + ) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 4, 8, 12, 19, 23, 27 => self::assertCount( + 1, + $row, + (string) $matcher->numberOfInvocations(), + ), + 17, 32 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ( + $matcher->numberOfInvocations() === 1 + || $matcher->numberOfInvocations() === 4 + || $matcher->numberOfInvocations() === 19 + ) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'General Info', + (string) $tableCell, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + if ( + $matcher->numberOfInvocations() === 2 + || $matcher->numberOfInvocations() === 5 + || $matcher->numberOfInvocations() === 20 + ) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'Time', + (string) $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'Level', + (string) $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + $level1->getName(), + (string) $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 6) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'Level', + (string) $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + $level2->getName(), + (string) $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 21) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'Level', + (string) $tableCell1, + (string) $matcher->numberOfInvocations(), + ); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + $level3->getName(), + (string) $tableCell2, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + if ( + $matcher->numberOfInvocations() === 8 + || $matcher->numberOfInvocations() === 23 + ) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'Extra', + (string) $tableCell, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + if ( + $matcher->numberOfInvocations() === 12 + || $matcher->numberOfInvocations() === 27 + ) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf( + TableCell::class, + $tableCell, + (string) $matcher->numberOfInvocations(), + ); + self::assertSame( + 'Context', + (string) $tableCell, + (string) $matcher->numberOfInvocations(), + ); + } + + return $table; + }, + ); $table->expects(self::exactly(3)) ->method('render'); @@ -1763,4 +3047,107 @@ public function testFormatBatch2(): void str_replace("\r\n", "\n", $formatted), ); } + + /** + * @throws Exception + * @throws RuntimeException + */ + public function testFormatBatch3(): void + { + $message = 'test message'; + $channel = 'test-channel'; + $tableStyle = StreamFormatter::BOX_STYLE; + $datetime = new DateTimeImmutable('now'); + + $expected1 = '============================================================================================================================================================================================================================================================================== + +test message + +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ General Info │ +│ Time │ ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' │ +│ Level │ ERROR │ +└──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +'; + $expected2 = '============================================================================================================================================================================================================================================================================== + +test message + +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ General Info │ +│ Time │ ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' │ +│ Level │ ERROR │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ Context │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ one │ NULL │ +│ two │ true │ +│ three │ false │ +│ four │ 0 │ abc │ +│ │ 1 │ xyz │ +└──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +'; + $expected3 = '============================================================================================================================================================================================================================================================================== + +test message + +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ General Info │ +│ Time │ ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' │ +│ Level │ ERROR │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ Extra │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ app │ test-app │ +└──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +'; + + $record1 = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: [], + extra: [], + ); + $record2 = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], + extra: [], + ); + $record3 = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: [], + extra: ['app' => 'test-app'], + ); + + $output = new BufferedOutput(); + $table = new Table($output); + + $formatter = new StreamFormatter($output, $table, null, $tableStyle); + + $formatted = $formatter->formatBatch([$record1, $record2, $record3]); + + file_put_contents('output.txt', $formatted); + + self::assertSame( + str_replace("\r\n", "\n", $expected1 . $expected2 . $expected3), + str_replace("\r\n", "\n", $formatted), + ); + } } From 8f9be160aaf84f2d9a35ffeff84ab2c026687744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 18:28:22 +0200 Subject: [PATCH 03/12] update formatter --- src/StreamFormatter.php | 167 ++++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 65 deletions(-) diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index fc946dc..13db47b 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -12,7 +12,6 @@ namespace Mimmi20\Monolog\Formatter; -use DateTimeImmutable; use Monolog\Formatter\LineFormatter; use Monolog\Formatter\NormalizerFormatter; use Monolog\Level; @@ -35,7 +34,6 @@ use function str_repeat; use function str_replace; use function trim; -use function ucfirst; use function var_export; final class StreamFormatter extends NormalizerFormatter @@ -127,10 +125,6 @@ public function setFormatter(LineFormatter $formatter): void */ public function format(LogRecord $record): string { - /** @var array<(array|scalar|null)>|scalar|null $vars */ - /** @phpstan-var array{message: string, context: array, level: Level, level_name: string, channel: string, datetime: DateTimeImmutable, extra: array} $vars */ - $vars = $this->normalizeRecord($record); - $message = $this->getFormatter()->format($record); $levelName = Level::fromValue($record->level->value)->getName(); @@ -171,65 +165,8 @@ public function format(LogRecord $record): string ], ); - foreach (['extra', 'context'] as $element) { - if ($vars[$element] === []) { - continue; - } - - $this->table->addRow(new TableSeparator()); - $this->table->addRow( - [new TableCell(ucfirst($element), ['colspan' => self::SPAN_ALL_COLUMS])], - ); - $this->table->addRow(new TableSeparator()); - - foreach ($vars[$element] as $key => $value) { - if (!is_string($key)) { - continue; - } - - if ( - is_array($record->{$element}) - && isset($record->{$element}[$key]) - && $record->{$element}[$key] instanceof Throwable - ) { - $exception = $record->{$element}[$key]; - - $value = [ - 'Code' => $exception->getCode(), - 'File' => $exception->getFile(), - 'Line' => $exception->getLine(), - 'Message' => $exception->getMessage(), - 'Trace' => $exception->getTraceAsString(), - 'Type' => $exception::class, - ]; - - $this->addFact($key, $value); - - $prev = $exception->getPrevious(); - - if ($prev instanceof Throwable) { - do { - $value = [ - 'Code' => $prev->getCode(), - 'File' => $prev->getFile(), - 'Line' => $prev->getLine(), - 'Message' => $prev->getMessage(), - 'Trace' => $prev->getTraceAsString(), - 'Type' => $prev::class, - ]; - - $this->addFact('previous Throwable', $value); - - $prev = $prev->getPrevious(); - } while ($prev instanceof Throwable); - } - - continue; - } - - $this->addFact($key, $value); - } - } + $this->addExtra($record->extra); + $this->addContext($record->context); $this->table->render(); @@ -362,4 +299,104 @@ private function addFact(string $name, mixed $value): void ], ); } + + /** + * @param array $context + * + * @throws RuntimeException + */ + private function addContext(array $context): void + { + if ($context === []) { + return; + } + + $this->table->addRow(new TableSeparator()); + $this->table->addRow( + [new TableCell('Context', ['colspan' => self::SPAN_ALL_COLUMS])], + ); + $this->table->addRow(new TableSeparator()); + + foreach ($context as $key => $value) { + if (!is_string($key)) { + continue; + } + + if ($context[$key] instanceof Throwable) { + $this->addThrowable($context[$key]); + + continue; + } + + $this->addFact($key, $this->normalize($value)); + } + } + + /** + * @param array $extra + * + * @throws RuntimeException + */ + private function addExtra(array $extra): void + { + if ($extra === []) { + return; + } + + $this->table->addRow(new TableSeparator()); + $this->table->addRow( + [new TableCell('Extra', ['colspan' => self::SPAN_ALL_COLUMS])], + ); + $this->table->addRow(new TableSeparator()); + + foreach ($extra as $key => $value) { + if (!is_string($key)) { + continue; + } + + if ($extra[$key] instanceof Throwable) { + $this->addThrowable($extra[$key]); + + continue; + } + + $this->addFact($key, $this->normalize($value)); + } + } + + /** @throws RuntimeException */ + private function addThrowable(Throwable $exception): void + { + $value = [ + 'Code' => $exception->getCode(), + 'File' => $exception->getFile(), + 'Line' => $exception->getLine(), + 'Message' => $exception->getMessage(), + 'Trace' => $exception->getTraceAsString(), + 'Type' => $exception::class, + ]; + + $this->addFact('Throwable', $value); + + $prev = $exception->getPrevious(); + + if (!$prev instanceof Throwable) { + return; + } + + do { + $value = [ + 'Code' => $prev->getCode(), + 'File' => $prev->getFile(), + 'Line' => $prev->getLine(), + 'Message' => $prev->getMessage(), + 'Trace' => $prev->getTraceAsString(), + 'Type' => $prev::class, + ]; + + $this->addFact('previous Throwable', $value); + + $prev = $prev->getPrevious(); + } while ($prev instanceof Throwable); + } } From 9361512ff782d576fdf5ea08de1a9bd73b5eeeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 18:40:10 +0200 Subject: [PATCH 04/12] update formatter --- src/StreamFormatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index 13db47b..3e12871 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -30,9 +30,9 @@ use function is_bool; use function is_scalar; use function is_string; -use function mb_strpos; use function str_repeat; use function str_replace; +use function str_starts_with; use function trim; use function var_export; @@ -232,7 +232,7 @@ private function convertToString(mixed $data): string private function replaceNewlines(string $str): string { if ($this->allowInlineLineBreaks) { - if (mb_strpos($str, '{') === 0) { + if (str_starts_with($str, '{')) { return str_replace(['\r', '\n'], ["\r", "\n"], $str); } From 503435cb9e23cd0a3ee22fb965698531ee3232fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 20:13:04 +0200 Subject: [PATCH 05/12] update tests --- .github/workflows/continuous-integration.yml | 2 +- phpcs.xml | 1 + src/StreamFormatter.php | 6 +- tests/StreamFormatterTest.php | 490 +++++++++++++++++++ 4 files changed, 496 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f172154..c3786a1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -160,7 +160,7 @@ jobs: - name: "Run mutation tests with infection/infection" env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=79 --min-msi=79 --coverage=.build/coverage --logger-github --no-progress -vv" + run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=85 --min-msi=85 --coverage=.build/coverage --logger-github --no-progress -vv" # This is a meta job to avoid to have to constantly change the protection rules # whenever we touch the matrix. diff --git a/phpcs.xml b/phpcs.xml index aa9d8c0..0a3f6b7 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -15,6 +15,7 @@ + diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index 3e12871..e39fe05 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -277,9 +277,11 @@ private function addFact(string $name, mixed $value): void ), ], ); - } else { - $this->table->addRow([new TableCell((string) $key), new TableCell($cellValue)]); + + continue; } + + $this->table->addRow([new TableCell((string) $key), new TableCell($cellValue)]); } return; diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index 5460015..8a8eca1 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -1506,6 +1506,66 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } + if ($matcher->numberOfInvocations() === 8) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 10) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 11) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 12) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 13) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + if ($matcher->numberOfInvocations() === 15) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -1514,6 +1574,16 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertSame('Context', (string) $tableCell); } + if ($matcher->numberOfInvocations() === 20) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('four', (string) $tableCell); + + return $table; + } + return $table; }, ); @@ -1680,6 +1750,66 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } + if ($matcher->numberOfInvocations() === 8) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 10) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 11) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 12) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 13) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + if ($matcher->numberOfInvocations() === 15) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -1861,6 +1991,186 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } + if ($matcher->numberOfInvocations() === 8) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 10) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 11) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 12) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 13) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 14) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('previous Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 15) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 16) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 17) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 18) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 19) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 20) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('previous Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 21) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 22) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 23) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 24) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 25) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + if ($matcher->numberOfInvocations() === 27) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -2045,6 +2355,186 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } + if ($matcher->numberOfInvocations() === 8) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 10) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 11) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 12) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 13) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 14) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('previous Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 15) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 16) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 17) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 18) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 19) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 20) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('previous Throwable', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 21) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('File', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 22) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Line', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 23) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Message', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 24) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Trace', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 25) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Type', (string) $tableCell); + + return $table; + } + if ($matcher->numberOfInvocations() === 27) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); From f961c66207d55d914e8912fd4b3161380c107eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 20:49:37 +0200 Subject: [PATCH 06/12] add more tests --- src/StreamFormatter.php | 6 -- tests/StreamFormatterTest.php | 121 +++++++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index e39fe05..b929034 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -324,12 +324,6 @@ private function addContext(array $context): void continue; } - if ($context[$key] instanceof Throwable) { - $this->addThrowable($context[$key]); - - continue; - } - $this->addFact($key, $this->normalize($value)); } } diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index 8a8eca1..c0ff502 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -697,7 +697,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, channel: $channel, level: $level, message: $message, - context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], + context: ['one' => null, 'two' => true, 0 => 'numeric-key', 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], ); @@ -858,7 +858,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, channel: $channel, level: $level, message: $message, - context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], + context: ['one' => null, 'two' => true, 0 => 'numeric-key', 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], ); @@ -1019,7 +1019,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, channel: $channel, level: $level, message: $message, - context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz']], + context: ['one' => null, 'two' => true, 0 => 'numeric-key', 'three' => false, 'four' => ['abc', 'xyz']], extra: ['app' => 'test-app'], ); @@ -1188,7 +1188,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, channel: $channel, level: $level, message: $message, - context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], + context: ['one' => null, 'two' => true, 0 => 'numeric-key', 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], extra: ['app' => 'test-app'], ); @@ -1358,7 +1358,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app'], + extra: ['app' => 'test-app', 0 => 'numeric-key'], ); $formatted = $formatter->format($record); @@ -1605,7 +1605,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 'Exception' => $exception], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception], ); $formatted = $formatter->format($record); @@ -1839,7 +1839,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 'Exception' => $exception], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception], ); $formatted = $formatter->format($record); @@ -2200,7 +2200,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 'Exception' => $exception3], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception3], ); $formatted = $formatter->format($record); @@ -2564,7 +2564,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 'Exception' => $exception3], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception3], ); $formatted = $formatter->format($record); @@ -3640,4 +3640,107 @@ public function testFormatBatch3(): void str_replace("\r\n", "\n", $formatted), ); } + + /** + * @throws Exception + * @throws RuntimeException + */ + public function testFormatBatch4(): void + { + $message = ' test message '; + $channel = 'test-channel'; + $tableStyle = StreamFormatter::BOX_STYLE; + $datetime = new DateTimeImmutable('now'); + + $expected1 = '============================================================================================================================================================================================================================================================================== + +test message + +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ General Info │ +│ Time │ ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' │ +│ Level │ ERROR │ +└──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +'; + $expected2 = '============================================================================================================================================================================================================================================================================== + +test message + +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ General Info │ +│ Time │ ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' │ +│ Level │ ERROR │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ Context │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ one │ NULL │ +│ two │ true │ +│ three │ false │ +│ four five │ 0 │ abc │ +│ │ 1 │ xyz │ +└──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +'; + $expected3 = '============================================================================================================================================================================================================================================================================== + +test message + +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ General Info │ +│ Time │ ' . $datetime->format( + NormalizerFormatter::SIMPLE_DATE, + ) . ' │ +│ Level │ ERROR │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ Extra │ +├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ app │ test-app │ +└──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +'; + + $record1 = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: [], + extra: [], + ); + $record2 = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: ['one' => null, 'two' => true, 'three' => false, ' four_five ' => ['abc', 'xyz']], + extra: [], + ); + $record3 = new LogRecord( + datetime: $datetime, + channel: $channel, + level: Level::Error, + message: $message, + context: [], + extra: ['app' => 'test-app'], + ); + + $output = new BufferedOutput(); + $table = new Table($output); + + $formatter = new StreamFormatter($output, $table, null, $tableStyle); + + $formatted = $formatter->formatBatch([$record1, $record2, $record3]); + + file_put_contents('output.txt', $formatted); + + self::assertSame( + str_replace("\r\n", "\n", $expected1 . $expected2 . $expected3), + str_replace("\r\n", "\n", $formatted), + ); + } } From d9308c4bae0e81d62c4d0f4ab1b5796232ca75a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 21:00:07 +0200 Subject: [PATCH 07/12] add more tests --- tests/ConfigProviderTest.php | 31 +++++++++++++++++++++++++++++++ tests/ModuleTest.php | 21 +++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/tests/ConfigProviderTest.php b/tests/ConfigProviderTest.php index 1f4d5f4..1ec24dc 100644 --- a/tests/ConfigProviderTest.php +++ b/tests/ConfigProviderTest.php @@ -50,4 +50,35 @@ public function testGetMonologFormatterConfig(): void self::assertIsArray($factories); self::assertCount(1, $factories); } + + /** @throws Exception */ + public function testInvoke(): void + { + $config = ($this->provider)(); + self::assertIsArray($config); + self::assertCount(1, $config); + + self::assertArrayHasKey('monolog_formatters', $config); + + $monologFormatterConfig = $config['monolog_formatters']; + self::assertIsArray($monologFormatterConfig); + self::assertCount(2, $monologFormatterConfig); + + self::assertArrayNotHasKey('abstract_factories', $monologFormatterConfig); + self::assertArrayNotHasKey('delegators', $monologFormatterConfig); + self::assertArrayNotHasKey('initializers', $monologFormatterConfig); + self::assertArrayNotHasKey('invokables', $monologFormatterConfig); + self::assertArrayNotHasKey('services', $monologFormatterConfig); + self::assertArrayNotHasKey('shared', $monologFormatterConfig); + + self::assertArrayHasKey('aliases', $monologFormatterConfig); + $aliases = $monologFormatterConfig['aliases']; + self::assertIsArray($aliases); + self::assertCount(1, $aliases); + + self::assertArrayHasKey('factories', $monologFormatterConfig); + $factories = $monologFormatterConfig['factories']; + self::assertIsArray($factories); + self::assertCount(1, $factories); + } } diff --git a/tests/ModuleTest.php b/tests/ModuleTest.php index 72b0fb3..3b8928f 100644 --- a/tests/ModuleTest.php +++ b/tests/ModuleTest.php @@ -28,5 +28,26 @@ public function testGetConfig(): void self::assertIsArray($config); self::assertCount(1, $config); self::assertArrayHasKey('monolog_formatters', $config); + + $monologFormatterConfig = $config['monolog_formatters']; + self::assertIsArray($monologFormatterConfig); + self::assertCount(2, $monologFormatterConfig); + + self::assertArrayNotHasKey('abstract_factories', $monologFormatterConfig); + self::assertArrayNotHasKey('delegators', $monologFormatterConfig); + self::assertArrayNotHasKey('initializers', $monologFormatterConfig); + self::assertArrayNotHasKey('invokables', $monologFormatterConfig); + self::assertArrayNotHasKey('services', $monologFormatterConfig); + self::assertArrayNotHasKey('shared', $monologFormatterConfig); + + self::assertArrayHasKey('aliases', $monologFormatterConfig); + $aliases = $monologFormatterConfig['aliases']; + self::assertIsArray($aliases); + self::assertCount(1, $aliases); + + self::assertArrayHasKey('factories', $monologFormatterConfig); + $factories = $monologFormatterConfig['factories']; + self::assertIsArray($factories); + self::assertCount(1, $factories); } } From 51f033a4490c4c2051445691fae1971c4a44c755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 21:24:22 +0200 Subject: [PATCH 08/12] add more tests --- .github/workflows/continuous-integration.yml | 2 +- tests/StreamFormatterTest.php | 162 ++++++++++++------- 2 files changed, 106 insertions(+), 58 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c3786a1..1692f07 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -160,7 +160,7 @@ jobs: - name: "Run mutation tests with infection/infection" env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=85 --min-msi=85 --coverage=.build/coverage --logger-github --no-progress -vv" + run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=92 --min-msi=92 --coverage=.build/coverage --logger-github --no-progress -vv" # This is a meta job to avoid to have to constantly change the protection rules # whenever we touch the matrix. diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index c0ff502..a1533e5 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -1424,12 +1424,12 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $matcher = self::exactly(22); + $matcher = self::exactly(23); $table->expects($matcher) ->method('addRow') ->willReturnCallback( static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { - if (in_array($matcher->numberOfInvocations(), [4, 6, 14, 16], true)) { + if (in_array($matcher->numberOfInvocations(), [4, 6, 15, 17], true)) { self::assertInstanceOf( TableSeparator::class, $row, @@ -1442,12 +1442,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertIsArray($row, (string) $matcher->numberOfInvocations()); match ($matcher->numberOfInvocations()) { - 1, 5, 15 => self::assertCount( + 1, 5, 16 => self::assertCount( 1, $row, (string) $matcher->numberOfInvocations(), ), - 8, 20 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + 8, 21 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), }; @@ -1507,11 +1507,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 8) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -1566,7 +1572,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } - if ($matcher->numberOfInvocations() === 15) { + if ($matcher->numberOfInvocations() === 16) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -1574,7 +1580,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertSame('Context', (string) $tableCell); } - if ($matcher->numberOfInvocations() === 20) { + if ($matcher->numberOfInvocations() === 21) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -1605,7 +1611,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception, 'system' => 'test-system'], ); $formatted = $formatter->format($record); @@ -1668,12 +1674,12 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $matcher = self::exactly(22); + $matcher = self::exactly(23); $table->expects($matcher) ->method('addRow') ->willReturnCallback( static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { - if (in_array($matcher->numberOfInvocations(), [4, 6, 14, 16], true)) { + if (in_array($matcher->numberOfInvocations(), [4, 6, 15, 17], true)) { self::assertInstanceOf( TableSeparator::class, $row, @@ -1686,12 +1692,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertIsArray($row, (string) $matcher->numberOfInvocations()); match ($matcher->numberOfInvocations()) { - 1, 5, 15 => self::assertCount( + 1, 5, 16 => self::assertCount( 1, $row, (string) $matcher->numberOfInvocations(), ), - 8, 20 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + 8, 21 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), }; @@ -1751,11 +1757,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 8) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -1810,7 +1822,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } - if ($matcher->numberOfInvocations() === 15) { + if ($matcher->numberOfInvocations() === 16) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -1839,7 +1851,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception, 'system' => 'test-system'], ); $formatted = $formatter->format($record); @@ -1905,12 +1917,12 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $matcher = self::exactly(34); + $matcher = self::exactly(35); $table->expects($matcher) ->method('addRow') ->willReturnCallback( static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { - if (in_array($matcher->numberOfInvocations(), [4, 6, 26, 28], true)) { + if (in_array($matcher->numberOfInvocations(), [4, 6, 27, 29], true)) { self::assertInstanceOf( TableSeparator::class, $row, @@ -1923,12 +1935,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertIsArray($row, (string) $matcher->numberOfInvocations()); match ($matcher->numberOfInvocations()) { - 1, 5, 27 => self::assertCount( + 1, 5, 28 => self::assertCount( 1, $row, (string) $matcher->numberOfInvocations(), ), - 8, 14, 20, 32 => self::assertCount( + 8, 14, 20, 33 => self::assertCount( 3, $row, (string) $matcher->numberOfInvocations(), @@ -1992,11 +2004,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 8) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -2052,11 +2070,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 14) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('previous Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('previous Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -2112,11 +2136,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 20) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('previous Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('previous Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -2171,7 +2201,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } - if ($matcher->numberOfInvocations() === 27) { + if ($matcher->numberOfInvocations() === 28) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -2200,7 +2230,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception3], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception3, 'system' => 'test-system'], ); $formatted = $formatter->format($record); @@ -2269,12 +2299,12 @@ static function (string | iterable $messages, int $options = OutputInterface::OU ->method('setRows') ->with([]) ->willReturnSelf(); - $matcher = self::exactly(34); + $matcher = self::exactly(35); $table->expects($matcher) ->method('addRow') ->willReturnCallback( static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level): Table { - if (in_array($matcher->numberOfInvocations(), [4, 6, 26, 28], true)) { + if (in_array($matcher->numberOfInvocations(), [4, 6, 27, 29], true)) { self::assertInstanceOf( TableSeparator::class, $row, @@ -2287,12 +2317,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertIsArray($row, (string) $matcher->numberOfInvocations()); match ($matcher->numberOfInvocations()) { - 1, 5, 27 => self::assertCount( + 1, 5, 28 => self::assertCount( 1, $row, (string) $matcher->numberOfInvocations(), ), - 8, 14, 20, 32 => self::assertCount( + 8, 14, 20, 33 => self::assertCount( 3, $row, (string) $matcher->numberOfInvocations(), @@ -2356,11 +2386,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 8) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -2416,11 +2452,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 14) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('previous Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('previous Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -2476,11 +2518,17 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, } if ($matcher->numberOfInvocations() === 20) { - $tableCell = $row[0]; - assert($tableCell instanceof TableCell); + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); - self::assertInstanceOf(TableCell::class, $tableCell); - self::assertSame('previous Throwable', (string) $tableCell); + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('previous Throwable', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('Code', (string) $tableCell2); return $table; } @@ -2535,7 +2583,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, return $table; } - if ($matcher->numberOfInvocations() === 27) { + if ($matcher->numberOfInvocations() === 28) { $tableCell = $row[0]; assert($tableCell instanceof TableCell); @@ -2564,7 +2612,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, level: $level, message: $message, context: ['one' => null, 'two' => true, 'three' => false, 'four' => ['abc', 'xyz'], 'five' => "test\ntest"], - extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception3], + extra: ['app' => 'test-app', 0 => 'numeric-key', 'Exception' => $exception3, 'system' => 'test-system'], ); $formatted = $formatter->format($record); From 2a1de2b09b0e30103a43584f2ae13d2a6793e941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sun, 28 May 2023 22:43:31 +0200 Subject: [PATCH 09/12] add more tests --- src/StreamFormatter.php | 82 +++++----- tests/StreamFormatterTest.php | 295 +++++++++++++++++++++++++++++++++- 2 files changed, 334 insertions(+), 43 deletions(-) diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index b929034..b3df264 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -27,14 +27,12 @@ use function array_keys; use function count; use function is_array; -use function is_bool; use function is_scalar; use function is_string; use function str_repeat; use function str_replace; use function str_starts_with; use function trim; -use function var_export; final class StreamFormatter extends NormalizerFormatter { @@ -208,40 +206,6 @@ private function getFormatter(): LineFormatter return $this->formatter; } - /** @throws RuntimeException if encoding fails and errors are not ignored */ - private function stringify(mixed $value): string - { - return $this->replaceNewlines($this->convertToString($value)); - } - - /** @throws RuntimeException if encoding fails and errors are not ignored */ - private function convertToString(mixed $data): string - { - if ($data === null || is_bool($data)) { - return var_export($data, true); - } - - if (is_scalar($data)) { - return (string) $data; - } - - return $this->toJson($data, true); - } - - /** @throws void */ - private function replaceNewlines(string $str): string - { - if ($this->allowInlineLineBreaks) { - if (str_starts_with($str, '{')) { - return str_replace(['\r', '\n'], ["\r", "\n"], $str); - } - - return $str; - } - - return str_replace(["\r\n", "\r", "\n"], ' ', $str); - } - /** @throws RuntimeException if encoding fails and errors are not ignored */ private function addFact(string $name, mixed $value): void { @@ -302,6 +266,52 @@ private function addFact(string $name, mixed $value): void ); } + /** @throws RuntimeException if encoding fails and errors are not ignored */ + private function stringify(mixed $value): string + { + return $this->replaceNewlines($this->convertToString($value)); + } + + /** @throws RuntimeException if encoding fails and errors are not ignored */ + private function convertToString(mixed $data): string + { + if (is_string($data)) { + return $data; + } + + if ($data === null) { + return 'null'; + } + + if ($data === true) { + return 'true'; + } + + if ($data === false) { + return 'false'; + } + + if (is_scalar($data)) { + return (string) $data; + } + + return $this->toJson($data, true); + } + + /** @throws void */ + private function replaceNewlines(string $str): string + { + if ($this->allowInlineLineBreaks) { + if (str_starts_with($str, '{')) { + return str_replace(['\r\n', '\r', '\n'], ["\r\n", "\r", "\n"], $str); + } + + return $str; + } + + return str_replace(["\r\n", "\r", "\n"], ' ', $str); + } + /** * @param array $context * diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index a1533e5..ccd8f78 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -18,6 +18,7 @@ use Monolog\Formatter\NormalizerFormatter; use Monolog\Level; use Monolog\LogRecord; +use Monolog\Utils; use OutOfRangeException; use PHPUnit\Framework\Exception; use PHPUnit\Framework\TestCase; @@ -35,6 +36,7 @@ use function assert; use function file_put_contents; use function in_array; +use function json_encode; use function str_repeat; use function str_replace; @@ -3007,10 +3009,14 @@ public function testFormat13(): void +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Context | +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| one | NULL | +| one | null | +| two | true | +| three | false | +| four | 42 | | five | test | | | test | | six | stdClass | {"a":"test-channel","b":"test message"} | +| seven | 47.11 | +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ '; @@ -3032,7 +3038,7 @@ public function testFormat13(): void channel: $channel, level: $level, message: $message, - context: ['one' => null, 'five' => "test\ntest", 'six' => $stdClass], + context: ['one' => null, 'two' => true, 'three' => false, 'four' => 42, 'five' => "test\ntest", 'six' => $stdClass, 'seven' => 47.11], extra: ['app' => 'test-app'], ); @@ -3087,7 +3093,7 @@ public function testFormat14(): void +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Context | +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| one | NULL | +| one | null | | five | test | | | test | | six | stdClass | {"a":"test-channel","b":" test message "} | @@ -3134,6 +3140,281 @@ public function testFormat14(): void ); } + /** + * @throws Exception + * @throws RuntimeException + */ + public function testFormat15(): void + { + $message1 = 'test message\rtest message 2\ntest message 3\r\ntest message 4'; + $message2 = 'test message 5\rtest message 6\ntest message 7\r\ntest message 8'; + $message3 = "test1\ntest2\rtest3\r\ntest4"; + $channel = 'test-channel'; + $tableStyle = 'default'; + $datetime = new DateTimeImmutable('now'); + $formattedMessage = 'this is a formatted message'; + $stdClass = new stdClass(); + $stdClass->a = $channel; + $stdClass->b = $message1; + $level = Level::Error; + $appName = 'test-app'; + + $expected = 'rendered-content'; + + $output = $this->getMockBuilder(BufferedOutput::class) + ->disableOriginalConstructor() + ->getMock(); + $output->expects(self::exactly(2)) + ->method('fetch') + ->willReturnOnConsecutiveCalls('', $expected); + $matcher = self::exactly(5); + $output->expects($matcher) + ->method('writeln') + ->willReturnCallback( + /** @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter */ + static function (string | iterable $messages, int $options = OutputInterface::OUTPUT_NORMAL) use ($matcher, $formattedMessage): void { + match ($matcher->numberOfInvocations()) { + 1 => self::assertSame(str_repeat('=', StreamFormatter::FULL_WIDTH), $messages), + 2, 4, 5 => self::assertSame('', $messages), + default => self::assertSame($formattedMessage, $messages), + }; + }, + ); + + $table = $this->getMockBuilder(Table::class) + ->disableOriginalConstructor() + ->getMock(); + $table->expects(self::once()) + ->method('setStyle') + ->with($tableStyle) + ->willReturnSelf(); + $table->expects(self::exactly(3)) + ->method('setColumnMaxWidth') + ->willReturnSelf(); + $table->expects(self::once()) + ->method('setColumnWidths') + ->with( + [StreamFormatter::WIDTH_FIRST_COLUMN, StreamFormatter::WIDTH_SECOND_COLUMN, StreamFormatter::WIDTH_THIRD_COLUMN], + ) + ->willReturnSelf(); + $table->expects(self::once()) + ->method('setRows') + ->with([]) + ->willReturnSelf(); + $matcher = self::exactly(14); + $table->expects($matcher) + ->method('addRow') + ->willReturnCallback( + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level, $message2, $message3, $appName, $stdClass): Table { + if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { + self::assertInstanceOf( + TableSeparator::class, + $row, + (string) $matcher->numberOfInvocations(), + ); + + return $table; + } + + self::assertIsArray($row, (string) $matcher->numberOfInvocations()); + + match ($matcher->numberOfInvocations()) { + 1, 5, 9 => self::assertCount(1, $row, (string) $matcher->numberOfInvocations()), + 13 => self::assertCount(3, $row, (string) $matcher->numberOfInvocations()), + default => self::assertCount(2, $row, (string) $matcher->numberOfInvocations()), + }; + + if ($matcher->numberOfInvocations() === 1) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('General Info', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 2) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Time', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame( + $datetime->format(NormalizerFormatter::SIMPLE_DATE), + (string) $tableCell2, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 3) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('Level', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($level->getName(), (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 5) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Extra', (string) $tableCell); + + return $table; + } + + if ($matcher->numberOfInvocations() === 7) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('app', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($appName, (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 9) { + $tableCell = $row[0]; + assert($tableCell instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell); + self::assertSame('Context', (string) $tableCell); + } + + if ($matcher->numberOfInvocations() === 11) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('one', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('null', (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 12) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('five', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($message3, (string) $tableCell2); + + return $table; + } + + if ($matcher->numberOfInvocations() === 13) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('six', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame('stdClass', (string) $tableCell2); + + $tableCell3 = $row[2]; + assert($tableCell3 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell3); + self::assertSame( + json_encode($stdClass, Utils::DEFAULT_JSON_FLAGS), + (string) $tableCell3, + ); + + return $table; + } + + if ($matcher->numberOfInvocations() === 14) { + $tableCell1 = $row[0]; + assert($tableCell1 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell1); + self::assertSame('seven', (string) $tableCell1); + + $tableCell2 = $row[1]; + assert($tableCell2 instanceof TableCell); + + self::assertInstanceOf(TableCell::class, $tableCell2); + self::assertSame($message2, (string) $tableCell2); + + return $table; + } + + return $table; + }, + ); + $table->expects(self::once()) + ->method('render'); + + $formatter = new StreamFormatter( + $output, + $table, + '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + $tableStyle, + null, + true, + ); + + $record = new LogRecord( + datetime: $datetime, + channel: $channel, + level: $level, + message: $message1, + context: ['one' => null, 'five' => $message3, 'six' => $stdClass, 'seven' => $message2], + extra: ['app' => $appName], + ); + + $lineFormatter = $this->getMockBuilder(LineFormatter::class) + ->disableOriginalConstructor() + ->getMock(); + $lineFormatter->expects(self::once()) + ->method('format') + ->with($record) + ->willReturn($formattedMessage); + + $formatter->setFormatter($lineFormatter); + + $formatted = $formatter->format($record); + + self::assertSame($expected, $formatted); + } + /** * @throws Exception * @throws RuntimeException @@ -3510,7 +3791,7 @@ public function testFormatBatch2(): void ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Context │ ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ one │ NULL │ +│ one │ null │ │ two │ true │ │ three │ false │ │ four │ 0 │ abc │ @@ -3535,7 +3816,7 @@ public function testFormatBatch2(): void ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Context │ ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ one │ NULL │ +│ one │ null │ │ two │ true │ │ three │ false │ │ four │ 0 │ abc │ @@ -3623,7 +3904,7 @@ public function testFormatBatch3(): void ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Context │ ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ one │ NULL │ +│ one │ null │ │ two │ true │ │ three │ false │ │ four │ 0 │ abc │ @@ -3726,7 +4007,7 @@ public function testFormatBatch4(): void ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Context │ ├──────────────────────┼──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ -│ one │ NULL │ +│ one │ null │ │ two │ true │ │ three │ false │ │ four five │ 0 │ abc │ From 7e49a428a8af8f6b5368b8d701bb844ccba80d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 29 May 2023 00:32:22 +0200 Subject: [PATCH 10/12] update test --- src/StreamFormatter.php | 188 +++++++++++++++++----------------- tests/StreamFormatterTest.php | 13 +-- 2 files changed, 95 insertions(+), 106 deletions(-) diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index b3df264..f7cf56a 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -206,6 +206,100 @@ private function getFormatter(): LineFormatter return $this->formatter; } + /** + * @param array $context + * + * @throws RuntimeException + */ + private function addContext(array $context): void + { + if ($context === []) { + return; + } + + $this->table->addRow(new TableSeparator()); + $this->table->addRow( + [new TableCell('Context', ['colspan' => self::SPAN_ALL_COLUMS])], + ); + $this->table->addRow(new TableSeparator()); + + foreach ($context as $key => $value) { + if (!is_string($key)) { + continue; + } + + $this->addFact($key, $this->normalize($value)); + } + } + + /** + * @param array $extra + * + * @throws RuntimeException + */ + private function addExtra(array $extra): void + { + if ($extra === []) { + return; + } + + $this->table->addRow(new TableSeparator()); + $this->table->addRow( + [new TableCell('Extra', ['colspan' => self::SPAN_ALL_COLUMS])], + ); + $this->table->addRow(new TableSeparator()); + + foreach ($extra as $key => $value) { + if (!is_string($key)) { + continue; + } + + if ($extra[$key] instanceof Throwable) { + $this->addThrowable($extra[$key]); + + continue; + } + + $this->addFact($key, $this->normalize($value)); + } + } + + /** @throws RuntimeException */ + private function addThrowable(Throwable $exception): void + { + $value = [ + 'Code' => $exception->getCode(), + 'File' => $exception->getFile(), + 'Line' => $exception->getLine(), + 'Message' => $exception->getMessage(), + 'Trace' => $exception->getTraceAsString(), + 'Type' => $exception::class, + ]; + + $this->addFact('Throwable', $value); + + $prev = $exception->getPrevious(); + + if (!$prev instanceof Throwable) { + return; + } + + do { + $value = [ + 'Code' => $prev->getCode(), + 'File' => $prev->getFile(), + 'Line' => $prev->getLine(), + 'Message' => $prev->getMessage(), + 'Trace' => $prev->getTraceAsString(), + 'Type' => $prev::class, + ]; + + $this->addFact('previous Throwable', $value); + + $prev = $prev->getPrevious(); + } while ($prev instanceof Throwable); + } + /** @throws RuntimeException if encoding fails and errors are not ignored */ private function addFact(string $name, mixed $value): void { @@ -311,98 +405,4 @@ private function replaceNewlines(string $str): string return str_replace(["\r\n", "\r", "\n"], ' ', $str); } - - /** - * @param array $context - * - * @throws RuntimeException - */ - private function addContext(array $context): void - { - if ($context === []) { - return; - } - - $this->table->addRow(new TableSeparator()); - $this->table->addRow( - [new TableCell('Context', ['colspan' => self::SPAN_ALL_COLUMS])], - ); - $this->table->addRow(new TableSeparator()); - - foreach ($context as $key => $value) { - if (!is_string($key)) { - continue; - } - - $this->addFact($key, $this->normalize($value)); - } - } - - /** - * @param array $extra - * - * @throws RuntimeException - */ - private function addExtra(array $extra): void - { - if ($extra === []) { - return; - } - - $this->table->addRow(new TableSeparator()); - $this->table->addRow( - [new TableCell('Extra', ['colspan' => self::SPAN_ALL_COLUMS])], - ); - $this->table->addRow(new TableSeparator()); - - foreach ($extra as $key => $value) { - if (!is_string($key)) { - continue; - } - - if ($extra[$key] instanceof Throwable) { - $this->addThrowable($extra[$key]); - - continue; - } - - $this->addFact($key, $this->normalize($value)); - } - } - - /** @throws RuntimeException */ - private function addThrowable(Throwable $exception): void - { - $value = [ - 'Code' => $exception->getCode(), - 'File' => $exception->getFile(), - 'Line' => $exception->getLine(), - 'Message' => $exception->getMessage(), - 'Trace' => $exception->getTraceAsString(), - 'Type' => $exception::class, - ]; - - $this->addFact('Throwable', $value); - - $prev = $exception->getPrevious(); - - if (!$prev instanceof Throwable) { - return; - } - - do { - $value = [ - 'Code' => $prev->getCode(), - 'File' => $prev->getFile(), - 'Line' => $prev->getLine(), - 'Message' => $prev->getMessage(), - 'Trace' => $prev->getTraceAsString(), - 'Type' => $prev::class, - ]; - - $this->addFact('previous Throwable', $value); - - $prev = $prev->getPrevious(); - } while ($prev instanceof Throwable); - } } diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index ccd8f78..cbfdbbf 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -18,7 +18,6 @@ use Monolog\Formatter\NormalizerFormatter; use Monolog\Level; use Monolog\LogRecord; -use Monolog\Utils; use OutOfRangeException; use PHPUnit\Framework\Exception; use PHPUnit\Framework\TestCase; @@ -36,7 +35,6 @@ use function assert; use function file_put_contents; use function in_array; -use function json_encode; use function str_repeat; use function str_replace; @@ -3205,7 +3203,7 @@ static function (string | iterable $messages, int $options = OutputInterface::OU $table->expects($matcher) ->method('addRow') ->willReturnCallback( - static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level, $message2, $message3, $appName, $stdClass): Table { + static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $level, $message2, $message3, $appName): Table { if (in_array($matcher->numberOfInvocations(), [4, 6, 8, 10], true)) { self::assertInstanceOf( TableSeparator::class, @@ -3348,15 +3346,6 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertInstanceOf(TableCell::class, $tableCell2); self::assertSame('stdClass', (string) $tableCell2); - $tableCell3 = $row[2]; - assert($tableCell3 instanceof TableCell); - - self::assertInstanceOf(TableCell::class, $tableCell3); - self::assertSame( - json_encode($stdClass, Utils::DEFAULT_JSON_FLAGS), - (string) $tableCell3, - ); - return $table; } From 972c01513f0547c0db349e5c28c8927b2b48ea69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 29 May 2023 20:41:43 +0200 Subject: [PATCH 11/12] update test --- .github/workflows/continuous-integration.yml | 2 +- composer.json | 2 +- src/StreamFormatter.php | 21 +- tests/StreamFormatterTest.php | 426 ++++++++++++++----- 4 files changed, 328 insertions(+), 123 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1692f07..e74ee1e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -160,7 +160,7 @@ jobs: - name: "Run mutation tests with infection/infection" env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=92 --min-msi=92 --coverage=.build/coverage --logger-github --no-progress -vv" + run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=94 --min-msi=94 --coverage=.build/coverage --logger-github --no-progress -vv" # This is a meta job to avoid to have to constantly change the protection rules # whenever we touch the matrix. diff --git a/composer.json b/composer.json index 347a262..10c9b21 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "infection/infection": "^0.27.0", "laminas/laminas-modulemanager": "^2.14.0", "laminas/laminas-servicemanager": "^3.21.0", - "mimmi20/coding-standard": "^5.0.3", + "mimmi20/coding-standard": "^5.0.5", "nikic/php-parser": "^v4.15.5", "phpstan/extension-installer": "^1.3.1", "phpstan/phpstan": "^1.10.15", diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index f7cf56a..c812fed 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -31,7 +31,6 @@ use function is_string; use function str_repeat; use function str_replace; -use function str_starts_with; use function trim; final class StreamFormatter extends NormalizerFormatter @@ -309,11 +308,7 @@ private function addFact(string $name, mixed $value): void $rowspan = count($value); foreach (array_keys($value) as $number => $key) { - $cellValue = $value[$key]; - - if (!is_string($cellValue)) { - $cellValue = $this->stringify($cellValue); - } + $cellValue = $this->stringify($value[$key]); if ($number === 0) { $this->table->addRow( @@ -345,9 +340,7 @@ private function addFact(string $name, mixed $value): void return; } - if (!is_string($value)) { - $value = $this->stringify($value); - } + $value = $this->stringify($value); $this->table->addRow( [ @@ -396,11 +389,11 @@ private function convertToString(mixed $data): string private function replaceNewlines(string $str): string { if ($this->allowInlineLineBreaks) { - if (str_starts_with($str, '{')) { - return str_replace(['\r\n', '\r', '\n'], ["\r\n", "\r", "\n"], $str); - } - - return $str; + return str_replace( + ['\\\\r\\\\n', '\\\r\\\n', '\\r\\n', '\r\n', '\\\\r', '\\\r', '\\r', '\r', '\\\\n', '\\\n', '\\n', '\n', "\r\n", "\r"], + "\n", + $str, + ); } return str_replace(["\r\n", "\r", "\n"], ' ', $str); diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index cbfdbbf..7c3b179 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -78,7 +78,7 @@ public function testConstructWithDefaults(): void $table->expects(self::never()) ->method('render'); - $formatter = new StreamFormatter($output, $table); + $formatter = new StreamFormatter(output: $output, table: $table); self::assertSame(NormalizerFormatter::SIMPLE_DATE, $formatter->getDateFormat()); self::assertSame(9, $formatter->getMaxNormalizeDepth()); @@ -144,13 +144,13 @@ public function testConstructWithValues(): void ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - $format, - $tableStyle, - $dateFormat, - true, - false, + output: $output, + table: $table, + format: $format, + tableStyle: $tableStyle, + dateFormat: $dateFormat, + allowInlineLineBreaks: true, + includeStacktraces: false, ); self::assertSame($dateFormat, $formatter->getDateFormat()); @@ -217,13 +217,13 @@ public function testConstructWithValues2(): void ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - $format, - $tableStyle, - $dateFormat, - false, - true, + output: $output, + table: $table, + format: $format, + tableStyle: $tableStyle, + dateFormat: $dateFormat, + allowInlineLineBreaks: false, + includeStacktraces: true, ); self::assertSame($dateFormat, $formatter->getDateFormat()); @@ -290,13 +290,13 @@ public function testConstructWithValues3(): void ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - $format, - $tableStyle, - $dateFormat, - false, - false, + output: $output, + table: $table, + format: $format, + tableStyle: $tableStyle, + dateFormat: $dateFormat, + allowInlineLineBreaks: false, + includeStacktraces: false, ); self::assertSame($dateFormat, $formatter->getDateFormat()); @@ -369,13 +369,13 @@ public function testConstructWithValues4(): void ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - $format, - $tableStyle, - $dateFormat, - true, - false, + output: $output, + table: $table, + format: $format, + tableStyle: $tableStyle, + dateFormat: $dateFormat, + allowInlineLineBreaks: true, + includeStacktraces: false, ); self::assertSame($dateFormat, $formatter->getDateFormat()); @@ -529,7 +529,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $table->expects(self::once()) ->method('render'); - $formatter = new StreamFormatter($output, $table); + $formatter = new StreamFormatter(output: $output, table: $table); $record = new LogRecord( datetime: $datetime, @@ -690,7 +690,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $table->expects(self::once()) ->method('render'); - $formatter = new StreamFormatter($output, $table); + $formatter = new StreamFormatter(output: $output, table: $table); $record = new LogRecord( datetime: $datetime, @@ -851,7 +851,11 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $table->expects(self::once()) ->method('render'); - $formatter = new StreamFormatter($output, $table, '%message% %context.two% %extra.app%'); + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: '%message% %context.two% %extra.app%', + ); $record = new LogRecord( datetime: $datetime, @@ -1012,7 +1016,11 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $table->expects(self::once()) ->method('render'); - $formatter = new StreamFormatter($output, $table, '%message% %context.four% %extra.app%'); + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: '%message% %context.four% %extra.app%', + ); $record = new LogRecord( datetime: $datetime, @@ -1175,12 +1183,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.five% %extra.app%', - $tableStyle, - null, - false, + output: $output, + table: $table, + format: '%message% %context.five% %extra.app%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: false, ); $record = new LogRecord( @@ -1344,12 +1352,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.five% %extra.app%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.five% %extra.app%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -1597,12 +1605,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.five% <%extra.Exception%>', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.five% <%extra.Exception%>', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -1837,12 +1845,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.five% %extra.app%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.five% %extra.app%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -2216,12 +2224,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.five% %extra.app%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.five% %extra.app%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -2598,12 +2606,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% context.one %context.five% %extra.app% extra.Exception', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% context.one %context.five% %extra.app% extra.Exception', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -2769,12 +2777,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -2942,12 +2950,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -3023,12 +3031,12 @@ public function testFormat13(): void $table = new Table($output); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -3103,12 +3111,12 @@ public function testFormat14(): void $table = new Table($output); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -3328,7 +3336,14 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, assert($tableCell2 instanceof TableCell); self::assertInstanceOf(TableCell::class, $tableCell2); - self::assertSame($message3, (string) $tableCell2); + self::assertSame( + str_replace( + ['\\\\r\\\\n', '\\\r\\\n', '\\r\\n', '\r\n', '\\\\r', '\\\r', '\\r', '\r', '\\\\n', '\\\n', '\\n', '\n', "\r\n", "\r"], + "\n", + $message3, + ), + (string) $tableCell2, + ); return $table; } @@ -3360,7 +3375,14 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, assert($tableCell2 instanceof TableCell); self::assertInstanceOf(TableCell::class, $tableCell2); - self::assertSame($message2, (string) $tableCell2); + self::assertSame( + str_replace( + ['\\\\r\\\\n', '\\\r\\\n', '\\r\\n', '\r\n', '\\\\r', '\\\r', '\\r', '\r', '\\\\n', '\\\n', '\\n', '\n'], + "\n", + $message2, + ), + (string) $tableCell2, + ); return $table; } @@ -3372,12 +3394,12 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, ->method('render'); $formatter = new StreamFormatter( - $output, - $table, - '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', - $tableStyle, - null, - true, + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, ); $record = new LogRecord( @@ -3404,6 +3426,182 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertSame($expected, $formatted); } + /** + * @throws Exception + * @throws RuntimeException + */ + public function testFormat16(): void + { + $message1 = 'test message\rtest message 2\ntest message 3\r\ntest message 4'; + $message2 = 'test message 5\rtest message 6\ntest message 7\r\ntest message 8'; + $message3 = "test1\ntest2\rtest3\r\ntest4"; + $channel = 'test-channel'; + $tableStyle = 'default'; + $datetime = new DateTimeImmutable('now'); + $formattedMessage = 'this is a formatted message'; + $stdClass = new stdClass(); + $stdClass->a = $channel; + $stdClass->b = $message1; + $level = Level::Error; + $appName = 'test-app'; + + $expected = <<format(NormalizerFormatter::SIMPLE_DATE)} | + | Level | ERROR | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Extra | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | app | test-app | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Context | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | one | null | + | five | test1 test2 test3 test4 | + | six | stdClass | {"a":"test-channel","b":"test message\\\\rtest message 2\\\\ntest message 3\\\\r\\\\ntest message 4"} | + | seven | test message 5\\rtest message 6\\ntest mes | + | | sage 7\\r\\ntest message 8 | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + + TXT; + + $output = new BufferedOutput(); + $table = new Table($output); + + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: false, + ); + + $record = new LogRecord( + datetime: $datetime, + channel: $channel, + level: $level, + message: $message1, + context: ['one' => null, 'five' => $message3, 'six' => $stdClass, 'seven' => $message2], + extra: ['app' => $appName], + ); + + $lineFormatter = $this->getMockBuilder(LineFormatter::class) + ->disableOriginalConstructor() + ->getMock(); + $lineFormatter->expects(self::once()) + ->method('format') + ->with($record) + ->willReturn($formattedMessage); + + $formatter->setFormatter($lineFormatter); + + $formatted = $formatter->format($record); + + self::assertSame( + str_replace(["\r\n", "\r"], "\n", $expected), + str_replace(["\r\n", "\r"], "\n", $formatted), + ); + } + + /** + * @throws Exception + * @throws RuntimeException + */ + public function testFormat17(): void + { + $message1 = 'test message\rtest message 2\ntest message 3\r\ntest message 4'; + $message2 = 'test message 5\rtest message 6\ntest message 7\r\ntest message 8'; + $message3 = "test1\ntest2\rtest3\r\ntest4"; + $channel = 'test-channel'; + $tableStyle = 'default'; + $datetime = new DateTimeImmutable('now'); + $formattedMessage = 'this is a formatted message'; + $stdClass = new stdClass(); + $stdClass->a = $channel; + $stdClass->b = $message1; + $level = Level::Error; + $appName = 'test-app'; + + $expected = <<format(NormalizerFormatter::SIMPLE_DATE)} | + | Level | ERROR | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Extra | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | app | test-app | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Context | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | one | null | + | five | test1 | + | | test2 | + | | test3 | + | | test4 | + | six | stdClass | {"a":"test-channel","b":"test message | + | | | test message 2 | + | | | test message 3 | + | | | test message 4"} | + | seven | test message 5 | + | | test message 6 | + | | test message 7 | + | | test message 8 | + +----------------------+----------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + + TXT; + + $output = new BufferedOutput(); + $table = new Table($output); + + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: '%message% %context.one% %context.five% %context% %extra.app% %extra.app% %extra%', + tableStyle: $tableStyle, + dateFormat: null, + allowInlineLineBreaks: true, + ); + + $record = new LogRecord( + datetime: $datetime, + channel: $channel, + level: $level, + message: $message1, + context: ['one' => null, 'five' => $message3, 'six' => $stdClass, 'seven' => $message2], + extra: ['app' => $appName], + ); + + $lineFormatter = $this->getMockBuilder(LineFormatter::class) + ->disableOriginalConstructor() + ->getMock(); + $lineFormatter->expects(self::once()) + ->method('format') + ->with($record) + ->willReturn($formattedMessage); + + $formatter->setFormatter($lineFormatter); + + $formatted = $formatter->format($record); + + self::assertSame( + str_replace(["\r\n", "\r"], "\n", $expected), + str_replace(["\r\n", "\r"], "\n", $formatted), + ); + } + /** * @throws Exception * @throws RuntimeException @@ -3732,7 +3930,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, $table->expects(self::exactly(3)) ->method('render'); - $formatter = new StreamFormatter($output, $table); + $formatter = new StreamFormatter(output: $output, table: $table); $formatted = $formatter->formatBatch([$record1, $record2, $record3]); @@ -3810,8 +4008,7 @@ public function testFormatBatch2(): void │ three │ false │ │ four │ 0 │ abc │ │ │ 1 │ xyz │ -│ five │ test │ -│ │ test │ +│ five │ test test │ └──────────────────────┴──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ '; @@ -3844,7 +4041,12 @@ public function testFormatBatch2(): void $output = new BufferedOutput(); $table = new Table($output); - $formatter = new StreamFormatter($output, $table, null, $tableStyle); + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: null, + tableStyle: $tableStyle, + ); $formatted = $formatter->formatBatch([$record1, $record2, $record3]); @@ -3947,7 +4149,12 @@ public function testFormatBatch3(): void $output = new BufferedOutput(); $table = new Table($output); - $formatter = new StreamFormatter($output, $table, null, $tableStyle); + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: null, + tableStyle: $tableStyle, + ); $formatted = $formatter->formatBatch([$record1, $record2, $record3]); @@ -4050,7 +4257,12 @@ public function testFormatBatch4(): void $output = new BufferedOutput(); $table = new Table($output); - $formatter = new StreamFormatter($output, $table, null, $tableStyle); + $formatter = new StreamFormatter( + output: $output, + table: $table, + format: null, + tableStyle: $tableStyle, + ); $formatted = $formatter->formatBatch([$record1, $record2, $record3]); From 14e7b604565830682a76871059e43185f0ca848c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 29 May 2023 21:03:37 +0200 Subject: [PATCH 12/12] update tests --- .github/workflows/continuous-integration.yml | 2 +- src/StreamFormatter.php | 2 +- tests/StreamFormatterTest.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index e74ee1e..500b4ab 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -160,7 +160,7 @@ jobs: - name: "Run mutation tests with infection/infection" env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=94 --min-msi=94 --coverage=.build/coverage --logger-github --no-progress -vv" + run: "vendor/bin/infection --show-mutations --only-covered --min-covered-msi=97 --min-msi=97 --coverage=.build/coverage --logger-github --no-progress -vv" # This is a meta job to avoid to have to constantly change the protection rules # whenever we touch the matrix. diff --git a/src/StreamFormatter.php b/src/StreamFormatter.php index c812fed..c79871f 100644 --- a/src/StreamFormatter.php +++ b/src/StreamFormatter.php @@ -390,7 +390,7 @@ private function replaceNewlines(string $str): string { if ($this->allowInlineLineBreaks) { return str_replace( - ['\\\\r\\\\n', '\\\r\\\n', '\\r\\n', '\r\n', '\\\\r', '\\\r', '\\r', '\r', '\\\\n', '\\\n', '\\n', '\n', "\r\n", "\r"], + ['\\\\r\\\\n', '\\r\\n', '\\\\r', '\\r', '\\\\n', '\\n', "\r\n", "\r"], "\n", $str, ); diff --git a/tests/StreamFormatterTest.php b/tests/StreamFormatterTest.php index 7c3b179..d0e3e1a 100644 --- a/tests/StreamFormatterTest.php +++ b/tests/StreamFormatterTest.php @@ -3338,7 +3338,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertInstanceOf(TableCell::class, $tableCell2); self::assertSame( str_replace( - ['\\\\r\\\\n', '\\\r\\\n', '\\r\\n', '\r\n', '\\\\r', '\\\r', '\\r', '\r', '\\\\n', '\\\n', '\\n', '\n', "\r\n", "\r"], + ['\\\\r\\\\n', '\\r\\n', '\\\\r', '\\r', '\\\\n', '\\n', "\r\n", "\r"], "\n", $message3, ), @@ -3377,7 +3377,7 @@ static function (TableSeparator | array $row) use ($matcher, $table, $datetime, self::assertInstanceOf(TableCell::class, $tableCell2); self::assertSame( str_replace( - ['\\\\r\\\\n', '\\\r\\\n', '\\r\\n', '\r\n', '\\\\r', '\\\r', '\\r', '\r', '\\\\n', '\\\n', '\\n', '\n'], + ['\\\\r\\\\n', '\\r\\n', '\\\\r', '\\r', '\\\\n', '\\n', "\r\n", "\r"], "\n", $message2, ),