From fb30485bbcc7d30868d5f40285ed43d9505e8703 Mon Sep 17 00:00:00 2001 From: tedbow <tedbow@240860.no-reply.drupal.org> Date: Thu, 18 Nov 2021 20:36:15 +0000 Subject: [PATCH] Issue #3250136 by phenaproxima, tedbow: Allow package_manager_bypass to record method invocations for functional tests --- .../package_manager_bypass/src/Beginner.php | 3 +- .../package_manager_bypass/src/Committer.php | 3 +- .../src/InvocationRecorderBase.php | 36 +++++++++++++++++++ .../package_manager_bypass/src/Stager.php | 3 +- tests/src/Functional/UpdaterFormTest.php | 26 ++++++++++++++ tests/src/Kernel/UpdaterTest.php | 25 +++++++------ 6 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php diff --git a/package_manager/tests/modules/package_manager_bypass/src/Beginner.php b/package_manager/tests/modules/package_manager_bypass/src/Beginner.php index 837efe750f..742d38bd85 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/Beginner.php +++ b/package_manager/tests/modules/package_manager_bypass/src/Beginner.php @@ -8,12 +8,13 @@ use PhpTuf\ComposerStager\Domain\Output\ProcessOutputCallbackInterface; /** * Defines an update beginner which doesn't do anything. */ -class Beginner implements BeginnerInterface { +class Beginner extends InvocationRecorderBase implements BeginnerInterface { /** * {@inheritdoc} */ public function begin(string $activeDir, string $stagingDir, ?array $exclusions = [], ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = 120): void { + $this->saveInvocationArguments($activeDir, $stagingDir, $exclusions); } } diff --git a/package_manager/tests/modules/package_manager_bypass/src/Committer.php b/package_manager/tests/modules/package_manager_bypass/src/Committer.php index 45ec4fb225..3518237b5b 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/Committer.php +++ b/package_manager/tests/modules/package_manager_bypass/src/Committer.php @@ -8,7 +8,7 @@ use PhpTuf\ComposerStager\Domain\Output\ProcessOutputCallbackInterface; /** * Defines an update committer which doesn't do any actual committing. */ -class Committer implements CommitterInterface { +class Committer extends InvocationRecorderBase implements CommitterInterface { /** * The decorated committer service. @@ -31,6 +31,7 @@ class Committer implements CommitterInterface { * {@inheritdoc} */ public function commit(string $stagingDir, string $activeDir, ?array $exclusions = [], ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = 120): void { + $this->saveInvocationArguments($activeDir, $stagingDir, $exclusions); } /** diff --git a/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php b/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php new file mode 100644 index 0000000000..feb4dc92f6 --- /dev/null +++ b/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\package_manager_bypass; + +/** + * Records information about method invocations. + * + * This can be used by functional tests to ensure that the bypassed Composer + * Stager services were called as expected. Kernel and unit tests should use + * regular mocks instead. + */ +abstract class InvocationRecorderBase { + + /** + * Returns the arguments from every invocation of the main class method. + * + * @return array[] + * The arguments from every invocation of the main class method. + */ + public function getInvocationArguments(): array { + return \Drupal::state()->get(static::class, []); + } + + /** + * Records the arguments from an invocation of the main class method. + * + * @param mixed ...$arguments + * The arguments that the main class method was called with. + */ + protected function saveInvocationArguments(...$arguments) { + $invocations = $this->getInvocationArguments(); + $invocations[] = $arguments; + \Drupal::state()->set(static::class, $invocations); + } + +} diff --git a/package_manager/tests/modules/package_manager_bypass/src/Stager.php b/package_manager/tests/modules/package_manager_bypass/src/Stager.php index f068b29483..bb93e47271 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/Stager.php +++ b/package_manager/tests/modules/package_manager_bypass/src/Stager.php @@ -8,12 +8,13 @@ use PhpTuf\ComposerStager\Domain\StagerInterface; /** * Defines an update stager which doesn't actually do anything. */ -class Stager implements StagerInterface { +class Stager extends InvocationRecorderBase implements StagerInterface { /** * {@inheritdoc} */ public function stage(array $composerCommand, string $stagingDir, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = 120): void { + $this->saveInvocationArguments($composerCommand, $stagingDir); } } diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php index 0010c52fce..92371ae2aa 100644 --- a/tests/src/Functional/UpdaterFormTest.php +++ b/tests/src/Functional/UpdaterFormTest.php @@ -131,6 +131,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $release_notes = $assert_session->elementExists('named', ['link', 'Release notes'], $cells[2]); $this->assertSame('Release notes for Drupal', $release_notes->getAttribute('title')); $assert_session->buttonExists('Update'); + $this->assertUpdateStagedTimes(0); } /** @@ -184,8 +185,10 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $assert_session->pageTextNotContains(static::$warningsExplanation); $page->pressButton('Update'); $this->checkForMetaRefresh(); + // If a validator flags an error, but doesn't throw, the update should still // be halted. + $this->assertUpdateStagedTimes(0); $assert_session->pageTextContainsOnce('An error has occurred.'); $page->clickLink('the error page'); $assert_session->pageTextContainsOnce((string) $expected_results[0]->getMessages()[0]); @@ -199,6 +202,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { TestChecker1::setTestResult($expected_results, PreCreateEvent::class); $page->pressButton('Update'); $this->checkForMetaRefresh(); + $this->assertUpdateStagedTimes(0); $assert_session->pageTextContainsOnce('An error has occurred.'); $page->clickLink('the error page'); // Since there's only one message, we shouldn't see the summary. @@ -240,6 +244,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->drupalGet('/admin/modules/automatic-update'); $page->pressButton('Update'); $this->checkForMetaRefresh(); + $this->assertUpdateStagedTimes(1); // Confirm we are on the confirmation page. $assert_session->addressEquals('/admin/automatic-update-ready'); @@ -260,7 +265,28 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { // Confirm we are on the confirmation page. $assert_session->addressEquals('/admin/automatic-update-ready'); + $this->assertUpdateStagedTimes(2); $assert_session->buttonExists('Continue'); } + /** + * Asserts the number of times an update was staged. + * + * @param int $attempted_times + * The expected number of times an update was staged. + */ + private function assertUpdateStagedTimes(int $attempted_times): void { + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $beginner */ + $beginner = $this->container->get('package_manager.beginner'); + $this->assertCount($attempted_times, $beginner->getInvocationArguments()); + + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */ + $stager = $this->container->get('package_manager.stager'); + $this->assertCount($attempted_times, $stager->getInvocationArguments()); + + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $committer */ + $committer = $this->container->get('package_manager.committer'); + $this->assertEmpty($committer->getInvocationArguments()); + } + } diff --git a/tests/src/Kernel/UpdaterTest.php b/tests/src/Kernel/UpdaterTest.php index 1a42cb00d3..86d6bc494d 100644 --- a/tests/src/Kernel/UpdaterTest.php +++ b/tests/src/Kernel/UpdaterTest.php @@ -4,8 +4,6 @@ namespace Drupal\Tests\automatic_updates\Kernel; use Drupal\package_manager\PathLocator; use Drupal\Tests\user\Traits\UserCreationTrait; -use PhpTuf\ComposerStager\Domain\StagerInterface; -use Prophecy\Argument; /** * @coversDefaultClass \Drupal\automatic_updates\Updater @@ -71,27 +69,32 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { // When we call Updater::stage(), the stored project versions should be // read from state and passed to Composer Stager's Stager service, in the - // form of a Composer command. We set up a mock here to ensure that those - // calls are made as expected. - $stager = $this->prophesize(StagerInterface::class); + // form of a Composer command. This is done using package_manager_bypass's + // invocation recorder, rather than a regular mock, in order to test that + // the invocation recorder itself works. // The production dependencies should be updated first... - $command = [ + $expected_require_arguments = [ 'require', 'drupal/core-recommended:9.8.1', '--update-with-all-dependencies', ]; - $stager->stage($command, Argument::cetera())->shouldBeCalled(); // ...followed by the dev dependencies. - $command = [ + $expected_require_dev_arguments = [ 'require', 'drupal/core-dev:9.8.1', '--update-with-all-dependencies', '--dev', ]; - $stager->stage($command, Argument::cetera())->shouldBeCalled(); - - $this->container->set('package_manager.stager', $stager->reveal()); $this->container->get('automatic_updates.updater')->stage(); + + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */ + $stager = $this->container->get('package_manager.stager'); + [ + $actual_require_arguments, + $actual_require_dev_arguments, + ] = $stager->getInvocationArguments(); + $this->assertSame($expected_require_arguments, $actual_require_arguments[0]); + $this->assertSame($expected_require_dev_arguments, $actual_require_dev_arguments[0]); } /** -- GitLab