diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml
index d4869162c92..33831a4260f 100644
--- a/.github/workflows/code_analysis.yaml
+++ b/.github/workflows/code_analysis.yaml
@@ -53,7 +53,7 @@ jobs:
-
name: 'Active Classes'
run: |
- vendor/bin/class-leak check bin config src rules utils --skip-suffix "Rector" --skip-type="Rector\\Utils\\Compiler\\Unprefixer" --skip-type="Rector\\NodeCollector\\BinaryOpConditionsCollector" --skip-type="Rector\\Set\\Contract\\SetListInterface" --skip-type="Rector\\DependencyInjection\\PHPStan\\RichParserFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\DuplicateRegistrationFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\SingleRegistrationFactory" --skip-type="Rector\\ChangesReporting\\Contract\\Output\\OutputFormatterInterface" --skip-type="Rector\\NodeTypeResolver\\Contract\\NodeTypeResolverInterface" --skip-type="Rector\\CodingStyle\\Contract\\ClassNameImport\\ClassNameImportSkipVoterInterface" --skip-type="Rector\\Php80\\Contract\\ConverterAttributeDecoratorInterface" --skip-type="Rector\\NodeNameResolver\\Contract\\NodeNameResolverInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpDocParser\\PhpDocTypeMapperInterface" --skip-type="Rector\\PhpAttribute\\Contract\\AnnotationToAttributeMapperInterface" --skip-type="Rector\\PHPStanStaticTypeMapper\\Contract\\TypeMapperInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpParser\\PhpParserNodeMapperInterface"
+ vendor/bin/class-leak check bin config src rules utils --skip-suffix "Rector" --skip-type="Rector\\Utils\\Compiler\\Unprefixer" --skip-type="Rector\\NodeCollector\\BinaryOpConditionsCollector" --skip-type="Rector\\Set\\Contract\\SetListInterface" --skip-type="Rector\\DependencyInjection\\PHPStan\\RichParserFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\DuplicateRegistrationFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\SingleRegistrationFactory" --skip-type="Rector\\ChangesReporting\\Contract\\Output\\OutputFormatterInterface" --skip-type="Rector\\NodeTypeResolver\\Contract\\NodeTypeResolverInterface" --skip-type="Rector\\CodingStyle\\Contract\\ClassNameImport\\ClassNameImportSkipVoterInterface" --skip-type="Rector\\Php80\\Contract\\ConverterAttributeDecoratorInterface" --skip-type="Rector\\NodeNameResolver\\Contract\\NodeNameResolverInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpDocParser\\PhpDocTypeMapperInterface" --skip-type="Rector\\PhpAttribute\\Contract\\AnnotationToAttributeMapperInterface" --skip-type="Rector\\PHPStanStaticTypeMapper\\Contract\\TypeMapperInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpParser\\PhpParserNodeMapperInterface" --skip-type="Rector\\Utils\\PHPUnit\\SlowestTests\\SlowTestExtension"
-
name: 'Compatible PHPStan versions'
diff --git a/phpunit.xml b/phpunit.xml
index 37c4eb12d7f..c134897098b 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -21,4 +21,8 @@
+
+
+
+
diff --git a/utils/PHPUnit/SlowestTests/RunnerFinishedSubscriber.php b/utils/PHPUnit/SlowestTests/RunnerFinishedSubscriber.php
new file mode 100644
index 00000000000..42452ea0809
--- /dev/null
+++ b/utils/PHPUnit/SlowestTests/RunnerFinishedSubscriber.php
@@ -0,0 +1,21 @@
+slowTestReporter->printSlowest();
+ }
+}
diff --git a/utils/PHPUnit/SlowestTests/SlowTestExtension.php b/utils/PHPUnit/SlowestTests/SlowTestExtension.php
new file mode 100644
index 00000000000..fde3cf46b27
--- /dev/null
+++ b/utils/PHPUnit/SlowestTests/SlowTestExtension.php
@@ -0,0 +1,24 @@
+registerSubscribers(
+ new TestPreparedSubscriber($slowTestReporter),
+ new TestFinishedSubscriber($slowTestReporter),
+ new RunnerFinishedSubscriber($slowTestReporter),
+ );
+ }
+}
diff --git a/utils/PHPUnit/SlowestTests/SlowTestReporter.php b/utils/PHPUnit/SlowestTests/SlowTestReporter.php
new file mode 100644
index 00000000000..8e7e0cb8c7a
--- /dev/null
+++ b/utils/PHPUnit/SlowestTests/SlowTestReporter.php
@@ -0,0 +1,67 @@
+
+ */
+ private array $startedAt = [];
+
+ /**
+ * Test name => accumulated duration in seconds (summed across data-provider fixtures).
+ * @var array
+ */
+ private array $durations = [];
+
+ public function testPrepared(string $testId, float $secondsSinceStart): void
+ {
+ $this->startedAt[$testId] = $secondsSinceStart;
+ }
+
+ public function testFinished(string $testId, string $testName, float $secondsSinceStart): void
+ {
+ if (! isset($this->startedAt[$testId])) {
+ return;
+ }
+
+ $this->durations[$testName] = ($this->durations[$testName] ?? 0.0) + ($secondsSinceStart - $this->startedAt[$testId]);
+ unset($this->startedAt[$testId]);
+ }
+
+ public function printSlowest(): void
+ {
+ $slowDurations = array_filter(
+ $this->durations,
+ static fn (float $seconds): bool => $seconds >= self::THRESHOLD_SECONDS
+ );
+
+ if ($slowDurations === []) {
+ return;
+ }
+
+ arsort($slowDurations);
+ $slowDurations = array_slice($slowDurations, 0, self::LIMIT, true);
+
+ echo PHP_EOL . sprintf('%d slowest tests (>= %.1fs):', count($slowDurations), self::THRESHOLD_SECONDS) . PHP_EOL;
+
+ $position = 1;
+ foreach ($slowDurations as $testName => $seconds) {
+ echo sprintf('%3d. %6.2fs %s', $position, $seconds, $testName) . PHP_EOL;
+ ++$position;
+ }
+
+ echo PHP_EOL;
+ }
+}
diff --git a/utils/PHPUnit/SlowestTests/TestFinishedSubscriber.php b/utils/PHPUnit/SlowestTests/TestFinishedSubscriber.php
new file mode 100644
index 00000000000..79dc10d26d1
--- /dev/null
+++ b/utils/PHPUnit/SlowestTests/TestFinishedSubscriber.php
@@ -0,0 +1,31 @@
+test();
+
+ // group all data-provider fixtures under one test method, so a slow rule shows as a single entry
+ $reportName = $test instanceof TestMethod ? $test->className() . '::' . $test->methodName() : $test->name();
+
+ $this->slowTestReporter->testFinished(
+ $test->id(),
+ $reportName,
+ $event->telemetryInfo()->durationSinceStart()->asFloat()
+ );
+ }
+}
diff --git a/utils/PHPUnit/SlowestTests/TestPreparedSubscriber.php b/utils/PHPUnit/SlowestTests/TestPreparedSubscriber.php
new file mode 100644
index 00000000000..49426b9de78
--- /dev/null
+++ b/utils/PHPUnit/SlowestTests/TestPreparedSubscriber.php
@@ -0,0 +1,24 @@
+slowTestReporter->testPrepared(
+ $event->test()->id(),
+ $event->telemetryInfo()->durationSinceStart()->asFloat()
+ );
+ }
+}