From a58a6a2e32a022c91dcb4294ae974bcdb1c30dd4 Mon Sep 17 00:00:00 2001 From: Sebastian Bergmann Date: Mon, 11 Sep 2023 15:08:28 +0200 Subject: [PATCH] Closes #5441 --- .psalm/baseline.xml | 23 +++ ChangeLog-10.4.md | 1 + phpunit.xml | 1 + phpunit.xsd | 1 + src/Event/Emitter/DispatchingEmitter.php | 18 ++- src/Event/Emitter/Emitter.php | 12 +- .../Test/Issue/DeprecationTriggered.php | 31 +++- .../Events/Test/Issue/NoticeTriggered.php | 31 +++- .../Test/Issue/PhpDeprecationTriggered.php | 31 +++- .../Events/Test/Issue/PhpNoticeTriggered.php | 31 +++- .../Events/Test/Issue/PhpWarningTriggered.php | 31 +++- .../Events/Test/Issue/WarningTriggered.php | 31 +++- src/Runner/Baseline/Baseline.php | 59 ++++++++ .../Exception/CannotLoadBaselineException.php | 20 +++ .../FileDoesNotHaveLineException.php | 31 ++++ src/Runner/Baseline/Generator.php | 80 ++++++++++ src/Runner/Baseline/Issue.php | 143 ++++++++++++++++++ src/Runner/Baseline/Reader.php | 100 ++++++++++++ .../Baseline/RelativePathCalculator.php | 103 +++++++++++++ src/Runner/Baseline/Subscriber/Subscriber.php | 28 ++++ .../TestTriggeredDeprecationSubscriber.php | 29 ++++ .../TestTriggeredNoticeSubscriber.php | 29 ++++ .../TestTriggeredPhpDeprecationSubscriber.php | 29 ++++ .../TestTriggeredPhpNoticeSubscriber.php | 29 ++++ .../TestTriggeredPhpWarningSubscriber.php | 29 ++++ .../TestTriggeredWarningSubscriber.php | 29 ++++ src/Runner/Baseline/Writer.php | 65 ++++++++ src/Runner/ErrorHandler.php | 30 ++++ src/Runner/TestResult/Collector.php | 24 +++ src/TextUI/Application.php | 53 +++++++ src/TextUI/Configuration/Cli/Builder.php | 24 +++ .../Configuration/Cli/Configuration.php | 53 ++++++- src/TextUI/Configuration/Configuration.php | 24 ++- .../Exception/NoBaselineException.php | 19 +++ src/TextUI/Configuration/Merger.php | 19 +++ src/TextUI/Configuration/Value/Source.php | 42 ++++- .../Xml/DefaultConfiguration.php | 2 + src/TextUI/Configuration/Xml/Loader.php | 9 ++ src/TextUI/Help.php | 3 + .../ProgressPrinter/ProgressPrinter.php | 24 +++ tests/_files/FileWithIssue.php | 14 ++ tests/_files/baseline/FileWithIssues.php | 11 ++ tests/_files/baseline/expected.xml | 12 ++ tests/_files/configuration_codecoverage.xml | 2 +- .../.gitignore | 1 + .../phpunit.xml | 11 ++ .../tests/Test.php | 23 +++ .../baseline/generate-baseline/.gitignore | 1 + .../baseline/generate-baseline/phpunit.xml | 14 ++ .../baseline/generate-baseline/tests/Test.php | 23 +++ .../baseline/invalid-baseline/baseline.xml | 3 + .../baseline/invalid-baseline/phpunit.xml | 14 ++ .../baseline/invalid-baseline/tests/Test.php | 23 +++ .../unsupported-baseline/baseline.xml | 8 + .../baseline/unsupported-baseline/phpunit.xml | 14 ++ .../unsupported-baseline/tests/Test.php | 23 +++ .../_files/baseline/use-baseline/baseline.xml | 8 + .../_files/baseline/use-baseline/phpunit.xml | 14 ++ .../baseline/use-baseline/tests/Test.php | 23 +++ .../_files/output-cli-help-color.txt | 3 + tests/end-to-end/_files/output-cli-usage.txt | 3 + .../baseline/baseline-does-not-exist.phpt | 29 ++++ .../baseline/baseline-invalid-xml.phpt | 29 ++++ .../baseline/generate-baseline.phpt | 54 +++++++ .../end-to-end/baseline/ignore-baseline.phpt | 35 +++++ .../baseline/unsupported-baseline.phpt | 27 ++++ tests/end-to-end/baseline/use-baseline.phpt | 22 +++ .../Test/Issue/DeprecationTriggeredTest.php | 47 +++++- .../Events/Test/Issue/NoticeTriggeredTest.php | 47 +++++- .../Issue/PhpDeprecationTriggeredTest.php | 47 +++++- .../Test/Issue/PhpNoticeTriggeredTest.php | 47 +++++- .../Test/Issue/PhpWarningTriggeredTest.php | 47 +++++- .../Test/Issue/WarningTriggeredTest.php | 47 +++++- tests/unit/Runner/Baseline/BaselineTest.php | 85 +++++++++++ tests/unit/Runner/Baseline/IssueTest.php | 96 ++++++++++++ tests/unit/Runner/Baseline/ReaderTest.php | 80 ++++++++++ .../Baseline/RelativePathCalculatorTest.php | 135 +++++++++++++++++ tests/unit/Runner/Baseline/WriterTest.php | 81 ++++++++++ .../TextUI/Configuration/Value/SourceTest.php | 63 ++++++++ .../TextUI/Configuration/Xml/LoaderTest.php | 4 + tests/unit/TextUI/SourceFilterTest.php | 12 ++ tests/unit/TextUI/SourceMapperTest.php | 12 ++ 82 files changed, 2565 insertions(+), 100 deletions(-) create mode 100644 src/Runner/Baseline/Baseline.php create mode 100644 src/Runner/Baseline/Exception/CannotLoadBaselineException.php create mode 100644 src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php create mode 100644 src/Runner/Baseline/Generator.php create mode 100644 src/Runner/Baseline/Issue.php create mode 100644 src/Runner/Baseline/Reader.php create mode 100644 src/Runner/Baseline/RelativePathCalculator.php create mode 100644 src/Runner/Baseline/Subscriber/Subscriber.php create mode 100644 src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php create mode 100644 src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php create mode 100644 src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php create mode 100644 src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php create mode 100644 src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php create mode 100644 src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php create mode 100644 src/Runner/Baseline/Writer.php create mode 100644 src/TextUI/Configuration/Exception/NoBaselineException.php create mode 100644 tests/_files/FileWithIssue.php create mode 100644 tests/_files/baseline/FileWithIssues.php create mode 100644 tests/_files/baseline/expected.xml create mode 100644 tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/.gitignore create mode 100644 tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/phpunit.xml create mode 100644 tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/tests/Test.php create mode 100644 tests/end-to-end/_files/baseline/generate-baseline/.gitignore create mode 100644 tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml create mode 100644 tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php create mode 100644 tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml create mode 100644 tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml create mode 100644 tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php create mode 100644 tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml create mode 100644 tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml create mode 100644 tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php create mode 100644 tests/end-to-end/_files/baseline/use-baseline/baseline.xml create mode 100644 tests/end-to-end/_files/baseline/use-baseline/phpunit.xml create mode 100644 tests/end-to-end/_files/baseline/use-baseline/tests/Test.php create mode 100644 tests/end-to-end/baseline/baseline-does-not-exist.phpt create mode 100644 tests/end-to-end/baseline/baseline-invalid-xml.phpt create mode 100644 tests/end-to-end/baseline/generate-baseline.phpt create mode 100644 tests/end-to-end/baseline/ignore-baseline.phpt create mode 100644 tests/end-to-end/baseline/unsupported-baseline.phpt create mode 100644 tests/end-to-end/baseline/use-baseline.phpt create mode 100644 tests/unit/Runner/Baseline/BaselineTest.php create mode 100644 tests/unit/Runner/Baseline/IssueTest.php create mode 100644 tests/unit/Runner/Baseline/ReaderTest.php create mode 100644 tests/unit/Runner/Baseline/RelativePathCalculatorTest.php create mode 100644 tests/unit/Runner/Baseline/WriterTest.php diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml index d438adbe551..1bb414f842b 100644 --- a/.psalm/baseline.xml +++ b/.psalm/baseline.xml @@ -531,6 +531,14 @@ =']]> + + + + + + ]]> + + Driver @@ -569,6 +577,17 @@ stop + + + $errorFile + $errorLine + $errorString + + + Issue::from($file, $line, null, $description) + Issue::from($file, $line, null, $description) + + GroupFilterIterator @@ -635,6 +654,9 @@ + + generateBaseline()]]> + nameAndVersion @@ -726,6 +748,7 @@ hasCoverageCacheDirectory + baseline detect diff --git a/ChangeLog-10.4.md b/ChangeLog-10.4.md index cf9e253ca73..766288f8062 100644 --- a/ChangeLog-10.4.md +++ b/ChangeLog-10.4.md @@ -6,6 +6,7 @@ All notable changes of the PHPUnit 10.4 release series are documented in this fi ### Added +* [#5441](https://github.com/sebastianbergmann/phpunit/issues/5441): Baseline for `E_(USER_)DEPRECATED`, `E_(USER_)NOTICE`, `E_STRICT`, and `E_(USER_)WARNING` * [#5462](https://github.com/sebastianbergmann/phpunit/pull/5462): Support for multiple arguments * [#5471](https://github.com/sebastianbergmann/phpunit/issues/5471): `assertFileMatchesFormat()` and `assertFileMatchesFormatFile()` * Attribute `id` attribute for `testCaseMethod` elements in the XML document generated by `--list-tests-xml` diff --git a/phpunit.xml b/phpunit.xml index ee249a2d47c..f4f103eb2d3 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -13,6 +13,7 @@ + tests/end-to-end/baseline tests/end-to-end/cli tests/end-to-end/code-coverage tests/end-to-end/event diff --git a/phpunit.xsd b/phpunit.xsd index c4c961d6db5..bd22b2ca2a7 100644 --- a/phpunit.xsd +++ b/phpunit.xsd @@ -24,6 +24,7 @@ + diff --git a/src/Event/Emitter/DispatchingEmitter.php b/src/Event/Emitter/DispatchingEmitter.php index 0a7999e49bc..66e6ec1eb83 100644 --- a/src/Event/Emitter/DispatchingEmitter.php +++ b/src/Event/Emitter/DispatchingEmitter.php @@ -752,7 +752,7 @@ public function testTriggeredPhpunitDeprecation(Code\Test $test, string $message * @throws InvalidArgumentException * @throws UnknownEventTypeException */ - public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void { $this->dispatcher->dispatch( new Test\PhpDeprecationTriggered( @@ -762,6 +762,7 @@ public function testTriggeredPhpDeprecation(Code\Test $test, string $message, st $file, $line, $suppressed, + $ignoredByBaseline, ), ); } @@ -770,7 +771,7 @@ public function testTriggeredPhpDeprecation(Code\Test $test, string $message, st * @throws InvalidArgumentException * @throws UnknownEventTypeException */ - public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void { $this->dispatcher->dispatch( new Test\DeprecationTriggered( @@ -780,6 +781,7 @@ public function testTriggeredDeprecation(Code\Test $test, string $message, strin $file, $line, $suppressed, + $ignoredByBaseline, ), ); } @@ -806,7 +808,7 @@ public function testTriggeredError(Code\Test $test, string $message, string $fil * @throws InvalidArgumentException * @throws UnknownEventTypeException */ - public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void { $this->dispatcher->dispatch( new Test\NoticeTriggered( @@ -816,6 +818,7 @@ public function testTriggeredNotice(Code\Test $test, string $message, string $fi $file, $line, $suppressed, + $ignoredByBaseline, ), ); } @@ -824,7 +827,7 @@ public function testTriggeredNotice(Code\Test $test, string $message, string $fi * @throws InvalidArgumentException * @throws UnknownEventTypeException */ - public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void { $this->dispatcher->dispatch( new Test\PhpNoticeTriggered( @@ -834,6 +837,7 @@ public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, $line, $suppressed, + $ignoredByBaseline, ), ); } @@ -842,7 +846,7 @@ public function testTriggeredPhpNotice(Code\Test $test, string $message, string * @throws InvalidArgumentException * @throws UnknownEventTypeException */ - public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void { $this->dispatcher->dispatch( new Test\WarningTriggered( @@ -852,6 +856,7 @@ public function testTriggeredWarning(Code\Test $test, string $message, string $f $file, $line, $suppressed, + $ignoredByBaseline, ), ); } @@ -860,7 +865,7 @@ public function testTriggeredWarning(Code\Test $test, string $message, string $f * @throws InvalidArgumentException * @throws UnknownEventTypeException */ - public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void { $this->dispatcher->dispatch( new Test\PhpWarningTriggered( @@ -870,6 +875,7 @@ public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, $line, $suppressed, + $ignoredByBaseline, ), ); } diff --git a/src/Event/Emitter/Emitter.php b/src/Event/Emitter/Emitter.php index cda52aca064..b60bafbf387 100644 --- a/src/Event/Emitter/Emitter.php +++ b/src/Event/Emitter/Emitter.php @@ -167,19 +167,19 @@ public function testSkipped(Code\Test $test, string $message): void; public function testTriggeredPhpunitDeprecation(Code\Test $test, string $message): void; - public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; - public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; public function testTriggeredError(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; - public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; - public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; - public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; - public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; public function testTriggeredPhpunitError(Code\Test $test, string $message): void; diff --git a/src/Event/Events/Test/Issue/DeprecationTriggered.php b/src/Event/Events/Test/Issue/DeprecationTriggered.php index f27705cd510..7eeb2340333 100644 --- a/src/Event/Events/Test/Issue/DeprecationTriggered.php +++ b/src/Event/Events/Test/Issue/DeprecationTriggered.php @@ -40,20 +40,22 @@ final class DeprecationTriggered implements Event */ private readonly int $line; private readonly bool $suppressed; + private readonly bool $ignoredByBaseline; /** * @psalm-param non-empty-string $message * @psalm-param non-empty-string $file * @psalm-param positive-int $line */ - public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) { - $this->telemetryInfo = $telemetryInfo; - $this->test = $test; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->suppressed = $suppressed; + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; } public function telemetryInfo(): Telemetry\Info @@ -95,6 +97,11 @@ public function wasSuppressed(): bool return $this->suppressed; } + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + public function asString(): string { $message = $this->message; @@ -103,9 +110,17 @@ public function asString(): string $message = PHP_EOL . $message; } + $status = ''; + + if ($this->ignoredByBaseline) { + $status = 'Baseline-Ignored '; + } elseif ($this->suppressed) { + $status = 'Suppressed '; + } + return sprintf( 'Test Triggered %sDeprecation (%s)%s', - $this->wasSuppressed() ? 'Suppressed ' : '', + $status, $this->test->id(), $message, ); diff --git a/src/Event/Events/Test/Issue/NoticeTriggered.php b/src/Event/Events/Test/Issue/NoticeTriggered.php index 57f2a908c50..d8a27bbd852 100644 --- a/src/Event/Events/Test/Issue/NoticeTriggered.php +++ b/src/Event/Events/Test/Issue/NoticeTriggered.php @@ -40,20 +40,22 @@ final class NoticeTriggered implements Event */ private readonly int $line; private readonly bool $suppressed; + private readonly bool $ignoredByBaseline; /** * @psalm-param non-empty-string $message * @psalm-param non-empty-string $file * @psalm-param positive-int $line */ - public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) { - $this->telemetryInfo = $telemetryInfo; - $this->test = $test; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->suppressed = $suppressed; + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; } public function telemetryInfo(): Telemetry\Info @@ -95,6 +97,11 @@ public function wasSuppressed(): bool return $this->suppressed; } + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + public function asString(): string { $message = $this->message; @@ -103,9 +110,17 @@ public function asString(): string $message = PHP_EOL . $message; } + $status = ''; + + if ($this->ignoredByBaseline) { + $status = 'Baseline-Ignored '; + } elseif ($this->suppressed) { + $status = 'Suppressed '; + } + return sprintf( 'Test Triggered %sNotice (%s)%s', - $this->wasSuppressed() ? 'Suppressed ' : '', + $status, $this->test->id(), $message, ); diff --git a/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php b/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php index 5846af0bf6e..a59e5c41cbb 100644 --- a/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php +++ b/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php @@ -40,20 +40,22 @@ final class PhpDeprecationTriggered implements Event */ private readonly int $line; private readonly bool $suppressed; + private readonly bool $ignoredByBaseline; /** * @psalm-param non-empty-string $message * @psalm-param non-empty-string $file * @psalm-param positive-int $line */ - public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) { - $this->telemetryInfo = $telemetryInfo; - $this->test = $test; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->suppressed = $suppressed; + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; } public function telemetryInfo(): Telemetry\Info @@ -95,6 +97,11 @@ public function wasSuppressed(): bool return $this->suppressed; } + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + public function asString(): string { $message = $this->message; @@ -103,9 +110,17 @@ public function asString(): string $message = PHP_EOL . $message; } + $status = ''; + + if ($this->ignoredByBaseline) { + $status = 'Baseline-Ignored '; + } elseif ($this->suppressed) { + $status = 'Suppressed '; + } + return sprintf( 'Test Triggered %sPHP Deprecation (%s)%s', - $this->wasSuppressed() ? 'Suppressed ' : '', + $status, $this->test->id(), $message, ); diff --git a/src/Event/Events/Test/Issue/PhpNoticeTriggered.php b/src/Event/Events/Test/Issue/PhpNoticeTriggered.php index d30efb07aa2..f03d0ba9efb 100644 --- a/src/Event/Events/Test/Issue/PhpNoticeTriggered.php +++ b/src/Event/Events/Test/Issue/PhpNoticeTriggered.php @@ -40,20 +40,22 @@ final class PhpNoticeTriggered implements Event */ private readonly int $line; private readonly bool $suppressed; + private readonly bool $ignoredByBaseline; /** * @psalm-param non-empty-string $message * @psalm-param non-empty-string $file * @psalm-param positive-int $line */ - public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) { - $this->telemetryInfo = $telemetryInfo; - $this->test = $test; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->suppressed = $suppressed; + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; } public function telemetryInfo(): Telemetry\Info @@ -95,6 +97,11 @@ public function wasSuppressed(): bool return $this->suppressed; } + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + public function asString(): string { $message = $this->message; @@ -103,9 +110,17 @@ public function asString(): string $message = PHP_EOL . $message; } + $status = ''; + + if ($this->ignoredByBaseline) { + $status = 'Baseline-Ignored '; + } elseif ($this->suppressed) { + $status = 'Suppressed '; + } + return sprintf( 'Test Triggered %sPHP Notice (%s)%s', - $this->wasSuppressed() ? 'Suppressed ' : '', + $status, $this->test->id(), $message, ); diff --git a/src/Event/Events/Test/Issue/PhpWarningTriggered.php b/src/Event/Events/Test/Issue/PhpWarningTriggered.php index d0ec61cca55..a93dc73e949 100644 --- a/src/Event/Events/Test/Issue/PhpWarningTriggered.php +++ b/src/Event/Events/Test/Issue/PhpWarningTriggered.php @@ -40,20 +40,22 @@ final class PhpWarningTriggered implements Event */ private readonly int $line; private readonly bool $suppressed; + private readonly bool $ignoredByBaseline; /** * @psalm-param non-empty-string $message * @psalm-param non-empty-string $file * @psalm-param positive-int $line */ - public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) { - $this->telemetryInfo = $telemetryInfo; - $this->test = $test; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->suppressed = $suppressed; + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; } public function telemetryInfo(): Telemetry\Info @@ -95,6 +97,11 @@ public function wasSuppressed(): bool return $this->suppressed; } + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + public function asString(): string { $message = $this->message; @@ -103,9 +110,17 @@ public function asString(): string $message = PHP_EOL . $message; } + $status = ''; + + if ($this->ignoredByBaseline) { + $status = 'Baseline-Ignored '; + } elseif ($this->suppressed) { + $status = 'Suppressed '; + } + return sprintf( 'Test Triggered %sPHP Warning (%s)%s', - $this->wasSuppressed() ? 'Suppressed ' : '', + $status, $this->test->id(), $message, ); diff --git a/src/Event/Events/Test/Issue/WarningTriggered.php b/src/Event/Events/Test/Issue/WarningTriggered.php index ddd005c7f4a..9bccafa1e64 100644 --- a/src/Event/Events/Test/Issue/WarningTriggered.php +++ b/src/Event/Events/Test/Issue/WarningTriggered.php @@ -40,20 +40,22 @@ final class WarningTriggered implements Event */ private readonly int $line; private readonly bool $suppressed; + private readonly bool $ignoredByBaseline; /** * @psalm-param non-empty-string $message * @psalm-param non-empty-string $file * @psalm-param positive-int $line */ - public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) { - $this->telemetryInfo = $telemetryInfo; - $this->test = $test; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->suppressed = $suppressed; + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; } public function telemetryInfo(): Telemetry\Info @@ -95,6 +97,11 @@ public function wasSuppressed(): bool return $this->suppressed; } + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + public function asString(): string { $message = $this->message; @@ -103,9 +110,17 @@ public function asString(): string $message = PHP_EOL . $message; } + $status = ''; + + if ($this->ignoredByBaseline) { + $status = 'Baseline-Ignored '; + } elseif ($this->suppressed) { + $status = 'Suppressed '; + } + return sprintf( 'Test Triggered %sWarning (%s)%s', - $this->wasSuppressed() ? 'Suppressed ' : '', + $status, $this->test->id(), $message, ); diff --git a/src/Runner/Baseline/Baseline.php b/src/Runner/Baseline/Baseline.php new file mode 100644 index 00000000000..4921f3188fe --- /dev/null +++ b/src/Runner/Baseline/Baseline.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Baseline +{ + public const VERSION = 1; + + /** + * @psalm-var array>> + */ + private array $issues = []; + + public function add(Issue $issue): void + { + if (!isset($this->issues[$issue->file()])) { + $this->issues[$issue->file()] = []; + } + + if (!isset($this->issues[$issue->file()][$issue->line()])) { + $this->issues[$issue->file()][$issue->line()] = []; + } + + $this->issues[$issue->file()][$issue->line()][] = $issue; + } + + public function has(Issue $issue): bool + { + if (!isset($this->issues[$issue->file()][$issue->line()])) { + return false; + } + + foreach ($this->issues[$issue->file()][$issue->line()] as $_issue) { + if ($_issue->equals($issue)) { + return true; + } + } + + return false; + } + + /** + * @psalm-return array>> + */ + public function groupedByFileAndLine(): array + { + return $this->issues; + } +} diff --git a/src/Runner/Baseline/Exception/CannotLoadBaselineException.php b/src/Runner/Baseline/Exception/CannotLoadBaselineException.php new file mode 100644 index 00000000000..c05e803e545 --- /dev/null +++ b/src/Runner/Baseline/Exception/CannotLoadBaselineException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotLoadBaselineException extends RuntimeException implements Exception +{ +} diff --git a/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php b/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php new file mode 100644 index 00000000000..1121fa3949e --- /dev/null +++ b/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function sprintf; +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileDoesNotHaveLineException extends RuntimeException implements Exception +{ + public function __construct(string $file, int $line) + { + parent::__construct( + sprintf( + 'File "%s" does not have line %d', + $file, + $line, + ), + ); + } +} diff --git a/src/Runner/Baseline/Generator.php b/src/Runner/Baseline/Generator.php new file mode 100644 index 00000000000..6a94baebc66 --- /dev/null +++ b/src/Runner/Baseline/Generator.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\UnknownSubscriberTypeException; +use PHPUnit\Runner\FileDoesNotExistException; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Generator +{ + private Baseline $baseline; + private readonly Source $source; + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function __construct(Facade $facade, Source $source) + { + $facade->registerSubscribers( + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + ); + + $this->baseline = new Baseline; + $this->source = $source; + } + + public function baseline(): Baseline + { + return $this->baseline; + } + + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function testTriggeredIssue(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): void + { + if (!$this->source->ignoreSuppressionOfPhpWarnings() && $event->wasSuppressed()) { + return; + } + + if ($this->source->restrictWarnings() && !(new SourceFilter)->includes($this->source, $event->file())) { + return; + } + + $this->baseline->add( + Issue::from( + $event->file(), + $event->line(), + null, + $event->message(), + ), + ); + } +} diff --git a/src/Runner/Baseline/Issue.php b/src/Runner/Baseline/Issue.php new file mode 100644 index 00000000000..d90b067b982 --- /dev/null +++ b/src/Runner/Baseline/Issue.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function assert; +use function file; +use function is_file; +use function sha1; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Issue +{ + /** + * @psalm-var non-empty-string + */ + private readonly string $file; + + /** + * @psalm-var positive-int + */ + private readonly int $line; + + /** + * @psalm-var non-empty-string + */ + private readonly string $hash; + + /** + * @psalm-var non-empty-string + */ + private readonly string $description; + + /** + * @psalm-param non-empty-string $file + * @psalm-param positive-int $line + * @psalm-param ?non-empty-string $hash + * @psalm-param non-empty-string $description + * + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public static function from(string $file, int $line, ?string $hash, string $description): self + { + if ($hash === null) { + $hash = self::calculateHash($file, $line); + } + + return new self($file, $line, $hash, $description); + } + + /** + * @psalm-param non-empty-string $file + * @psalm-param positive-int $line + * @psalm-param non-empty-string $hash + * @psalm-param non-empty-string $description + */ + private function __construct(string $file, int $line, string $hash, string $description) + { + $this->file = $file; + $this->line = $line; + $this->hash = $hash; + $this->description = $description; + } + + /** + * @psalm-return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @psalm-return positive-int + */ + public function line(): int + { + return $this->line; + } + + /** + * @psalm-return non-empty-string + */ + public function hash(): string + { + return $this->hash; + } + + /** + * @psalm-return non-empty-string + */ + public function description(): string + { + return $this->description; + } + + public function equals(self $other): bool + { + return $this->file() === $other->file() && + $this->line() === $other->line() && + $this->hash() === $other->hash() && + $this->description() === $other->description(); + } + + /** + * @psalm-param non-empty-string $file + * @psalm-param positive-int $line + * + * @psalm-return non-empty-string + * + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + private static function calculateHash(string $file, int $line): string + { + if (!is_file($file)) { + throw new FileDoesNotExistException($file); + } + + $lines = file($file, FILE_IGNORE_NEW_LINES); + $key = $line - 1; + + if (!isset($lines[$key])) { + throw new FileDoesNotHaveLineException($file, $line); + } + + $hash = sha1($lines[$key]); + + assert(!empty($hash)); + + return $hash; + } +} diff --git a/src/Runner/Baseline/Reader.php b/src/Runner/Baseline/Reader.php new file mode 100644 index 00000000000..3aa5cfbc89a --- /dev/null +++ b/src/Runner/Baseline/Reader.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function assert; +use function dirname; +use function file_exists; +use function realpath; +use function sprintf; +use function str_replace; +use function trim; +use DOMElement; +use DOMXPath; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Reader +{ + /** + * @psalm-param non-empty-string $baselineFile + * + * @throws CannotLoadBaselineException + */ + public function read(string $baselineFile): Baseline + { + if (!file_exists($baselineFile)) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s, file does not exist', + $baselineFile, + ), + ); + } + + try { + $document = (new XmlLoader)->loadFile($baselineFile); + } catch (XmlException $e) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline: %s', + trim($e->getMessage()), + ), + ); + } + + $version = (int) $document->documentElement->getAttribute('version'); + + if ($version !== Baseline::VERSION) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s, version %d is not supported', + $baselineFile, + $version, + ), + ); + } + + $baseline = new Baseline; + $baselineDirectory = dirname(realpath($baselineFile)); + $xpath = new DOMXPath($document); + + foreach ($xpath->query('file') as $fileElement) { + assert($fileElement instanceof DOMElement); + + $file = $baselineDirectory . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $fileElement->getAttribute('path')); + + foreach ($xpath->query('line', $fileElement) as $lineElement) { + assert($lineElement instanceof DOMElement); + + $line = (int) $lineElement->getAttribute('number'); + $hash = $lineElement->getAttribute('hash'); + + foreach ($xpath->query('issue', $lineElement) as $issueElement) { + assert($issueElement instanceof DOMElement); + + $description = $issueElement->textContent; + + assert(!empty($file)); + assert($line > 0); + assert(!empty($hash)); + assert(!empty($description)); + + $baseline->add(Issue::from($file, $line, $hash, $description)); + } + } + } + + return $baseline; + } +} diff --git a/src/Runner/Baseline/RelativePathCalculator.php b/src/Runner/Baseline/RelativePathCalculator.php new file mode 100644 index 00000000000..06ed7057de9 --- /dev/null +++ b/src/Runner/Baseline/RelativePathCalculator.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function array_fill; +use function array_merge; +use function array_slice; +use function assert; +use function count; +use function explode; +use function implode; +use function str_replace; +use function strpos; +use function substr; +use function trim; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see Copied from https://github.com/phpstan/phpstan-src/blob/1.10.33/src/File/ParentDirectoryRelativePathHelper.php + */ +final class RelativePathCalculator +{ + /** + * @psalm-var non-empty-string $baselineDirectory + */ + private readonly string $baselineDirectory; + + /** + * @psalm-param non-empty-string $baselineDirectory + */ + public function __construct(string $baselineDirectory) + { + $this->baselineDirectory = $baselineDirectory; + } + + /** + * @psalm-param non-empty-string $filename + * + * @psalm-return non-empty-string + */ + public function calculate(string $filename): string + { + $result = implode('/', $this->parts($filename)); + + assert($result !== ''); + + return $result; + } + + /** + * @psalm-param non-empty-string $filename + * + * @psalm-return list + */ + public function parts(string $filename): array + { + $schemePosition = strpos($filename, '://'); + + if ($schemePosition !== false) { + $filename = substr($filename, $schemePosition + 3); + + assert($filename !== ''); + } + + $parentParts = explode('/', trim(str_replace('\\', '/', $this->baselineDirectory), '/')); + $parentPartsCount = count($parentParts); + $filenameParts = explode('/', trim(str_replace('\\', '/', $filename), '/')); + $filenamePartsCount = count($filenameParts); + + $i = 0; + + for (; $i < $filenamePartsCount; $i++) { + if ($parentPartsCount < $i + 1) { + break; + } + + $parentPath = implode('/', array_slice($parentParts, 0, $i + 1)); + $filenamePath = implode('/', array_slice($filenameParts, 0, $i + 1)); + + if ($parentPath !== $filenamePath) { + break; + } + } + + if ($i === 0) { + return [$filename]; + } + + $dotsCount = $parentPartsCount - $i; + + assert($dotsCount >= 0); + + return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i)); + } +} diff --git a/src/Runner/Baseline/Subscriber/Subscriber.php b/src/Runner/Baseline/Subscriber/Subscriber.php new file mode 100644 index 00000000000..b3ba386c893 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/Subscriber.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class Subscriber +{ + private readonly Generator $generator; + + public function __construct(Generator $generator) + { + $this->generator = $generator; + } + + protected function generator(): Generator + { + return $this->generator; + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..f26ed2ecf31 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(DeprecationTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 00000000000..a531fbcad9c --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(NoticeTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 00000000000..a7a5d9f117d --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpDeprecationTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 00000000000..26085fb63d0 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpNoticeTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 00000000000..a0e617b4f5f --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpWarningTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php b/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 00000000000..793b7149138 --- /dev/null +++ b/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(WarningTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/src/Runner/Baseline/Writer.php b/src/Runner/Baseline/Writer.php new file mode 100644 index 00000000000..28540930068 --- /dev/null +++ b/src/Runner/Baseline/Writer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function assert; +use function dirname; +use function file_put_contents; +use XMLWriter; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Writer +{ + /** + * @psalm-param non-empty-string $baselineFile + */ + public function write(string $baselineFile, Baseline $baseline): void + { + $pathCalculator = new RelativePathCalculator(dirname($baselineFile)); + + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + + $writer->startElement('files'); + $writer->writeAttribute('version', (string) Baseline::VERSION); + + foreach ($baseline->groupedByFileAndLine() as $file => $lines) { + assert(!empty($file)); + + $writer->startElement('file'); + $writer->writeAttribute('path', $pathCalculator->calculate($file)); + + foreach ($lines as $line => $issues) { + $writer->startElement('line'); + $writer->writeAttribute('number', (string) $line); + $writer->writeAttribute('hash', $issues[0]->hash()); + + foreach ($issues as $issue) { + $writer->startElement('issue'); + $writer->writeCData($issue->description()); + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + + file_put_contents($baselineFile, $writer->outputMemory()); + } +} diff --git a/src/Runner/ErrorHandler.php b/src/Runner/ErrorHandler.php index 517f8472e89..f8ac78fc2f8 100644 --- a/src/Runner/ErrorHandler.php +++ b/src/Runner/ErrorHandler.php @@ -21,6 +21,8 @@ use function set_error_handler; use PHPUnit\Event; use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; +use PHPUnit\Runner\Baseline\Baseline; +use PHPUnit\Runner\Baseline\Issue; use PHPUnit\Util\ExcludeList; /** @@ -29,6 +31,7 @@ final class ErrorHandler { private static ?self $instance = null; + private ?Baseline $baseline = null; private bool $enabled = false; public static function instance(): self @@ -47,6 +50,8 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil return false; } + $ignoredByBaseline = $this->ignoredByBaseline($errorFile, $errorLine, $errorString); + switch ($errorNumber) { case E_NOTICE: case E_STRICT: @@ -56,6 +61,7 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil $errorFile, $errorLine, $suppressed, + $ignoredByBaseline, ); break; @@ -67,6 +73,7 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil $errorFile, $errorLine, $suppressed, + $ignoredByBaseline, ); break; @@ -78,6 +85,7 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil $errorFile, $errorLine, $suppressed, + $ignoredByBaseline, ); break; @@ -89,6 +97,7 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil $errorFile, $errorLine, $suppressed, + $ignoredByBaseline, ); break; @@ -100,6 +109,7 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil $errorFile, $errorLine, $suppressed, + $ignoredByBaseline, ); break; @@ -111,6 +121,7 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil $errorFile, $errorLine, $suppressed, + $ignoredByBaseline, ); break; @@ -160,4 +171,23 @@ public function disable(): void $this->enabled = false; } + + public function use(Baseline $baseline): void + { + $this->baseline = $baseline; + } + + /** + * @psalm-param non-empty-string $file + * @psalm-param positive-int $line + * @psalm-param non-empty-string $description + */ + private function ignoredByBaseline(string $file, int $line, string $description): bool + { + if ($this->baseline === null) { + return false; + } + + return $this->baseline->has(Issue::from($file, $line, null, $description)); + } } diff --git a/src/Runner/TestResult/Collector.php b/src/Runner/TestResult/Collector.php index 6ca23f33df9..d06aadd2fe6 100644 --- a/src/Runner/TestResult/Collector.php +++ b/src/Runner/TestResult/Collector.php @@ -342,6 +342,10 @@ public function testConsideredRisky(ConsideredRisky $event): void public function testTriggeredDeprecation(DeprecationTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if (!$this->source->ignoreSuppressionOfDeprecations() && $event->wasSuppressed()) { return; } @@ -368,6 +372,10 @@ public function testTriggeredDeprecation(DeprecationTriggered $event): void public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if (!$this->source->ignoreSuppressionOfPhpDeprecations() && $event->wasSuppressed()) { return; } @@ -425,6 +433,10 @@ public function testTriggeredError(ErrorTriggered $event): void public function testTriggeredNotice(NoticeTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if (!$this->source->ignoreSuppressionOfNotices() && $event->wasSuppressed()) { return; } @@ -451,6 +463,10 @@ public function testTriggeredNotice(NoticeTriggered $event): void public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if (!$this->source->ignoreSuppressionOfPhpNotices() && $event->wasSuppressed()) { return; } @@ -477,6 +493,10 @@ public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void public function testTriggeredWarning(WarningTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if (!$this->source->ignoreSuppressionOfWarnings() && $event->wasSuppressed()) { return; } @@ -503,6 +523,10 @@ public function testTriggeredWarning(WarningTriggered $event): void public function testTriggeredPhpWarning(PhpWarningTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if (!$this->source->ignoreSuppressionOfPhpWarnings() && $event->wasSuppressed()) { return; } diff --git a/src/TextUI/Application.php b/src/TextUI/Application.php index a430a372af1..f4d0a8b7144 100644 --- a/src/TextUI/Application.php +++ b/src/TextUI/Application.php @@ -28,7 +28,12 @@ use PHPUnit\Logging\TestDox\PlainTextRenderer as TestDoxTextRenderer; use PHPUnit\Logging\TestDox\TestResultCollector as TestDoxResultCollector; use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadataApi; +use PHPUnit\Runner\Baseline\CannotLoadBaselineException; +use PHPUnit\Runner\Baseline\Generator as BaselineGenerator; +use PHPUnit\Runner\Baseline\Reader; +use PHPUnit\Runner\Baseline\Writer; use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\ErrorHandler; use PHPUnit\Runner\Extension\ExtensionBootstrapper; use PHPUnit\Runner\Extension\Facade as ExtensionFacade; use PHPUnit\Runner\Extension\PharLoader; @@ -166,6 +171,8 @@ public function run(array $argv): int ); } + $baselineGenerator = $this->configureBaseline($configuration); + EventFacade::instance()->seal(); $timer = new Timer; @@ -209,6 +216,20 @@ public function run(array $argv): int CodeCoverage::instance()->generateReports($printer, $configuration); + if (isset($baselineGenerator)) { + (new Writer)->write( + $configuration->generateBaseline(), + $baselineGenerator->baseline(), + ); + + $printer->print( + sprintf( + PHP_EOL . 'Baseline written to %s.' . PHP_EOL, + realpath($configuration->generateBaseline()), + ), + ); + } + $shellExitCode = (new ShellExitCodeCalculator)->calculate( $configuration->failOnDeprecation(), $configuration->failOnEmptyTestSuite(), @@ -596,4 +617,36 @@ private function initializeTestResultCache(Configuration $configuration): Result return new NullResultCache; } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + private function configureBaseline(Configuration $configuration): ?BaselineGenerator + { + if ($configuration->hasGenerateBaseline()) { + return new BaselineGenerator( + EventFacade::instance(), + $configuration->source(), + ); + } + + if ($configuration->source()->useBaseline()) { + /** @psalm-suppress MissingThrowsDocblock */ + $baselineFile = $configuration->source()->baseline(); + $baseline = null; + + try { + $baseline = (new Reader)->read($baselineFile); + } catch (CannotLoadBaselineException $e) { + EventFacade::emitter()->testRunnerTriggeredWarning($e->getMessage()); + } + + if ($baseline !== null) { + ErrorHandler::instance()->use($baseline); + } + } + + return null; + } } diff --git a/src/TextUI/Configuration/Cli/Builder.php b/src/TextUI/Configuration/Cli/Builder.php index 2add6a8a9c1..08f14a26df6 100644 --- a/src/TextUI/Configuration/Cli/Builder.php +++ b/src/TextUI/Configuration/Cli/Builder.php @@ -55,6 +55,9 @@ final class Builder 'enforce-time-limit', 'exclude-group=', 'filter=', + 'generate-baseline=', + 'use-baseline=', + 'ignore-baseline', 'generate-configuration', 'globals-backup', 'group=', @@ -193,6 +196,9 @@ public function fromParameters(array $parameters): Configuration $stopOnSkipped = null; $stopOnWarning = null; $filter = null; + $generateBaseline = null; + $useBaseline = null; + $ignoreBaseline = false; $generateConfiguration = false; $migrateConfiguration = false; $groups = null; @@ -369,6 +375,21 @@ public function fromParameters(array $parameters): Configuration break; + case '--generate-baseline': + $generateBaseline = $option[1]; + + break; + + case '--use-baseline': + $useBaseline = $option[1]; + + break; + + case '--ignore-baseline': + $ignoreBaseline = true; + + break; + case '--generate-configuration': $generateConfiguration = true; @@ -840,6 +861,9 @@ public function fromParameters(array $parameters): Configuration $stopOnSkipped, $stopOnWarning, $filter, + $generateBaseline, + $useBaseline, + $ignoreBaseline, $generateConfiguration, $migrateConfiguration, $groups, diff --git a/src/TextUI/Configuration/Cli/Configuration.php b/src/TextUI/Configuration/Cli/Configuration.php index ec84bd5de53..cca980eadaa 100644 --- a/src/TextUI/Configuration/Cli/Configuration.php +++ b/src/TextUI/Configuration/Cli/Configuration.php @@ -69,6 +69,9 @@ final class Configuration private readonly ?bool $stopOnSkipped; private readonly ?bool $stopOnWarning; private readonly ?string $filter; + private readonly ?string $generateBaseline; + private readonly ?string $useBaseline; + private readonly bool $ignoreBaseline; private readonly bool $generateConfiguration; private readonly bool $migrateConfiguration; private readonly ?array $groups; @@ -122,7 +125,7 @@ final class Configuration * @psalm-param list $arguments * @psalm-param ?non-empty-list $testSuffixes */ - public function __construct(array $arguments, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticProperties, ?bool $beStrictAboutChangesToGlobalState, ?string $bootstrap, ?string $cacheDirectory, ?bool $cacheResult, ?string $cacheResultFile, bool $checkVersion, ?string $colors, null|int|string $columns, ?string $configurationFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $pathCoverage, ?string $coverageCacheDirectory, bool $warmCoverageCache, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?bool $failOnDeprecation, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnNotice, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?bool $stopOnDefect, ?bool $stopOnDeprecation, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnNotice, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $filter, bool $generateConfiguration, bool $migrateConfiguration, ?array $groups, ?array $testsCovering, ?array $testsUsing, bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, bool $listGroups, bool $listSuites, bool $listTests, ?string $listTestsXml, ?bool $noCoverage, ?bool $noExtensions, ?bool $noOutput, ?bool $noProgress, ?bool $noResults, ?bool $noLogging, ?bool $processIsolation, ?int $randomOrderSeed, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?string $teamcityLogfile, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?array $testSuffixes, ?string $testSuite, ?string $excludeTestSuite, bool $useDefaultConfiguration, ?bool $displayDetailsOnIncompleteTests, ?bool $displayDetailsOnSkippedTests, ?bool $displayDetailsOnTestsThatTriggerDeprecations, ?bool $displayDetailsOnTestsThatTriggerErrors, ?bool $displayDetailsOnTestsThatTriggerNotices, ?bool $displayDetailsOnTestsThatTriggerWarnings, bool $version, ?array $coverageFilter, ?string $logEventsText, ?string $logEventsVerboseText, ?bool $printerTeamCity, ?bool $printerTestDox) + public function __construct(array $arguments, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticProperties, ?bool $beStrictAboutChangesToGlobalState, ?string $bootstrap, ?string $cacheDirectory, ?bool $cacheResult, ?string $cacheResultFile, bool $checkVersion, ?string $colors, null|int|string $columns, ?string $configurationFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $pathCoverage, ?string $coverageCacheDirectory, bool $warmCoverageCache, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?bool $failOnDeprecation, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnNotice, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?bool $stopOnDefect, ?bool $stopOnDeprecation, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnNotice, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $filter, ?string $generateBaseline, ?string $useBaseline, bool $ignoreBaseline, bool $generateConfiguration, bool $migrateConfiguration, ?array $groups, ?array $testsCovering, ?array $testsUsing, bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, bool $listGroups, bool $listSuites, bool $listTests, ?string $listTestsXml, ?bool $noCoverage, ?bool $noExtensions, ?bool $noOutput, ?bool $noProgress, ?bool $noResults, ?bool $noLogging, ?bool $processIsolation, ?int $randomOrderSeed, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?string $teamcityLogfile, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?array $testSuffixes, ?string $testSuite, ?string $excludeTestSuite, bool $useDefaultConfiguration, ?bool $displayDetailsOnIncompleteTests, ?bool $displayDetailsOnSkippedTests, ?bool $displayDetailsOnTestsThatTriggerDeprecations, ?bool $displayDetailsOnTestsThatTriggerErrors, ?bool $displayDetailsOnTestsThatTriggerNotices, ?bool $displayDetailsOnTestsThatTriggerWarnings, bool $version, ?array $coverageFilter, ?string $logEventsText, ?string $logEventsVerboseText, ?bool $printerTeamCity, ?bool $printerTestDox) { $this->arguments = $arguments; $this->atLeastVersion = $atLeastVersion; @@ -174,6 +177,9 @@ public function __construct(array $arguments, ?string $atLeastVersion, ?bool $ba $this->stopOnSkipped = $stopOnSkipped; $this->stopOnWarning = $stopOnWarning; $this->filter = $filter; + $this->generateBaseline = $generateBaseline; + $this->useBaseline = $useBaseline; + $this->ignoreBaseline = $ignoreBaseline; $this->generateConfiguration = $generateConfiguration; $this->migrateConfiguration = $migrateConfiguration; $this->groups = $groups; @@ -1186,6 +1192,51 @@ public function filter(): string return $this->filter; } + /** + * @psalm-assert-if-true !null $this->generateBaseline + */ + public function hasGenerateBaseline(): bool + { + return $this->generateBaseline !== null; + } + + /** + * @throws Exception + */ + public function generateBaseline(): string + { + if (!$this->hasGenerateBaseline()) { + throw new Exception; + } + + return $this->generateBaseline; + } + + /** + * @psalm-assert-if-true !null $this->useBaseline + */ + public function hasUseBaseline(): bool + { + return $this->useBaseline !== null; + } + + /** + * @throws Exception + */ + public function useBaseline(): string + { + if (!$this->hasUseBaseline()) { + throw new Exception; + } + + return $this->useBaseline; + } + + public function ignoreBaseline(): bool + { + return $this->ignoreBaseline; + } + public function generateConfiguration(): bool { return $this->generateConfiguration; diff --git a/src/TextUI/Configuration/Configuration.php b/src/TextUI/Configuration/Configuration.php index 30043d837d7..72cea86e9e2 100644 --- a/src/TextUI/Configuration/Configuration.php +++ b/src/TextUI/Configuration/Configuration.php @@ -137,6 +137,7 @@ final class Configuration private readonly Php $php; private readonly bool $controlGarbageCollector; private readonly int $numberOfTestsBeforeGarbageCollection; + private readonly ?string $generateBaseline; /** * @psalm-param list $cliArguments @@ -144,7 +145,7 @@ final class Configuration * @psalm-param non-empty-list $testSuffixes * @psalm-param list}> $extensionBootstrappers */ - public function __construct(array $cliArguments, ?string $configurationFile, ?string $bootstrap, bool $cacheResult, ?string $cacheDirectory, ?string $coverageCacheDirectory, Source $source, string $testResultCacheFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4j, int $coverageCrap4jThreshold, ?string $coverageHtml, int $coverageHtmlLowUpperBound, int $coverageHtmlHighLowerBound, string $coverageHtmlColorSuccessLow, string $coverageHtmlColorSuccessMedium, string $coverageHtmlColorSuccessHigh, string $coverageHtmlColorWarning, string $coverageHtmlColorDanger, ?string $coverageHtmlCustomCssFile, ?string $coveragePhp, ?string $coverageText, bool $coverageTextShowUncoveredFiles, bool $coverageTextShowOnlySummary, ?string $coverageXml, bool $pathCoverage, bool $ignoreDeprecatedCodeUnitsFromCodeCoverage, bool $disableCodeCoverageIgnore, bool $failOnDeprecation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnDeprecation, bool $stopOnError, bool $stopOnFailure, bool $stopOnIncomplete, bool $stopOnNotice, bool $stopOnRisky, bool $stopOnSkipped, bool $stopOnWarning, bool $outputToStandardErrorStream, int|string $columns, bool $noExtensions, ?string $pharExtensionDirectory, array $extensionBootstrappers, bool $backupGlobals, bool $backupStaticProperties, bool $beStrictAboutChangesToGlobalState, bool $colors, bool $processIsolation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, bool $reportUselessTests, bool $strictCoverage, bool $disallowTestOutput, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, bool $registerMockObjectsFromTestArgumentsRecursively, bool $noProgress, bool $noResults, bool $noOutput, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies, ?string $logfileTeamcity, ?string $logfileJunit, ?string $logfileTestdoxHtml, ?string $logfileTestdoxText, ?string $logEventsText, ?string $logEventsVerboseText, bool $teamCityOutput, bool $testDoxOutput, ?array $testsCovering, ?array $testsUsing, ?string $filter, ?array $groups, ?array $excludeGroups, int $randomOrderSeed, bool $includeUncoveredFiles, TestSuiteCollection $testSuite, string $includeTestSuite, string $excludeTestSuite, ?string $defaultTestSuite, array $testSuffixes, Php $php, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection) + public function __construct(array $cliArguments, ?string $configurationFile, ?string $bootstrap, bool $cacheResult, ?string $cacheDirectory, ?string $coverageCacheDirectory, Source $source, string $testResultCacheFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4j, int $coverageCrap4jThreshold, ?string $coverageHtml, int $coverageHtmlLowUpperBound, int $coverageHtmlHighLowerBound, string $coverageHtmlColorSuccessLow, string $coverageHtmlColorSuccessMedium, string $coverageHtmlColorSuccessHigh, string $coverageHtmlColorWarning, string $coverageHtmlColorDanger, ?string $coverageHtmlCustomCssFile, ?string $coveragePhp, ?string $coverageText, bool $coverageTextShowUncoveredFiles, bool $coverageTextShowOnlySummary, ?string $coverageXml, bool $pathCoverage, bool $ignoreDeprecatedCodeUnitsFromCodeCoverage, bool $disableCodeCoverageIgnore, bool $failOnDeprecation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnDeprecation, bool $stopOnError, bool $stopOnFailure, bool $stopOnIncomplete, bool $stopOnNotice, bool $stopOnRisky, bool $stopOnSkipped, bool $stopOnWarning, bool $outputToStandardErrorStream, int|string $columns, bool $noExtensions, ?string $pharExtensionDirectory, array $extensionBootstrappers, bool $backupGlobals, bool $backupStaticProperties, bool $beStrictAboutChangesToGlobalState, bool $colors, bool $processIsolation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, bool $reportUselessTests, bool $strictCoverage, bool $disallowTestOutput, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, bool $registerMockObjectsFromTestArgumentsRecursively, bool $noProgress, bool $noResults, bool $noOutput, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies, ?string $logfileTeamcity, ?string $logfileJunit, ?string $logfileTestdoxHtml, ?string $logfileTestdoxText, ?string $logEventsText, ?string $logEventsVerboseText, bool $teamCityOutput, bool $testDoxOutput, ?array $testsCovering, ?array $testsUsing, ?string $filter, ?array $groups, ?array $excludeGroups, int $randomOrderSeed, bool $includeUncoveredFiles, TestSuiteCollection $testSuite, string $includeTestSuite, string $excludeTestSuite, ?string $defaultTestSuite, array $testSuffixes, Php $php, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, ?string $generateBaseline) { $this->cliArguments = $cliArguments; $this->configurationFile = $configurationFile; @@ -247,6 +248,7 @@ public function __construct(array $cliArguments, ?string $configurationFile, ?st $this->php = $php; $this->controlGarbageCollector = $controlGarbageCollector; $this->numberOfTestsBeforeGarbageCollection = $numberOfTestsBeforeGarbageCollection; + $this->generateBaseline = $generateBaseline; } /** @@ -1260,4 +1262,24 @@ public function numberOfTestsBeforeGarbageCollection(): int { return $this->numberOfTestsBeforeGarbageCollection; } + + /** + * @psalm-assert-if-true !null $this->generateBaseline + */ + public function hasGenerateBaseline(): bool + { + return $this->generateBaseline !== null; + } + + /** + * @throws NoBaselineException + */ + public function generateBaseline(): string + { + if (!$this->hasGenerateBaseline()) { + throw new NoBaselineException; + } + + return $this->generateBaseline; + } } diff --git a/src/TextUI/Configuration/Exception/NoBaselineException.php b/src/TextUI/Configuration/Exception/NoBaselineException.php new file mode 100644 index 00000000000..eb8cf3ba174 --- /dev/null +++ b/src/TextUI/Configuration/Exception/NoBaselineException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoBaselineException extends RuntimeException implements Exception +{ +} diff --git a/src/TextUI/Configuration/Merger.php b/src/TextUI/Configuration/Merger.php index 1e778dfa3c7..287b1916e48 100644 --- a/src/TextUI/Configuration/Merger.php +++ b/src/TextUI/Configuration/Merger.php @@ -705,6 +705,22 @@ public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlC $sourceExcludeFiles = $xmlConfiguration->source()->excludeFiles(); } + $useBaseline = null; + $generateBaseline = null; + + if (!$cliConfiguration->hasGenerateBaseline()) { + if ($cliConfiguration->hasUseBaseline()) { + $useBaseline = $cliConfiguration->useBaseline(); + } elseif ($xmlConfiguration->source()->hasBaseline()) { + $useBaseline = $xmlConfiguration->source()->baseline(); + } + } else { + $generateBaseline = $cliConfiguration->generateBaseline(); + } + + assert($useBaseline !== ''); + assert($generateBaseline !== ''); + return new Configuration( $cliConfiguration->arguments(), $configurationFile, @@ -713,6 +729,8 @@ public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlC $cacheDirectory, $coverageCacheDirectory, new Source( + $useBaseline, + $cliConfiguration->ignoreBaseline(), FilterDirectoryCollection::fromArray($sourceIncludeDirectories), $sourceIncludeFiles, $sourceExcludeDirectories, @@ -834,6 +852,7 @@ public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlC ), $xmlConfiguration->phpunit()->controlGarbageCollector(), $xmlConfiguration->phpunit()->numberOfTestsBeforeGarbageCollection(), + $generateBaseline, ); } } diff --git a/src/TextUI/Configuration/Value/Source.php b/src/TextUI/Configuration/Value/Source.php index 8b5ddfce7a4..b22edba8508 100644 --- a/src/TextUI/Configuration/Value/Source.php +++ b/src/TextUI/Configuration/Value/Source.php @@ -16,6 +16,11 @@ */ final class Source { + /** + * @psalm-var non-empty-string + */ + private readonly ?string $baseline; + private readonly bool $ignoreBaseline; private readonly FilterDirectoryCollection $includeDirectories; private readonly FileCollection $includeFiles; private readonly FilterDirectoryCollection $excludeDirectories; @@ -31,8 +36,13 @@ final class Source private readonly bool $ignoreSuppressionOfWarnings; private readonly bool $ignoreSuppressionOfPhpWarnings; - public function __construct(FilterDirectoryCollection $includeDirectories, FileCollection $includeFiles, FilterDirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $restrictDeprecations, bool $restrictNotices, bool $restrictWarnings, bool $ignoreSuppressionOfDeprecations, bool $ignoreSuppressionOfPhpDeprecations, bool $ignoreSuppressionOfErrors, bool $ignoreSuppressionOfNotices, bool $ignoreSuppressionOfPhpNotices, bool $ignoreSuppressionOfWarnings, bool $ignoreSuppressionOfPhpWarnings) + /** + * @psalm-param non-empty-string $baseline + */ + public function __construct(?string $baseline, bool $ignoreBaseline, FilterDirectoryCollection $includeDirectories, FileCollection $includeFiles, FilterDirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $restrictDeprecations, bool $restrictNotices, bool $restrictWarnings, bool $ignoreSuppressionOfDeprecations, bool $ignoreSuppressionOfPhpDeprecations, bool $ignoreSuppressionOfErrors, bool $ignoreSuppressionOfNotices, bool $ignoreSuppressionOfPhpNotices, bool $ignoreSuppressionOfWarnings, bool $ignoreSuppressionOfPhpWarnings) { + $this->baseline = $baseline; + $this->ignoreBaseline = $ignoreBaseline; $this->includeDirectories = $includeDirectories; $this->includeFiles = $includeFiles; $this->excludeDirectories = $excludeDirectories; @@ -49,6 +59,36 @@ public function __construct(FilterDirectoryCollection $includeDirectories, FileC $this->ignoreSuppressionOfPhpWarnings = $ignoreSuppressionOfPhpWarnings; } + /** + * @psalm-assert-if-true !null $this->baseline + */ + public function useBaseline(): bool + { + return $this->hasBaseline() && !$this->ignoreBaseline; + } + + /** + * @psalm-assert-if-true !null $this->baseline + */ + public function hasBaseline(): bool + { + return $this->baseline !== null; + } + + /** + * @psalm-return non-empty-string + * + * @throws NoBaselineException + */ + public function baseline(): string + { + if (!$this->hasBaseline()) { + throw new NoBaselineException; + } + + return $this->baseline; + } + public function includeDirectories(): FilterDirectoryCollection { return $this->includeDirectories; diff --git a/src/TextUI/Configuration/Xml/DefaultConfiguration.php b/src/TextUI/Configuration/Xml/DefaultConfiguration.php index 00f73810d20..5c652e95b32 100644 --- a/src/TextUI/Configuration/Xml/DefaultConfiguration.php +++ b/src/TextUI/Configuration/Xml/DefaultConfiguration.php @@ -36,6 +36,8 @@ public static function create(): self return new self( ExtensionBootstrapCollection::fromArray([]), new Source( + null, + false, CodeCoverageFilterDirectoryCollection::fromArray([]), FileCollection::fromArray([]), CodeCoverageFilterDirectoryCollection::fromArray([]), diff --git a/src/TextUI/Configuration/Xml/Loader.php b/src/TextUI/Configuration/Xml/Loader.php index 7294777d7b9..2c219292fb5 100644 --- a/src/TextUI/Configuration/Xml/Loader.php +++ b/src/TextUI/Configuration/Xml/Loader.php @@ -244,6 +244,7 @@ private function toAbsolutePath(string $filename, string $path): string private function source(string $filename, DOMXPath $xpath): Source { + $baseline = null; $restrictDeprecations = false; $restrictNotices = false; $restrictWarnings = false; @@ -258,6 +259,12 @@ private function source(string $filename, DOMXPath $xpath): Source $element = $this->element($xpath, 'source'); if ($element) { + $baseline = $this->getStringAttribute($element, 'baseline'); + + if ($baseline !== null) { + $baseline = $this->toAbsolutePath($filename, $baseline); + } + $restrictDeprecations = $this->getBooleanAttribute($element, 'restrictDeprecations', false); $restrictNotices = $this->getBooleanAttribute($element, 'restrictNotices', false); $restrictWarnings = $this->getBooleanAttribute($element, 'restrictWarnings', false); @@ -271,6 +278,8 @@ private function source(string $filename, DOMXPath $xpath): Source } return new Source( + $baseline, + false, $this->readFilterDirectories($filename, $xpath, 'source/include/directory'), $this->readFilterFiles($filename, $xpath, 'source/include/file'), $this->readFilterDirectories($filename, $xpath, 'source/exclude/directory'), diff --git a/src/TextUI/Help.php b/src/TextUI/Help.php index 075986d263a..23b4ab121ca 100644 --- a/src/TextUI/Help.php +++ b/src/TextUI/Help.php @@ -42,6 +42,9 @@ final class Help ['arg' => '--cache-directory ', 'desc' => 'Specify cache directory'], ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format'], + ['arg' => '--generate-baseline ', 'desc' => 'Generate baseline for issues'], + ['arg' => '--use-baseline ', 'desc' => 'Use baseline to ignore issues'], + ['arg' => '--ignore-baseline', 'desc' => 'Do not use baseline to ignore issues'], ], 'Selection' => [ diff --git a/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php b/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php index b7ddf8f99bb..f94f078edbd 100644 --- a/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php +++ b/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php @@ -99,6 +99,10 @@ public function testMarkedIncomplete(): void public function testTriggeredNotice(NoticeTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if ($this->source->restrictNotices() && !(new SourceFilter)->includes($this->source, $event->file())) { return; @@ -113,6 +117,10 @@ public function testTriggeredNotice(NoticeTriggered $event): void public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if ($this->source->restrictNotices() && !(new SourceFilter)->includes($this->source, $event->file())) { return; @@ -127,6 +135,10 @@ public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void public function testTriggeredDeprecation(DeprecationTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if ($this->source->restrictDeprecations() && !(new SourceFilter)->includes($this->source, $event->file())) { return; @@ -141,6 +153,10 @@ public function testTriggeredDeprecation(DeprecationTriggered $event): void public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if ($this->source->restrictDeprecations() && !(new SourceFilter)->includes($this->source, $event->file())) { return; @@ -165,6 +181,10 @@ public function testConsideredRisky(): void public function testTriggeredWarning(WarningTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if ($this->source->restrictWarnings() && !(new SourceFilter)->includes($this->source, $event->file())) { return; @@ -179,6 +199,10 @@ public function testTriggeredWarning(WarningTriggered $event): void public function testTriggeredPhpWarning(PhpWarningTriggered $event): void { + if ($event->ignoredByBaseline()) { + return; + } + if ($this->source->restrictWarnings() && !(new SourceFilter)->includes($this->source, $event->file())) { return; diff --git a/tests/_files/FileWithIssue.php b/tests/_files/FileWithIssue.php new file mode 100644 index 00000000000..caaa2ce65e7 --- /dev/null +++ b/tests/_files/FileWithIssue.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture; + +final class FileWithIssue +{ +} diff --git a/tests/_files/baseline/FileWithIssues.php b/tests/_files/baseline/FileWithIssues.php new file mode 100644 index 00000000000..13bdbb6af19 --- /dev/null +++ b/tests/_files/baseline/FileWithIssues.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +$a = $b; +$b = $c; diff --git a/tests/_files/baseline/expected.xml b/tests/_files/baseline/expected.xml new file mode 100644 index 00000000000..f41e43f5995 --- /dev/null +++ b/tests/_files/baseline/expected.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/tests/_files/configuration_codecoverage.xml b/tests/_files/configuration_codecoverage.xml index 016c2b88214..5b9cb80971b 100644 --- a/tests/_files/configuration_codecoverage.xml +++ b/tests/_files/configuration_codecoverage.xml @@ -1,7 +1,7 @@ - + /path/to/files /path/to/file diff --git a/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/.gitignore b/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/.gitignore new file mode 100644 index 00000000000..8d20d1464c8 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/.gitignore @@ -0,0 +1 @@ +baseline.xml diff --git a/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/phpunit.xml b/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/phpunit.xml new file mode 100644 index 00000000000..7e6630c6765 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests + + + diff --git a/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/tests/Test.php b/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/tests/Test.php new file mode 100644 index 00000000000..d9ed86d8a59 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline-no-baseline-configured/tests/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/generate-baseline/.gitignore b/tests/end-to-end/_files/baseline/generate-baseline/.gitignore new file mode 100644 index 00000000000..8d20d1464c8 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/.gitignore @@ -0,0 +1 @@ +baseline.xml diff --git a/tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml new file mode 100644 index 00000000000..bf0ee15c129 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + diff --git a/tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php new file mode 100644 index 00000000000..d9ed86d8a59 --- /dev/null +++ b/tests/end-to-end/_files/baseline/generate-baseline/tests/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml b/tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml new file mode 100644 index 00000000000..b359f0ed462 --- /dev/null +++ b/tests/end-to-end/_files/baseline/invalid-baseline/baseline.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml new file mode 100644 index 00000000000..bf0ee15c129 --- /dev/null +++ b/tests/end-to-end/_files/baseline/invalid-baseline/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + diff --git a/tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php new file mode 100644 index 00000000000..d9ed86d8a59 --- /dev/null +++ b/tests/end-to-end/_files/baseline/invalid-baseline/tests/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml b/tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml new file mode 100644 index 00000000000..1745bb2c8db --- /dev/null +++ b/tests/end-to-end/_files/baseline/unsupported-baseline/baseline.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml new file mode 100644 index 00000000000..bf0ee15c129 --- /dev/null +++ b/tests/end-to-end/_files/baseline/unsupported-baseline/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + diff --git a/tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php new file mode 100644 index 00000000000..d9ed86d8a59 --- /dev/null +++ b/tests/end-to-end/_files/baseline/unsupported-baseline/tests/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/baseline/use-baseline/baseline.xml b/tests/end-to-end/_files/baseline/use-baseline/baseline.xml new file mode 100644 index 00000000000..54457526106 --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/baseline.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/end-to-end/_files/baseline/use-baseline/phpunit.xml b/tests/end-to-end/_files/baseline/use-baseline/phpunit.xml new file mode 100644 index 00000000000..bf0ee15c129 --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/phpunit.xml @@ -0,0 +1,14 @@ + + + + + tests + + + + + + diff --git a/tests/end-to-end/_files/baseline/use-baseline/tests/Test.php b/tests/end-to-end/_files/baseline/use-baseline/tests/Test.php new file mode 100644 index 00000000000..d9ed86d8a59 --- /dev/null +++ b/tests/end-to-end/_files/baseline/use-baseline/tests/Test.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Baseline; + +use function trigger_error; +use PHPUnit\Framework\TestCase; + +final class Test extends TestCase +{ + public function testOne(): void + { + trigger_error('deprecation', E_USER_DEPRECATED); + + $this->assertTrue(true); + } +} diff --git a/tests/end-to-end/_files/output-cli-help-color.txt b/tests/end-to-end/_files/output-cli-help-color.txt index 7b7e19a5361..60af5ed4ae3 100644 --- a/tests/end-to-end/_files/output-cli-help-color.txt +++ b/tests/end-to-end/_files/output-cli-help-color.txt @@ -16,6 +16,9 @@ --generate-configuration  Generate configuration file with suggested settings --migrate-configuration  Migrate configuration file to current format + --generate-baseline   Generate baseline for issues + --use-baseline   Use baseline to ignore issues + --ignore-baseline  Do not use baseline to ignore issues Selection: diff --git a/tests/end-to-end/_files/output-cli-usage.txt b/tests/end-to-end/_files/output-cli-usage.txt index 7435ec16d3c..a3a03dd4de9 100644 --- a/tests/end-to-end/_files/output-cli-usage.txt +++ b/tests/end-to-end/_files/output-cli-usage.txt @@ -14,6 +14,9 @@ Configuration: --cache-directory Specify cache directory --generate-configuration Generate configuration file with suggested settings --migrate-configuration Migrate configuration file to current format + --generate-baseline Generate baseline for issues + --use-baseline Use baseline to ignore issues + --ignore-baseline Do not use baseline to ignore issues Selection: diff --git a/tests/end-to-end/baseline/baseline-does-not-exist.phpt b/tests/end-to-end/baseline/baseline-does-not-exist.phpt new file mode 100644 index 00000000000..a1fdb9068d7 --- /dev/null +++ b/tests/end-to-end/baseline/baseline-does-not-exist.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit --configuration ../_files/baseline/unsupported-baseline/phpunit.xml --use-baseline does-not-exist.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot read baseline does-not-exist.xml, file does not exist + +WARNINGS! +Tests: 1, Assertions: 1, Warnings: 1, Deprecations: 1. diff --git a/tests/end-to-end/baseline/baseline-invalid-xml.phpt b/tests/end-to-end/baseline/baseline-invalid-xml.phpt new file mode 100644 index 00000000000..7d09c5b0d95 --- /dev/null +++ b/tests/end-to-end/baseline/baseline-invalid-xml.phpt @@ -0,0 +1,29 @@ +--TEST-- +phpunit --configuration ../_files/baseline/invalid-baseline/phpunit.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot read baseline: Could not load "%sbaseline.xml": + +Premature end of data in tag file line 3 + +WARNINGS! +Tests: 1, Assertions: 1, Warnings: 1, Deprecations: 1. diff --git a/tests/end-to-end/baseline/generate-baseline.phpt b/tests/end-to-end/baseline/generate-baseline.phpt new file mode 100644 index 00000000000..70b6db3e955 --- /dev/null +++ b/tests/end-to-end/baseline/generate-baseline.phpt @@ -0,0 +1,54 @@ +--TEST-- +phpunit --configuration ../_files/baseline/generate-baseline/phpunit.xml --generate-baseline +--FILE-- +run($_SERVER['argv']); + +print file_get_contents($baseline); + +@unlink($baseline); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 deprecation: + +1) %sTest.php:%d +deprecation + +Triggered by: + +* PHPUnit\TestFixture\Baseline\Test::testOne + %sTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. + +Baseline written to %sbaseline.xml. + + + + + + + + diff --git a/tests/end-to-end/baseline/ignore-baseline.phpt b/tests/end-to-end/baseline/ignore-baseline.phpt new file mode 100644 index 00000000000..a56345af0d3 --- /dev/null +++ b/tests/end-to-end/baseline/ignore-baseline.phpt @@ -0,0 +1,35 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline/phpunit.xml --ignore-baseline +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +1 test triggered 1 deprecation: + +1) %sTest.php:%d +deprecation + +Triggered by: + +* PHPUnit\TestFixture\Baseline\Test::testOne + %sTest.php:%d + +OK, but there were issues! +Tests: 1, Assertions: 1, Deprecations: 1. diff --git a/tests/end-to-end/baseline/unsupported-baseline.phpt b/tests/end-to-end/baseline/unsupported-baseline.phpt new file mode 100644 index 00000000000..75c59c28227 --- /dev/null +++ b/tests/end-to-end/baseline/unsupported-baseline.phpt @@ -0,0 +1,27 @@ +--TEST-- +phpunit --configuration ../_files/baseline/unsupported-baseline/phpunit.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +D 1 / 1 (100%) + +Time: %s, Memory: %s + +There was 1 PHPUnit test runner warning: + +1) Cannot read baseline %sbaseline.xml, version 0 is not supported + +WARNINGS! +Tests: 1, Assertions: 1, Warnings: 1, Deprecations: 1. diff --git a/tests/end-to-end/baseline/use-baseline.phpt b/tests/end-to-end/baseline/use-baseline.phpt new file mode 100644 index 00000000000..679b163727b --- /dev/null +++ b/tests/end-to-end/baseline/use-baseline.phpt @@ -0,0 +1,22 @@ +--TEST-- +phpunit --configuration ../_files/baseline/use-baseline/phpunit.xml +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s +Configuration: %s + +. 1 / 1 (100%) + +Time: %s, Memory: %s + +OK (1 test, 1 assertion) diff --git a/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php index b760244dc42..c0327243b12 100644 --- a/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php +++ b/tests/unit/Event/Events/Test/Issue/DeprecationTriggeredTest.php @@ -20,12 +20,13 @@ final class DeprecationTriggeredTest extends AbstractEventTestCase { public function testConstructorSetsValues(): void { - $telemetryInfo = $this->telemetryInfo(); - $test = $this->testValueObject(); - $message = 'message'; - $file = 'file'; - $line = 1; - $suppressed = false; + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; $event = new DeprecationTriggered( $telemetryInfo, @@ -34,6 +35,7 @@ public function testConstructorSetsValues(): void $file, $line, $suppressed, + $ignoredByBaseline, ); $this->assertSame($telemetryInfo, $event->telemetryInfo()); @@ -42,6 +44,39 @@ public function testConstructorSetsValues(): void $this->assertSame($file, $event->file()); $this->assertSame($line, $event->line()); $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); $this->assertSame('Test Triggered Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new DeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Baseline-Ignored Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new DeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Suppressed Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } } diff --git a/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php index b1f5cb24514..9211afd5f54 100644 --- a/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php +++ b/tests/unit/Event/Events/Test/Issue/NoticeTriggeredTest.php @@ -20,12 +20,13 @@ final class NoticeTriggeredTest extends AbstractEventTestCase { public function testConstructorSetsValues(): void { - $telemetryInfo = $this->telemetryInfo(); - $test = $this->testValueObject(); - $message = 'message'; - $file = 'file'; - $line = 1; - $suppressed = false; + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; $event = new NoticeTriggered( $telemetryInfo, @@ -34,6 +35,7 @@ public function testConstructorSetsValues(): void $file, $line, $suppressed, + $ignoredByBaseline, ); $this->assertSame($telemetryInfo, $event->telemetryInfo()); @@ -42,6 +44,39 @@ public function testConstructorSetsValues(): void $this->assertSame($file, $event->file()); $this->assertSame($line, $event->line()); $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); $this->assertSame('Test Triggered Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new NoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Baseline-Ignored Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new NoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Suppressed Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } } diff --git a/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php index f09b85faea3..eec5479f8e2 100644 --- a/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php +++ b/tests/unit/Event/Events/Test/Issue/PhpDeprecationTriggeredTest.php @@ -20,12 +20,13 @@ final class PhpDeprecationTriggeredTest extends AbstractEventTestCase { public function testConstructorSetsValues(): void { - $telemetryInfo = $this->telemetryInfo(); - $test = $this->testValueObject(); - $message = 'message'; - $file = 'file'; - $line = 1; - $suppressed = false; + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; $event = new PhpDeprecationTriggered( $telemetryInfo, @@ -34,6 +35,7 @@ public function testConstructorSetsValues(): void $file, $line, $suppressed, + $ignoredByBaseline, ); $this->assertSame($telemetryInfo, $event->telemetryInfo()); @@ -42,6 +44,39 @@ public function testConstructorSetsValues(): void $this->assertSame($file, $event->file()); $this->assertSame($line, $event->line()); $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); $this->assertSame('Test Triggered PHP Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new PhpDeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Baseline-Ignored PHP Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new PhpDeprecationTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Suppressed PHP Deprecation (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } } diff --git a/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php index e94a375fab9..3ac97f0f077 100644 --- a/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php +++ b/tests/unit/Event/Events/Test/Issue/PhpNoticeTriggeredTest.php @@ -20,12 +20,13 @@ final class PhpNoticeTriggeredTest extends AbstractEventTestCase { public function testConstructorSetsValues(): void { - $telemetryInfo = $this->telemetryInfo(); - $test = $this->testValueObject(); - $message = 'message'; - $file = 'file'; - $line = 1; - $suppressed = false; + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; $event = new PhpNoticeTriggered( $telemetryInfo, @@ -34,6 +35,7 @@ public function testConstructorSetsValues(): void $file, $line, $suppressed, + $ignoredByBaseline, ); $this->assertSame($telemetryInfo, $event->telemetryInfo()); @@ -42,6 +44,39 @@ public function testConstructorSetsValues(): void $this->assertSame($file, $event->file()); $this->assertSame($line, $event->line()); $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); $this->assertSame('Test Triggered PHP Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new PhpNoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Baseline-Ignored PHP Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new PhpNoticeTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Suppressed PHP Notice (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } } diff --git a/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php index 78f9f19a5c1..1fa7cc99c47 100644 --- a/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php +++ b/tests/unit/Event/Events/Test/Issue/PhpWarningTriggeredTest.php @@ -20,12 +20,13 @@ final class PhpWarningTriggeredTest extends AbstractEventTestCase { public function testConstructorSetsValues(): void { - $telemetryInfo = $this->telemetryInfo(); - $test = $this->testValueObject(); - $message = 'message'; - $file = 'file'; - $line = 1; - $suppressed = false; + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; $event = new PhpWarningTriggered( $telemetryInfo, @@ -34,6 +35,7 @@ public function testConstructorSetsValues(): void $file, $line, $suppressed, + $ignoredByBaseline, ); $this->assertSame($telemetryInfo, $event->telemetryInfo()); @@ -42,6 +44,39 @@ public function testConstructorSetsValues(): void $this->assertSame($file, $event->file()); $this->assertSame($line, $event->line()); $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); $this->assertSame('Test Triggered PHP Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new PhpWarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Baseline-Ignored PHP Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new PhpWarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Suppressed PHP Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } } diff --git a/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php b/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php index 7f81d60760d..c95ecb6bdea 100644 --- a/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php +++ b/tests/unit/Event/Events/Test/Issue/WarningTriggeredTest.php @@ -20,12 +20,13 @@ final class WarningTriggeredTest extends AbstractEventTestCase { public function testConstructorSetsValues(): void { - $telemetryInfo = $this->telemetryInfo(); - $test = $this->testValueObject(); - $message = 'message'; - $file = 'file'; - $line = 1; - $suppressed = false; + $telemetryInfo = $this->telemetryInfo(); + $test = $this->testValueObject(); + $message = 'message'; + $file = 'file'; + $line = 1; + $suppressed = false; + $ignoredByBaseline = false; $event = new WarningTriggered( $telemetryInfo, @@ -34,6 +35,7 @@ public function testConstructorSetsValues(): void $file, $line, $suppressed, + $ignoredByBaseline, ); $this->assertSame($telemetryInfo, $event->telemetryInfo()); @@ -42,6 +44,39 @@ public function testConstructorSetsValues(): void $this->assertSame($file, $event->file()); $this->assertSame($line, $event->line()); $this->assertSame($suppressed, $event->wasSuppressed()); + $this->assertSame($ignoredByBaseline, $event->ignoredByBaseline()); $this->assertSame('Test Triggered Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); } + + public function testCanBeIgnoredByBaseline(): void + { + $event = new WarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + false, + true, + ); + + $this->assertTrue($event->ignoredByBaseline()); + $this->assertSame('Test Triggered Baseline-Ignored Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } + + public function testCanBeSuppressed(): void + { + $event = new WarningTriggered( + $this->telemetryInfo(), + $this->testValueObject(), + 'message', + 'file', + 1, + true, + false, + ); + + $this->assertTrue($event->wasSuppressed()); + $this->assertSame('Test Triggered Suppressed Warning (FooTest::testBar)' . PHP_EOL . 'message', $event->asString()); + } } diff --git a/tests/unit/Runner/Baseline/BaselineTest.php b/tests/unit/Runner/Baseline/BaselineTest.php new file mode 100644 index 00000000000..a93cf49c883 --- /dev/null +++ b/tests/unit/Runner/Baseline/BaselineTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Baseline::class)] +#[Small] +final class BaselineTest extends TestCase +{ + public function testGroupsIssuesByFileAndLine(): void + { + $baseline = new Baseline; + + $baseline->add($this->issue()); + + $issues = $baseline->groupedByFileAndLine(); + + $this->assertCount(1, $issues); + $this->assertArrayHasKey($this->issue()->file(), $issues); + + $lines = $issues[$this->issue()->file()]; + + $this->assertCount(1, $lines); + $this->assertArrayHasKey(10, $lines); + + $line = $lines[10]; + + $this->assertCount(1, $line); + $this->assertArrayHasKey(0, $line); + + $this->assertTrue($this->issue()->equals($line[0])); + } + + public function testCanBeQueried(): void + { + $baseline = new Baseline; + + $baseline->add($this->issue()); + + $this->assertTrue($baseline->has($this->issue())); + $this->assertFalse($baseline->has($this->anotherIssue())); + $this->assertFalse($baseline->has($this->yetAnotherIssue())); + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } + + private function yetAnotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'yet another issue', + ); + } +} diff --git a/tests/unit/Runner/Baseline/IssueTest.php b/tests/unit/Runner/Baseline/IssueTest.php new file mode 100644 index 00000000000..b484f3c93f4 --- /dev/null +++ b/tests/unit/Runner/Baseline/IssueTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\FileDoesNotExistException; + +#[CoversClass(Issue::class)] +#[CoversClass(FileDoesNotExistException::class)] +#[CoversClass(FileDoesNotHaveLineException::class)] +#[Small] +final class IssueTest extends TestCase +{ + public function testHasFile(): void + { + $this->assertSame( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + $this->issue()->file(), + ); + } + + public function testHasLine(): void + { + $this->assertSame(10, $this->issue()->line()); + } + + public function testHasHash(): void + { + $this->assertSame('6bf70f0b8c461415955e2d3a97cfbe664f5b957b', $this->issue()->hash()); + } + + public function testHasDescription(): void + { + $this->assertSame('Undefined variable $b', $this->issue()->description()); + } + + public function testIsComparable(): void + { + $this->assertTrue($this->issue()->equals($this->issue())); + $this->assertFalse($this->issue()->equals($this->anotherIssue())); + } + + public function testCannotBeCreatedForFileThatDoesNotExist(): void + { + $this->expectException(FileDoesNotExistException::class); + + Issue::from( + 'does-not-exist.php', + 1, + null, + 'description', + ); + } + + public function testCannotBeCreatedForLineThatDoesNotExist(): void + { + $this->expectException(FileDoesNotHaveLineException::class); + + Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 1234, + null, + 'description', + ); + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } +} diff --git a/tests/unit/Runner/Baseline/ReaderTest.php b/tests/unit/Runner/Baseline/ReaderTest.php new file mode 100644 index 00000000000..0f95fedda48 --- /dev/null +++ b/tests/unit/Runner/Baseline/ReaderTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Reader::class)] +#[Small] +final class ReaderTest extends TestCase +{ + public function testReadsBaselineFromFileWithValidXml(): void + { + $baseline = (new Reader)->read(__DIR__ . '/../../../_files/baseline/expected.xml'); + + $this->assertTrue($baseline->has($this->issue())); + $this->assertTrue($baseline->has($this->anotherIssue())); + $this->assertTrue($baseline->has($this->yetAnotherIssue())); + } + + public function testCannotReadBaselineFromFileThatDoesNotExist(): void + { + $this->expectException(CannotLoadBaselineException::class); + + (new Reader)->read('does-not-exist.xml'); + } + + public function testCannotReadBaselineFromFileWithInvalidXml(): void + { + $this->expectException(CannotLoadBaselineException::class); + + (new Reader)->read(realpath(__DIR__ . '/../../../end-to-end/_files/baseline/invalid-baseline/baseline.xml')); + } + + public function testCannotReadBaselineFromFileWithIncompatibleXml(): void + { + $this->expectException(CannotLoadBaselineException::class); + + (new Reader)->read(realpath(__DIR__ . '/../../../end-to-end/_files/baseline/unsupported-baseline/baseline.xml')); + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } + + private function yetAnotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'yet another issue', + ); + } +} diff --git a/tests/unit/Runner/Baseline/RelativePathCalculatorTest.php b/tests/unit/Runner/Baseline/RelativePathCalculatorTest.php new file mode 100644 index 00000000000..7ca8ac54dc1 --- /dev/null +++ b/tests/unit/Runner/Baseline/RelativePathCalculatorTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(RelativePathCalculator::class)] +#[Small] +/** + * @see Copied from https://github.com/phpstan/phpstan-src/blob/1.10.33/src/File/ParentDirectoryRelativePathHelper.php + */ +final class RelativePathCalculatorTest extends TestCase +{ + public static function dataGetRelativePath(): array + { + return [ + [ + '/usr/var/www', + '/usr/var/www/test.php', + 'test.php', + ], + [ + '/usr/var/www/foo/bar/baz', + '/usr/var/www/test.php', + '../../../test.php', + ], + [ + '/', + '/usr/var/www/test.php', + '/usr/var/www/test.php', + ], + [ + '/usr/var/www', + '/usr/var/www/src/test.php', + 'src/test.php', + ], + [ + '/usr/var/www/', + '/usr/var/www/src/test.php', + 'src/test.php', + ], + [ + '/usr/var/www', + '/usr/var/test.php', + '../test.php', + ], + [ + '/usr/var/www/', + '/usr/var/test.php', + '../test.php', + ], + [ + '/usr/var/www/', + '/usr/var/web/test.php', + '../web/test.php', + ], + [ + '/usr/var/www/', + '/usr/var/web/foo/test.php', + '../web/foo/test.php', + ], + [ + '/', + '/test.php', + '/test.php', + ], + [ + '/var/www', + '/usr/test.php', + '/usr/test.php', + ], + [ + 'C:\\var', + 'C:\\var\\test.php', + 'test.php', + ], + [ + 'C:\\var', + 'C:\\var\\src\\test.php', + 'src/test.php', + ], + [ + 'C:\\var', + 'C:\\test.php', + '../test.php', + ], + [ + 'C:\\var\\', + 'C:\\usr\\test.php', + '../usr/test.php', + ], + [ + 'C:\\', + 'C:\\test.php', + 'test.php', + ], + [ + 'C:\\', + 'C:\\src\\test.php', + 'src/test.php', + ], + [ + 'C:\\var', + 'D:\\var\\src\\test.php', + 'D:\\var\\src\\test.php', + ], + [ + '/usr/var/www', + 'file:///usr/var/www/test.php', + 'test.php', + ], + ]; + } + + #[DataProvider('dataGetRelativePath')] + public function testGetRelativePath(string $baselineDirectory, string $filename, string $expectedRelativePath): void + { + $calculator = new RelativePathCalculator($baselineDirectory); + + $this->assertSame( + $expectedRelativePath, + $calculator->calculate($filename), + ); + } +} diff --git a/tests/unit/Runner/Baseline/WriterTest.php b/tests/unit/Runner/Baseline/WriterTest.php new file mode 100644 index 00000000000..d9fe42ac411 --- /dev/null +++ b/tests/unit/Runner/Baseline/WriterTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function realpath; +use function unlink; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Writer::class)] +#[Small] +final class WriterTest extends TestCase +{ + private string $target; + + protected function setUp(): void + { + $this->target = realpath(__DIR__ . '/../../../_files/baseline') . DIRECTORY_SEPARATOR . 'actual.xml'; + } + + protected function tearDown(): void + { + @unlink($this->target); + } + + public function testWritesBaselineToFileInXmlFormat(): void + { + (new Writer)->write($this->target, $this->baseline()); + + $this->assertXmlFileEqualsXmlFile(__DIR__ . '/../../../_files/baseline/expected.xml', $this->target); + } + + private function baseline(): Baseline + { + $baseline = new Baseline; + + $baseline->add($this->issue()); + $baseline->add($this->anotherIssue()); + $baseline->add($this->yetAnotherIssue()); + + return $baseline; + } + + private function issue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'Undefined variable $b', + ); + } + + private function anotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 11, + null, + 'Undefined variable $c', + ); + } + + private function yetAnotherIssue(): Issue + { + return Issue::from( + realpath(__DIR__ . '/../../../_files/baseline/FileWithIssues.php'), + 10, + null, + 'yet another issue', + ); + } +} diff --git a/tests/unit/TextUI/Configuration/Value/SourceTest.php b/tests/unit/TextUI/Configuration/Value/SourceTest.php index 69d75dcaf5a..1ec21c5eb71 100644 --- a/tests/unit/TextUI/Configuration/Value/SourceTest.php +++ b/tests/unit/TextUI/Configuration/Value/SourceTest.php @@ -29,6 +29,8 @@ public function testHasIncludeDirectories(): void $includeDirectories = FilterDirectoryCollection::fromArray([]); $source = new Source( + null, + false, $includeDirectories, FileCollection::fromArray([]), FilterDirectoryCollection::fromArray([]), @@ -53,6 +55,8 @@ public function testHasIncludeFiles(): void $includeFiles = FileCollection::fromArray([]); $source = new Source( + null, + false, FilterDirectoryCollection::fromArray([]), $includeFiles, FilterDirectoryCollection::fromArray([]), @@ -77,6 +81,8 @@ public function testHasExcludeDirectories(): void $excludeDirectories = FilterDirectoryCollection::fromArray([]); $source = new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray([]), $excludeDirectories, @@ -101,6 +107,8 @@ public function testHasExcludeFiles(): void $excludeFiles = FileCollection::fromArray([]); $source = new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray([]), FilterDirectoryCollection::fromArray([]), @@ -119,4 +127,59 @@ public function testHasExcludeFiles(): void $this->assertSame($excludeFiles, $source->excludeFiles()); } + + public function testMayHaveBaseline(): void + { + $baseline = 'baseline.xml'; + + $source = new Source( + $baseline, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ); + + $this->assertSame($baseline, $source->baseline()); + $this->assertTrue($source->hasBaseline()); + } + + public function testMayNotHaveBaseline(): void + { + $source = new Source( + null, + false, + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + FilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ); + + $this->assertFalse($source->hasBaseline()); + + $this->expectException(NoBaselineException::class); + + $source->baseline(); + } } diff --git a/tests/unit/TextUI/Configuration/Xml/LoaderTest.php b/tests/unit/TextUI/Configuration/Xml/LoaderTest.php index a29b3d6678c..6878a49f67a 100644 --- a/tests/unit/TextUI/Configuration/Xml/LoaderTest.php +++ b/tests/unit/TextUI/Configuration/Xml/LoaderTest.php @@ -14,6 +14,7 @@ use const PHP_VERSION; use function file_put_contents; use function iterator_to_array; +use function realpath; use function sys_get_temp_dir; use function uniqid; use function unlink; @@ -140,6 +141,9 @@ public function testSourceConfigurationIsReadCorrectly(): void { $source = $this->configuration('configuration_codecoverage.xml')->source(); + $this->assertTrue($source->hasBaseline()); + $this->assertSame(realpath(__DIR__ . '/../../../../_files') . DIRECTORY_SEPARATOR . '.phpunit/baseline.xml', $source->baseline()); + $directory = iterator_to_array($source->includeDirectories(), false)[0]; $this->assertSame('/path/to/files', $directory->path()); diff --git a/tests/unit/TextUI/SourceFilterTest.php b/tests/unit/TextUI/SourceFilterTest.php index 1311e20c6a8..e1e9711628e 100644 --- a/tests/unit/TextUI/SourceFilterTest.php +++ b/tests/unit/TextUI/SourceFilterTest.php @@ -28,6 +28,8 @@ public static function provider(): array true, $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php', new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray( [ @@ -52,6 +54,8 @@ public static function provider(): array false, $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php', new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray( [ @@ -84,6 +88,8 @@ public static function provider(): array false, $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php', new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray( [ @@ -112,6 +118,8 @@ public static function provider(): array true, $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php', new Source( + null, + false, FilterDirectoryCollection::fromArray( [ new FilterDirectory( @@ -140,6 +148,8 @@ public static function provider(): array false, $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php', new Source( + null, + false, FilterDirectoryCollection::fromArray( [ new FilterDirectory( @@ -172,6 +182,8 @@ public static function provider(): array false, $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php', new Source( + null, + false, FilterDirectoryCollection::fromArray( [ new FilterDirectory( diff --git a/tests/unit/TextUI/SourceMapperTest.php b/tests/unit/TextUI/SourceMapperTest.php index 5ac7bbcfe56..8e7531adf84 100644 --- a/tests/unit/TextUI/SourceMapperTest.php +++ b/tests/unit/TextUI/SourceMapperTest.php @@ -31,6 +31,8 @@ public static function provider(): array $fixtureDirectory . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php' => true, ], new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray( [ @@ -55,6 +57,8 @@ public static function provider(): array [ ], new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray( [ @@ -87,6 +91,8 @@ public static function provider(): array [ ], new Source( + null, + false, FilterDirectoryCollection::fromArray([]), FileCollection::fromArray( [ @@ -127,6 +133,8 @@ public static function provider(): array $fixtureDirectory . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'f' . DIRECTORY_SEPARATOR . 'h' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php' => true, ], new Source( + null, + false, FilterDirectoryCollection::fromArray( [ new FilterDirectory( @@ -166,6 +174,8 @@ public static function provider(): array $fixtureDirectory . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'f' . DIRECTORY_SEPARATOR . 'h' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php' => true, ], new Source( + null, + false, FilterDirectoryCollection::fromArray( [ new FilterDirectory( @@ -203,6 +213,8 @@ public static function provider(): array $fixtureDirectory . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'f' . DIRECTORY_SEPARATOR . 'h' . DIRECTORY_SEPARATOR . 'PrefixSuffix.php' => true, ], new Source( + null, + false, FilterDirectoryCollection::fromArray( [ new FilterDirectory(