Skip to content
Snippets Groups Projects
Commit 7933e07a authored by Ted Bowman's avatar Ted Bowman
Browse files

Issue #3276072 by tedbow: UpdateReleaseValidator doesn't handle legacy version numbers

parent b08578a2
No related branches found
No related tags found
1 merge request!289Issue #3276072: UpdateReleaseValidator doesn't handle legacy version numbers
......@@ -37,7 +37,7 @@ class ExtensionUpdater extends Stage {
foreach ($project_versions as $project_name => $version) {
$package = "drupal/$project_name";
$group = array_key_exists($package, $require_dev) ? 'dev' : 'production';
$package_versions[$group][$package] = static::convertToSemanticVersion($version);
$package_versions[$group][$package] = LegacyVersionUtility::convertToSemanticVersion($version);
}
// Ensure that package versions are available to pre-create event
......@@ -92,28 +92,4 @@ class ExtensionUpdater extends Stage {
}
}
/**
* Converts version numbers to semantic versions if needed.
*
* @param string $project_version
* The version number.
*
* @return string
* The version number, converted if needed.
*/
private static function convertToSemanticVersion(string $project_version): string {
if (stripos($project_version, '8.x-') === 0) {
$project_version = substr($project_version, 4);
$version_parts = explode('-', $project_version);
$project_version = $version_parts[0] . '.0';
if (count($version_parts) === 2) {
$project_version .= '-' . $version_parts[1];
}
return $project_version;
}
else {
return $project_version;
}
}
}
<?php
namespace Drupal\automatic_updates_extensions;
use Drupal\Core\Extension\ExtensionVersion;
/**
* A utility class for dealing with legacy version numbers.
*
* @internal
* This is an internal utility class that could change in any release and
* should not be used by external code.
*/
final class LegacyVersionUtility {
/**
* Converts a version number to a semantic version if needed.
*
* @param string $version
* The version number.
*
* @return string
* The version number, converted if needed.
*/
public static function convertToSemanticVersion(string $version): string {
if (self::isLegacyVersion($version)) {
$version = substr($version, 4);
$version_parts = explode('-', $version);
$version = $version_parts[0] . '.0';
if (count($version_parts) === 2) {
$version .= '-' . $version_parts[1];
}
return $version;
}
else {
return $version;
}
}
/**
* Converts a version number to a legacy version if needed and possible.
*
* @param string $version_string
* The version number.
*
* @return string
* The version number, converted if needed, or NULL if not possible. Only
* semantic version numbers that have patch level of 0 can be converted into
* legacy version numbers.
*/
public static function convertToLegacyVersion($version_string): ?string {
if (self::isLegacyVersion($version_string)) {
return $version_string;
}
$version = ExtensionVersion::createFromVersionString($version_string);
if ($extra = $version->getVersionExtra()) {
$version_string_without_extra = str_replace("-$extra", '', $version_string);
}
else {
$version_string_without_extra = $version_string;
}
[,, $patch] = explode('.', $version_string_without_extra);
// A semantic version can only be converted to legacy if it's patch level is
// '0'.
if ($patch !== '0') {
return NULL;
}
return '8.x-' . $version->getMajorVersion() . '.' . $version->getMinorVersion() . ($extra ? "-$extra" : '');
}
/**
* Determines if a version is legacy.
*
* @param string $version
* The version number.
*
* @return bool
* TRUE if the version is a legacy version number, otherwise FALSE.
*/
private static function isLegacyVersion(string $version): bool {
return stripos($version, '8.x-') === 0;
}
}
......@@ -4,6 +4,7 @@ namespace Drupal\automatic_updates_extensions\Validator;
use Drupal\automatic_updates\ProjectInfo;
use Drupal\automatic_updates_extensions\ExtensionUpdater;
use Drupal\automatic_updates_extensions\LegacyVersionUtility;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\package_manager\Event\PreCreateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -31,18 +32,38 @@ class UpdateReleaseValidator implements EventSubscriberInterface {
$all_versions = $stage->getPackageVersions();
$messages = [];
foreach (['production', 'dev'] as $package_type) {
foreach ($all_versions[$package_type] as $package_name => $version) {
foreach ($all_versions[$package_type] as $package_name => $sematic_version) {
$package_parts = explode('/', $package_name);
$project_name = $package_parts[1];
// If the version isn't in the list of installable releases, then it
// isn't secure and supported and the user should receive an error.
$releases = (new ProjectInfo($project_name))->getInstallableReleases();
if (empty($releases) || !array_key_exists($version, $releases)) {
$is_missing_version = FALSE;
if (empty($releases)) {
$is_missing_version = TRUE;
}
elseif (!array_key_exists($sematic_version, $releases)) {
$legacy_version = LegacyVersionUtility::convertToLegacyVersion($sematic_version);
if ($legacy_version) {
if (!array_key_exists($legacy_version, $releases)) {
// If we cannot find the version using semantic or legacy then the
// version is missing.
$is_missing_version = TRUE;
}
}
else {
// If we cannot convert the semantic version into a legacy version
// then the version is missing.
$is_missing_version = TRUE;
}
}
if ($is_missing_version) {
$messages[] = $this->t('Project @project_name to version @version', [
'@project_name' => $project_name,
'@version' => $version,
'@version' => $sematic_version,
]);
}
}
}
if ($messages) {
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Test legacy versions -->
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>AAA Update test</title>
<short_name>aaa_update_test</short_name>
<dc:creator>Drupal</dc:creator>
<supported_branches>8.x-2.,8.x-1.</supported_branches>
<project_status>published</project_status>
<link>http://example.com/project/aaa_update_test</link>
<terms>
<term><name>Projects</name><value>AAA Update test project</value></term>
</terms>
<releases>
<release>
<name>AAA Update test 8.x-3.0</name>
<version>8.x-3.0</version>
<tag>8.x-3.0</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-3-0-release</release_link>
<download_link>http://example.com/aaa_update_test-8-3-0.tar.gz</download_link>
<date>1584195300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-2.1</name>
<version>8.x-2.1</version>
<tag>8.x-2.1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-1.tar.gz</download_link>
<date>1581603300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-2.1-beta1</name>
<version>8.x-2.1-beta1</version>
<tag>8.x-2.1-beta1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-1-beta1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-1-beta1.tar.gz</download_link>
<date>1579011300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-2.1-alpha1</name>
<version>8.x-2.1-alpha1</version>
<tag>8.x-2.1-alpha1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-1-alpha1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-1-alpha1.tar.gz</download_link>
<date>1576419300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-2.0</name>
<version>8.x-2.0</version>
<tag>8.x-2.0</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-0-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-0.tar.gz</download_link>
<date>1573827300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-2.0-beta1</name>
<version>8.x-2.0-beta1</version>
<tag>8.x-2.0-beta1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-0-beta1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-0-beta1.tar.gz</download_link>
<date>1571235300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-2.0-alpha1</name>
<version>8.x-2.0-alpha1</version>
<tag>8.x-2.0-alpha1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-2-0-alpha1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-2-0-alpha1.tar.gz</download_link>
<date>1568643300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-1.1</name>
<version>8.x-1.1</version>
<tag>8.x-1.1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-1.tar.gz</download_link>
<date>1566051300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-1.1-beta1</name>
<version>8.x-1.1-beta1</version>
<tag>8.x-1.1-beta1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-1-beta1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-1-beta1.tar.gz</download_link>
<date>1563459300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-1.1-alpha1</name>
<version>8.x-1.1-alpha1</version>
<tag>8.x-1.1-alpha1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-1-alpha1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-1-alpha1.tar.gz</download_link>
<date>1560867300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-1.0</name>
<version>8.x-1.0</version>
<tag>8.x-1.0</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-0-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-0.tar.gz</download_link>
<date>1558275300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-1.0-beta1</name>
<version>8.x-1.0-beta1</version>
<tag>8.x-1.0-beta1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-0-beta1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-0-beta1.tar.gz</download_link>
<date>1555683300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>AAA Update test 8.x-1.0-alpha1</name>
<version>8.x-1.0-alpha1</version>
<tag>8.x-1.0-alpha1</tag>
<status>published</status>
<release_link>http://example.com/aaa_update_test-8-x-1-0-alpha1-release</release_link>
<download_link>http://example.com/aaa_update_test-8-x-1-0-alpha1.tar.gz</download_link>
<date>1553091300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
</releases>
</project>
......@@ -35,6 +35,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
'automatic_updates_extensions',
'block',
'semver_test',
'aaa_update_test',
];
/**
......@@ -47,13 +48,13 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
/**
* Data provider for testSuccessfulUpdate().
*
* @return bool[]
* @return array[]
* The test cases.
*/
public function providerMaintanceMode() {
public function providerSuccessfulUpdate() {
return [
'maintiance_mode_on' => [TRUE],
'maintiance_mode_off' => [FALSE],
'maintiance_mode_on, semver' => [TRUE, 'semver_test', '8.1.0', '8.1.1'],
'maintiance_mode_off, legacy' => [FALSE, 'aaa_update_test', '8.x-2.0', '8.x-2.1'],
];
}
......@@ -97,9 +98,16 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
/**
* Asserts the table shows the updates.
*
* @param string $expected_project_title
* The expected project title.
* @param string $expected_installed_version
* The expected installed version.
* @param string $expected_update_version
* The expected update version.
*/
private function assertTableShowsUpdates() {
$this->assertUpdateTableRow($this->assertSession(), 'Semver Test', '8.1.0', '8.1.1');
private function assertTableShowsUpdates(string $expected_project_title, string $expected_installed_version, string $expected_update_version): void {
$this->assertUpdateTableRow($this->assertSession(), $expected_project_title, $expected_installed_version, $expected_update_version);
}
/**
......@@ -107,11 +115,19 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
*
* @param bool $maintenance_mode_on
* Whether maintenance should be on at the beginning of the update.
* @param string $project_name
* The project name.
* @param string $installed_version
* The installed version.
* @param string $update_version
* The update version.
*
* @dataProvider providerMaintanceMode
* @dataProvider providerSuccessfulUpdate
*/
public function testSuccessfulUpdate(bool $maintenance_mode_on): void {
$this->setProjectInstalledVersion('8.1.0');
public function testSuccessfulUpdate(bool $maintenance_mode_on, string $project_name, string $installed_version, string $update_version): void {
$this->updateProject = $project_name;
$this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/$project_name.1.1.xml");
$this->setProjectInstalledVersion($installed_version);
$this->checkForUpdates();
$state = $this->container->get('state');
$state->set('system.maintenance_mode', $maintenance_mode_on);
......@@ -120,7 +136,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
// Navigate to the automatic updates form.
$this->drupalGet('/admin/reports/updates');
$this->clickLink('Update Extensions');
$this->assertTableShowsUpdates();
$this->assertTableShowsUpdates(
$project_name === 'semver_test' ? 'Semver Test' : 'AAA Update test',
$installed_version,
$update_version
);
$page->checkField('projects[' . $this->updateProject . ']');
$page->pressButton('Update');
$this->checkForMetaRefresh();
......@@ -154,7 +174,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$user = $this->createUser(['administer software updates']);
$this->drupalLogin($user);
$this->drupalGet('admin/reports/updates/automatic-update-extensions');
$this->assertTableShowsUpdates();
$this->assertTableShowsUpdates('Semver Test', '8.1.0', '8.1.1');
$assert->pageTextContains('Automatic Updates Form');
$assert->buttonExists('Update');
}
......@@ -189,7 +209,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->setProjectInstalledVersion('8.1.0');
$this->checkForUpdates();
$this->drupalGet('admin/reports/updates/automatic-update-extensions');
$this->assertTableShowsUpdates();
$this->assertTableShowsUpdates('Semver Test', '8.1.0', '8.1.1');
$message = t("You've not experienced Shakespeare until you have read him in the original Klingon.");
$error = ValidationResult::createError([$message]);
TestSubscriber1::setTestResult([$error], ReadinessCheckEvent::class);
......@@ -213,7 +233,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
// Navigate to the automatic updates form.
$this->drupalGet('/admin/reports/updates');
$this->clickLink('Update Extensions');
$this->assertTableShowsUpdates();
$this->assertTableShowsUpdates('Semver Test', '8.1.0', '8.1.1');
$assert->pageTextContains(static::$warningsExplanation);
$assert->pageTextNotContains(static::$errorsExplanation);
$assert->buttonExists('Update');
......
......@@ -2,6 +2,7 @@
namespace Drupal\Tests\automatic_updates_extensions\Kernel\Valdiator;
use Drupal\automatic_updates_extensions\LegacyVersionUtility;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\ValidationResult;
use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsKernelTestBase;
......@@ -16,6 +17,8 @@ class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBas
/**
* Tests updating to a release.
*
* @param string $project
* The project to update.
* @param string $installed_version
* The installed version of the project.
* @param string $update_version
......@@ -25,20 +28,20 @@ class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBas
*
* @dataProvider providerTestRelease
*/
public function testRelease(string $installed_version, string $update_version, bool $error_expected) {
$this->enableModules(['semver_test']);
$module_info = ['version' => $installed_version, 'project' => 'semver_test'];
public function testRelease(string $project, string $installed_version, string $update_version, bool $error_expected) {
$this->enableModules([$project]);
$module_info = ['version' => $installed_version, 'project' => $project];
$this->config('update_test.settings')
->set("system_info.semver_test", $module_info)
->set("system_info.$project", $module_info)
->save();
$this->setReleaseMetadataForProjects([
'semver_test' => __DIR__ . '/../../../fixtures/release-history/semver_test.1.1.xml',
$project => __DIR__ . "/../../../fixtures/release-history/$project.1.1.xml",
'drupal' => __DIR__ . '/../../../../../tests/fixtures/release-history/drupal.9.8.2.xml',
]);
if ($error_expected) {
$expected_results = [
ValidationResult::createError(
["Project semver_test to version $update_version"],
["Project $project to version " . LegacyVersionUtility::convertToSemanticVersion($update_version)],
t('Cannot update because the following project version is not in the list of installable releases.')
),
];
......@@ -47,7 +50,7 @@ class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBas
$expected_results = [];
}
$this->assertUpdaterResults(['semver_test' => $update_version], $expected_results, PreCreateEvent::class);
$this->assertUpdaterResults([$project => $update_version], $expected_results, PreCreateEvent::class);
}
/**
......@@ -58,8 +61,10 @@ class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBas
*/
public function providerTestRelease() {
return [
'supported update' => ['8.1.0', '8.1.1', FALSE],
'update to unsupported branch' => ['8.1.0', '8.2.0', TRUE],
'semver, supported update' => ['semver_test', '8.1.0', '8.1.1', FALSE],
'semver, update to unsupported branch' => ['semver_test', '8.1.0', '8.2.0', TRUE],
'legacy, supported update' => ['aaa_update_test', '8.x-2.0', '8.x-2.1', FALSE],
'legacy, update to unsupported branch' => ['aaa_update_test', '8.x-2.0', '8.x-3.0', TRUE],
];
}
......
<?php
namespace Drupal\Tests\automatic_updates_extensions\Unit;
use Drupal\automatic_updates_extensions\LegacyVersionUtility;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\automatic_updates_extensions\LegacyVersionUtility
*
* @group automatic_updates_extensions
*/
class LegacyVersionUtilityTest extends UnitTestCase {
/**
* @covers ::convertToSemanticVersion
*
* @param string $version_number
* The version number to covert.
* @param string $expected
* The expected result.
*
* @dataProvider providerConvertToSemanticVersion
*/
public function testConvertToSemanticVersion(string $version_number, string $expected) {
$this->assertSame($expected, LegacyVersionUtility::convertToSemanticVersion($version_number));
}
/**
* Data provider for testConvertToSemanticVersion()
*
* @return string[][]
* The test cases.
*/
public function providerConvertToSemanticVersion() {
return [
'8.x-1.2' => ['8.x-1.2', '1.2.0'],
'8.x-1.2-alpha1' => ['8.x-1.2-alpha1', '1.2.0-alpha1'],
'1.2.0' => ['1.2.0', '1.2.0'],
'1.2.0-alpha1' => ['1.2.0-alpha1', '1.2.0-alpha1'],
];
}
/**
* @covers ::convertToLegacyVersion
*
* @param string $version_number
* The version number to covert.
* @param string|null $expected
* The expected result.
*
* @dataProvider providerConvertToLegacyVersion
*/
public function testConvertToLegacyVersion(string $version_number, ?string $expected) {
$this->assertSame($expected, LegacyVersionUtility::convertToLegacyVersion($version_number));
}
/**
* Data provider for testConvertToLegacyVersion()
*
* @return array[]
* The test cases.
*/
public function providerConvertToLegacyVersion() {
return [
'1.2.0' => ['1.2.0', '8.x-1.2'],
'1.2.0-alpha1' => ['1.2.0-alpha1', '8.x-1.2-alpha1'],
'8.x-1.2' => ['8.x-1.2', '8.x-1.2'],
'8.x-1.2-alpha1' => ['8.x-1.2-alpha1', '8.x-1.2-alpha1'],
'1.2.3' => ['1.2.3', NULL],
'1.2.3-alpha1' => ['1.2.3-alpha1', NULL],
];
}
}
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