Issue #2313917 by tedbow, pwolanin, jhedstrom, Wim Leers, Mixologic, larowlan, Mile23, Gábor Hojtsy, xjm, Berdir: Core version key in module's .info.yml doesn't respect core semantic versioning
							parent
							
								
									45ff41df49
								
							
						
					
					
						commit
						f0e313a422
					
				| 
						 | 
				
			
			@ -58,8 +58,7 @@ function update_check_incompatibility($name, $type = 'module') {
 | 
			
		|||
    $file = $themes[$name];
 | 
			
		||||
  }
 | 
			
		||||
  if (!isset($file)
 | 
			
		||||
      || !isset($file->info['core'])
 | 
			
		||||
      || $file->info['core'] != \Drupal::CORE_COMPATIBILITY
 | 
			
		||||
      || $file->info['core_incompatible']
 | 
			
		||||
      || version_compare(phpversion(), $file->info['php']) < 0) {
 | 
			
		||||
    return TRUE;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Composer\Semver\Semver;
 | 
			
		||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
 | 
			
		||||
use Drupal\Core\Serialization\Yaml;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +11,34 @@ use Drupal\Core\Serialization\Yaml;
 | 
			
		|||
 */
 | 
			
		||||
class InfoParserDynamic implements InfoParserInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The earliest Drupal version that supports the 'core_version_requirement'.
 | 
			
		||||
   */
 | 
			
		||||
  const FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION = '8.7.7';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if a version satisfies the given constraints.
 | 
			
		||||
   *
 | 
			
		||||
   * This method uses \Composer\Semver\Semver::satisfies() but returns FALSE if
 | 
			
		||||
   * the version or constraints are not valid instead of throwing an exception.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $version
 | 
			
		||||
   *   The version.
 | 
			
		||||
   * @param string $constraints
 | 
			
		||||
   *   The constraints.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the version satisfies the constraints, otherwise FALSE.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function satisfies($version, $constraints) {
 | 
			
		||||
    try {
 | 
			
		||||
      return Semver::satisfies($version, $constraints);
 | 
			
		||||
    }
 | 
			
		||||
    catch (\UnexpectedValueException $exception) {
 | 
			
		||||
      return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +57,45 @@ class InfoParserDynamic implements InfoParserInterface {
 | 
			
		|||
      if (!empty($missing_keys)) {
 | 
			
		||||
        throw new InfoParserException('Missing required keys (' . implode(', ', $missing_keys) . ') in ' . $filename);
 | 
			
		||||
      }
 | 
			
		||||
      if ($parsed_info['type'] === 'profile' && isset($parsed_info['core_version_requirement'])) {
 | 
			
		||||
        // @todo Support the 'core_version_requirement' key in profiles in
 | 
			
		||||
        //   https://www.drupal.org/node/3070401.
 | 
			
		||||
        throw new InfoParserException("The 'core_version_requirement' key is not supported in profiles in $filename");
 | 
			
		||||
      }
 | 
			
		||||
      if (!isset($parsed_info['core']) && !isset($parsed_info['core_version_requirement'])) {
 | 
			
		||||
        throw new InfoParserException("The 'core' or the 'core_version_requirement' key must be present in " . $filename);
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($parsed_info['core']) && !preg_match("/^\d\.x$/", $parsed_info['core'])) {
 | 
			
		||||
        throw new InfoParserException("Invalid 'core' value \"{$parsed_info['core']}\" in " . $filename);
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($parsed_info['core_version_requirement'])) {
 | 
			
		||||
        $supports_pre_core_version_requirement_version = static::isConstraintSatisfiedByPreviousVersion($parsed_info['core_version_requirement'], static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION);
 | 
			
		||||
        // If the 'core_version_requirement' constraint does not satisfy any
 | 
			
		||||
        // Drupal 8 versions before 8.7.7 then 'core' cannot be set or it will
 | 
			
		||||
        // effectively support all versions of Drupal 8 because
 | 
			
		||||
        // 'core_version_requirement' will be ignored in previous versions.
 | 
			
		||||
        if (!$supports_pre_core_version_requirement_version && isset($parsed_info['core'])) {
 | 
			
		||||
          throw new InfoParserException("The 'core_version_requirement' constraint ({$parsed_info['core_version_requirement']}) requires the 'core' key not be set in " . $filename);
 | 
			
		||||
        }
 | 
			
		||||
        // 'core_version_requirement' can not be used to specify Drupal 8
 | 
			
		||||
        // versions before 8.7.7 because these versions do not use the
 | 
			
		||||
        // 'core_version_requirement' key. Do not throw the exception if the
 | 
			
		||||
        // constraint also is satisfied by 8.0.0-alpha1 to allow constraints
 | 
			
		||||
        // such as '^8' or '^8 || ^9'.
 | 
			
		||||
        if ($supports_pre_core_version_requirement_version && !static::satisfies('8.0.0-alpha1', $parsed_info['core_version_requirement'])) {
 | 
			
		||||
          throw new InfoParserException("The 'core_version_requirement' can not be used to specify compatibility for a specific version before " . static::FIRST_CORE_VERSION_REQUIREMENT_SUPPORTED_VERSION . " in $filename");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Determine if the extension is compatible with the current version of
 | 
			
		||||
      // Drupal core.
 | 
			
		||||
      try {
 | 
			
		||||
        $core_version_constraint = isset($parsed_info['core_version_requirement']) ? $parsed_info['core_version_requirement'] : $parsed_info['core'];
 | 
			
		||||
        $parsed_info['core_incompatible'] = !static::satisfies(\Drupal::VERSION, $core_version_constraint);
 | 
			
		||||
      }
 | 
			
		||||
      catch (\UnexpectedValueException $exception) {
 | 
			
		||||
        $parsed_info['core_incompatible'] = TRUE;
 | 
			
		||||
      }
 | 
			
		||||
      if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
 | 
			
		||||
        $parsed_info['version'] = \Drupal::VERSION;
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +128,80 @@ class InfoParserDynamic implements InfoParserInterface {
 | 
			
		|||
   *   An array of required keys.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getRequiredKeys() {
 | 
			
		||||
    return ['type', 'core', 'name'];
 | 
			
		||||
    return ['type', 'name'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if a constraint is satisfied by earlier versions of Drupal 8.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $constraint
 | 
			
		||||
   *   A core semantic version constraint.
 | 
			
		||||
   * @param string $version
 | 
			
		||||
   *   A core version.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the constraint is satisfied by a core version prior to the
 | 
			
		||||
   *   provided version.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function isConstraintSatisfiedByPreviousVersion($constraint, $version) {
 | 
			
		||||
    static $evaluated_constraints = [];
 | 
			
		||||
    // Any particular constraint and version combination only needs to be
 | 
			
		||||
    // evaluated once.
 | 
			
		||||
    if (!isset($evaluated_constraints[$constraint][$version])) {
 | 
			
		||||
      $evaluated_constraints[$constraint][$version] = FALSE;
 | 
			
		||||
      foreach (static::getAllPreviousCoreVersions($version) as $previous_version) {
 | 
			
		||||
        if (static::satisfies($previous_version, $constraint)) {
 | 
			
		||||
          $evaluated_constraints[$constraint][$version] = TRUE;
 | 
			
		||||
          // The constraint only has to satisfy one previous version so break
 | 
			
		||||
          // when the first one is found.
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $evaluated_constraints[$constraint][$version];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets all the versions of Drupal 8 before a specific version.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $version
 | 
			
		||||
   *   The version to get versions before.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   All of the applicable Drupal 8 releases.
 | 
			
		||||
   */
 | 
			
		||||
  protected static function getAllPreviousCoreVersions($version) {
 | 
			
		||||
    static $versions_lists = [];
 | 
			
		||||
    // Check if list of previous versions for the specified version has already
 | 
			
		||||
    // been created.
 | 
			
		||||
    if (empty($versions_lists[$version])) {
 | 
			
		||||
      // Loop through all minor versions including 8.7.
 | 
			
		||||
      foreach (range(0, 7) as $minor) {
 | 
			
		||||
        // The largest patch number in a release was 17 in 8.6.17. Use 27 to
 | 
			
		||||
        // leave room for future security releases.
 | 
			
		||||
        foreach (range(0, 27) as $patch) {
 | 
			
		||||
          $patch_version = "8.$minor.$patch";
 | 
			
		||||
          if ($patch_version === $version) {
 | 
			
		||||
            // Reverse the order of the versions so that they will be evaluated
 | 
			
		||||
            // from the most recent versions first.
 | 
			
		||||
            $versions_lists[$version] = array_reverse($versions_lists[$version]);
 | 
			
		||||
            return $versions_lists[$version];
 | 
			
		||||
          }
 | 
			
		||||
          if ($patch === 0) {
 | 
			
		||||
            // If this is a '0' patch release like '8.1.0' first create the
 | 
			
		||||
            // pre-release versions such as '8.1.0-alpha1' and '8.1.0-rc1'.
 | 
			
		||||
            foreach (['alpha', 'beta', 'rc'] as $prerelease) {
 | 
			
		||||
              // The largest prerelease number was  in 8.0.0-beta16.
 | 
			
		||||
              foreach (range(0, 16) as $prerelease_number) {
 | 
			
		||||
                $versions_lists[$version][] = "$patch_version-$prerelease$prerelease_number";
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          $versions_lists[$version][] = $patch_version;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $versions_lists[$version];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,11 +81,17 @@ class ModuleInstaller implements ModuleInstallerInterface {
 | 
			
		|||
   */
 | 
			
		||||
  public function install(array $module_list, $enable_dependencies = TRUE) {
 | 
			
		||||
    $extension_config = \Drupal::configFactory()->getEditable('core.extension');
 | 
			
		||||
    // Get all module data so we can find dependencies and sort and find the
 | 
			
		||||
    // core requirements. The module list needs to be reset so that it can
 | 
			
		||||
    // re-scan and include any new modules that may have been added directly
 | 
			
		||||
    // into the filesystem.
 | 
			
		||||
    $module_data = \Drupal::service('extension.list.module')->reset()->getList();
 | 
			
		||||
    foreach ($module_list as $module) {
 | 
			
		||||
      if (!empty($module_data[$module]->info['core_incompatible'])) {
 | 
			
		||||
        throw new MissingDependencyException("Unable to install modules: module '$module' is incompatible with this version of Drupal core.");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($enable_dependencies) {
 | 
			
		||||
      // Get all module data so we can find dependencies and sort.
 | 
			
		||||
      // The module list needs to be reset so that it can re-scan and include
 | 
			
		||||
      // any new modules that may have been added directly into the filesystem.
 | 
			
		||||
      $module_data = \Drupal::service('extension.list.module')->reset()->getList();
 | 
			
		||||
      $module_list = $module_list ? array_combine($module_list, $module_list) : [];
 | 
			
		||||
      if ($missing_modules = array_diff_key($module_list, $module_data)) {
 | 
			
		||||
        // One or more of the given modules doesn't exist.
 | 
			
		||||
| 
						 | 
				
			
			@ -110,6 +116,9 @@ class ModuleInstaller implements ModuleInstallerInterface {
 | 
			
		|||
 | 
			
		||||
          // Skip already installed modules.
 | 
			
		||||
          if (!isset($module_list[$dependency]) && !isset($installed_modules[$dependency])) {
 | 
			
		||||
            if ($module_data[$dependency]->info['core_incompatible']) {
 | 
			
		||||
              throw new MissingDependencyException("Unable to install modules: module '$module'. Its dependency module '$dependency' is incompatible with this version of Drupal core.");
 | 
			
		||||
            }
 | 
			
		||||
            $module_list[$dependency] = $dependency;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -222,8 +222,6 @@ class SystemController extends ControllerBase {
 | 
			
		|||
      }
 | 
			
		||||
 | 
			
		||||
      if (empty($theme->status)) {
 | 
			
		||||
        // Ensure this theme is compatible with this version of core.
 | 
			
		||||
        $theme->incompatible_core = !isset($theme->info['core']) || ($theme->info['core'] != \DRUPAL::CORE_COMPATIBILITY);
 | 
			
		||||
        // Require the 'content' region to make sure the main page
 | 
			
		||||
        // content has a common place in all themes.
 | 
			
		||||
        $theme->incompatible_region = !isset($theme->info['regions']['content']);
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +232,7 @@ class SystemController extends ControllerBase {
 | 
			
		|||
        $theme->incompatible_engine = isset($theme->info['engine']) && !isset($theme->owner);
 | 
			
		||||
      }
 | 
			
		||||
      $theme->operations = [];
 | 
			
		||||
      if (!empty($theme->status) || !$theme->incompatible_core && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine) {
 | 
			
		||||
      if (!empty($theme->status) || !$theme->info['core_incompatible'] && !$theme->incompatible_php && !$theme->incompatible_base && !$theme->incompatible_engine) {
 | 
			
		||||
        // Create the operations links.
 | 
			
		||||
        $query['theme'] = $theme->getName();
 | 
			
		||||
        if ($this->themeAccess->checkAccess($theme->getName())) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -294,10 +294,14 @@ class ModulesListForm extends FormBase {
 | 
			
		|||
    $reasons = [];
 | 
			
		||||
 | 
			
		||||
    // Check the core compatibility.
 | 
			
		||||
    if ($module->info['core'] != \Drupal::CORE_COMPATIBILITY) {
 | 
			
		||||
    if ($module->info['core_incompatible']) {
 | 
			
		||||
      $compatible = FALSE;
 | 
			
		||||
      $reasons[] = $this->t('This version is not compatible with Drupal @core_version and should be replaced.', [
 | 
			
		||||
        '@core_version' => \Drupal::CORE_COMPATIBILITY,
 | 
			
		||||
        '@core_version' => \Drupal::VERSION,
 | 
			
		||||
      ]);
 | 
			
		||||
      $row['#requires']['core'] = $this->t('Drupal Core (@core_requirement) (<span class="admin-missing">incompatible with</span> version @core_version)', [
 | 
			
		||||
        '@core_requirement' => isset($module->info['core_version_requirement']) ? $module->info['core_version_requirement'] : $module->info['core'],
 | 
			
		||||
        '@core_version' => \Drupal::VERSION,
 | 
			
		||||
      ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -341,7 +345,7 @@ class ModulesListForm extends FormBase {
 | 
			
		|||
        }
 | 
			
		||||
        // Disable the checkbox if the dependency is incompatible with this
 | 
			
		||||
        // version of Drupal core.
 | 
			
		||||
        elseif ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) {
 | 
			
		||||
        elseif ($modules[$dependency]->info['core_incompatible']) {
 | 
			
		||||
          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', [
 | 
			
		||||
            '@module' => $name,
 | 
			
		||||
          ]);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -293,8 +293,8 @@ function template_preprocess_system_themes_page(&$variables) {
 | 
			
		|||
 | 
			
		||||
      // Make sure to provide feedback on compatibility.
 | 
			
		||||
      $current_theme['incompatible'] = '';
 | 
			
		||||
      if (!empty($theme->incompatible_core)) {
 | 
			
		||||
        $current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains the correct 'core' value.", ['@core_version' => \Drupal::CORE_COMPATIBILITY]);
 | 
			
		||||
      if (!empty($theme->info['core_incompatible'])) {
 | 
			
		||||
        $current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains a compatible 'core' or 'core_version_requirement' value.", ['@core_version' => \Drupal::VERSION]);
 | 
			
		||||
      }
 | 
			
		||||
      elseif (!empty($theme->incompatible_region)) {
 | 
			
		||||
        $current_theme['incompatible'] = t("This theme is missing a 'content' region.");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								core/modules/system/tests/fixtures/update/drupal-8.update-test-semver-update-n-enabled.php
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										38
									
								
								core/modules/system/tests/fixtures/update/drupal-8.update-test-semver-update-n-enabled.php
								
								
								
									vendored
								
								
									Normal file
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Database to mimic the installation of the update_test_semver_update_n module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
 | 
			
		||||
$connection = Database::getConnection();
 | 
			
		||||
 | 
			
		||||
// Set the schema version.
 | 
			
		||||
$connection->merge('key_value')
 | 
			
		||||
  ->condition('collection', 'system.schema')
 | 
			
		||||
  ->condition('name', 'update_test_semver_update_n')
 | 
			
		||||
  ->fields([
 | 
			
		||||
    'collection' => 'system.schema',
 | 
			
		||||
    'name' => 'update_test_semver_update_n',
 | 
			
		||||
    'value' => 'i:8000;',
 | 
			
		||||
  ])
 | 
			
		||||
  ->execute();
 | 
			
		||||
 | 
			
		||||
// Update core.extension.
 | 
			
		||||
$extensions = $connection->select('config')
 | 
			
		||||
  ->fields('config', ['data'])
 | 
			
		||||
  ->condition('collection', '')
 | 
			
		||||
  ->condition('name', 'core.extension')
 | 
			
		||||
  ->execute()
 | 
			
		||||
  ->fetchField();
 | 
			
		||||
$extensions = unserialize($extensions);
 | 
			
		||||
$extensions['module']['update_test_semver_update_n'] = 8000;
 | 
			
		||||
$connection->update('config')
 | 
			
		||||
  ->fields([
 | 
			
		||||
    'data' => serialize($extensions),
 | 
			
		||||
  ])
 | 
			
		||||
  ->condition('collection', '')
 | 
			
		||||
  ->condition('name', 'core.extension')
 | 
			
		||||
  ->execute();
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
name: 'System core incompatible semver test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for testing core incompatible semver.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: 1.0.0
 | 
			
		||||
core_version_requirement: ^7
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
name: 'System core ^8 version test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for testing core using semver.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: 1.0.0
 | 
			
		||||
core_version_requirement: ^8
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
name: 'System incompatible core 1.x version test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for testing system core incompatibility.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: 1.0.0
 | 
			
		||||
core: 1.x
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +71,7 @@ function system_test_system_info_alter(&$info, Extension $file, $type) {
 | 
			
		|||
    'system_incompatible_core_version_dependencies_test',
 | 
			
		||||
    'system_incompatible_module_version_test',
 | 
			
		||||
    'system_incompatible_core_version_test',
 | 
			
		||||
    'system_incompatible_core_version_test_1x',
 | 
			
		||||
  ])) {
 | 
			
		||||
    $info['hidden'] = FALSE;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
name: 'Update test hook_update_n semver'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Support module for update testing with core semver value.'
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
core_version_requirement: ^8
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Update hooks for the update_test_semver_update_n module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Update 8001.
 | 
			
		||||
 */
 | 
			
		||||
function update_test_semver_update_n_update_8001() {
 | 
			
		||||
  \Drupal::state()->set('update_test_semver_update_n_update_8001', 'Yes, I was run. Thanks for testing!');
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ BROKEN;
 | 
			
		|||
 | 
			
		||||
    // Confirm that the error message is shown.
 | 
			
		||||
    $this->assertSession()
 | 
			
		||||
      ->pageTextContains('Modules could not be listed due to an error: Missing required keys (core) in ' . $path . '/broken.info.yml');
 | 
			
		||||
      ->pageTextContains("The 'core' or the 'core_version_requirement' key must be present in " . $path . '/broken.info.yml');
 | 
			
		||||
 | 
			
		||||
    // Check that the module filter text box is available.
 | 
			
		||||
    $this->assertTrue($this->xpath('//input[@name="text"]'));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,6 +102,29 @@ class DependencyTest extends ModuleTestBase {
 | 
			
		|||
    $this->assert(count($checkbox) == 1, 'Checkbox for the module is disabled.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests enabling modules with different core version specifications.
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreCompatibility() {
 | 
			
		||||
    $assert_session = $this->assertSession();
 | 
			
		||||
 | 
			
		||||
    // Test incompatible 'core_version_requirement'.
 | 
			
		||||
    $this->drupalGet('admin/modules');
 | 
			
		||||
    $assert_session->fieldDisabled('modules[system_incompatible_core_version_test_1x][enable]');
 | 
			
		||||
    $assert_session->fieldDisabled('modules[system_core_incompatible_semver_test][enable]');
 | 
			
		||||
 | 
			
		||||
    // Test compatible 'core_version_requirement' and compatible 'core'.
 | 
			
		||||
    $this->drupalGet('admin/modules');
 | 
			
		||||
    $assert_session->fieldEnabled('modules[common_test][enable]');
 | 
			
		||||
    $assert_session->fieldEnabled('modules[system_core_semver_test][enable]');
 | 
			
		||||
 | 
			
		||||
    // Ensure the modules can actually be installed.
 | 
			
		||||
    $edit['modules[common_test][enable]'] = 'common_test';
 | 
			
		||||
    $edit['modules[system_core_semver_test][enable]'] = 'system_core_semver_test';
 | 
			
		||||
    $this->drupalPostForm('admin/modules', $edit, t('Install'));
 | 
			
		||||
    $this->assertModules(['common_test', 'system_core_semver_test'], TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests enabling a module that depends on a module which fails hook_requirements().
 | 
			
		||||
   */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -367,8 +367,11 @@ class ThemeTest extends BrowserTestBase {
 | 
			
		|||
    $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', ['@base_theme' => 'not_real_test_basetheme']));
 | 
			
		||||
    $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', ['@base_theme' => 'test_invalid_basetheme']));
 | 
			
		||||
    $this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', ['@theme_engine' => 'not_real_engine']));
 | 
			
		||||
    // Check for the error text of a theme with the wrong core version.
 | 
			
		||||
    $this->assertText("This theme is not compatible with Drupal 8.x. Check that the .info.yml file contains the correct 'core' value.");
 | 
			
		||||
    // Check for the error text of a theme with the wrong core version
 | 
			
		||||
    // using 7.x and ^7.
 | 
			
		||||
    $incompatible_core_message = 'This theme is not compatible with Drupal ' . \Drupal::VERSION . ". Check that the .info.yml file contains a compatible 'core' or 'core_version_requirement' value.";
 | 
			
		||||
    $this->assertThemeIncompatibleText('Theme test with invalid core version', $incompatible_core_message);
 | 
			
		||||
    $this->assertThemeIncompatibleText('Theme test with invalid semver core version', $incompatible_core_message);
 | 
			
		||||
    // Check for the error text of a theme without a content region.
 | 
			
		||||
    $this->assertText("This theme is missing a 'content' region.");
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -437,24 +440,28 @@ class ThemeTest extends BrowserTestBase {
 | 
			
		|||
   * Tests installing a theme and setting it as default.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInstallAndSetAsDefault() {
 | 
			
		||||
    $this->drupalGet('admin/appearance');
 | 
			
		||||
    // Bartik is uninstalled in the test profile and has the third "Install and
 | 
			
		||||
    // set as default" link.
 | 
			
		||||
    $this->clickLink(t('Install and set as default'), 2);
 | 
			
		||||
    // Test the confirmation message.
 | 
			
		||||
    $this->assertText('Bartik is now the default theme.');
 | 
			
		||||
    // Make sure Bartik is now set as the default theme in config.
 | 
			
		||||
    $this->assertEqual($this->config('system.theme')->get('default'), 'bartik');
 | 
			
		||||
    $themes = [
 | 
			
		||||
      'bartik' => 'Bartik',
 | 
			
		||||
      'test_core_semver' => 'Theme test with semver core version',
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($themes as $theme_machine_name => $theme_name) {
 | 
			
		||||
      $this->drupalGet('admin/appearance');
 | 
			
		||||
      $this->getSession()->getPage()->findLink("Install $theme_name as default theme")->click();
 | 
			
		||||
      // Test the confirmation message.
 | 
			
		||||
      $this->assertText("$theme_name is now the default theme.");
 | 
			
		||||
      // Make sure the theme is now set as the default theme in config.
 | 
			
		||||
      $this->assertEqual($this->config('system.theme')->get('default'), $theme_machine_name);
 | 
			
		||||
 | 
			
		||||
    // This checks for a regression. See https://www.drupal.org/node/2498691.
 | 
			
		||||
    $this->assertNoText('The bartik theme was not found.');
 | 
			
		||||
      // This checks for a regression. See https://www.drupal.org/node/2498691.
 | 
			
		||||
      $this->assertNoText("The $theme_machine_name theme was not found.");
 | 
			
		||||
 | 
			
		||||
    $themes = \Drupal::service('theme_handler')->rebuildThemeData();
 | 
			
		||||
    $version = $themes['bartik']->info['version'];
 | 
			
		||||
      $themes = \Drupal::service('theme_handler')->rebuildThemeData();
 | 
			
		||||
      $version = $themes[$theme_machine_name]->info['version'];
 | 
			
		||||
 | 
			
		||||
    // Confirm Bartik is indicated as the default theme.
 | 
			
		||||
    $out = $this->getSession()->getPage()->getContent();
 | 
			
		||||
    $this->assertTrue((bool) preg_match('/Bartik ' . preg_quote($version) . '\s{2,}\(default theme\)/', $out));
 | 
			
		||||
      // Confirm the theme is indicated as the default theme.
 | 
			
		||||
      $out = $this->getSession()->getPage()->getContent();
 | 
			
		||||
      $this->assertTrue((bool) preg_match("/$theme_name " . preg_quote($version) . '\s{2,}\(default theme\)/', $out));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			@ -470,4 +477,16 @@ class ThemeTest extends BrowserTestBase {
 | 
			
		|||
    $this->assertText('The configuration options have been saved.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Asserts that expected incompatibility text is displayed for a theme.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $theme_name
 | 
			
		||||
   *   Theme name to select element on page. This can be a partial name.
 | 
			
		||||
   * @param string $expected_text
 | 
			
		||||
   *   The expected incompatibility text.
 | 
			
		||||
   */
 | 
			
		||||
  private function assertThemeIncompatibleText($theme_name, $expected_text) {
 | 
			
		||||
    $this->assertSession()->elementExists('css', ".theme-info:contains(\"$theme_name\") .incompatible:contains(\"$expected_text\")");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
name: 'Theme test with semver core version'
 | 
			
		||||
type: theme
 | 
			
		||||
description: 'Test theme which has semver core version.'
 | 
			
		||||
version: VERSION
 | 
			
		||||
core_version_requirement: ^8 || ^9
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
name: 'Theme test with invalid semver core version'
 | 
			
		||||
type: theme
 | 
			
		||||
description: 'Test theme which has an invalid semver core version.'
 | 
			
		||||
version: VERSION
 | 
			
		||||
core_version_requirement: ^7
 | 
			
		||||
| 
						 | 
				
			
			@ -699,7 +699,7 @@ function update_verify_update_archive($project, $archive_file, $directory) {
 | 
			
		|||
    $info = \Drupal::service('info_parser')->parse($file->uri);
 | 
			
		||||
 | 
			
		||||
    // If the module or theme is incompatible with Drupal core, set an error.
 | 
			
		||||
    if (empty($info['core']) || $info['core'] != \Drupal::CORE_COMPATIBILITY) {
 | 
			
		||||
    if ($info['core_incompatible']) {
 | 
			
		||||
      $incompatible[] = !empty($info['name']) ? $info['name'] : t('Unknown');
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
| 
						 | 
				
			
			@ -717,7 +717,7 @@ function update_verify_update_archive($project, $archive_file, $directory) {
 | 
			
		|||
      '%archive_file contains a version of %names that is not compatible with Drupal @version.',
 | 
			
		||||
      '%archive_file contains versions of modules or themes that are not compatible with Drupal @version: %names',
 | 
			
		||||
      [
 | 
			
		||||
        '@version' => \Drupal::CORE_COMPATIBILITY,
 | 
			
		||||
        '@version' => \Drupal::VERSION,
 | 
			
		||||
        '%archive_file' => $file_system->basename($archive_file),
 | 
			
		||||
        '%names' => implode(', ', $incompatible),
 | 
			
		||||
      ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ class UpdatePathTestBaseTest extends UpdatePathTestBase {
 | 
			
		|||
    $this->databaseDumpFiles = [
 | 
			
		||||
      __DIR__ . '/../../../../modules/system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
 | 
			
		||||
      __DIR__ . '/../../../../modules/system/tests/fixtures/update/drupal-8.update-test-schema-enabled.php',
 | 
			
		||||
      __DIR__ . '/../../../../modules/system/tests/fixtures/update/drupal-8.update-test-semver-update-n-enabled.php',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -99,8 +100,11 @@ class UpdatePathTestBaseTest extends UpdatePathTestBase {
 | 
			
		|||
 | 
			
		||||
    // Ensure schema has changed.
 | 
			
		||||
    $this->assertEqual(drupal_get_installed_schema_version('update_test_schema', TRUE), 8001);
 | 
			
		||||
    $this->assertEqual(drupal_get_installed_schema_version('update_test_semver_update_n', TRUE), 8001);
 | 
			
		||||
    // Ensure the index was added for column a.
 | 
			
		||||
    $this->assertTrue($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.');
 | 
			
		||||
    // Ensure update_test_semver_update_n_update_8001 was run.
 | 
			
		||||
    $this->assertEquals(\Drupal::state()->get('update_test_semver_update_n_update_8001'), 'Yes, I was run. Thanks for testing!');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
namespace Drupal\KernelTests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Database\Database;
 | 
			
		||||
use Drupal\Core\Extension\MissingDependencyException;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,4 +86,60 @@ class ModuleInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->assertTrue($module_installer->install(['module_test']));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests install with a module with an invalid core version constraint.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerTestInvalidCoreInstall
 | 
			
		||||
   * @covers ::install
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidCoreInstall($module_name, $install_dependencies) {
 | 
			
		||||
    $this->expectException(MissingDependencyException::class);
 | 
			
		||||
    $this->expectExceptionMessage("Unable to install modules: module '$module_name' is incompatible with this version of Drupal core.");
 | 
			
		||||
    $this->container->get('module_installer')->install([$module_name], $install_dependencies);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Dataprovider for testInvalidCoreInstall().
 | 
			
		||||
   */
 | 
			
		||||
  public function providerTestInvalidCoreInstall() {
 | 
			
		||||
    return [
 | 
			
		||||
      'no dependencies system_incompatible_core_version_test_1x' => [
 | 
			
		||||
        'system_incompatible_core_version_test_1x',
 | 
			
		||||
        FALSE,
 | 
			
		||||
      ],
 | 
			
		||||
      'install_dependencies system_incompatible_core_version_test_1x' => [
 | 
			
		||||
        'system_incompatible_core_version_test_1x',
 | 
			
		||||
        TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      'no dependencies system_core_incompatible_semver_test' => [
 | 
			
		||||
        'system_core_incompatible_semver_test',
 | 
			
		||||
        FALSE,
 | 
			
		||||
      ],
 | 
			
		||||
      'install_dependencies system_core_incompatible_semver_test' => [
 | 
			
		||||
        'system_core_incompatible_semver_test',
 | 
			
		||||
        TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests install with a dependency with an invalid core version constraint.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::install
 | 
			
		||||
   */
 | 
			
		||||
  public function testDependencyInvalidCoreInstall() {
 | 
			
		||||
    $this->expectException(MissingDependencyException::class);
 | 
			
		||||
    $this->expectExceptionMessage("Unable to install modules: module 'system_incompatible_core_version_dependencies_test'. Its dependency module 'system_incompatible_core_version_test' is incompatible with this version of Drupal core.");
 | 
			
		||||
    $this->container->get('module_installer')->install(['system_incompatible_core_version_dependencies_test']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests no dependencies install with a dependency with invalid core.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::install
 | 
			
		||||
   */
 | 
			
		||||
  public function testDependencyInvalidCoreInstallNoDependencies() {
 | 
			
		||||
    $this->assertTrue($this->container->get('module_installer')->install(['system_incompatible_core_version_dependencies_test'], FALSE));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
namespace Drupal\Tests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\InfoParser;
 | 
			
		||||
use Drupal\Core\Extension\InfoParserException;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
use org\bovigo\vfs\vfsStream;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,10 +99,211 @@ MISSINGKEYS;
 | 
			
		|||
    ]);
 | 
			
		||||
    $filename = vfsStream::url('modules/fixtures/missing_keys.info.txt');
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage('Missing required keys (type, core, name) in vfs://modules/fixtures/missing_keys.info.txt');
 | 
			
		||||
    $this->expectExceptionMessage('Missing required keys (type, name) in vfs://modules/fixtures/missing_keys.info.txt');
 | 
			
		||||
    $this->infoParser->parse($filename);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that missing 'core' and 'core_version_requirement' keys are detected.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   */
 | 
			
		||||
  public function testMissingCoreCoreVersionRequirement() {
 | 
			
		||||
    $missing_core_and_core_version_requirement = <<<MISSING_CORE_AND_CORE_VERSION_REQUIREMENT
 | 
			
		||||
# info.yml for testing core and core_version_requirement.
 | 
			
		||||
package: Core
 | 
			
		||||
version: VERSION
 | 
			
		||||
type: module
 | 
			
		||||
name: Skynet
 | 
			
		||||
dependencies:
 | 
			
		||||
  - self_awareness
 | 
			
		||||
MISSING_CORE_AND_CORE_VERSION_REQUIREMENT;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        'missing_core_and_core_version_requirement.info.txt' => $missing_core_and_core_version_requirement,
 | 
			
		||||
        'missing_core_and_core_version_requirement-duplicate.info.txt' => $missing_core_and_core_version_requirement,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $exception_message = "The 'core' or the 'core_version_requirement' key must be present in vfs://modules/fixtures/missing_core_and_core_version_requirement";
 | 
			
		||||
    // Set the expected exception for the 2nd call to parse().
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage("$exception_message-duplicate.info.txt");
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/missing_core_and_core_version_requirement.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
    catch (InfoParserException $exception) {
 | 
			
		||||
      $this->assertSame("$exception_message.info.txt", $exception->getMessage());
 | 
			
		||||
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/missing_core_and_core_version_requirement-duplicate.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that 'core_version_requirement: ^8.8' is valid with no 'core' key.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreVersionRequirement88() {
 | 
			
		||||
    $core_version_requirement = <<<BOTH_CORE_VERSION_REQUIREMENT
 | 
			
		||||
# info.yml for testing core and core_version_requirement keys.
 | 
			
		||||
package: Core
 | 
			
		||||
core_version_requirement: ^8.8
 | 
			
		||||
version: VERSION
 | 
			
		||||
type: module
 | 
			
		||||
name: Module for That
 | 
			
		||||
dependencies:
 | 
			
		||||
  - field
 | 
			
		||||
BOTH_CORE_VERSION_REQUIREMENT;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    foreach (['1', '2'] as $file_delta) {
 | 
			
		||||
      $filename = "core_version_requirement-$file_delta.info.txt";
 | 
			
		||||
      vfsStream::create([
 | 
			
		||||
        'fixtures' => [
 | 
			
		||||
          $filename => $core_version_requirement,
 | 
			
		||||
        ],
 | 
			
		||||
      ]);
 | 
			
		||||
      $info_values = $this->infoParser->parse(vfsStream::url("modules/fixtures/$filename"));
 | 
			
		||||
      $this->assertSame($info_values['core_version_requirement'], '^8.8', "Expected core_version_requirement for file: $filename");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that 'core_version_requirement: ^8.8' is invalid with a 'core' key.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreCoreVersionRequirement88() {
 | 
			
		||||
    $core_and_core_version_requirement_88 = <<<BOTH_CORE_CORE_VERSION_REQUIREMENT_88
 | 
			
		||||
# info.yml for testing core and core_version_requirement keys.
 | 
			
		||||
package: Core
 | 
			
		||||
core: 8.x
 | 
			
		||||
core_version_requirement: ^8.8
 | 
			
		||||
version: VERSION
 | 
			
		||||
type: module
 | 
			
		||||
name: Form auto submitter
 | 
			
		||||
dependencies:
 | 
			
		||||
  - field
 | 
			
		||||
BOTH_CORE_CORE_VERSION_REQUIREMENT_88;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        'core_and_core_version_requirement_88.info.txt' => $core_and_core_version_requirement_88,
 | 
			
		||||
        'core_and_core_version_requirement_88-duplicate.info.txt' => $core_and_core_version_requirement_88,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $exception_message = "The 'core_version_requirement' constraint (^8.8) requires the 'core' key not be set in vfs://modules/fixtures/core_and_core_version_requirement_88";
 | 
			
		||||
    // Set the expected exception for the 2nd call to parse().
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage("$exception_message-duplicate.info.txt");
 | 
			
		||||
    try {
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/core_and_core_version_requirement_88.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
    catch (InfoParserException $exception) {
 | 
			
		||||
      $this->assertSame("$exception_message.info.txt", $exception->getMessage());
 | 
			
		||||
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/core_and_core_version_requirement_88-duplicate.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests a invalid 'core' key.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidCore() {
 | 
			
		||||
    $invalid_core = <<<INVALID_CORE
 | 
			
		||||
# info.yml for testing invalid core key.
 | 
			
		||||
package: Core
 | 
			
		||||
core: ^8
 | 
			
		||||
version: VERSION
 | 
			
		||||
type: module
 | 
			
		||||
name: Llama or Alpaca
 | 
			
		||||
description: Tells whether an image is of a Llama or Alpaca
 | 
			
		||||
dependencies:
 | 
			
		||||
  - llama_detector
 | 
			
		||||
  - alpaca_detector
 | 
			
		||||
INVALID_CORE;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        'invalid_core.info.txt' => $invalid_core,
 | 
			
		||||
        'invalid_core-duplicate.info.txt' => $invalid_core,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $exception_message = "Invalid 'core' value \"^8\" in vfs://modules/fixtures/invalid_core";
 | 
			
		||||
    // Set the expected exception for the 2nd call to parse().
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage("$exception_message-duplicate.info.txt");
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/invalid_core.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
    catch (InfoParserException $exception) {
 | 
			
		||||
      $this->assertSame("$exception_message.info.txt", $exception->getMessage());
 | 
			
		||||
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/invalid_core-duplicate.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests a invalid 'core_version_requirement'.
 | 
			
		||||
   *
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerCoreVersionRequirementInvalid
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreVersionRequirementInvalid($test_case, $constraint) {
 | 
			
		||||
    $invalid_core_version_requirement = <<<INVALID_CORE_VERSION_REQUIREMENT
 | 
			
		||||
# info.yml for core_version_requirement validation.
 | 
			
		||||
name: Gracie Evaluator
 | 
			
		||||
description: 'Determines if Gracie is a "Good Dog". The answer is always "Yes".'
 | 
			
		||||
package: Core
 | 
			
		||||
type: module
 | 
			
		||||
version: VERSION
 | 
			
		||||
core_version_requirement: '$constraint'
 | 
			
		||||
dependencies:
 | 
			
		||||
  - goodness_api
 | 
			
		||||
INVALID_CORE_VERSION_REQUIREMENT;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        "invalid_core_version_requirement-$test_case.info.txt" => $invalid_core_version_requirement,
 | 
			
		||||
        "invalid_core_version_requirement-$test_case-duplicate.info.txt" => $invalid_core_version_requirement,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $exception_message = "The 'core_version_requirement' can not be used to specify compatibility for a specific version before 8.7.7 in vfs://modules/fixtures/invalid_core_version_requirement-$test_case";
 | 
			
		||||
    // Set the expected exception for the 2nd call to parse().
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage("$exception_message-duplicate.info.txt");
 | 
			
		||||
    try {
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url("modules/fixtures/invalid_core_version_requirement-$test_case.info.txt"));
 | 
			
		||||
    }
 | 
			
		||||
    catch (InfoParserException $exception) {
 | 
			
		||||
      $this->assertSame("$exception_message.info.txt", $exception->getMessage());
 | 
			
		||||
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url("modules/fixtures/invalid_core_version_requirement-$test_case-duplicate.info.txt"));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Dataprovider for testCoreVersionRequirementInvalid().
 | 
			
		||||
   */
 | 
			
		||||
  public function providerCoreVersionRequirementInvalid() {
 | 
			
		||||
    return [
 | 
			
		||||
      '8.0.0-alpha2' => ['alpha2', '8.0.0-alpha2'],
 | 
			
		||||
      '8.6.0-rc1' => ['rc1', '8.6.0-rc1'],
 | 
			
		||||
      '^8.7' => ['8_7', '^8.7'],
 | 
			
		||||
      '>8.6.3' => ['gt8_6_3', '>8.6.3'],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that missing required key is detected.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			@ -123,12 +325,20 @@ MISSINGKEY;
 | 
			
		|||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        'missing_key.info.txt' => $missing_key,
 | 
			
		||||
        'missing_key-duplicate.info.txt' => $missing_key,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $filename = vfsStream::url('modules/fixtures/missing_key.info.txt');
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage('Missing required keys (type) in vfs://modules/fixtures/missing_key.info.txt');
 | 
			
		||||
    $this->infoParser->parse($filename);
 | 
			
		||||
    try {
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/missing_key.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
    catch (InfoParserException $exception) {
 | 
			
		||||
      $this->assertSame('Missing required keys (type) in vfs://modules/fixtures/missing_key.info.txt', $exception->getMessage());
 | 
			
		||||
 | 
			
		||||
      $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
      $this->expectExceptionMessage('Missing required keys (type) in vfs://modules/fixtures/missing_key-duplicate.info.txt');
 | 
			
		||||
      $this->infoParser->parse(vfsStream::url('modules/fixtures/missing_key-duplicate.info.txt'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			@ -148,15 +358,111 @@ double_colon: dummyClassName::method
 | 
			
		|||
COMMONTEST;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
 | 
			
		||||
    foreach (['1', '2'] as $file_delta) {
 | 
			
		||||
      $filename = "common_test-$file_delta.info.txt";
 | 
			
		||||
      vfsStream::create([
 | 
			
		||||
        'fixtures' => [
 | 
			
		||||
          $filename => $common,
 | 
			
		||||
        ],
 | 
			
		||||
      ]);
 | 
			
		||||
      $info_values = $this->infoParser->parse(vfsStream::url("modules/fixtures/$filename"));
 | 
			
		||||
      $this->assertEquals($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.');
 | 
			
		||||
      $this->assertEquals($info_values['version'], \Drupal::VERSION, 'Constant value was parsed correctly.');
 | 
			
		||||
      $this->assertEquals($info_values['double_colon'], 'dummyClassName::method', 'Value containing double-colon was parsed correctly.');
 | 
			
		||||
      $this->assertSame('8.x', $info_values['core']);
 | 
			
		||||
      $this->assertFalse(isset($info_values['core_version_requirement']));
 | 
			
		||||
      $this->assertFalse($info_values['core_incompatible']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::parse
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider providerCoreIncompatibility
 | 
			
		||||
   */
 | 
			
		||||
  public function testCoreIncompatibility($test_case, $constraint, $expected) {
 | 
			
		||||
    $core_incompatibility = <<<CORE_INCOMPATIBILITY
 | 
			
		||||
core_version_requirement: $constraint
 | 
			
		||||
name: common_test
 | 
			
		||||
type: module
 | 
			
		||||
description: 'testing info file parsing'
 | 
			
		||||
simple_string: 'A simple string'
 | 
			
		||||
version: "VERSION"
 | 
			
		||||
double_colon: dummyClassName::method
 | 
			
		||||
CORE_INCOMPATIBILITY;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('modules');
 | 
			
		||||
    foreach (['1', '2'] as $file_delta) {
 | 
			
		||||
      $filename = "core_incompatible-$test_case-$file_delta.info.txt";
 | 
			
		||||
      vfsStream::create([
 | 
			
		||||
        'fixtures' => [
 | 
			
		||||
          $filename => $core_incompatibility,
 | 
			
		||||
        ],
 | 
			
		||||
      ]);
 | 
			
		||||
      $info_values = $this->infoParser->parse(vfsStream::url("modules/fixtures/$filename"));
 | 
			
		||||
      $this->assertSame($expected, $info_values['core_incompatible'], "core_incompatible correct in file: $filename");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Dataprovider for testCoreIncompatibility().
 | 
			
		||||
   */
 | 
			
		||||
  public function providerCoreIncompatibility() {
 | 
			
		||||
    list($major, $minor) = explode('.', \Drupal::VERSION);
 | 
			
		||||
 | 
			
		||||
    $next_minor = $minor + 1;
 | 
			
		||||
    $next_major = $major + 1;
 | 
			
		||||
    return [
 | 
			
		||||
      'next_minor' => [
 | 
			
		||||
        'next_minor',
 | 
			
		||||
        "^$major.$next_minor",
 | 
			
		||||
        TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      'current_major_next_major' => [
 | 
			
		||||
        'current_major_next_major',
 | 
			
		||||
        "^$major || ^$next_major",
 | 
			
		||||
        FALSE,
 | 
			
		||||
      ],
 | 
			
		||||
      'previous_major_next_major' => [
 | 
			
		||||
        'previous_major_next_major',
 | 
			
		||||
        "^1 || ^$next_major",
 | 
			
		||||
        TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      'invalid' => [
 | 
			
		||||
        'invalid',
 | 
			
		||||
        'this-string-is-invalid',
 | 
			
		||||
        TRUE,
 | 
			
		||||
      ],
 | 
			
		||||
      'current_minor' => [
 | 
			
		||||
        'current_minor',
 | 
			
		||||
        "~$major.$minor",
 | 
			
		||||
        FALSE,
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test a profile info file with the 'core_version_requirement' key.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidProfile() {
 | 
			
		||||
    $profile = <<<PROFILE_TEST
 | 
			
		||||
core: 8.x
 | 
			
		||||
core_version_requirement: ^8
 | 
			
		||||
name: The Perfect Profile
 | 
			
		||||
type: profile
 | 
			
		||||
description: 'This profile makes Drupal perfect. You should have no complaints.'
 | 
			
		||||
PROFILE_TEST;
 | 
			
		||||
 | 
			
		||||
    vfsStream::setup('profiles');
 | 
			
		||||
    vfsStream::create([
 | 
			
		||||
      'fixtures' => [
 | 
			
		||||
        'common_test.info.txt' => $common,
 | 
			
		||||
        'invalid_profile.info.txt' => $profile,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $info_values = $this->infoParser->parse(vfsStream::url('modules/fixtures/common_test.info.txt'));
 | 
			
		||||
    $this->assertEquals($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.');
 | 
			
		||||
    $this->assertEquals($info_values['version'], \Drupal::VERSION, 'Constant value was parsed correctly.');
 | 
			
		||||
    $this->assertEquals($info_values['double_colon'], 'dummyClassName::method', 'Value containing double-colon was parsed correctly.');
 | 
			
		||||
    $this->expectException('\Drupal\Core\Extension\InfoParserException');
 | 
			
		||||
    $this->expectExceptionMessage("The 'core_version_requirement' key is not supported in profiles in vfs://profiles/fixtures/invalid_profile.info.txt");
 | 
			
		||||
    $this->infoParser->parse(vfsStream::url('profiles/fixtures/invalid_profile.info.txt'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -491,6 +491,35 @@ class WebAssert extends MinkWebAssert {
 | 
			
		|||
    return $node;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks that a given form field element is enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $field
 | 
			
		||||
   *   One of id|name|label|value for the field.
 | 
			
		||||
   * @param \Behat\Mink\Element\TraversableElement $container
 | 
			
		||||
   *   (optional) The document to check against. Defaults to the current page.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Behat\Mink\Element\NodeElement
 | 
			
		||||
   *   The matching element.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Behat\Mink\Exception\ElementNotFoundException
 | 
			
		||||
   * @throws \Behat\Mink\Exception\ExpectationException
 | 
			
		||||
   */
 | 
			
		||||
  public function fieldEnabled($field, TraversableElement $container = NULL) {
 | 
			
		||||
    $container = $container ?: $this->session->getPage();
 | 
			
		||||
    $node = $container->findField($field);
 | 
			
		||||
 | 
			
		||||
    if ($node === NULL) {
 | 
			
		||||
      throw new ElementNotFoundException($this->session->getDriver(), 'field', 'id|name|label|value', $field);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($node->hasAttribute('disabled')) {
 | 
			
		||||
      throw new ExpectationException("Field $field is not enabled", $this->session->getDriver());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $node;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks that specific hidden field exists.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue