diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml
index 6c8580d8381..0f35d1a45d2 100644
--- a/.psalm/baseline.xml
+++ b/.psalm/baseline.xml
@@ -1315,6 +1315,9 @@
getName
+
+
+
$this->getException($failures[0])
diff --git a/src/Framework/Attributes/After.php b/src/Framework/Attributes/After.php
new file mode 100644
index 00000000000..6496221a1da
--- /dev/null
+++ b/src/Framework/Attributes/After.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class After
+{
+}
diff --git a/src/Framework/Attributes/AfterClass.php b/src/Framework/Attributes/AfterClass.php
new file mode 100644
index 00000000000..36176ad1868
--- /dev/null
+++ b/src/Framework/Attributes/AfterClass.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class AfterClass
+{
+}
diff --git a/src/Framework/Attributes/BackupGlobals.php b/src/Framework/Attributes/BackupGlobals.php
new file mode 100644
index 00000000000..c5d9e79c57f
--- /dev/null
+++ b/src/Framework/Attributes/BackupGlobals.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+final class BackupGlobals
+{
+ /**
+ * @var bool
+ */
+ private $enabled;
+
+ public function __construct(bool $enabled)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function enabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/Framework/Attributes/BackupStaticProperties.php b/src/Framework/Attributes/BackupStaticProperties.php
new file mode 100644
index 00000000000..04d46029ba4
--- /dev/null
+++ b/src/Framework/Attributes/BackupStaticProperties.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+final class BackupStaticProperties
+{
+ /**
+ * @var bool
+ */
+ private $enabled;
+
+ public function __construct(bool $enabled)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function enabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/Framework/Attributes/Before.php b/src/Framework/Attributes/Before.php
new file mode 100644
index 00000000000..029b7ec7d5b
--- /dev/null
+++ b/src/Framework/Attributes/Before.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class Before
+{
+}
diff --git a/src/Framework/Attributes/BeforeClass.php b/src/Framework/Attributes/BeforeClass.php
new file mode 100644
index 00000000000..8fcd0c3d553
--- /dev/null
+++ b/src/Framework/Attributes/BeforeClass.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class BeforeClass
+{
+}
diff --git a/src/Framework/Attributes/CodeCoverageIgnore.php b/src/Framework/Attributes/CodeCoverageIgnore.php
new file mode 100644
index 00000000000..f6db84178c9
--- /dev/null
+++ b/src/Framework/Attributes/CodeCoverageIgnore.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+final class CodeCoverageIgnore
+{
+}
diff --git a/src/Framework/Attributes/CoversClass.php b/src/Framework/Attributes/CoversClass.php
new file mode 100644
index 00000000000..edeae216295
--- /dev/null
+++ b/src/Framework/Attributes/CoversClass.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+final class CoversClass
+{
+ /**
+ * @psalm-var class-string
+ */
+ private $className;
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function __construct(string $className)
+ {
+ $this->className = $className;
+ }
+
+ /**
+ * @psalm-return class-string
+ */
+ public function className(): string
+ {
+ return $this->className;
+ }
+}
diff --git a/src/Framework/Attributes/CoversFunction.php b/src/Framework/Attributes/CoversFunction.php
new file mode 100644
index 00000000000..1598d86a32e
--- /dev/null
+++ b/src/Framework/Attributes/CoversFunction.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+final class CoversFunction
+{
+ /**
+ * @var string
+ */
+ private $functionName;
+
+ public function __construct(string $functionName)
+ {
+ $this->functionName = $functionName;
+ }
+
+ public function functionName(): string
+ {
+ return $this->functionName;
+ }
+}
diff --git a/src/Framework/Attributes/CoversNothing.php b/src/Framework/Attributes/CoversNothing.php
new file mode 100644
index 00000000000..a2611a62b32
--- /dev/null
+++ b/src/Framework/Attributes/CoversNothing.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+final class CoversNothing
+{
+}
diff --git a/src/Framework/Attributes/DataProvider.php b/src/Framework/Attributes/DataProvider.php
new file mode 100644
index 00000000000..0301b521178
--- /dev/null
+++ b/src/Framework/Attributes/DataProvider.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::IS_REPEATABLE)]
+final class DataProvider
+{
+ /**
+ * @var string
+ */
+ private $methodName;
+
+ public function __construct(string $methodName)
+ {
+ $this->methodName = $methodName;
+ }
+
+ public function methodName(): string
+ {
+ return $this->methodName;
+ }
+}
diff --git a/src/Framework/Attributes/DataProviderExternal.php b/src/Framework/Attributes/DataProviderExternal.php
new file mode 100644
index 00000000000..8d9869b54bc
--- /dev/null
+++ b/src/Framework/Attributes/DataProviderExternal.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::IS_REPEATABLE)]
+final class DataProviderExternal
+{
+ /**
+ * @psalm-var class-string
+ */
+ private $className;
+
+ /**
+ * @var string
+ */
+ private $methodName;
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function __construct(string $className, string $methodName)
+ {
+ $this->className = $className;
+ $this->methodName = $methodName;
+ }
+
+ /**
+ * @psalm-return class-string
+ */
+ public function className(): string
+ {
+ return $this->className;
+ }
+
+ public function methodName(): string
+ {
+ return $this->methodName;
+ }
+}
diff --git a/src/Framework/Attributes/Depends.php b/src/Framework/Attributes/Depends.php
new file mode 100644
index 00000000000..be97877d179
--- /dev/null
+++ b/src/Framework/Attributes/Depends.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::IS_REPEATABLE)]
+final class Depends
+{
+ /**
+ * @var string
+ */
+ private $methodName;
+
+ public function __construct(string $methodName)
+ {
+ $this->methodName = $methodName;
+ }
+
+ public function methodName(): string
+ {
+ return $this->methodName;
+ }
+}
diff --git a/src/Framework/Attributes/DependsExternal.php b/src/Framework/Attributes/DependsExternal.php
new file mode 100644
index 00000000000..7fa8765517c
--- /dev/null
+++ b/src/Framework/Attributes/DependsExternal.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::IS_REPEATABLE)]
+final class DependsExternal
+{
+ /**
+ * @psalm-var class-string
+ */
+ private $className;
+
+ /**
+ * @var string
+ */
+ private $methodName;
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function __construct(string $className, string $methodName)
+ {
+ $this->className = $className;
+ $this->methodName = $methodName;
+ }
+
+ /**
+ * @psalm-return class-string
+ */
+ public function className(): string
+ {
+ return $this->className;
+ }
+
+ public function methodName(): string
+ {
+ return $this->methodName;
+ }
+}
diff --git a/src/Framework/Attributes/DoesNotPerformAssertions.php b/src/Framework/Attributes/DoesNotPerformAssertions.php
new file mode 100644
index 00000000000..dae8829c0cd
--- /dev/null
+++ b/src/Framework/Attributes/DoesNotPerformAssertions.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+final class DoesNotPerformAssertions
+{
+}
diff --git a/src/Framework/Attributes/Group.php b/src/Framework/Attributes/Group.php
new file mode 100644
index 00000000000..0e8a3f8be77
--- /dev/null
+++ b/src/Framework/Attributes/Group.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class Group
+{
+ /**
+ * @var string
+ */
+ private $name;
+
+ public function __construct(string $name)
+ {
+ $this->name = $name;
+ }
+
+ public function name(): string
+ {
+ return $this->name;
+ }
+}
diff --git a/src/Framework/Attributes/Large.php b/src/Framework/Attributes/Large.php
new file mode 100644
index 00000000000..61ea26ce160
--- /dev/null
+++ b/src/Framework/Attributes/Large.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final class Large extends Group
+{
+ public function name(): string
+ {
+ return 'large';
+ }
+}
diff --git a/src/Framework/Attributes/Medium.php b/src/Framework/Attributes/Medium.php
new file mode 100644
index 00000000000..fbf5eddc194
--- /dev/null
+++ b/src/Framework/Attributes/Medium.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final class Medium extends Group
+{
+ public function name(): string
+ {
+ return 'medium';
+ }
+}
diff --git a/src/Framework/Attributes/PostCondition.php b/src/Framework/Attributes/PostCondition.php
new file mode 100644
index 00000000000..32363718b5b
--- /dev/null
+++ b/src/Framework/Attributes/PostCondition.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class PostCondition
+{
+}
diff --git a/src/Framework/Attributes/PreCondition.php b/src/Framework/Attributes/PreCondition.php
new file mode 100644
index 00000000000..f5bcc2cf217
--- /dev/null
+++ b/src/Framework/Attributes/PreCondition.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class PreCondition
+{
+}
diff --git a/src/Framework/Attributes/PreserveGlobalState.php b/src/Framework/Attributes/PreserveGlobalState.php
new file mode 100644
index 00000000000..492090c7105
--- /dev/null
+++ b/src/Framework/Attributes/PreserveGlobalState.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+final class PreserveGlobalState
+{
+ /**
+ * @var bool
+ */
+ private $enabled;
+
+ public function __construct(bool $enabled)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function enabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/Framework/Attributes/RequiresFunction.php b/src/Framework/Attributes/RequiresFunction.php
new file mode 100644
index 00000000000..8b4fc850065
--- /dev/null
+++ b/src/Framework/Attributes/RequiresFunction.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class RequiresFunction
+{
+ /**
+ * @var string
+ */
+ private $functionName;
+
+ public function __construct(string $functionName)
+ {
+ $this->functionName = $functionName;
+ }
+
+ public function functionName(): string
+ {
+ return $this->functionName;
+ }
+}
diff --git a/src/Framework/Attributes/RequiresOperatingSystem.php b/src/Framework/Attributes/RequiresOperatingSystem.php
new file mode 100644
index 00000000000..903b42d3c44
--- /dev/null
+++ b/src/Framework/Attributes/RequiresOperatingSystem.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+class RequiresOperatingSystem
+{
+ /**
+ * @var string
+ */
+ private $regularExpression;
+
+ public function __construct(string $regularExpression)
+ {
+ $this->regularExpression = $regularExpression;
+ }
+
+ public function regularExpression(): string
+ {
+ return $this->regularExpression;
+ }
+}
diff --git a/src/Framework/Attributes/RequiresOperatingSystemFamily.php b/src/Framework/Attributes/RequiresOperatingSystemFamily.php
new file mode 100644
index 00000000000..27fabe3a274
--- /dev/null
+++ b/src/Framework/Attributes/RequiresOperatingSystemFamily.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+class RequiresOperatingSystemFamily
+{
+ /**
+ * @var string
+ */
+ private $operatingSystemFamily;
+
+ public function __construct(string $operatingSystemFamily)
+ {
+ $this->operatingSystemFamily = $operatingSystemFamily;
+ }
+
+ public function operatingSystemFamily(): string
+ {
+ return $this->operatingSystemFamily;
+ }
+}
diff --git a/src/Framework/Attributes/RequiresPhp.php b/src/Framework/Attributes/RequiresPhp.php
new file mode 100644
index 00000000000..546e2420e96
--- /dev/null
+++ b/src/Framework/Attributes/RequiresPhp.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+class RequiresPhp
+{
+ /**
+ * @var string
+ */
+ private $version;
+
+ /**
+ * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne'
+ */
+ private $operator;
+
+ /**
+ * @psalm-param '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator
+ */
+ public function __construct(string $version, string $operator = '>=')
+ {
+ $this->version = $version;
+ $this->operator = $operator;
+ }
+
+ public function version(): string
+ {
+ return $this->version;
+ }
+
+ public function operator(): string
+ {
+ return $this->operator;
+ }
+}
diff --git a/src/Framework/Attributes/RequiresPhpExtension.php b/src/Framework/Attributes/RequiresPhpExtension.php
new file mode 100644
index 00000000000..0bf1871596e
--- /dev/null
+++ b/src/Framework/Attributes/RequiresPhpExtension.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class RequiresPhpExtension
+{
+ /**
+ * @var string
+ */
+ private $extension;
+
+ /**
+ * @var string
+ */
+ private $version;
+
+ /**
+ * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne'
+ */
+ private $operator;
+
+ /**
+ * @psalm-param '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator
+ */
+ public function __construct(string $extension, string $version, string $operator = '>=')
+ {
+ $this->extension = $extension;
+ $this->version = $version;
+ $this->operator = $operator;
+ }
+
+ public function extension(): string
+ {
+ return $this->extension;
+ }
+
+ public function version(): string
+ {
+ return $this->version;
+ }
+
+ public function operator(): string
+ {
+ return $this->operator;
+ }
+}
diff --git a/src/Framework/Attributes/RequiresPhpunit.php b/src/Framework/Attributes/RequiresPhpunit.php
new file mode 100644
index 00000000000..b2c6db97988
--- /dev/null
+++ b/src/Framework/Attributes/RequiresPhpunit.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+class RequiresPhpunit
+{
+ /**
+ * @var string
+ */
+ private $version;
+
+ /**
+ * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne'
+ */
+ private $operator;
+
+ /**
+ * @psalm-param '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator
+ */
+ public function __construct(string $version, string $operator = '>=')
+ {
+ $this->version = $version;
+ $this->operator = $operator;
+ }
+
+ public function version(): string
+ {
+ return $this->version;
+ }
+
+ public function operator(): string
+ {
+ return $this->operator;
+ }
+}
diff --git a/src/Framework/Attributes/RunInSeparateProcess.php b/src/Framework/Attributes/RunInSeparateProcess.php
new file mode 100644
index 00000000000..1dfbac53d32
--- /dev/null
+++ b/src/Framework/Attributes/RunInSeparateProcess.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class RunInSeparateProcess
+{
+}
diff --git a/src/Framework/Attributes/RunTestsInSeparateProcesses.php b/src/Framework/Attributes/RunTestsInSeparateProcesses.php
new file mode 100644
index 00000000000..17f7182ed58
--- /dev/null
+++ b/src/Framework/Attributes/RunTestsInSeparateProcesses.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final class RunTestsInSeparateProcesses
+{
+}
diff --git a/src/Framework/Attributes/Small.php b/src/Framework/Attributes/Small.php
new file mode 100644
index 00000000000..05f7e8eb59e
--- /dev/null
+++ b/src/Framework/Attributes/Small.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS)]
+final class Small extends Group
+{
+ public function name(): string
+ {
+ return 'small';
+ }
+}
diff --git a/src/Framework/Attributes/Test.php b/src/Framework/Attributes/Test.php
new file mode 100644
index 00000000000..f3c8daaf508
--- /dev/null
+++ b/src/Framework/Attributes/Test.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD)]
+final class Test
+{
+}
diff --git a/src/Framework/Attributes/TestDox.php b/src/Framework/Attributes/TestDox.php
new file mode 100644
index 00000000000..913a1e5a041
--- /dev/null
+++ b/src/Framework/Attributes/TestDox.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
+class TestDox
+{
+ /**
+ * @var string
+ */
+ private $text;
+
+ public function __construct(string $text)
+ {
+ $this->text = $text;
+ }
+
+ public function text(): string
+ {
+ return $this->text;
+ }
+}
diff --git a/src/Framework/Attributes/TestWith.php b/src/Framework/Attributes/TestWith.php
new file mode 100644
index 00000000000..5bfc303ce52
--- /dev/null
+++ b/src/Framework/Attributes/TestWith.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class TestWith
+{
+ /**
+ * @var string
+ */
+ private $json;
+
+ public function __construct(string $json)
+ {
+ $this->json = $json;
+ }
+
+ public function json(): string
+ {
+ return $this->json;
+ }
+}
diff --git a/src/Framework/Attributes/Ticket.php b/src/Framework/Attributes/Ticket.php
new file mode 100644
index 00000000000..19c042cb3c9
--- /dev/null
+++ b/src/Framework/Attributes/Ticket.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+final class Ticket extends Group
+{
+}
diff --git a/src/Framework/Attributes/UsesClass.php b/src/Framework/Attributes/UsesClass.php
new file mode 100644
index 00000000000..fce7ea4e0a5
--- /dev/null
+++ b/src/Framework/Attributes/UsesClass.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+final class UsesClass
+{
+ /**
+ * @var string
+ */
+ private $className;
+
+ public function __construct(string $className)
+ {
+ $this->className = $className;
+ }
+
+ public function className(): string
+ {
+ return $this->className;
+ }
+}
diff --git a/src/Framework/Attributes/UsesFunction.php b/src/Framework/Attributes/UsesFunction.php
new file mode 100644
index 00000000000..1ae496ca05d
--- /dev/null
+++ b/src/Framework/Attributes/UsesFunction.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\Framework\Attributes;
+
+use Attribute;
+
+#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+final class UsesFunction
+{
+ /**
+ * @var string
+ */
+ private $functionName;
+
+ public function __construct(string $functionName)
+ {
+ $this->functionName = $functionName;
+ }
+
+ public function functionName(): string
+ {
+ return $this->functionName;
+ }
+}
diff --git a/src/Util/Metadata/After.php b/src/Util/Metadata/After.php
new file mode 100644
index 00000000000..48f3fcb8b8c
--- /dev/null
+++ b/src/Util/Metadata/After.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class After extends Metadata
+{
+ public function isAfter(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/AfterClass.php b/src/Util/Metadata/AfterClass.php
new file mode 100644
index 00000000000..3556721073e
--- /dev/null
+++ b/src/Util/Metadata/AfterClass.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class AfterClass extends Metadata
+{
+ public function isAfterClass(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/BackupGlobals.php b/src/Util/Metadata/BackupGlobals.php
new file mode 100644
index 00000000000..5fee5542163
--- /dev/null
+++ b/src/Util/Metadata/BackupGlobals.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class BackupGlobals extends Metadata
+{
+ /**
+ * @var bool
+ */
+ private $enabled;
+
+ public function __construct(bool $enabled)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function isBackupGlobals(): bool
+ {
+ return true;
+ }
+
+ public function enabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/Util/Metadata/BackupStaticProperties.php b/src/Util/Metadata/BackupStaticProperties.php
new file mode 100644
index 00000000000..8cdbd4a144f
--- /dev/null
+++ b/src/Util/Metadata/BackupStaticProperties.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class BackupStaticProperties extends Metadata
+{
+ /**
+ * @var bool
+ */
+ private $enabled;
+
+ public function __construct(bool $enabled)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function isBackupStaticProperties(): bool
+ {
+ return true;
+ }
+
+ public function enabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/Util/Metadata/Before.php b/src/Util/Metadata/Before.php
new file mode 100644
index 00000000000..fdadccb7bb4
--- /dev/null
+++ b/src/Util/Metadata/Before.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class Before extends Metadata
+{
+ public function isBefore(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/BeforeClass.php b/src/Util/Metadata/BeforeClass.php
new file mode 100644
index 00000000000..66fd40ce74e
--- /dev/null
+++ b/src/Util/Metadata/BeforeClass.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class BeforeClass extends Metadata
+{
+ public function isBeforeClass(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/CodeCoverageIgnore.php b/src/Util/Metadata/CodeCoverageIgnore.php
new file mode 100644
index 00000000000..9d6c1283da9
--- /dev/null
+++ b/src/Util/Metadata/CodeCoverageIgnore.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class CodeCoverageIgnore extends Metadata
+{
+ public function isCodeCoverageIgnore(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/CoversNothing.php b/src/Util/Metadata/CoversNothing.php
new file mode 100644
index 00000000000..341e5b1d368
--- /dev/null
+++ b/src/Util/Metadata/CoversNothing.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class CoversNothing extends Metadata
+{
+ public function isCoversNothing(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/DoesNotPerformAssertions.php b/src/Util/Metadata/DoesNotPerformAssertions.php
new file mode 100644
index 00000000000..fbc784ea0d9
--- /dev/null
+++ b/src/Util/Metadata/DoesNotPerformAssertions.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class DoesNotPerformAssertions extends Metadata
+{
+ public function isDoesNotPerformAssertions(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/Group.php b/src/Util/Metadata/Group.php
new file mode 100644
index 00000000000..2ec2b55ffb5
--- /dev/null
+++ b/src/Util/Metadata/Group.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class Group extends Metadata
+{
+ /**
+ * @var string
+ */
+ private $groupName;
+
+ public function __construct(string $groupName)
+ {
+ $this->groupName = $groupName;
+ }
+
+ public function isGroup(): bool
+ {
+ return true;
+ }
+
+ public function groupName(): string
+ {
+ return $this->groupName;
+ }
+}
diff --git a/src/Util/Metadata/Metadata.php b/src/Util/Metadata/Metadata.php
new file mode 100644
index 00000000000..0553603edf5
--- /dev/null
+++ b/src/Util/Metadata/Metadata.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+abstract class Metadata
+{
+ public function isAfter(): bool
+ {
+ return false;
+ }
+
+ public function isAfterClass(): bool
+ {
+ return false;
+ }
+
+ public function isBackupGlobals(): bool
+ {
+ return false;
+ }
+
+ public function isBackupStaticProperties(): bool
+ {
+ return false;
+ }
+
+ public function isBeforeClass(): bool
+ {
+ return false;
+ }
+
+ public function isBefore(): bool
+ {
+ return false;
+ }
+
+ public function isCodeCoverageIgnore(): bool
+ {
+ return false;
+ }
+
+ public function isCoversNothing(): bool
+ {
+ return false;
+ }
+
+ public function isDoesNotPerformAssertions(): bool
+ {
+ return false;
+ }
+
+ public function isGroup(): bool
+ {
+ return false;
+ }
+
+ public function isRunTestsInSeparateProcesses(): bool
+ {
+ return false;
+ }
+
+ public function isRunInSeparateProcess(): bool
+ {
+ return false;
+ }
+
+ public function isTest(): bool
+ {
+ return false;
+ }
+
+ public function isPreCondition(): bool
+ {
+ return false;
+ }
+
+ public function isPostCondition(): bool
+ {
+ return false;
+ }
+
+ public function isPreserveGlobalState(): bool
+ {
+ return false;
+ }
+}
diff --git a/src/Util/Metadata/MetadataCollection.php b/src/Util/Metadata/MetadataCollection.php
new file mode 100644
index 00000000000..8fe367ac973
--- /dev/null
+++ b/src/Util/Metadata/MetadataCollection.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+use function count;
+use Countable;
+use IteratorAggregate;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class MetadataCollection implements Countable, IteratorAggregate
+{
+ /**
+ * @var Metadata[]
+ */
+ private $metadata;
+
+ /**
+ * @param Metadata[] $metadata
+ */
+ public static function fromArray(array $metadata): self
+ {
+ return new self(...$metadata);
+ }
+
+ private function __construct(Metadata ...$metadata)
+ {
+ $this->metadata = $metadata;
+ }
+
+ /**
+ * @return Metadata[]
+ */
+ public function asArray(): array
+ {
+ return $this->metadata;
+ }
+
+ public function count(): int
+ {
+ return count($this->metadata);
+ }
+
+ public function getIterator(): MetadataCollectionIterator
+ {
+ return new MetadataCollectionIterator($this);
+ }
+
+ public function mergeWith(self $other): self
+ {
+ return new self(
+ array_merge(
+ $this->asArray(),
+ $other->asArray()
+ )
+ );
+ }
+}
diff --git a/src/Util/Metadata/MetadataCollectionIterator.php b/src/Util/Metadata/MetadataCollectionIterator.php
new file mode 100644
index 00000000000..5a65d105c4b
--- /dev/null
+++ b/src/Util/Metadata/MetadataCollectionIterator.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+use function count;
+use function iterator_count;
+use Countable;
+use Iterator;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+final class MetadataCollectionIterator implements Countable, Iterator
+{
+ /**
+ * @var Metadata[]
+ */
+ private $metadata;
+
+ /**
+ * @var int
+ */
+ private $position = 0;
+
+ public function __construct(MetadataCollection $metadata)
+ {
+ $this->metadata = $metadata->asArray();
+ }
+
+ public function count(): int
+ {
+ return iterator_count($this);
+ }
+
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ public function valid(): bool
+ {
+ return $this->position < count($this->metadata);
+ }
+
+ public function key(): int
+ {
+ return $this->position;
+ }
+
+ public function current(): Metadata
+ {
+ return $this->metadata[$this->position];
+ }
+
+ public function next(): void
+ {
+ $this->position++;
+ }
+}
diff --git a/src/Util/Metadata/PostCondition.php b/src/Util/Metadata/PostCondition.php
new file mode 100644
index 00000000000..c379e7f74fe
--- /dev/null
+++ b/src/Util/Metadata/PostCondition.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class PostCondition extends Metadata
+{
+ public function isPostCondition(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/PreCondition.php b/src/Util/Metadata/PreCondition.php
new file mode 100644
index 00000000000..a47df01f21c
--- /dev/null
+++ b/src/Util/Metadata/PreCondition.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class PreCondition extends Metadata
+{
+ public function isPreCondition(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/PreserveGlobalState.php b/src/Util/Metadata/PreserveGlobalState.php
new file mode 100644
index 00000000000..1252f3622fe
--- /dev/null
+++ b/src/Util/Metadata/PreserveGlobalState.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class PreserveGlobalState extends Metadata
+{
+ /**
+ * @var bool
+ */
+ private $enabled;
+
+ public function __construct(bool $enabled)
+ {
+ $this->enabled = $enabled;
+ }
+
+ public function isPreserveGlobalState(): bool
+ {
+ return true;
+ }
+
+ public function enabled(): bool
+ {
+ return $this->enabled;
+ }
+}
diff --git a/src/Util/Metadata/Reader/AnnotationReader.php b/src/Util/Metadata/Reader/AnnotationReader.php
new file mode 100644
index 00000000000..c09d113c79b
--- /dev/null
+++ b/src/Util/Metadata/Reader/AnnotationReader.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+final class AnnotationReader implements Reader
+{
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forClass(string $className): MetadataCollection
+ {
+ return MetadataCollection::fromArray([]);
+ }
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forMethod(string $className, string $methodName): MetadataCollection
+ {
+ return MetadataCollection::fromArray([]);
+ }
+}
diff --git a/src/Util/Metadata/Reader/AttributeReader.php b/src/Util/Metadata/Reader/AttributeReader.php
new file mode 100644
index 00000000000..0dcf6a24d76
--- /dev/null
+++ b/src/Util/Metadata/Reader/AttributeReader.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+final class AttributeReader implements Reader
+{
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forClass(string $className): MetadataCollection
+ {
+ return MetadataCollection::fromArray([]);
+ }
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forMethod(string $className, string $methodName): MetadataCollection
+ {
+ return MetadataCollection::fromArray([]);
+ }
+}
diff --git a/src/Util/Metadata/Reader/CachingReader.php b/src/Util/Metadata/Reader/CachingReader.php
new file mode 100644
index 00000000000..ccb1826b92f
--- /dev/null
+++ b/src/Util/Metadata/Reader/CachingReader.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+final class CachingReader implements Reader
+{
+ /**
+ * @var Reader
+ */
+ private $reader;
+
+ /**
+ * @var array
+ */
+ private $cache = [];
+
+ public function __construct(Reader $reader)
+ {
+ $this->reader = $reader;
+ }
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forClass(string $className): MetadataCollection
+ {
+ if (isset($this->cache[$className])) {
+ return $this->cache[$className];
+ }
+
+ $this->cache[$className] = $this->reader->forClass($className);
+
+ return $this->cache[$className];
+ }
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forMethod(string $className, string $methodName): MetadataCollection
+ {
+ $key = $className . '::' . $methodName;
+
+ if (isset($this->cache[$key])) {
+ return $this->cache[$key];
+ }
+
+ $this->cache[$key] = $this->reader->forMethod($className, $methodName);
+
+ return $this->cache[$key];
+ }
+}
diff --git a/src/Util/Metadata/Reader/MergingParser.php b/src/Util/Metadata/Reader/MergingParser.php
new file mode 100644
index 00000000000..3b1b0e1b602
--- /dev/null
+++ b/src/Util/Metadata/Reader/MergingParser.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+final class MergingParser implements Reader
+{
+ /**
+ * @var Reader[]
+ */
+ private $readers;
+
+ public function __construct(Reader ...$readers)
+ {
+ $this->readers = $readers;
+ }
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forClass(string $className): MetadataCollection
+ {
+ $metadata = MetadataCollection::fromArray([]);
+
+ foreach ($this->readers as $reader) {
+ $metadata = $metadata->mergeWith($reader->forClass($className));
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forMethod(string $className, string $methodName): MetadataCollection
+ {
+ $metadata = MetadataCollection::fromArray([]);
+
+ foreach ($this->readers as $reader) {
+ $metadata = $metadata->mergeWith($reader->forMethod($className, $methodName));
+ }
+
+ return $metadata;
+ }
+}
diff --git a/src/Util/Metadata/Reader/Reader.php b/src/Util/Metadata/Reader/Reader.php
new file mode 100644
index 00000000000..ab41595bb94
--- /dev/null
+++ b/src/Util/Metadata/Reader/Reader.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+interface Reader
+{
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forClass(string $className): MetadataCollection;
+
+ /**
+ * @psalm-param class-string $className
+ */
+ public function forMethod(string $className, string $methodName): MetadataCollection;
+}
diff --git a/src/Util/Metadata/Reader/Registry.php b/src/Util/Metadata/Reader/Registry.php
new file mode 100644
index 00000000000..abb7cd8d835
--- /dev/null
+++ b/src/Util/Metadata/Reader/Registry.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * Attribute and annotation information is static within a single PHP process.
+ * It is therefore okay to use a Singleton registry here.
+ *
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ */
+final class Registry
+{
+ /**
+ * @var ?Reader
+ */
+ private static $instance;
+
+ public static function reader(): Reader
+ {
+ return self::$instance ?? self::$instance = self::build();
+ }
+
+ private function __construct()
+ {
+ }
+
+ private static function build(): Reader
+ {
+ if (PHP_MAJOR_VERSION >= 8) {
+ return new CachingReader(
+ new MergingParser(
+ new AttributeReader,
+ new AnnotationReader
+ )
+ );
+ }
+
+ return new CachingReader(new AnnotationReader);
+ }
+}
diff --git a/src/Util/Metadata/RunInSeparateProcess.php b/src/Util/Metadata/RunInSeparateProcess.php
new file mode 100644
index 00000000000..f225cd40812
--- /dev/null
+++ b/src/Util/Metadata/RunInSeparateProcess.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class RunInSeparateProcess extends Metadata
+{
+ public function isRunInSeparateProcess(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/RunTestsInSeparateProcesses.php b/src/Util/Metadata/RunTestsInSeparateProcesses.php
new file mode 100644
index 00000000000..4c0cf658a8b
--- /dev/null
+++ b/src/Util/Metadata/RunTestsInSeparateProcesses.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class RunTestsInSeparateProcesses extends Metadata
+{
+ public function isRunTestsInSeparateProcesses(): bool
+ {
+ return true;
+ }
+}
diff --git a/src/Util/Metadata/Test.php b/src/Util/Metadata/Test.php
new file mode 100644
index 00000000000..0bd4d70999e
--- /dev/null
+++ b/src/Util/Metadata/Test.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace PHPUnit\Util\Metadata;
+
+/**
+ * @internal This class is not covered by the backward compatibility promise for PHPUnit
+ * @psalm-immutable
+ */
+final class Test extends Metadata
+{
+ public function isTest(): bool
+ {
+ return true;
+ }
+}