From 583198da5115b86f17ba245308c09f90d5c9dc12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sun, 10 Mar 2024 21:24:42 +0100 Subject: [PATCH 1/3] feat: Provide a non optimized requirement list --- .../AppRequirementsFactory.php | 30 +++++++++- src/RequirementChecker/Requirement.php | 20 +++++++ src/RequirementChecker/RequirementType.php | 1 + .../RequirementsBuilder.php | 59 +++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/RequirementChecker/AppRequirementsFactory.php b/src/RequirementChecker/AppRequirementsFactory.php index db83165f2..74a02a516 100644 --- a/src/RequirementChecker/AppRequirementsFactory.php +++ b/src/RequirementChecker/AppRequirementsFactory.php @@ -28,11 +28,39 @@ final class AppRequirementsFactory { private const SELF_PACKAGE = null; + public function createUnfiltered( + ComposerJson $composerJson, + ComposerLock $composerLock, + CompressionAlgorithm $compressionAlgorithm, + ): Requirements { + return $this + ->createBuilder( + $composerJson, + $composerLock, + $compressionAlgorithm, + ) + ->getAll(); + } + public function create( ComposerJson $composerJson, ComposerLock $composerLock, CompressionAlgorithm $compressionAlgorithm, ): Requirements { + return $this + ->createBuilder( + $composerJson, + $composerLock, + $compressionAlgorithm, + ) + ->build(); + } + + private function createBuilder( + ComposerJson $composerJson, + ComposerLock $composerLock, + CompressionAlgorithm $compressionAlgorithm, + ): RequirementsBuilder { $requirementsBuilder = new RequirementsBuilder(); self::retrievePhpVersionRequirements($requirementsBuilder, $composerJson, $composerLock); @@ -40,7 +68,7 @@ public function create( self::collectComposerLockExtensionRequirements($composerLock, $requirementsBuilder); self::collectComposerJsonExtensionRequirements($composerJson, $requirementsBuilder); - return $requirementsBuilder->build(); + return $requirementsBuilder; } private static function retrievePhpVersionRequirements( diff --git a/src/RequirementChecker/Requirement.php b/src/RequirementChecker/Requirement.php index fe1d8e3ef..6c198c4bb 100644 --- a/src/RequirementChecker/Requirement.php +++ b/src/RequirementChecker/Requirement.php @@ -86,6 +86,26 @@ public static function forRequiredExtension(string $extension, ?string $packageN ); } + public static function forProvidedExtension(string $extension, ?string $packageName): self + { + return new self( + RequirementType::PROVIDED_EXTENSION, + $extension, + $packageName, + null === $packageName + ? sprintf( + 'This application provides the extension "%s".', + $extension, + ) + : sprintf( + 'The package "%s" provides the extension "%s".', + $packageName, + $extension, + ), + '', + ); + } + public static function forConflictingExtension(string $extension, ?string $packageName): self { return new self( diff --git a/src/RequirementChecker/RequirementType.php b/src/RequirementChecker/RequirementType.php index e5edb1882..89d5e642a 100644 --- a/src/RequirementChecker/RequirementType.php +++ b/src/RequirementChecker/RequirementType.php @@ -18,5 +18,6 @@ enum RequirementType: string { case PHP = 'php'; case EXTENSION = 'extension'; + case PROVIDED_EXTENSION = 'provided-extension'; case EXTENSION_CONFLICT = 'extension-conflict'; } diff --git a/src/RequirementChecker/RequirementsBuilder.php b/src/RequirementChecker/RequirementsBuilder.php index 0ce061248..f13bebeba 100644 --- a/src/RequirementChecker/RequirementsBuilder.php +++ b/src/RequirementChecker/RequirementsBuilder.php @@ -49,6 +49,40 @@ public function addConflictingExtension(Extension $extension, ?string $source): $this->conflictingExtensions[$extension->name][] = $source; } + public function getAll(): Requirements + { + $requirements = $this->predefinedRequirements; + + foreach ($this->getUnfilteredSortedRequiredExtensions() as $extensionName => $sources) { + foreach ($sources as $source) { + $requirements[] = Requirement::forRequiredExtension( + $extensionName, + $source, + ); + } + } + + foreach ($this->getSortedProvidedExtensions() as $extensionName => $sources) { + foreach ($sources as $source) { + $requirements[] = Requirement::forProvidedExtension( + $extensionName, + $source, + ); + } + } + + foreach ($this->getSortedConflictedExtensions() as $extensionName => $sources) { + foreach ($sources as $source) { + $requirements[] = Requirement::forConflictingExtension( + $extensionName, + $source, + ); + } + } + + return new Requirements($requirements); + } + public function build(): Requirements { $requirements = $this->predefinedRequirements; @@ -74,6 +108,31 @@ public function build(): Requirements return new Requirements($requirements); } + /** + * @return array> + */ + private function getUnfilteredSortedRequiredExtensions(): array + { + return array_map( + self::createSortedDistinctList(...), + self::sortByExtensionName( + $this->requiredExtensions, + ), + ); + } + /** + * @return array> + */ + private function getSortedProvidedExtensions(): array + { + return array_map( + self::createSortedDistinctList(...), + self::sortByExtensionName( + $this->providedExtensions, + ), + ); + } + /** * @return array> */ From 4c1321df2e59c7fdf4e31471d8f0acd734778e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sun, 10 Mar 2024 23:42:29 +0100 Subject: [PATCH 2/3] update tests --- .../AppRequirementsFactory.php | 2 +- src/RequirementChecker/Requirement.php | 11 +- .../RequirementsBuilder.php | 3 +- tests/RequirementChecker/RequirementTest.php | 36 ++++ .../RequirementsBuilderTest.php | 189 +++++++++++------- 5 files changed, 167 insertions(+), 74 deletions(-) diff --git a/src/RequirementChecker/AppRequirementsFactory.php b/src/RequirementChecker/AppRequirementsFactory.php index 74a02a516..5aede6d6e 100644 --- a/src/RequirementChecker/AppRequirementsFactory.php +++ b/src/RequirementChecker/AppRequirementsFactory.php @@ -39,7 +39,7 @@ public function createUnfiltered( $composerLock, $compressionAlgorithm, ) - ->getAll(); + ->all(); } public function create( diff --git a/src/RequirementChecker/Requirement.php b/src/RequirementChecker/Requirement.php index 6c198c4bb..c5752b275 100644 --- a/src/RequirementChecker/Requirement.php +++ b/src/RequirementChecker/Requirement.php @@ -102,7 +102,16 @@ public static function forProvidedExtension(string $extension, ?string $packageN $packageName, $extension, ), - '', + null === $packageName + ? sprintf( + 'This application does not require the extension "%s", it is provided by the application itself.', + $extension, + ) + : sprintf( + 'This application does not require the extension "%s", it is provided by the package "%s".', + $packageName, + $extension, + ), ); } diff --git a/src/RequirementChecker/RequirementsBuilder.php b/src/RequirementChecker/RequirementsBuilder.php index f13bebeba..d4518edc9 100644 --- a/src/RequirementChecker/RequirementsBuilder.php +++ b/src/RequirementChecker/RequirementsBuilder.php @@ -49,7 +49,7 @@ public function addConflictingExtension(Extension $extension, ?string $source): $this->conflictingExtensions[$extension->name][] = $source; } - public function getAll(): Requirements + public function all(): Requirements { $requirements = $this->predefinedRequirements; @@ -120,6 +120,7 @@ private function getUnfilteredSortedRequiredExtensions(): array ), ); } + /** * @return array> */ diff --git a/tests/RequirementChecker/RequirementTest.php b/tests/RequirementChecker/RequirementTest.php index f72403734..ef30569de 100644 --- a/tests/RequirementChecker/RequirementTest.php +++ b/tests/RequirementChecker/RequirementTest.php @@ -95,6 +95,42 @@ public function test_it_can_be_created_for_an_extension_constraint_for_a_package self::assertItCanBeCreatedFromItsArrayForm($requirement, $actual); } + public function test_it_can_be_created_for_a_provided_extension_constraint(): void + { + $requirement = Requirement::forProvidedExtension('mbstring', null); + + $expected = [ + 'type' => 'provided-extension', + 'condition' => 'mbstring', + 'source' => null, + 'message' => 'This application provides the extension "mbstring".', + 'helpMessage' => 'This application does not require the extension "mbstring", it is provided by the application itself.', + ]; + + $actual = $requirement->toArray(); + + self::assertSame($expected, $actual); + self::assertItCanBeCreatedFromItsArrayForm($requirement, $actual); + } + + public function test_it_can_be_created_for_a_provided_extension_constraint_for_a_package(): void + { + $requirement = Requirement::forProvidedExtension('mbstring', 'box/test'); + + $expected = [ + 'type' => 'provided-extension', + 'condition' => 'mbstring', + 'source' => 'box/test', + 'message' => 'The package "box/test" provides the extension "mbstring".', + 'helpMessage' => 'This application does not require the extension "box/test", it is provided by the package "mbstring".', + ]; + + $actual = $requirement->toArray(); + + self::assertSame($expected, $actual); + self::assertItCanBeCreatedFromItsArrayForm($requirement, $actual); + } + public function test_it_can_be_created_for_a_conflicting_extension_constraint(): void { $requirement = Requirement::forConflictingExtension('mbstring', null); diff --git a/tests/RequirementChecker/RequirementsBuilderTest.php b/tests/RequirementChecker/RequirementsBuilderTest.php index 028ffe97a..738160931 100644 --- a/tests/RequirementChecker/RequirementsBuilderTest.php +++ b/tests/RequirementChecker/RequirementsBuilderTest.php @@ -25,13 +25,19 @@ #[CoversClass(RequirementsBuilder::class)] final class RequirementsBuilderTest extends TestCase { - public function test_it_can_build_requirements_from_an_empty_list(): void + private RequirementsBuilder $builder; + + protected function setUp(): void { - $requirements = (new RequirementsBuilder())->build(); + $this->builder = new RequirementsBuilder(); + } + public function test_it_can_build_requirements_from_an_empty_list(): void + { $expected = new Requirements([]); - self::assertEquals($expected, $requirements); + $this->assertBuiltRequirementsEquals($expected); + $this->assertAllRequirementsEquals($expected); } public function test_it_can_build_requirements_from_predefined_requirements(): void @@ -42,39 +48,36 @@ public function test_it_can_build_requirements_from_predefined_requirements(): v Requirement::forConflictingExtension('http', null), ]; - $builder = new RequirementsBuilder(); - foreach ($predefinedRequirements as $predefinedRequirement) { - $builder->addRequirement($predefinedRequirement); + $this->builder->addRequirement($predefinedRequirement); } $expected = new Requirements($predefinedRequirements); - $actual = $builder->build(); - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expected); + $this->assertAllRequirementsEquals($expected); } public function test_it_can_build_requirements_from_required_extensions(): void { - $builder = new RequirementsBuilder(); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package1', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package2', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('phar'), 'package1', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('openssl'), 'package3', ); // Duplicate - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('openssl'), 'package3', ); @@ -86,51 +89,51 @@ public function test_it_can_build_requirements_from_required_extensions(): void Requirement::forRequiredExtension('phar', 'package1'), ]); - $actual = $builder->build(); - - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expected); + $this->assertAllRequirementsEquals($expected); } public function test_it_can_build_requirements_from_provided_extensions(): void { - $builder = new RequirementsBuilder(); - $builder->addProvidedExtension( + $this->builder->addProvidedExtension( new Extension('http'), 'package1', ); - $builder->addProvidedExtension( + $this->builder->addProvidedExtension( new Extension('http'), 'package2', ); - $expected = new Requirements([]); - - $actual = $builder->build(); + $expectedBuiltRequirements = new Requirements([]); + $expectedAllRequirements = new Requirements([ + Requirement::forProvidedExtension('http', 'package1'), + Requirement::forProvidedExtension('http', 'package2'), + ]); - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expectedBuiltRequirements); + $this->assertAllRequirementsEquals($expectedAllRequirements); } public function test_it_can_build_requirements_from_conflicting_extensions(): void { - $builder = new RequirementsBuilder(); - $builder->addConflictingExtension( + $this->builder->addConflictingExtension( new Extension('http'), 'package1', ); - $builder->addConflictingExtension( + $this->builder->addConflictingExtension( new Extension('http'), 'package2', ); - $builder->addConflictingExtension( + $this->builder->addConflictingExtension( new Extension('phar'), 'package1', ); - $builder->addConflictingExtension( + $this->builder->addConflictingExtension( new Extension('openssl'), 'package3', ); // Duplicate - $builder->addConflictingExtension( + $this->builder->addConflictingExtension( new Extension('openssl'), 'package3', ); @@ -142,88 +145,96 @@ public function test_it_can_build_requirements_from_conflicting_extensions(): vo Requirement::forConflictingExtension('phar', 'package1'), ]); - $actual = $builder->build(); - - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expected); + $this->assertAllRequirementsEquals($expected); } public function test_it_removes_extension_requirements_if_they_are_provided(): void { - $builder = new RequirementsBuilder(); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package1', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package2', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('phar'), 'package1', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('openssl'), 'package3', ); - $builder->addProvidedExtension( + $this->builder->addProvidedExtension( new Extension('http'), 'package3', ); - $expected = new Requirements([ + $expectedBuiltRequirements = new Requirements([ Requirement::forRequiredExtension('openssl', 'package3'), Requirement::forRequiredExtension('phar', 'package1'), ]); + $expectedAllRequirements = new Requirements([ + Requirement::forRequiredExtension('http', 'package1'), + Requirement::forRequiredExtension('http', 'package2'), + Requirement::forRequiredExtension('openssl', 'package3'), + Requirement::forRequiredExtension('phar', 'package1'), + Requirement::forProvidedExtension('http', 'package3'), + ]); - $actual = $builder->build(); - - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expectedBuiltRequirements); + $this->assertAllRequirementsEquals($expectedAllRequirements); } public function test_it_does_not_remove_extension_conflicts_if_they_are_provided(): void { - $builder = new RequirementsBuilder(); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package1', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package2', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('phar'), 'package1', ); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('openssl'), 'package3', ); - $builder->addProvidedExtension( + $this->builder->addProvidedExtension( new Extension('http'), 'package3', ); - $expected = new Requirements([ + $expectedBuiltRequirements = new Requirements([ Requirement::forRequiredExtension('openssl', 'package3'), Requirement::forRequiredExtension('phar', 'package1'), ]); + $expectedAllRequirements = new Requirements([ + Requirement::forRequiredExtension('http', 'package1'), + Requirement::forRequiredExtension('http', 'package2'), + Requirement::forRequiredExtension('openssl', 'package3'), + Requirement::forRequiredExtension('phar', 'package1'), + Requirement::forProvidedExtension('http', 'package3'), + ]); - $actual = $builder->build(); - - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expectedBuiltRequirements); + $this->assertAllRequirementsEquals($expectedAllRequirements); } public function test_it_can_have_an_extension_that_is_required_and_conflicting_at_the_same_time(): void { // This scenario does not really make sense but ensuring this does not happen is Composer's job not Box. - $builder = new RequirementsBuilder(); - $builder->addRequiredExtension( + $this->builder->addRequiredExtension( new Extension('http'), 'package1', ); - $builder->addConflictingExtension( + $this->builder->addConflictingExtension( new Extension('http'), 'package2', ); @@ -233,9 +244,11 @@ public function test_it_can_have_an_extension_that_is_required_and_conflicting_a Requirement::forConflictingExtension('http', 'package2'), ]); - $actual = $builder->build(); + $builtRequirements = $this->builder->build(); + $allRequirements = $this->builder->all(); - self::assertEquals($expected, $actual); + self::assertEquals($expected, $builtRequirements); + self::assertEquals($expected, $allRequirements); } // TODO: this could be solved @@ -243,16 +256,15 @@ public function test_it_does_not_remove_predefined_requirements_even_if_they_are { $predefinedRequirement = Requirement::forRequiredExtension('http', null); - $builder = new RequirementsBuilder(); - $builder->addRequirement($predefinedRequirement); - $builder->addProvidedExtension( + $this->builder->addRequirement($predefinedRequirement); + $this->builder->addProvidedExtension( new Extension('http'), 'package3', ); $expected = new Requirements([$predefinedRequirement]); - $actual = $builder->build(); + $actual = $this->builder->build(); self::assertEquals($expected, $actual); } @@ -262,25 +274,23 @@ public function test_it_ensures_the_requirements_built_are_consistent( array $predefinedRequirements, array $requiredExtensionSourcePairs, array $conflictingExtensionSourcePairs, - Requirements $expected, + Requirements $expectedBuiltRequirements, + Requirements $expectedAllRequirements, ): void { - $builder = new RequirementsBuilder(); - foreach ($predefinedRequirements as $predefinedRequirement) { - $builder->addRequirement($predefinedRequirement); + $this->builder->addRequirement($predefinedRequirement); } foreach ($requiredExtensionSourcePairs as [$requiredExtension, $source]) { - $builder->addRequiredExtension($requiredExtension, $source); + $this->builder->addRequiredExtension($requiredExtension, $source); } foreach ($conflictingExtensionSourcePairs as [$conflictingExtension, $source]) { - $builder->addConflictingExtension($conflictingExtension, $source); + $this->builder->addConflictingExtension($conflictingExtension, $source); } - $actual = $builder->build(); - - self::assertEquals($expected, $actual); + $this->assertBuiltRequirementsEquals($expectedBuiltRequirements); + $this->assertAllRequirementsEquals($expectedAllRequirements); } public static function requirementsProvider(): iterable @@ -302,6 +312,11 @@ public static function requirementsProvider(): iterable $predefinedRequirementNull, $predefinedRequirementA, ]), + new Requirements([ + $predefinedRequirementZ, + $predefinedRequirementNull, + $predefinedRequirementA, + ]), ]; yield 'required extension sources' => [ @@ -317,6 +332,11 @@ public static function requirementsProvider(): iterable Requirement::forRequiredExtension('noop', 'A'), Requirement::forRequiredExtension('noop', 'Z'), ]), + new Requirements([ + Requirement::forRequiredExtension('noop', null), + Requirement::forRequiredExtension('noop', 'A'), + Requirement::forRequiredExtension('noop', 'Z'), + ]), ]; yield 'required extensions' => [ @@ -330,6 +350,10 @@ public static function requirementsProvider(): iterable Requirement::forRequiredExtension('a-ext', null), Requirement::forRequiredExtension('z-ext', null), ]), + new Requirements([ + Requirement::forRequiredExtension('a-ext', null), + Requirement::forRequiredExtension('z-ext', null), + ]), ]; yield 'conflicting extension sources' => [ @@ -345,6 +369,11 @@ public static function requirementsProvider(): iterable Requirement::forConflictingExtension('noop', 'A'), Requirement::forConflictingExtension('noop', 'Z'), ]), + new Requirements([ + Requirement::forConflictingExtension('noop', null), + Requirement::forConflictingExtension('noop', 'A'), + Requirement::forConflictingExtension('noop', 'Z'), + ]), ]; yield 'conflicting extensions' => [ @@ -358,6 +387,24 @@ public static function requirementsProvider(): iterable Requirement::forConflictingExtension('a-ext', null), Requirement::forConflictingExtension('z-ext', null), ]), + new Requirements([ + Requirement::forConflictingExtension('a-ext', null), + Requirement::forConflictingExtension('z-ext', null), + ]), ]; } + + private function assertBuiltRequirementsEquals(Requirements $expected): void + { + $actual = $this->builder->build(); + + self::assertEquals($expected, $actual); + } + + private function assertAllRequirementsEquals(Requirements $expected): void + { + $actual = $this->builder->all(); + + self::assertEquals($expected, $actual); + } } From 0f0f7e8c93aa96508a6eda20627d1368dd510e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Mon, 11 Mar 2024 09:39:29 +0100 Subject: [PATCH 3/3] fix infection --- .../RequirementsBuilderTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/RequirementChecker/RequirementsBuilderTest.php b/tests/RequirementChecker/RequirementsBuilderTest.php index 738160931..559c0ae71 100644 --- a/tests/RequirementChecker/RequirementsBuilderTest.php +++ b/tests/RequirementChecker/RequirementsBuilderTest.php @@ -274,6 +274,7 @@ public function test_it_ensures_the_requirements_built_are_consistent( array $predefinedRequirements, array $requiredExtensionSourcePairs, array $conflictingExtensionSourcePairs, + array $providedExtensionSourcePairs, Requirements $expectedBuiltRequirements, Requirements $expectedAllRequirements, ): void { @@ -289,6 +290,10 @@ public function test_it_ensures_the_requirements_built_are_consistent( $this->builder->addConflictingExtension($conflictingExtension, $source); } + foreach ($providedExtensionSourcePairs as [$conflictingExtension, $source]) { + $this->builder->addProvidedExtension($conflictingExtension, $source); + } + $this->assertBuiltRequirementsEquals($expectedBuiltRequirements); $this->assertAllRequirementsEquals($expectedAllRequirements); } @@ -307,6 +312,7 @@ public static function requirementsProvider(): iterable ], [], [], + [], new Requirements([ $predefinedRequirementZ, $predefinedRequirementNull, @@ -327,6 +333,7 @@ public static function requirementsProvider(): iterable [new Extension('noop'), 'A'], ], [], + [], new Requirements([ Requirement::forRequiredExtension('noop', null), Requirement::forRequiredExtension('noop', 'A'), @@ -346,6 +353,7 @@ public static function requirementsProvider(): iterable [new Extension('a-ext'), null], ], [], + [], new Requirements([ Requirement::forRequiredExtension('a-ext', null), Requirement::forRequiredExtension('z-ext', null), @@ -364,6 +372,7 @@ public static function requirementsProvider(): iterable [new Extension('noop'), null], [new Extension('noop'), 'A'], ], + [], new Requirements([ Requirement::forConflictingExtension('noop', null), Requirement::forConflictingExtension('noop', 'A'), @@ -383,6 +392,7 @@ public static function requirementsProvider(): iterable [new Extension('z-ext'), null], [new Extension('a-ext'), null], ], + [], new Requirements([ Requirement::forConflictingExtension('a-ext', null), Requirement::forConflictingExtension('z-ext', null), @@ -392,6 +402,38 @@ public static function requirementsProvider(): iterable Requirement::forConflictingExtension('z-ext', null), ]), ]; + + yield 'provided extension sources' => [ + [], + [], + [], + [ + [new Extension('noop'), 'Z'], + [new Extension('noop'), null], + [new Extension('noop'), 'A'], + ], + new Requirements([]), + new Requirements([ + Requirement::forProvidedExtension('noop', null), + Requirement::forProvidedExtension('noop', 'A'), + Requirement::forProvidedExtension('noop', 'Z'), + ]), + ]; + + yield 'provided extensions' => [ + [], + [], + [], + [ + [new Extension('z-ext'), null], + [new Extension('a-ext'), null], + ], + new Requirements([]), + new Requirements([ + Requirement::forProvidedExtension('a-ext', null), + Requirement::forProvidedExtension('z-ext', null), + ]), + ]; } private function assertBuiltRequirementsEquals(Requirements $expected): void