From 6d50846ac6420f4c00cd1ad386e4ecce87c54c35 Mon Sep 17 00:00:00 2001 From: Kunal Sachdev <57170-kunal.sachdev@users.noreply.drupalcode.org> Date: Tue, 27 Jun 2023 19:25:44 +0000 Subject: [PATCH] Issue #3354914 by kunal.sachdev, phenaproxima, Wim Leers, tedbow: Package Manager should have status report entry to inform user of Composer version being used --- package_manager/package_manager.install | 30 +++++++ package_manager/src/ComposerInspector.php | 2 +- .../Functional/ComposerRequirementTest.php | 55 +++++++++++++ .../src/Kernel/ComposerInspectorTest.php | 79 +++++++++++++------ 4 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 package_manager/tests/src/Functional/ComposerRequirementTest.php diff --git a/package_manager/package_manager.install b/package_manager/package_manager.install index 3d9fe0c606..5d59d0a8a3 100644 --- a/package_manager/package_manager.install +++ b/package_manager/package_manager.install @@ -7,7 +7,9 @@ declare(strict_types = 1); +use Drupal\package_manager\ComposerInspector; use Drupal\package_manager\Exception\StageFailureMarkerException; +use PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinderInterface; /** * Implements hook_requirements(). @@ -39,6 +41,34 @@ function package_manager_requirements(string $phase) { ]; } } + + if ($phase !== 'runtime') { + return $requirements; + } + /** @var \PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinderInterface $executable_finder */ + $executable_finder = \Drupal::service(ExecutableFinderInterface::class); + + // Report the Composer version in use, as well as its path. + $title = t('Composer version'); + try { + $requirements['package_manager_composer'] = [ + 'title' => $title, + 'description' => t('@version (<code>@path</code>)', [ + '@version' => \Drupal::service(ComposerInspector::class)->getVersion(), + '@path' => $executable_finder->find('composer'), + ]), + 'severity' => REQUIREMENT_INFO, + ]; + } + catch (\Throwable $e) { + $requirements['package_manager_composer'] = [ + 'title' => $title, + 'description' => t('Composer was not found. The error message was: @message', [ + '@message' => $e->getMessage(), + ]), + 'severity' => REQUIREMENT_ERROR, + ]; + } return $requirements; } diff --git a/package_manager/src/ComposerInspector.php b/package_manager/src/ComposerInspector.php index 1a4fc0725a..013d0ca7da 100644 --- a/package_manager/src/ComposerInspector.php +++ b/package_manager/src/ComposerInspector.php @@ -279,7 +279,7 @@ class ComposerInspector implements LoggerAwareInterface { * @throws \UnexpectedValueException * Thrown if the Composer version cannot be determined. */ - private function getVersion(): string { + public function getVersion(): string { $this->runner->run(['--format=json'], $this->processCallback->reset()); $data = $this->processCallback->parseJsonOutput(); if (isset($data['application']['name']) diff --git a/package_manager/tests/src/Functional/ComposerRequirementTest.php b/package_manager/tests/src/Functional/ComposerRequirementTest.php new file mode 100644 index 0000000000..8eb7729b7e --- /dev/null +++ b/package_manager/tests/src/Functional/ComposerRequirementTest.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\Tests\package_manager\Functional; + +use Drupal\package_manager\ComposerInspector; +use Drupal\Tests\BrowserTestBase; +use PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinderInterface; + +/** + * Tests that Package Manager shows the Composer version on the status report. + * + * @group package_manager + * @internal + */ +class ComposerRequirementTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['package_manager']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * Tests that Composer and file syncer info is listed on the status report. + */ + public function testComposerInfoShown(): void { + /** @var \PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinderInterface $executable_finder */ + $executable_finder = $this->container->get(ExecutableFinderInterface::class); + $composer_path = $executable_finder->find('composer'); + $composer_version = $this->container->get(ComposerInspector::class)->getVersion(); + + $account = $this->drupalCreateUser(['administer site configuration']); + $this->drupalLogin($account); + $this->drupalGet('/admin/reports/status'); + $assert_session = $this->assertSession(); + $assert_session->pageTextContains('Composer version'); + $assert_session->responseContains("$composer_version (<code>$composer_path</code>)"); + + // If the path to Composer is invalid, we should see the error message + // that gets raised when we try to get its version. + $this->config('package_manager.settings') + ->set('executables.composer', '/path/to/composer') + ->save(); + $this->getSession()->reload(); + $assert_session->statusCodeEquals(200); + $assert_session->pageTextContains('Composer was not found. The error message was: The command "\'/path/to/composer\' \'--format=json\'" failed.'); + } + +} diff --git a/package_manager/tests/src/Kernel/ComposerInspectorTest.php b/package_manager/tests/src/Kernel/ComposerInspectorTest.php index b956182b96..02cc502af1 100644 --- a/package_manager/tests/src/Kernel/ComposerInspectorTest.php +++ b/package_manager/tests/src/Kernel/ComposerInspectorTest.php @@ -20,6 +20,7 @@ use PhpTuf\ComposerStager\Domain\Service\Precondition\ComposerIsAvailableInterfa use PhpTuf\ComposerStager\Domain\Service\ProcessRunner\ComposerRunnerInterface; use PhpTuf\ComposerStager\Infrastructure\Factory\Path\PathFactory; use Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; /** * @coversDefaultClass \Drupal\package_manager\ComposerInspector @@ -240,29 +241,12 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase { * [null, "Unable to determine Composer version"] */ public function testVersionCheck(?string $reported_version, ?string $expected_message): void { - $runner = $this->prophesize(ComposerRunnerInterface::class); - - $pass_version_to_output_callback = function (array $arguments_passed_to_runner) use ($reported_version): void { - $command_output = Json::encode([ - 'application' => [ - 'name' => 'Composer', - 'version' => $reported_version, - ], - ]); - - /** @var \Drupal\package_manager\ProcessOutputCallback $callback */ - [, $callback] = $arguments_passed_to_runner; - $callback($callback::OUT, $command_output); - }; + $runner = $this->mockComposerRunner($reported_version); - // We expect the runner to be called with two arguments: an array whose - // first item is `--format=json`, and an output callback. The result of the - // version check is statically cached, so the runner should only be called - // once, even though we call validate() twice in this test. - $runner->run( - Argument::withEntry(0, '--format=json'), - Argument::type(ProcessOutputCallback::class) - )->will($pass_version_to_output_callback)->shouldBeCalledOnce(); + // The result of the version check is statically cached, so the runner + // should only be called once, even though we call validate() twice in this + // test. + $runner->getMethodProphecies('run')[0]->shouldBeCalledOnce(); // The runner should be called with `validate` as the first argument, but // it won't affect the outcome of this test. $runner->run(Argument::withEntry(0, 'validate')); @@ -294,6 +278,22 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase { $inspector->validate($project_root); } + /** + * @covers ::getVersion + * + * @testWith ["2.5.6"] + * [null] + */ + public function testGetVersion(?string $reported_version): void { + $this->container->set(ComposerRunnerInterface::class, $this->mockComposerRunner($reported_version)->reveal()); + + if (empty($reported_version)) { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Unable to determine Composer version'); + } + $this->assertSame($reported_version, $this->container->get(ComposerInspector::class)->getVersion()); + } + /** * @covers ::validate */ @@ -474,4 +474,39 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase { $this->assertSame($expected_value, $actual_value); } + /** + * Mocks the Composer runner service to return a particular version string. + * + * @param string|null $reported_version + * The version number that `composer --format=json` should return. + * + * @return \Prophecy\Prophecy\ObjectProphecy + * The configurator for the mocked Composer runner. + */ + private function mockComposerRunner(?string $reported_version): ObjectProphecy { + $runner = $this->prophesize(ComposerRunnerInterface::class); + + $pass_version_to_output_callback = function (array $arguments_passed_to_runner) use ($reported_version): void { + $command_output = Json::encode([ + 'application' => [ + 'name' => 'Composer', + 'version' => $reported_version, + ], + ]); + + /** @var \Drupal\package_manager\ProcessOutputCallback $callback */ + [, $callback] = $arguments_passed_to_runner; + $callback($callback::OUT, $command_output); + }; + + // We expect the runner to be called with two arguments: an array whose + // first item is `--format=json`, and an output callback. + $runner->run( + Argument::withEntry(0, '--format=json'), + Argument::type(ProcessOutputCallback::class) + )->will($pass_version_to_output_callback); + + return $runner; + } + } -- GitLab