From ef80a8d5c1bc463ea99f330010efb4436fd7589f Mon Sep 17 00:00:00 2001 From: Sebastian Bergmann Date: Mon, 18 Dec 2023 06:02:39 +0100 Subject: [PATCH] Closes #5605 --- ChangeLog-11.0.md | 1 + src/Framework/TestCase.php | 71 ++++++++++++++++ src/Runner/DeprecationCollector/Collector.php | 56 +++++++++++++ src/Runner/DeprecationCollector/Facade.php | 55 +++++++++++++ .../Subscriber/Subscriber.php | 28 +++++++ .../Subscriber/TestPreparedSubscriber.php | 24 ++++++ .../TestTriggeredDeprecationSubscriber.php | 24 ++++++ src/TextUI/Application.php | 2 + .../_files/TestForDeprecatedFeatureTest.php | 82 +++++++++++++++++++ .../generic/deprecation-can-be-expected.phpt | 36 ++++++++ 10 files changed, 379 insertions(+) create mode 100644 src/Runner/DeprecationCollector/Collector.php create mode 100644 src/Runner/DeprecationCollector/Facade.php create mode 100644 src/Runner/DeprecationCollector/Subscriber/Subscriber.php create mode 100644 src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php create mode 100644 src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php create mode 100644 tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php create mode 100644 tests/end-to-end/generic/deprecation-can-be-expected.phpt diff --git a/ChangeLog-11.0.md b/ChangeLog-11.0.md index 3a860b25163..5745354bae8 100644 --- a/ChangeLog-11.0.md +++ b/ChangeLog-11.0.md @@ -7,6 +7,7 @@ All notable changes of the PHPUnit 11.0 release series are documented in this fi ### Added * [#5600](https://github.com/sebastianbergmann/phpunit/pull/5600): Assertions for comparing arrays while ignoring a specified list of keys +* [#5605](https://github.com/sebastianbergmann/phpunit/pull/5605): `expectUserDeprecationMessage()` and `expectUserDeprecationMessageMatches()` for expecting `E_USER_DEPRECATED` issues ### Changed diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index 745b28046d4..42b9b34a50c 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -47,6 +47,7 @@ use function ob_start; use function parse_url; use function pathinfo; +use function preg_match; use function preg_replace; use function setlocale; use function sprintf; @@ -84,6 +85,7 @@ use PHPUnit\Metadata\Api\HookMethods; use PHPUnit\Metadata\Api\Requirements; use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; use PHPUnit\TestRunner\TestResult\PassedTests; use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; use PHPUnit\Util\Test as TestUtil; @@ -188,6 +190,16 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T */ private array $failureTypes = []; + /** + * @psalm-var list + */ + private array $expectedUserDeprecationMessage = []; + + /** + * @psalm-var list + */ + private array $expectedUserDeprecationMessageRegularExpression = []; + /** * @psalm-param non-empty-string $name * @@ -446,6 +458,7 @@ final public function runBare(): void $this->wasPrepared = true; $this->testResult = $this->runTest(); + $this->verifyDeprecationExpectations(); $this->verifyMockObjects(); $this->invokePostConditionHookMethods($hookMethods, $emitter); @@ -1080,6 +1093,22 @@ final protected function expectNotToPerformAssertions(): void $this->doesNotPerformAssertions = true; } + /** + * @psalm-param non-empty-string $expectedUserDeprecationMessage + */ + final protected function expectUserDeprecationMessage(string $expectedUserDeprecationMessage): void + { + $this->expectedUserDeprecationMessage[] = $expectedUserDeprecationMessage; + } + + /** + * @psalm-param non-empty-string $expectedUserDeprecationMessageRegularExpression + */ + final protected function expectUserDeprecationMessageMatches(string $expectedUserDeprecationMessageRegularExpression): void + { + $this->expectedUserDeprecationMessageRegularExpression[] = $expectedUserDeprecationMessageRegularExpression; + } + /** * Returns a builder object to create mock objects using a fluent interface. * @@ -1535,6 +1564,48 @@ protected function onNotSuccessfulTest(Throwable $t): never throw $t; } + /** + * @throws ExpectationFailedException + */ + private function verifyDeprecationExpectations(): void + { + foreach ($this->expectedUserDeprecationMessage as $deprecationExpectation) { + $this->numberOfAssertionsPerformed++; + + if (!in_array($deprecationExpectation, DeprecationCollector::deprecations(), true)) { + throw new ExpectationFailedException( + sprintf( + 'Expected deprecation with message "%s" was not triggered', + $deprecationExpectation, + ), + ); + } + } + + foreach ($this->expectedUserDeprecationMessageRegularExpression as $deprecationExpectation) { + $this->numberOfAssertionsPerformed++; + + $expectedDeprecationTriggered = false; + + foreach (DeprecationCollector::deprecations() as $deprecation) { + if (@preg_match($deprecationExpectation, $deprecation) > 0) { + $expectedDeprecationTriggered = true; + + break; + } + } + + if (!$expectedDeprecationTriggered) { + throw new ExpectationFailedException( + sprintf( + 'Expected deprecation with message matching regular expression "%s" was not triggered', + $deprecationExpectation, + ), + ); + } + } + } + /** * @throws Throwable */ diff --git a/src/Runner/DeprecationCollector/Collector.php b/src/Runner/DeprecationCollector/Collector.php new file mode 100644 index 00000000000..4232768a17c --- /dev/null +++ b/src/Runner/DeprecationCollector/Collector.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\UnknownSubscriberTypeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Collector +{ + /** + * @psalm-var list + */ + private array $deprecations = []; + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function __construct(Facade $facade) + { + $facade->registerSubscribers( + new TestPreparedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + ); + } + + /** + * @psalm-return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + public function testPrepared(): void + { + $this->deprecations = []; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + $this->deprecations[] = $event->message(); + } +} diff --git a/src/Runner/DeprecationCollector/Facade.php b/src/Runner/DeprecationCollector/Facade.php new file mode 100644 index 00000000000..cad90dd38b8 --- /dev/null +++ b/src/Runner/DeprecationCollector/Facade.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\UnknownSubscriberTypeException; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?Collector $collector = null; + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public static function init(): void + { + self::collector(); + } + + /** + * @psalm-return list + * + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public static function deprecations(): array + { + return self::collector()->deprecations(); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + private static function collector(): Collector + { + if (self::$collector === null) { + self::$collector = new Collector(EventFacade::instance()); + } + + return self::$collector; + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/Subscriber.php b/src/Runner/DeprecationCollector/Subscriber/Subscriber.php new file mode 100644 index 00000000000..5722b8c91cd --- /dev/null +++ b/src/Runner/DeprecationCollector/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\DeprecationCollector; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class Subscriber +{ + private readonly Collector $collector; + + public function __construct(Collector $collector) + { + $this->collector = $collector; + } + + protected function collector(): Collector + { + return $this->collector; + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php b/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 00000000000..c76cd9ccd90 --- /dev/null +++ b/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared(); + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php b/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 00000000000..9e9ddd28fd4 --- /dev/null +++ b/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/src/TextUI/Application.php b/src/TextUI/Application.php index bc2de739627..a87b4fba1be 100644 --- a/src/TextUI/Application.php +++ b/src/TextUI/Application.php @@ -32,6 +32,7 @@ use PHPUnit\Runner\Baseline\Reader; use PHPUnit\Runner\Baseline\Writer; use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; use PHPUnit\Runner\ErrorHandler; use PHPUnit\Runner\Extension\ExtensionBootstrapper; use PHPUnit\Runner\Extension\Facade as ExtensionFacade; @@ -154,6 +155,7 @@ public function run(array $argv): int $testDoxResultCollector = $this->testDoxResultCollector($configuration); TestResultFacade::init(); + DeprecationCollector::init(); $resultCache = $this->initializeTestResultCache($configuration); diff --git a/tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php new file mode 100644 index 00000000000..e322b407fa7 --- /dev/null +++ b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\Event; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\TestCase; + +final class TestForDeprecatedFeatureTest extends TestCase +{ + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + + @trigger_error('message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationsOnExactDeprecationMessagesWorkWhenExpectedDeprecationsAreTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + $this->expectUserDeprecationMessage('another message'); + + @trigger_error('message', E_USER_DEPRECATED); + @trigger_error('another message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + } + + #[IgnoreDeprecations] + public function testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessage('message'); + + @trigger_error('another message', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + + @trigger_error('...message...', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationsOnDeprecationMessagesMatchingRegularExpressionsWorkWhenExpectedDeprecationsAreTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/foo/'); + $this->expectUserDeprecationMessageMatches('/bar/'); + + @trigger_error('...foo...', E_USER_DEPRECATED); + @trigger_error('...bar...', E_USER_DEPRECATED); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + } + + #[IgnoreDeprecations] + public function testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered(): void + { + $this->expectUserDeprecationMessageMatches('/message/'); + + @trigger_error('something else', E_USER_DEPRECATED); + } +} diff --git a/tests/end-to-end/generic/deprecation-can-be-expected.phpt b/tests/end-to-end/generic/deprecation-can-be-expected.phpt new file mode 100644 index 00000000000..17ea5eda2e1 --- /dev/null +++ b/tests/end-to-end/generic/deprecation-can-be-expected.phpt @@ -0,0 +1,36 @@ +--TEST-- +E_USER_DEPRECATED issues can be expected +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit %s by Sebastian Bergmann and contributors. + +Runtime: %s + +..FF..FF 8 / 8 (100%) + +Time: %s, Memory: %s + +There were 4 failures: + +1) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message "message" was not triggered + +2) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered +Expected deprecation with message "message" was not triggered + +3) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +4) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +FAILURES! +Tests: 8, Assertions: 10, Failures: 4.