Skip to content
Snippets Groups Projects
Commit 52726e4c authored by Adam G-H's avatar Adam G-H
Browse files

Issue #3231999 by phenaproxima, tedbow: Ensure Composer is version 2.x

parent e34cd75f
No related branches found
No related tags found
1 merge request!34Issue #3231999: Ensure Composer is version 2.x
......@@ -85,7 +85,7 @@ services:
- { name: event_subscriber }
automatic_updates.composer_executable_validator:
class: Drupal\automatic_updates\Validator\ComposerExecutableValidator
arguments: ['@automatic_updates.exec_finder']
arguments: ['@automatic_updates.composer_runner']
tags:
- { name: event_subscriber }
automatic_updates.path_locator:
......
......@@ -5,30 +5,42 @@ namespace Drupal\automatic_updates\Validator;
use Drupal\automatic_updates\AutomaticUpdatesEvents;
use Drupal\automatic_updates\Event\UpdateEvent;
use Drupal\automatic_updates\Validation\ValidationResult;
use PhpTuf\ComposerStager\Exception\IOException;
use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\StringTranslation\StringTranslationTrait;
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.
* Validates that the Composer executable can be found in the correct version.
*/
class ComposerExecutableValidator implements EventSubscriberInterface {
class ComposerExecutableValidator implements EventSubscriberInterface, ProcessOutputCallbackInterface {
use StringTranslationTrait;
/**
* The Composer runner.
*
* @var \PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface
*/
protected $composer;
/**
* The executable finder service.
* The detected version of Composer.
*
* @var \PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface
* @var string
*/
protected $executableFinder;
protected $version;
/**
* Constructs a ComposerExecutableValidator object.
*
* @param \PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface $executable_finder
* The executable finder service.
* @param \PhpTuf\ComposerStager\Infrastructure\Process\Runner\ComposerRunnerInterface $composer
* The Composer runner.
*/
public function __construct(ExecutableFinderInterface $executable_finder) {
$this->executableFinder = $executable_finder;
public function __construct(ComposerRunnerInterface $composer) {
$this->composer = $composer;
}
/**
......@@ -39,13 +51,34 @@ class ComposerExecutableValidator implements EventSubscriberInterface {
*/
public function checkForComposerExecutable(UpdateEvent $event): void {
try {
$this->executableFinder->find('composer');
$this->composer->run(['--version'], $this);
}
catch (IOException $e) {
catch (ExceptionInterface $e) {
$error = ValidationResult::createError([
$e->getMessage(),
]);
$event->addValidationResult($error);
return;
}
if ($this->version) {
$major_version = ExtensionVersion::createFromVersionString($this->version)
->getMajorVersion();
if ($major_version < 2) {
$error = ValidationResult::createError([
$this->t('Composer 2 or later is required, but version @version was detected.', [
'@version' => $this->version,
]),
]);
$event->addValidationResult($error);
}
}
else {
$error = ValidationResult::createError([
$this->t('The Composer version could not be detected.'),
]);
$event->addValidationResult($error);
}
}
......@@ -58,4 +91,15 @@ class ComposerExecutableValidator implements EventSubscriberInterface {
];
}
/**
* {@inheritdoc}
*/
public function __invoke(string $type, string $buffer): void {
$matched = [];
// Search for a semantic version number and optional stability flag.
if (preg_match('/([0-9]+\.?){3}-?((alpha|beta|rc)[0-9]*)?/i', $buffer, $matched)) {
$this->version = $matched[0];
}
}
}
......@@ -3,10 +3,12 @@
namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation;
use Drupal\automatic_updates\Validation\ValidationResult;
use Drupal\automatic_updates\Validator\ComposerExecutableValidator;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use PhpTuf\ComposerStager\Exception\IOException;
use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface;
use Prophecy\Argument;
/**
* @covers \Drupal\automatic_updates\Validator\ComposerExecutableValidator
......@@ -46,4 +48,104 @@ class ComposerExecutableValidatorTest extends KernelTestBase {
$this->assertValidationResultsEqual([$error], $results);
}
/**
* 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\automatic_updates\Validation\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('automatic_updates.composer_runner', $runner->reveal());
// If the validator can't find a recognized, supported version of Composer,
// it should produce errors.
$actual_results = $this->container->get('automatic_updates.readiness_validation_manager')
->run()
->getResults();
$this->assertValidationResultsEqual($expected_results, $actual_results);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment