From c959ebb2121a8f93f7be5c9691bd52d82719bfb7 Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Fri, 29 Oct 2021 15:47:03 +0000 Subject: [PATCH] Issue #3246638 by phenaproxima, tedbow: Move ComposerExecutableValidator into Package Manager --- automatic_updates.services.yml | 5 +- package_manager/package_manager.services.yml | 9 + .../ComposerExecutableValidator.php | 17 +- .../StageValidatorInterface.php | 21 +++ .../ComposerExecutableValidatorTest.php | 161 ++++++++++++++++++ .../tests/src/Traits/ValidationTestTrait.php | 32 ++++ .../tests}/src/Unit/ValidationResultTest.php | 4 +- .../PackageManagerReadinessCheck.php | 54 ++++++ .../ComposerExecutableValidatorTest.php | 136 ++------------- tests/src/Traits/ValidationTestTrait.php | 26 +-- 10 files changed, 310 insertions(+), 155 deletions(-) rename {src/Validator => package_manager/src/EventSubscriber}/ComposerExecutableValidator.php (81%) create mode 100644 package_manager/src/EventSubscriber/StageValidatorInterface.php create mode 100644 package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php create mode 100644 package_manager/tests/src/Traits/ValidationTestTrait.php rename {tests => package_manager/tests}/src/Unit/ValidationResultTest.php (97%) create mode 100644 src/Validator/PackageManagerReadinessCheck.php diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml index 4850a512fd..bfcb27a22d 100644 --- a/automatic_updates.services.yml +++ b/automatic_updates.services.yml @@ -47,8 +47,9 @@ services: tags: - { name: event_subscriber } automatic_updates.composer_executable_validator: - class: Drupal\automatic_updates\Validator\ComposerExecutableValidator - arguments: ['@package_manager.composer_runner','@string_translation'] + class: Drupal\automatic_updates\Validator\PackageManagerReadinessCheck + arguments: + - '@package_manager.validator.composer_executable' tags: - { name: event_subscriber } automatic_updates.disk_space_validator: diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml index da8091f9d9..8b207d342e 100644 --- a/package_manager/package_manager.services.yml +++ b/package_manager/package_manager.services.yml @@ -84,3 +84,12 @@ services: arguments: - '@config.factory' - '%app.root%' + + # Validators. + package_manager.validator.composer_executable: + class: Drupal\package_manager\EventSubscriber\ComposerExecutableValidator + arguments: + - '@package_manager.composer_runner' + - '@string_translation' + tags: + - { name: event_subscriber } diff --git a/src/Validator/ComposerExecutableValidator.php b/package_manager/src/EventSubscriber/ComposerExecutableValidator.php similarity index 81% rename from src/Validator/ComposerExecutableValidator.php rename to package_manager/src/EventSubscriber/ComposerExecutableValidator.php index 3b2c337b91..1b12e8bcc1 100644 --- a/src/Validator/ComposerExecutableValidator.php +++ b/package_manager/src/EventSubscriber/ComposerExecutableValidator.php @@ -1,8 +1,9 @@ <?php -namespace Drupal\automatic_updates\Validator; +namespace Drupal\package_manager\EventSubscriber; -use Drupal\automatic_updates\Event\ReadinessCheckEvent; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\Event\StageEvent; use Drupal\package_manager\ValidationResult; use Drupal\Core\Extension\ExtensionVersion; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -10,12 +11,11 @@ use Drupal\Core\StringTranslation\TranslationInterface; use PhpTuf\ComposerStager\Domain\Output\ProcessOutputCallbackInterface; use PhpTuf\ComposerStager\Exception\ExceptionInterface; use PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Validates that the Composer executable can be found in the correct version. */ -class ComposerExecutableValidator implements EventSubscriberInterface, ProcessOutputCallbackInterface { +class ComposerExecutableValidator implements StageValidatorInterface, ProcessOutputCallbackInterface { use StringTranslationTrait; @@ -47,12 +47,9 @@ class ComposerExecutableValidator implements EventSubscriberInterface, ProcessOu } /** - * Validates that the Composer executable can be found. - * - * @param \Drupal\automatic_updates\Event\ReadinessCheckEvent $event - * The event object. + * {@inheritdoc} */ - public function checkForComposerExecutable(ReadinessCheckEvent $event): void { + public function validateStage(StageEvent $event): void { try { $this->composer->run(['--version'], $this); } @@ -90,7 +87,7 @@ class ComposerExecutableValidator implements EventSubscriberInterface, ProcessOu */ public static function getSubscribedEvents() { return [ - ReadinessCheckEvent::class => 'checkForComposerExecutable', + PreCreateEvent::class => 'validateStage', ]; } diff --git a/package_manager/src/EventSubscriber/StageValidatorInterface.php b/package_manager/src/EventSubscriber/StageValidatorInterface.php new file mode 100644 index 0000000000..d232683c44 --- /dev/null +++ b/package_manager/src/EventSubscriber/StageValidatorInterface.php @@ -0,0 +1,21 @@ +<?php + +namespace Drupal\package_manager\EventSubscriber; + +use Drupal\package_manager\Event\StageEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Defines an interface for services that validate a stage over its life cycle. + */ +interface StageValidatorInterface extends EventSubscriberInterface { + + /** + * Validates a stage at various points during its life cycle. + * + * @param \Drupal\package_manager\Event\StageEvent $event + * The event object. + */ + public function validateStage(StageEvent $event): void; + +} diff --git a/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php b/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php new file mode 100644 index 0000000000..73379cbf9e --- /dev/null +++ b/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php @@ -0,0 +1,161 @@ +<?php + +namespace Drupal\Tests\package_manager\Kernel; + +use Drupal\KernelTests\KernelTestBase; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\EventSubscriber\ComposerExecutableValidator; +use Drupal\package_manager\ValidationResult; +use Drupal\Tests\package_manager\Traits\ValidationTestTrait; +use PhpTuf\ComposerStager\Exception\IOException; +use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface; +use Prophecy\Argument; + +/** + * @covers \Drupal\package_manager\EventSubscriber\ComposerExecutableValidator + * + * @group package_manager + */ +class ComposerExecutableValidatorTest extends KernelTestBase { + + use ValidationTestTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = ['package_manager']; + + /** + * Runs the validator under test, and asserts its results match expectations. + * + * @param \Drupal\package_manager\ValidationResult[] $expected_results + * The expected validation results. + */ + private function assertResults(array $expected_results): void { + $stage = $this->prophesize('\Drupal\package_manager\Stage'); + $event = new PreCreateEvent($stage->reveal()); + $this->container->get('package_manager.validator.composer_executable') + ->validateStage($event); + + $this->assertValidationResultsEqual($expected_results, $event->getResults()); + } + + /** + * Tests that an error is raised if the Composer executable isn't found. + */ + public function testErrorIfComposerNotFound(): void { + $exception = new IOException("This is your regularly scheduled error."); + + // The executable finder throws an exception if it can't find the requested + // executable. + $exec_finder = $this->prophesize(ExecutableFinderInterface::class); + $exec_finder->find('composer') + ->willThrow($exception) + ->shouldBeCalled(); + $this->container->set('package_manager.executable_finder', $exec_finder->reveal()); + + // The validator should translate that exception into an error. + $error = ValidationResult::createError([ + $exception->getMessage(), + ]); + $this->assertResults([$error]); + } + + /** + * Data provider for ::testComposerVersionValidation(). + * + * @return array[] + * Sets of arguments to pass to the test method. + */ + public function providerComposerVersionValidation(): array { + // Invalid or undetectable Composer versions will always produce the same + // error. + $invalid_version = ValidationResult::createError(['The Composer version could not be detected.']); + + // Unsupported Composer versions will report the detected version number + // in the validation result, so we need a function to churn out those fake + // results for the test method. + $unsupported_version = function (string $version): ValidationResult { + return ValidationResult::createError([ + "Composer 2 or later is required, but version $version was detected.", + ]); + }; + + return [ + // A valid 2.x version of Composer should not produce any errors. + [ + '2.1.6', + [], + ], + [ + '1.10.22', + [$unsupported_version('1.10.22')], + ], + [ + '1.7.3', + [$unsupported_version('1.7.3')], + ], + [ + '2.0.0-alpha3', + [], + ], + [ + '2.1.0-RC1', + [], + ], + [ + '1.0.0-RC', + [$unsupported_version('1.0.0-RC')], + ], + [ + '1.0.0-beta1', + [$unsupported_version('1.0.0-beta1')], + ], + [ + '1.9-dev', + [$invalid_version], + ], + [ + '@package_version@', + [$invalid_version], + ], + ]; + } + + /** + * Tests validation of various Composer versions. + * + * @param string $reported_version + * The version of Composer that `composer --version` should report. + * @param \Drupal\package_manager\ValidationResult[] $expected_results + * The expected validation results. + * + * @dataProvider providerComposerVersionValidation + */ + public function testComposerVersionValidation(string $reported_version, array $expected_results): void { + // Mock the output of `composer --version`, will be passed to the validator, + // which is itself a callback function that gets called repeatedly as + // Composer produces output. + /** @var \PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface|\Prophecy\Prophecy\ObjectProphecy $runner */ + $runner = $this->prophesize('\PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface'); + + $runner->run(['--version'], Argument::type(ComposerExecutableValidator::class)) + // Whatever is passed to ::run() will be passed to this mock callback in + // $arguments, and we know exactly what that will contain: an array of + // command arguments for Composer, and the validator object. + ->will(function (array $arguments) use ($reported_version) { + /** @var \Drupal\package_manager\EventSubscriber\ComposerExecutableValidator $validator */ + $validator = $arguments[1]; + // Invoke the validator (which, as mentioned, is a callback function), + // with fake output from `composer --version`. It should try to tease a + // recognized, supported version number out of this output. + $validator($validator::OUT, "Composer version $reported_version"); + }); + $this->container->set('package_manager.composer_runner', $runner->reveal()); + + // If the validator can't find a recognized, supported version of Composer, + // it should produce errors. + $this->assertResults($expected_results); + } + +} diff --git a/package_manager/tests/src/Traits/ValidationTestTrait.php b/package_manager/tests/src/Traits/ValidationTestTrait.php new file mode 100644 index 0000000000..9cfeff13a2 --- /dev/null +++ b/package_manager/tests/src/Traits/ValidationTestTrait.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\Tests\package_manager\Traits; + +/** + * Contains helpful methods for testing stage validators. + */ +trait ValidationTestTrait { + + /** + * Asserts two validation result sets are equal. + * + * @param \Drupal\package_manager\ValidationResult[] $expected_results + * The expected validation results. + * @param \Drupal\package_manager\ValidationResult[] $actual_results + * The actual validation results. + */ + protected function assertValidationResultsEqual(array $expected_results, array $actual_results): void { + $this->assertCount(count($expected_results), $actual_results); + + foreach ($expected_results as $expected_result) { + $actual_result = array_shift($actual_results); + $this->assertSame($expected_result->getSeverity(), $actual_result->getSeverity()); + $this->assertSame((string) $expected_result->getSummary(), (string) $actual_result->getSummary()); + $this->assertSame( + array_map('strval', $expected_result->getMessages()), + array_map('strval', $actual_result->getMessages()) + ); + } + } + +} diff --git a/tests/src/Unit/ValidationResultTest.php b/package_manager/tests/src/Unit/ValidationResultTest.php similarity index 97% rename from tests/src/Unit/ValidationResultTest.php rename to package_manager/tests/src/Unit/ValidationResultTest.php index 69a32aa1fc..26de5cd6e7 100644 --- a/tests/src/Unit/ValidationResultTest.php +++ b/package_manager/tests/src/Unit/ValidationResultTest.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\Tests\automatic_updates\Unit; +namespace Drupal\Tests\package_manager\Unit; use Drupal\package_manager\ValidationResult; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -10,7 +10,7 @@ use Drupal\Tests\UnitTestCase; /** * @coversDefaultClass \Drupal\package_manager\ValidationResult * - * @group automatic_updates + * @group package_manager */ class ValidationResultTest extends UnitTestCase { diff --git a/src/Validator/PackageManagerReadinessCheck.php b/src/Validator/PackageManagerReadinessCheck.php new file mode 100644 index 0000000000..72d8bb142d --- /dev/null +++ b/src/Validator/PackageManagerReadinessCheck.php @@ -0,0 +1,54 @@ +<?php + +namespace Drupal\automatic_updates\Validator; + +use Drupal\automatic_updates\Event\ReadinessCheckEvent; +use Drupal\package_manager\EventSubscriber\StageValidatorInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * An adapter to run another stage validator during readiness checking. + * + * This class exists to facilitate re-use of Package Manager's stage validators + * during update readiness checks, in addition to whatever events they normally + * subscribe to. + */ +class PackageManagerReadinessCheck implements EventSubscriberInterface { + + /** + * The validator to run. + * + * @var \Drupal\package_manager\EventSubscriber\StageValidatorInterface + */ + protected $validator; + + /** + * Constructs a PackageManagerReadinessCheck object. + * + * @param \Drupal\package_manager\EventSubscriber\StageValidatorInterface $validator + * The Package Manager validator to run during readiness checking. + */ + public function __construct(StageValidatorInterface $validator) { + $this->validator = $validator; + } + + /** + * Performs a readiness check by proxying to a Package Manager validator. + * + * @param \Drupal\automatic_updates\Event\ReadinessCheckEvent $event + * The event object. + */ + public function validate(ReadinessCheckEvent $event): void { + $this->validator->validateStage($event); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + ReadinessCheckEvent::class => 'validate', + ]; + } + +} diff --git a/tests/src/Kernel/ReadinessValidation/ComposerExecutableValidatorTest.php b/tests/src/Kernel/ReadinessValidation/ComposerExecutableValidatorTest.php index 2a389bfb70..fd7fe341f0 100644 --- a/tests/src/Kernel/ReadinessValidation/ComposerExecutableValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/ComposerExecutableValidatorTest.php @@ -2,17 +2,17 @@ namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation; -use Drupal\package_manager\ValidationResult; -use Drupal\automatic_updates\Validator\ComposerExecutableValidator; +use Drupal\automatic_updates\Event\ReadinessCheckEvent; +use Drupal\package_manager\EventSubscriber\StageValidatorInterface; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; -use PhpTuf\ComposerStager\Exception\IOException; -use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface; use Prophecy\Argument; /** - * @covers \Drupal\automatic_updates\Validator\ComposerExecutableValidator + * Tests that the Composer executable is validated during readiness checking. * * @group automatic_updates + * + * @see \Drupal\Tests\package_manager\Kernel\ComposerExecutableValidatorTest */ class ComposerExecutableValidatorTest extends AutomaticUpdatesKernelTestBase { @@ -25,121 +25,19 @@ class ComposerExecutableValidatorTest extends AutomaticUpdatesKernelTestBase { ]; /** - * Tests that an error is raised if the Composer executable isn't found. - */ - public function testErrorIfComposerNotFound(): void { - $exception = new IOException("This is your regularly scheduled error."); - - // The executable finder throws an exception if it can't find the requested - // executable. - $exec_finder = $this->prophesize(ExecutableFinderInterface::class); - $exec_finder->find('composer') - ->willThrow($exception) - ->shouldBeCalled(); - $this->container->set('package_manager.executable_finder', $exec_finder->reveal()); - - // The validator should translate that exception into an error. - $error = ValidationResult::createError([ - $exception->getMessage(), - ]); - $this->assertCheckerResultsFromManager([$error], TRUE); - } - - /** - * Data provider for ::testComposerVersionValidation(). - * - * @return array[] - * Sets of arguments to pass to the test method. + * Tests that the Composer executable is validated during readiness checking. */ - public function providerComposerVersionValidation(): array { - // Invalid or undetectable Composer versions will always produce the same - // error. - $invalid_version = ValidationResult::createError(['The Composer version could not be detected.']); - - // Unsupported Composer versions will report the detected version number - // in the validation result, so we need a function to churn out those fake - // results for the test method. - $unsupported_version = function (string $version): ValidationResult { - return ValidationResult::createError([ - "Composer 2 or later is required, but version $version was detected.", - ]); - }; - - return [ - // A valid 2.x version of Composer should not produce any errors. - [ - '2.1.6', - [], - ], - [ - '1.10.22', - [$unsupported_version('1.10.22')], - ], - [ - '1.7.3', - [$unsupported_version('1.7.3')], - ], - [ - '2.0.0-alpha3', - [], - ], - [ - '2.1.0-RC1', - [], - ], - [ - '1.0.0-RC', - [$unsupported_version('1.0.0-RC')], - ], - [ - '1.0.0-beta1', - [$unsupported_version('1.0.0-beta1')], - ], - [ - '1.9-dev', - [$invalid_version], - ], - [ - '@package_version@', - [$invalid_version], - ], - ]; - } - - /** - * Tests validation of various Composer versions. - * - * @param string $reported_version - * The version of Composer that `composer --version` should report. - * @param \Drupal\package_manager\ValidationResult[] $expected_results - * The expected validation results. - * - * @dataProvider providerComposerVersionValidation - */ - public function testComposerVersionValidation(string $reported_version, array $expected_results): void { - // Mock the output of `composer --version`, will be passed to the validator, - // which is itself a callback function that gets called repeatedly as - // Composer produces output. - /** @var \PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface|\Prophecy\Prophecy\ObjectProphecy $runner */ - $runner = $this->prophesize('\PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface'); - - $runner->run(['--version'], Argument::type(ComposerExecutableValidator::class)) - // Whatever is passed to ::run() will be passed to this mock callback in - // $arguments, and we know exactly what that will contain: an array of - // command arguments for Composer, and the validator object. - ->will(function (array $arguments) use ($reported_version) { - /** @var \Drupal\automatic_updates\Validator\ComposerExecutableValidator $validator */ - $validator = $arguments[1]; - // Invoke the validator (which, as mentioned, is a callback function), - // with fake output from `composer --version`. It should try to tease a - // recognized, supported version number out of this output. - $validator($validator::OUT, "Composer version $reported_version"); - }); - $this->container->set('package_manager.composer_runner', $runner->reveal()); - - // If the validator can't find a recognized, supported version of Composer, - // it should produce errors. - $this->assertCheckerResultsFromManager($expected_results, TRUE); + public function testComposerExecutableIsValidated(): void { + // Set up a mocked version of the Composer executable validator, to prove + // that it gets called with a readiness check event, when we run readiness + // checks. + $event = Argument::type(ReadinessCheckEvent::class); + $validator = $this->prophesize(StageValidatorInterface::class); + $validator->validateStage($event)->shouldBeCalled(); + $this->container->set('package_manager.validator.composer_executable', $validator->reveal()); + + $this->container->get('automatic_updates.readiness_validation_manager') + ->run(); } } diff --git a/tests/src/Traits/ValidationTestTrait.php b/tests/src/Traits/ValidationTestTrait.php index b7020c5ea7..592d24b39a 100644 --- a/tests/src/Traits/ValidationTestTrait.php +++ b/tests/src/Traits/ValidationTestTrait.php @@ -4,11 +4,15 @@ namespace Drupal\Tests\automatic_updates\Traits; use Drupal\package_manager\ValidationResult; +use Drupal\Tests\package_manager\Traits\ValidationTestTrait as PackageManagerValidationTestTrait; + /** * Common methods for testing validation. */ trait ValidationTestTrait { + use PackageManagerValidationTestTrait; + /** * Expected explanation text when readiness checkers return error messages. * @@ -88,28 +92,6 @@ trait ValidationTestTrait { } } - /** - * Asserts two validation result sets are equal. - * - * @param \Drupal\package_manager\ValidationResult[] $expected_results - * The expected validation results. - * @param \Drupal\package_manager\ValidationResult[]|null $actual_results - * The actual validation results or NULL if known are available. - */ - protected function assertValidationResultsEqual(array $expected_results, array $actual_results): void { - $this->assertCount(count($expected_results), $actual_results); - - foreach ($expected_results as $expected_result) { - $actual_result = array_shift($actual_results); - $this->assertSame($expected_result->getSeverity(), $actual_result->getSeverity()); - $this->assertSame((string) $expected_result->getSummary(), (string) $actual_result->getSummary()); - $this->assertSame( - array_map('strval', $expected_result->getMessages()), - array_map('strval', $actual_result->getMessages()) - ); - } - } - /** * Gets the messages of a particular type from the manager. * -- GitLab