diff --git a/src/Runner/ErrorHandler.php b/src/Runner/ErrorHandler.php index 54f35aa4147..94a2bb60024 100644 --- a/src/Runner/ErrorHandler.php +++ b/src/Runner/ErrorHandler.php @@ -59,6 +59,11 @@ final class ErrorHandler private ?int $originalErrorReportingLevel = null; private readonly Source $source; + /** + * @var list + */ + private array $globalDeprecations = []; + /** * @var ?array{functions: list, methods: list} */ @@ -197,6 +202,23 @@ public function __invoke(int $errorNumber, string $errorString, string $errorFil return false; } + public function deprecationHandler(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool + { + $this->globalDeprecations[] = [$errorNumber, $errorString, $errorFile, $errorLine]; + + return true; + } + + public function registerDeprecationHandler(): void + { + set_error_handler([self::$instance, 'deprecationHandler'], E_USER_DEPRECATED); + } + + public function restoreDeprecationHandler(): void + { + restore_error_handler(); + } + public function enable(): void { if ($this->enabled) { @@ -213,6 +235,7 @@ public function enable(): void $this->enabled = true; $this->originalErrorReportingLevel = error_reporting(); + $this->triggerGlobalDeprecations(); error_reporting($this->originalErrorReportingLevel & self::UNHANDLEABLE_LEVELS); } @@ -422,4 +445,11 @@ private function stackTrace(): string return $buffer; } + + private function triggerGlobalDeprecations(): void + { + foreach ($this->globalDeprecations ?? [] as $d) { + $this->__invoke(...$d); + } + } } diff --git a/src/TextUI/Application.php b/src/TextUI/Application.php index ca6da700543..d8965d41bab 100644 --- a/src/TextUI/Application.php +++ b/src/TextUI/Application.php @@ -178,8 +178,12 @@ public function run(array $argv): int EventFacade::instance()->seal(); + ErrorHandler::instance()->registerDeprecationHandler(); + $testSuite = $this->buildTestSuite($configuration); + ErrorHandler::instance()->restoreDeprecationHandler(); + $this->executeCommandsThatRequireTheTestSuite($configuration, $cliConfiguration, $testSuite); if ($testSuite->isEmpty() && !$configuration->hasCliArguments() && $configuration->testSuite()->isEmpty()) { diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/phpunit.xml b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/phpunit.xml new file mode 100644 index 00000000000..db9632cd009 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests + + + + + + src + + + diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/src/FirstPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/src/FirstPartyClass.php new file mode 100644 index 00000000000..e94fad12de3 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/src/FirstPartyClass.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestFixture\SelfDirectIndirect; + +final class FirstPartyClass +{ + public function method(): true + { + return ThirdPartyClass::A; + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/tests/FirstPartyClassTest.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/tests/FirstPartyClassTest.php new file mode 100644 index 00000000000..b5c44b5867b --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/tests/FirstPartyClassTest.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\TestFixture\SelfDirectIndirect; + +use PHPUnit\Framework\TestCase; + +final class FirstPartyClassTest extends TestCase +{ + public function testOne(): void + { + $this->assertTrue((new FirstPartyClass)->method()); + } +} diff --git a/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/vendor/ThirdPartyClass.php b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/vendor/ThirdPartyClass.php new file mode 100644 index 00000000000..a87d69cef99 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/_files/deprecation-trigger-class/vendor/ThirdPartyClass.php @@ -0,0 +1,9 @@ + require __DIR__ . '/../src/' . $file, + 'ThirdPartyClass.php' => require __DIR__ . '/' . $file, + default => throw new LogicException + }; +}); diff --git a/tests/end-to-end/deprecation-trigger/deprecation-trigger-class.phpt b/tests/end-to-end/deprecation-trigger/deprecation-trigger-class.phpt new file mode 100644 index 00000000000..fe6b7ff2d47 --- /dev/null +++ b/tests/end-to-end/deprecation-trigger/deprecation-trigger-class.phpt @@ -0,0 +1,36 @@ +--TEST-- +The right events are emitted in the right order for a test that loads a class that triggers E_USER_DEPRECATED +--FILE-- +run($_SERVER['argv']); +--EXPECTF-- +PHPUnit Started (PHPUnit %s using %s) +Test Runner Configured +Bootstrap Finished (%sautoload.php) +Event Facade Sealed +Test Suite Loaded (1 test) +Test Runner Started +Test Suite Sorted +Test Runner Execution Started (1 test) +Test Suite Started (%sphpunit.xml, 1 test) +Test Suite Started (default, 1 test) +Test Suite Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 1 test) +Test Preparation Started (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Prepared (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Triggered Deprecation (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne, issue triggered by third-party code, suppressed using operator) in %s:%d +This class is deprecated +Test Passed (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest::testOne) +Test Suite Finished (PHPUnit\TestFixture\SelfDirectIndirect\FirstPartyClassTest, 1 test) +Test Suite Finished (default, 1 test) +Test Suite Finished (%sphpunit.xml, 1 test) +Test Runner Execution Finished +Test Runner Finished +PHPUnit Finished (Shell Exit Code: 0) diff --git a/tests/unit/Runner/ErrorHandlerTest.php b/tests/unit/Runner/ErrorHandlerTest.php new file mode 100644 index 00000000000..9eeb5ec0dc4 --- /dev/null +++ b/tests/unit/Runner/ErrorHandlerTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const E_USER_DEPRECATED; +use function trigger_error; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use ReflectionClass; + +#[CoversClass(ErrorHandler::class)] +#[Small] +final class ErrorHandlerTest extends TestCase +{ + public function testThrowsExceptionWhenUsingInvalidOrderOption(): void + { + $errorHandler = ErrorHandler::instance(); + $errorHandler->registerDeprecationHandler(); + trigger_error('deprecation', E_USER_DEPRECATED); + $errorHandler->restoreDeprecationHandler(); + $refl = new ReflectionClass($errorHandler); + $globalDeprecations = $refl->getProperty('globalDeprecations'); + $registeredDeprecations = $globalDeprecations->getValue($errorHandler); + $this->assertCount(1, $registeredDeprecations); + $this->assertSame('deprecation', $registeredDeprecations[0][1]); + } +}