diff --git a/src/Event/Dispatcher/CollectingDispatcher.php b/src/Event/Dispatcher/CollectingDispatcher.php index c431b93e6a5..e3e9462ee6a 100644 --- a/src/Event/Dispatcher/CollectingDispatcher.php +++ b/src/Event/Dispatcher/CollectingDispatcher.php @@ -9,6 +9,9 @@ */ namespace PHPUnit\Event; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\DeprecationCollector\TestTriggeredDeprecationSubscriber; + /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -17,15 +20,25 @@ final class CollectingDispatcher implements Dispatcher { private EventCollection $events; + private DirectDispatcher $isolatedDirectDispatcher; - public function __construct() + public function __construct(DirectDispatcher $directDispatcher) { - $this->events = new EventCollection; + $this->isolatedDirectDispatcher = $directDispatcher; + $this->events = new EventCollection; + + $this->isolatedDirectDispatcher->registerSubscriber(new TestTriggeredDeprecationSubscriber(DeprecationCollector::collector())); } public function dispatch(Event $event): void { $this->events->add($event); + + try { + $this->isolatedDirectDispatcher->dispatch($event); + } catch (UnknownEventTypeException) { + // Do nothing. + } } public function flush(): EventCollection diff --git a/src/Event/Facade.php b/src/Event/Facade.php index 7304148192c..12e9b072d9f 100644 --- a/src/Event/Facade.php +++ b/src/Event/Facade.php @@ -16,6 +16,7 @@ use PHPUnit\Event\Telemetry\HRTime; use PHPUnit\Event\Telemetry\Php81GarbageCollectorStatusProvider; use PHPUnit\Event\Telemetry\Php83GarbageCollectorStatusProvider; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit @@ -92,7 +93,11 @@ public function registerTracer(Tracer\Tracer $tracer): void */ public function initForIsolation(HRTime $offset): CollectingDispatcher { - $dispatcher = new CollectingDispatcher; + DeprecationCollector::initForIsolation(); + + $dispatcher = new CollectingDispatcher( + new DirectDispatcher($this->typeMap()), + ); $this->emitter = new DispatchingEmitter( $dispatcher, diff --git a/src/Runner/DeprecationCollector/Facade.php b/src/Runner/DeprecationCollector/Facade.php index a2816b99425..06b29b65ab8 100644 --- a/src/Runner/DeprecationCollector/Facade.php +++ b/src/Runner/DeprecationCollector/Facade.php @@ -22,7 +22,8 @@ */ final class Facade { - private static ?Collector $collector = null; + private static null|Collector|InIsolationCollector $collector = null; + private static bool $inIsolation = false; /** * @throws EventFacadeIsSealedException @@ -33,6 +34,12 @@ public static function init(): void self::collector(); } + public static function initForIsolation(): void + { + self::$inIsolation = true; + self::collector(); + } + /** * @throws EventFacadeIsSealedException * @throws UnknownSubscriberTypeException @@ -59,15 +66,23 @@ public static function filteredDeprecations(): array * @throws EventFacadeIsSealedException * @throws UnknownSubscriberTypeException */ - private static function collector(): Collector + public static function collector(): Collector|InIsolationCollector { if (self::$collector === null) { - self::$collector = new Collector( - EventFacade::instance(), - new IssueFilter( - ConfigurationRegistry::get()->source(), - ), - ); + if (self::$inIsolation) { + self::$collector = new InIsolationCollector( + new IssueFilter( + ConfigurationRegistry::get()->source(), + ), + ); + } else { + self::$collector = new Collector( + EventFacade::instance(), + new IssueFilter( + ConfigurationRegistry::get()->source(), + ), + ); + } } return self::$collector; diff --git a/src/Runner/DeprecationCollector/InIsolationCollector.php b/src/Runner/DeprecationCollector/InIsolationCollector.php new file mode 100644 index 00000000000..31287622175 --- /dev/null +++ b/src/Runner/DeprecationCollector/InIsolationCollector.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\DeprecationCollector; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\TestRunner\IssueFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InIsolationCollector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var list + */ + private array $deprecations = []; + + /** + * @var list + */ + private array $filteredDeprecations = []; + + public function __construct(IssueFilter $issueFilter) + { + $this->issueFilter = $issueFilter; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function filteredDeprecations(): array + { + return $this->filteredDeprecations; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + $this->deprecations[] = $event->message(); + + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $this->filteredDeprecations[] = $event->message(); + } +} diff --git a/src/Runner/DeprecationCollector/Subscriber/Subscriber.php b/src/Runner/DeprecationCollector/Subscriber/Subscriber.php index 9c034ae721b..65af6ab7060 100644 --- a/src/Runner/DeprecationCollector/Subscriber/Subscriber.php +++ b/src/Runner/DeprecationCollector/Subscriber/Subscriber.php @@ -16,14 +16,14 @@ */ abstract class Subscriber { - private readonly Collector $collector; + private readonly Collector|InIsolationCollector $collector; - public function __construct(Collector $collector) + public function __construct(Collector|InIsolationCollector $collector) { $this->collector = $collector; } - protected function collector(): Collector + protected function collector(): Collector|InIsolationCollector { return $this->collector; } diff --git a/tests/end-to-end/generic/_files/TestForDeprecatedFeatureInIsolationTest.php b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureInIsolationTest.php new file mode 100644 index 00000000000..34022e9fe9e --- /dev/null +++ b/tests/end-to-end/generic/_files/TestForDeprecatedFeatureInIsolationTest.php @@ -0,0 +1,84 @@ + + * + * 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\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\TestCase; + +#[RunTestsInSeparateProcesses] +final class TestForDeprecatedFeatureInIsolationTest 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/regression/6102.phpt b/tests/end-to-end/regression/6102-1.phpt similarity index 100% rename from tests/end-to-end/regression/6102.phpt rename to tests/end-to-end/regression/6102-1.phpt diff --git a/tests/end-to-end/regression/6102-2.phpt b/tests/end-to-end/regression/6102-2.phpt new file mode 100644 index 00000000000..df41f69a556 --- /dev/null +++ b/tests/end-to-end/regression/6102-2.phpt @@ -0,0 +1,38 @@ +--TEST-- +https://github.com/sebastianbergmann/phpunit/issues/6102 +--XFAIL-- +https://github.com/sebastianbergmann/phpunit/issues/6102 +--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\TestForDeprecatedFeatureInIsolationTest::testExpectationOnExactDeprecationMessageWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message "message" was not triggered + +2) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnExactDeprecationMessageWorksWhenUnexpectedDeprecationIsTriggered +Expected deprecation with message "message" was not triggered + +3) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenExpectedDeprecationIsNotTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +4) PHPUnit\TestFixture\Event\TestForDeprecatedFeatureInIsolationTest::testExpectationOnDeprecationMessageMatchingRegularExpressionWorksWhenUnepectedDeprecationIsTriggered +Expected deprecation with message matching regular expression "/message/" was not triggered + +FAILURES! +Tests: 8, Assertions: 10, Failures: 4. diff --git a/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php b/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php index 76de0edfd50..35d8b9e10a1 100644 --- a/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php +++ b/tests/unit/Event/Dispatcher/CollectingDispatcherTest.php @@ -19,14 +19,20 @@ final class CollectingDispatcherTest extends TestCase { public function testHasNoCollectedEventsWhenFlushedImmediatelyAfterCreation(): void { - $dispatcher = new CollectingDispatcher; + $typeMap = new TypeMap; + $typeMap->addMapping(Test\DeprecationTriggeredSubscriber::class, Test\DeprecationTriggered::class); + + $dispatcher = new CollectingDispatcher(new DirectDispatcher($typeMap)); $this->assertEmpty($dispatcher->flush()); } public function testCollectsDispatchedEventsUntilFlushed(): void { - $dispatcher = new CollectingDispatcher; + $typeMap = new TypeMap; + $typeMap->addMapping(Test\DeprecationTriggeredSubscriber::class, Test\DeprecationTriggered::class); + + $dispatcher = new CollectingDispatcher(new DirectDispatcher($typeMap)); $event = $this->createStub(Event::class); $dispatcher->dispatch($event);