From 0a9696f1081758a70d34cc3251536f957cd09f7d Mon Sep 17 00:00:00 2001 From: Matt Glaman Date: Fri, 21 Dec 2018 16:54:30 -0600 Subject: [PATCH] Add PHPUnit and automate testing --- .editorconfig | 12 +- .gitignore | 5 +- .travis.yml | 1 + composer.json | 16 ++- phpunit.xml | 28 +++++ src/Drupal/ServiceMap.php | 14 +-- ...erGetStorageDynamicReturnTypeExtension.php | 4 +- ...tStorageDynamicReturnTypeExtensionTest.php | 105 ++++++++++++++++++ tests/ServiceMapFactoryTest.php | 77 +++++++++++++ 9 files changed, 241 insertions(+), 21 deletions(-) create mode 100644 phpunit.xml create mode 100644 tests/EntityTypeManagerGetStorageDynamicReturnTypeExtensionTest.php create mode 100644 tests/ServiceMapFactoryTest.php diff --git a/.editorconfig b/.editorconfig index 3e5142f6..304c687c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,18 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# PHP PSR-2 Coding Standards +# http://www.php-fig.org/psr/psr-2/ + +root = true + [*.php] +charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -charset = utf-8 indent_style = space +indent_size = 4 + [*.neon] indent_style = tab diff --git a/.gitignore b/.gitignore index 87e974f9..95f4b2b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ composer.phar composer.lock /vendor/ - -# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock +/clover.xml diff --git a/.travis.yml b/.travis.yml index fa99a2c7..4dcfb3b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ install: script: - ./vendor/bin/phpcs src - ./vendor/bin/phpstan analyze src + - ./vendor/bin/phpunit cache: directories: - $HOME/.composer/cache diff --git a/composer.json b/composer.json index ed76b0b3..9373e166 100644 --- a/composer.json +++ b/composer.json @@ -13,16 +13,20 @@ "phpstan/phpstan": "^0.10.6", "symfony/yaml": "^4.2" }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, "require-dev": { "phpstan/phpstan-strict-rules": "^0.10.1", - "squizlabs/php_codesniffer": "^3.3" + "squizlabs/php_codesniffer": "^3.3", + "phpunit/phpunit": "^7.5" }, "suggest": { "phpstan/phpstan-deprecation-rules": "For catching deprecations, especially in Drupal core." + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "autoload-dev": { + "classmap": ["tests/"] } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..0a404961 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,28 @@ + + + + + tests + + + + + + + + + src + + ./vendor + ./tests + + + + diff --git a/src/Drupal/ServiceMap.php b/src/Drupal/ServiceMap.php index 4425d9ab..89c6d65e 100644 --- a/src/Drupal/ServiceMap.php +++ b/src/Drupal/ServiceMap.php @@ -16,7 +16,11 @@ public function __construct(array $drupalServices) foreach ($drupalServices as $serviceId => $serviceDefinition) { // @todo support factories if (!isset($serviceDefinition['class'])) { - continue; + if (isset($serviceDefinition['alias'], $drupalServices[$serviceDefinition['alias']])) { + $serviceDefinition['class'] = $drupalServices[$serviceDefinition['alias']]['class']; + } else { + continue; + } } $this->services[$serviceId] = new DrupalServiceDefinition( $serviceId, @@ -27,14 +31,6 @@ public function __construct(array $drupalServices) } } - /** - * @return DrupalServiceDefinition[] - */ - public function getServices(): array - { - return $this->services; - } - public function getService(string $id): ?DrupalServiceDefinition { return $this->services[$id] ?? null; diff --git a/src/Type/EntityTypeManagerGetStorageDynamicReturnTypeExtension.php b/src/Type/EntityTypeManagerGetStorageDynamicReturnTypeExtension.php index 82ecae88..0b9e08ec 100644 --- a/src/Type/EntityTypeManagerGetStorageDynamicReturnTypeExtension.php +++ b/src/Type/EntityTypeManagerGetStorageDynamicReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\ShouldNotHappenException; class EntityTypeManagerGetStorageDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -38,7 +39,8 @@ public function getTypeFromMethodCall( ): Type { $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); if (!isset($methodCall->args[0])) { - return $returnType; + // Parameter is required. + throw new ShouldNotHappenException(); } $arg1 = $methodCall->args[0]->value; diff --git a/tests/EntityTypeManagerGetStorageDynamicReturnTypeExtensionTest.php b/tests/EntityTypeManagerGetStorageDynamicReturnTypeExtensionTest.php new file mode 100644 index 00000000..2f007c32 --- /dev/null +++ b/tests/EntityTypeManagerGetStorageDynamicReturnTypeExtensionTest.php @@ -0,0 +1,105 @@ +getClass()); + } + + /** + * @dataProvider getEntityStorageProvider + * + * @covers \PHPStan\Type\EntityTypeManagerGetStorageDynamicReturnTypeExtension::__construct + * @covers \PHPStan\Type\EntityTypeManagerGetStorageDynamicReturnTypeExtension::getTypeFromMethodCall + */ + public function testGetTypeFromMethodCall($entityType, $storageClass) + { + $x = new EntityTypeManagerGetStorageDynamicReturnTypeExtension([ + 'node' => 'Drupal\node\NodeStorage', + 'search_api_index' => 'Drupal\search_api\Entity\SearchApiConfigEntityStorage', + ]); + + $methodReflection = $this->prophesize(MethodReflection::class); + $methodReflection->getName()->willReturn('getStorage'); + + $defaultObjectType = $this->prophesize(ObjectType::class); + $defaultObjectType->getClassName()->willReturn('Drupal\Core\Entity\EntityStorageInterface'); + $variantsParametersAcceptor = $this->prophesize(ParametersAcceptor::class); + $variantsParametersAcceptor->getReturnType()->willReturn($defaultObjectType->reveal()); + $methodReflection->getVariants()->willReturn([$variantsParametersAcceptor->reveal()]); + + if ($entityType === null) { + $this->expectException(ShouldNotHappenException::class); + $methodCall = new MethodCall( + $this->prophesize(Expr::class)->reveal(), + 'getStorage' + ); + } else { + $methodCall = new MethodCall( + $this->prophesize(Expr::class)->reveal(), + 'getStorage', + [new Arg($entityType)] + ); + } + + $scope = $this->prophesize(Scope::class); + + $type = $x->getTypeFromMethodCall( + $methodReflection->reveal(), + $methodCall, + $scope->reveal() + ); + self::assertInstanceOf(ObjectType::class, $type); + assert($type instanceof ObjectType); + self::assertEquals($storageClass, $type->getClassName()); + } + + public function getEntityStorageProvider(): \Iterator + { + yield [new String_('node'), 'Drupal\node\NodeStorage']; + yield [new String_('user'), 'Drupal\Core\Entity\EntityStorageInterface']; + yield [new String_('search_api_index'), 'Drupal\search_api\Entity\SearchApiConfigEntityStorage']; + yield [null, null]; + yield [$this->prophesize(MethodCall::class)->reveal(), 'Drupal\Core\Entity\EntityStorageInterface']; + yield [$this->prophesize(Expr\StaticCall::class)->reveal(), 'Drupal\Core\Entity\EntityStorageInterface']; + yield [$this->prophesize(Expr\BinaryOp\Concat::class)->reveal(), 'Drupal\Core\Entity\EntityStorageInterface']; + } + + /** + * @covers \PHPStan\Type\EntityTypeManagerGetStorageDynamicReturnTypeExtension::__construct + * @covers \PHPStan\Type\EntityTypeManagerGetStorageDynamicReturnTypeExtension::isMethodSupported + */ + public function testIsMethodSupported() + { + $x = new EntityTypeManagerGetStorageDynamicReturnTypeExtension([]); + + $valid = $this->prophesize(MethodReflection::class); + $valid->getName()->willReturn('getStorage'); + self::assertTrue($x->isMethodSupported($valid->reveal())); + + $invalid = $this->prophesize(MethodReflection::class); + $invalid->getName()->willReturn('getAccessControlHandler'); + self::assertFalse($x->isMethodSupported($invalid->reveal())); + } +} diff --git a/tests/ServiceMapFactoryTest.php b/tests/ServiceMapFactoryTest.php new file mode 100644 index 00000000..9c952e40 --- /dev/null +++ b/tests/ServiceMapFactoryTest.php @@ -0,0 +1,77 @@ + [ + 'class' => 'Drupal\Core\Entity\EntityTypeManager' + ], + 'skipped_factory' => [ + 'factory' => 'cache_factory:get', + 'arguments' => ['cache'], + ], + 'config.storage.staging' => [ + 'class' => 'Drupal\Core\Config\FileStorage', + ], + 'config.storage.sync' => [ + 'alias' => 'config.storage.staging', + ] + ]); + $validator($factory->create()->getService($id)); + } + + public function getServiceProvider(): \Iterator + { + yield [ + 'unknown', + function (?DrupalServiceDefinition $service): void { + self::assertNull($service, 'unknown'); + }, + ]; + yield [ + 'entity_type.manager', + function (?DrupalServiceDefinition $service): void { + self::assertNotNull($service); + self::assertEquals('entity_type.manager', $service->getId()); + self::assertEquals('Drupal\Core\Entity\EntityTypeManager', $service->getClass()); + self::assertTrue($service->isPublic()); + self::assertNull($service->getAlias()); + } + ]; + // For now factories are skipped. + yield [ + 'skipped_factory', + function (?DrupalServiceDefinition $service): void { + self::assertNull($service); + }, + ]; + yield [ + 'config.storage.sync', + function (?DrupalServiceDefinition $service): void { + self::assertNotNull($service); + self::assertEquals('config.storage.staging', $service->getAlias()); + } + ]; + } + +}