diff --git a/core/modules/auto_updates/auto_updates.info.yml b/core/modules/auto_updates/auto_updates.info.yml index 1d396122f25e8c8a922acb70fe5b2738722e7c31..8f3b0999b2b0e01313899dcd0fbfaa3352840d66 100644 --- a/core/modules/auto_updates/auto_updates.info.yml +++ b/core/modules/auto_updates/auto_updates.info.yml @@ -1,6 +1,6 @@ name: 'Automatic Updates' type: module -description: 'Experimental module to develop automatic updates. Currently the module provides checks for update readiness but does not yet provide update functionality.' +description: 'Automatically updates Drupal core.' package: Core version: VERSION lifecycle: experimental diff --git a/core/modules/auto_updates/auto_updates.install b/core/modules/auto_updates/auto_updates.install index 1923c8c5297f1407493e9b5c74a6796585904594..b7ab9425fef6dd3aeef26b679271c95a0773f69d 100644 --- a/core/modules/auto_updates/auto_updates.install +++ b/core/modules/auto_updates/auto_updates.install @@ -12,6 +12,7 @@ */ function auto_updates_requirements($phase) { if ($phase === 'runtime') { + // Check that site is ready to perform automatic updates. /** @var \Drupal\auto_updates\Validation\ReadinessRequirements $readiness_requirement */ $readiness_requirement = \Drupal::classResolver(ReadinessRequirements::class); return $readiness_requirement->getRequirements(); diff --git a/core/modules/auto_updates/auto_updates.module b/core/modules/auto_updates/auto_updates.module index 8ebaf73a8d4afe0881bca104c4fb943cc866e7ec..e4aa6851bdd5ee7165a70a21019712e7503ae4b5 100644 --- a/core/modules/auto_updates/auto_updates.module +++ b/core/modules/auto_updates/auto_updates.module @@ -47,6 +47,7 @@ function auto_updates_page_top() { 'auto_updates.report_update', 'auto_updates.module_update', ]; + // @see auto_updates_module_implements_alter() $route_name = \Drupal::routeMatch()->getRouteName(); if (!in_array($route_name, $skip_routes, TRUE) && function_exists('update_page_top')) { update_page_top(); @@ -63,8 +64,7 @@ function auto_updates_module_implements_alter(&$implementations, $hook) { // Remove hook_page_top() implementation from the Update module. This ' // implementation displays error messages about security releases. We call // this implementation in our own auto_updates_page_top() except on our - // own routes to avoid stale messages about the security releases after an - // update. + // own routes to avoid these messages while an update is in progress. unset($implementations['update']); } } @@ -114,7 +114,9 @@ function auto_updates_modules_uninstalled() { */ function auto_updates_form_update_manager_update_form_alter(&$form, FormStateInterface $form_state, $form_id) { // Remove current message that core updates are not supported with a link to - // use this modules form. + // use this module's form. The local task to 'update_manager_update_form' is + // replaced by our own from but this original form would still accessible via + // by its original URL. if (isset($form['manual_updates']['#rows']['drupal']['data']['title'])) { $current_route = \Drupal::routeMatch()->getRouteName(); if ($current_route === 'update.module_update') { @@ -141,6 +143,8 @@ function auto_updates_form_update_settings_alter(array &$form, FormStateInterfac $drupal_project = $recommender->getProjectInfo(); $version = ExtensionVersion::createFromVersionString($drupal_project['existing_version']); $current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion(); + // @todo In https://www.drupal.org/node/2998285 use the update XML to + // determine when the installed of core will become unsupported. $supported_until_version = $version->getMajorVersion() . '.' . ((int) $version->getMinorVersion() + ProjectSecurityData::CORE_MINORS_WITH_SECURITY_COVERAGE) . '.0'; diff --git a/core/modules/auto_updates/auto_updates.routing.yml b/core/modules/auto_updates/auto_updates.routing.yml index e3a5d1bb9d7c2d943b95106a8d530823f62f1662..77fcaf6d764e976c945ad0f51abdfcbdb929716f 100644 --- a/core/modules/auto_updates/auto_updates.routing.yml +++ b/core/modules/auto_updates/auto_updates.routing.yml @@ -13,7 +13,7 @@ auto_updates.confirmation_page: requirements: _permission: 'administer software updates' _access_update_manager: 'TRUE' -# Links to our updater form appear in two different sets of local tasks. To ensure the breadcrumbs and paths are +# Links to our updater form appear in three different sets of local tasks. To ensure the breadcrumbs and paths are # consistent with the other local tasks in each set, we need two separate routes to the same form. auto_updates.report_update: path: '/admin/reports/updates/automatic-update' diff --git a/core/modules/auto_updates/src/Event/ReadinessCheckEvent.php b/core/modules/auto_updates/src/Event/ReadinessCheckEvent.php index a1d2bfd9b42fd91275f18982400e7f5faa681a4a..bb57325de21e0dead98566fbc0572d406945a67f 100644 --- a/core/modules/auto_updates/src/Event/ReadinessCheckEvent.php +++ b/core/modules/auto_updates/src/Event/ReadinessCheckEvent.php @@ -53,7 +53,7 @@ public function getPackageVersions(): array { } /** - * {@inheritdoc} + * Adds warning information to the event. */ public function addWarning(array $messages, ?TranslatableMarkup $summary = NULL) { $this->results[] = ValidationResult::createWarning($messages, $summary); diff --git a/core/modules/auto_updates/src/Validator/PackageManagerReadinessCheck.php b/core/modules/auto_updates/src/Validator/PackageManagerReadinessCheck.php index b8a6dbe9317365e5bbbfdaf698967e7d24d2d8ba..0d4838f266a52a72a2774d2c4d7b5d44064dda4f 100644 --- a/core/modules/auto_updates/src/Validator/PackageManagerReadinessCheck.php +++ b/core/modules/auto_updates/src/Validator/PackageManagerReadinessCheck.php @@ -3,7 +3,7 @@ namespace Drupal\auto_updates\Validator; use Drupal\auto_updates\Event\ReadinessCheckEvent; -use Drupal\package_manager\EventSubscriber\PreOperationStageValidatorInterface; +use Drupal\package_manager\Validator\PreOperationStageValidatorInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -18,14 +18,14 @@ class PackageManagerReadinessCheck implements EventSubscriberInterface { /** * The validator to run. * - * @var \Drupal\package_manager\EventSubscriber\PreOperationStageValidatorInterface + * @var \Drupal\package_manager\Validator\PreOperationStageValidatorInterface */ protected $validator; /** * Constructs a PackageManagerReadinessCheck object. * - * @param \Drupal\package_manager\EventSubscriber\PreOperationStageValidatorInterface $validator + * @param \Drupal\package_manager\Validator\PreOperationStageValidatorInterface $validator * The Package Manager validator to run during readiness checking. */ public function __construct(PreOperationStageValidatorInterface $validator) { diff --git a/core/modules/auto_updates/tests/src/Build/UpdateTestBase.php b/core/modules/auto_updates/tests/src/Build/UpdateTestBase.php index 8ef429977846dc114388eb587992dff0d4939833..ff95f788ba7e73d2e8edf922cc9723afd0b5a132 100644 --- a/core/modules/auto_updates/tests/src/Build/UpdateTestBase.php +++ b/core/modules/auto_updates/tests/src/Build/UpdateTestBase.php @@ -32,17 +32,6 @@ protected function tearDown(): void { protected function createTestProject(string $template): void { parent::createTestProject($template); - // BEGIN: DELETE FROM CORE MERGE REQUEST - // Install Automatic Updates into the test project and ensure it wasn't - // symlinked. - if (__NAMESPACE__ === 'Drupal\Tests\automatic_updates\Build') { - $dir = 'project'; - $this->runComposer('composer config repo.automatic_updates path ' . __DIR__ . '/../../..', $dir); - $this->runComposer('composer require --no-update "drupal/automatic_updates:@dev"', $dir); - $output = $this->runComposer('COMPOSER_MIRROR_PATH_REPOS=1 composer update --with-all-dependencies', $dir); - $this->assertStringNotContainsString('Symlinking', $output); - } - // END: DELETE FROM CORE MERGE REQUEST // Install Drupal. Always allow test modules to be installed in the UI and, // for easier debugging, always display errors in their dubious glory. $this->installQuickStart('minimal'); diff --git a/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php b/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php index 553570872117ff715bf8c95d2989007fe43c55f7..af4287913ca557e55f6f0f76d0da4d58e5d31306 100644 --- a/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/PackageManagerReadinessChecksTest.php @@ -4,7 +4,7 @@ use Drupal\auto_updates\Event\ReadinessCheckEvent; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\package_manager\EventSubscriber\PreOperationStageValidatorInterface; +use Drupal\package_manager\Validator\PreOperationStageValidatorInterface; use Drupal\Tests\auto_updates\Kernel\AutoUpdatesKernelTestBase; use Prophecy\Argument; diff --git a/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php index c84e7c5e7a6657fe02ee46942c8e0b83c9d69cbe..8ad7063c8489fc604fcf5c84db369fe55dc42b70 100644 --- a/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php @@ -118,9 +118,15 @@ public function testEventConsumesExceptionResults(): void { copy("$fixture/composer.lock", 'public://composer.lock'); $event_dispatcher = $this->container->get('event_dispatcher'); - // Disable the disk space validator, since it doesn't work with vfsStream. - $disk_space_validator = $this->container->get('package_manager.validator.disk_space'); - $event_dispatcher->removeSubscriber($disk_space_validator); + // Disable the disk space validator, since it doesn't work with vfsStream, + // and the excluded paths subscriber, since it won't deal with this tiny + // virtual file system correctly. + $disable_subscribers = array_map([$this->container, 'get'], [ + 'package_manager.validator.disk_space', + 'package_manager.excluded_paths_subscriber', + ]); + array_walk($disable_subscribers, [$event_dispatcher, 'removeSubscriber']); + // Just before the staged changes are applied, delete the composer.json file // to trigger an error. This uses the highest possible priority to guarantee // it runs before any other subscribers. diff --git a/core/modules/package_manager/package_manager.info.yml b/core/modules/package_manager/package_manager.info.yml index 9af8cfa9ad5128fda3770f5a28356f8038f5facb..dd204c7f0f32bfcb93bdfe99ae12628b7f94439d 100644 --- a/core/modules/package_manager/package_manager.info.yml +++ b/core/modules/package_manager/package_manager.info.yml @@ -1,6 +1,6 @@ name: 'Package Manager' type: module -description: 'API module providing functionality for staging package updates.' +description: 'API module providing functionality for staging package installs and updates with Composer.' package: Core version: VERSION lifecycle: experimental diff --git a/core/modules/package_manager/package_manager.install b/core/modules/package_manager/package_manager.install deleted file mode 100644 index 0793ddea7a27e2cef93be3aae26f01de6d642bec..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/package_manager.install +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -/** - * @file - * Contains install and update functions for Package Manager. - */ - -/** - * Implements hook_requirements(). - */ -function package_manager_requirements() { - if (!class_exists('PhpTuf\ComposerStager\Domain\Beginner')) { - return [ - 'package_manager' => [ - 'description' => t('External dependencies for Package Manager are not available. Composer must be used to download the module with dependencies.'), - 'severity' => REQUIREMENT_ERROR, - ], - ]; - } -} diff --git a/core/modules/package_manager/package_manager.services.yml b/core/modules/package_manager/package_manager.services.yml index f5e04442d0d43f9b48c55d34e6e6c0f87c5245f3..7480aaae798c0165513bc8d5cbfd78d6181ea9e1 100644 --- a/core/modules/package_manager/package_manager.services.yml +++ b/core/modules/package_manager/package_manager.services.yml @@ -77,21 +77,21 @@ services: # Validators. package_manager.validator.composer_executable: - class: Drupal\package_manager\EventSubscriber\ComposerExecutableValidator + class: Drupal\package_manager\Validator\ComposerExecutableValidator arguments: - '@package_manager.composer_runner' - '@string_translation' tags: - { name: event_subscriber } package_manager.validator.disk_space: - class: Drupal\package_manager\EventSubscriber\DiskSpaceValidator + class: Drupal\package_manager\Validator\DiskSpaceValidator arguments: - '@package_manager.path_locator' - '@string_translation' tags: - { name: event_subscriber } package_manager.validator.pending_updates: - class: Drupal\package_manager\EventSubscriber\PendingUpdatesValidator + class: Drupal\package_manager\Validator\PendingUpdatesValidator arguments: - '%app.root%' - '@update.post_update_registry' @@ -99,7 +99,7 @@ services: tags: - { name: event_subscriber } package_manager.validator.lock_file: - class: Drupal\package_manager\EventSubscriber\LockFileValidator + class: Drupal\package_manager\Validator\LockFileValidator arguments: - '@state' - '@package_manager.path_locator' @@ -107,7 +107,7 @@ services: tags: - { name: event_subscriber } package_manager.validator.file_system: - class: Drupal\package_manager\EventSubscriber\WritableFileSystemValidator + class: Drupal\package_manager\Validator\WritableFileSystemValidator arguments: - '@package_manager.path_locator' - '%app.root%' @@ -115,7 +115,7 @@ services: tags: - { name: event_subscriber } package_manager.validator.composer_settings: - class: Drupal\package_manager\EventSubscriber\ComposerSettingsValidator + class: Drupal\package_manager\Validator\ComposerSettingsValidator arguments: - '@string_translation' tags: diff --git a/core/modules/package_manager/src/ComposerUtility.php b/core/modules/package_manager/src/ComposerUtility.php index 6a111b74a911b77919483aa09bf9673d5d91030d..cfab9e40873d4b93f71d8072f4379ee177547266 100644 --- a/core/modules/package_manager/src/ComposerUtility.php +++ b/core/modules/package_manager/src/ComposerUtility.php @@ -48,7 +48,7 @@ public function getComposer(): Composer { } /** - * Creates a utility object using the files in a given directory. + * Creates an instance of this class using the files in a given directory. * * @param string $dir * The directory that contains composer.json and composer.lock. @@ -111,7 +111,8 @@ protected static function getCorePackageList(): array { * @return string[] * The names of the required core packages. * - * @todo Make this return a keyed array of packages, not just names. + * @todo Make this return a keyed array of packages, not just names in + * https://www.drupal.org/i/3258059. */ public function getCorePackageNames(): array { $core_packages = array_intersect( @@ -135,7 +136,8 @@ public function getCorePackageNames(): array { * @return string[] * The names of the core packages in the dev requirements. * - * @todo Make this return a keyed array of packages, not just names. + * @todo Make this return a keyed array of packages, not just names in + * https://www.drupal.org/i/3258059. */ public function getCoreDevPackageNames(): array { $dev_packages = $this->composer->getPackage()->getDevRequires(); diff --git a/core/modules/package_manager/src/Event/PostDestroyEvent.php b/core/modules/package_manager/src/Event/PostDestroyEvent.php index 3388a988e126da919dfbc598946910f38b86cbbe..283bf69e347b1cbc3a2e9631dfec8524c6f1c910 100644 --- a/core/modules/package_manager/src/Event/PostDestroyEvent.php +++ b/core/modules/package_manager/src/Event/PostDestroyEvent.php @@ -4,6 +4,11 @@ /** * Event fired after the staging area is destroyed. + * + * If the stage is being force destroyed, ::getStage() may return an object of a + * different class than the one that originally created the staging area. + * + * @see \Drupal\package_manager\Stage::destroy() */ class PostDestroyEvent extends StageEvent { } diff --git a/core/modules/package_manager/src/Event/PostRequireEvent.php b/core/modules/package_manager/src/Event/PostRequireEvent.php index 55b4184dd3d96daaeeba55317c227fd0bbfb089c..c35076f5cf905eee0a28921f5013ddf33b764a51 100644 --- a/core/modules/package_manager/src/Event/PostRequireEvent.php +++ b/core/modules/package_manager/src/Event/PostRequireEvent.php @@ -3,7 +3,7 @@ namespace Drupal\package_manager\Event; /** - * Event fired after packages are added to the staging area. + * Event fired after packages are updated to the staging area. */ class PostRequireEvent extends StageEvent { } diff --git a/core/modules/package_manager/src/Event/PreDestroyEvent.php b/core/modules/package_manager/src/Event/PreDestroyEvent.php index d4918f0b8b75c96a1b62a141b029c086996a3f54..3a876312a24a6d503f0ec98a68c23f2c08d9e279 100644 --- a/core/modules/package_manager/src/Event/PreDestroyEvent.php +++ b/core/modules/package_manager/src/Event/PreDestroyEvent.php @@ -4,6 +4,11 @@ /** * Event fired before the staging area is destroyed. + * + * If the stage is being force destroyed, ::getStage() may return an object of a + * different class than the one that originally created the staging area. + * + * @see \Drupal\package_manager\Stage::destroy() */ class PreDestroyEvent extends PreOperationStageEvent { } diff --git a/core/modules/package_manager/src/Event/PreOperationStageEvent.php b/core/modules/package_manager/src/Event/PreOperationStageEvent.php index ee826f3ac35cdfe6f3a987112786d16a666b7ff9..832677a82e8a3454c328e1ad8103622fff9c5ede 100644 --- a/core/modules/package_manager/src/Event/PreOperationStageEvent.php +++ b/core/modules/package_manager/src/Event/PreOperationStageEvent.php @@ -11,7 +11,7 @@ abstract class PreOperationStageEvent extends StageEvent { /** - * {@inheritdoc} + * Adds error information to the event. */ public function addError(array $messages, ?TranslatableMarkup $summary = NULL) { $this->results[] = ValidationResult::createError($messages, $summary); diff --git a/core/modules/package_manager/src/Event/PreRequireEvent.php b/core/modules/package_manager/src/Event/PreRequireEvent.php index 355bb5047fb89719c0b0711db51d675be0368387..bcb9bfc6337e449fd180a9a8cc027acc0cb500fe 100644 --- a/core/modules/package_manager/src/Event/PreRequireEvent.php +++ b/core/modules/package_manager/src/Event/PreRequireEvent.php @@ -3,7 +3,7 @@ namespace Drupal\package_manager\Event; /** - * Event fired before packages are added to the staging area. + * Event fired before packages are updated to the staging area. */ class PreRequireEvent extends PreOperationStageEvent { } diff --git a/core/modules/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php b/core/modules/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php index b65ff7a70921bc61e90f3a927e9e471a01b6ba82..a54a4e2816e77c549c27aca46f6a574600507465 100644 --- a/core/modules/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php +++ b/core/modules/package_manager/src/EventSubscriber/ExcludedPathsSubscriber.php @@ -11,6 +11,7 @@ use Drupal\package_manager\PathLocator; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; /** * Defines an event subscriber to exclude certain paths from staging areas. @@ -180,6 +181,20 @@ public function ignoreCommonPaths(StageEvent $event): void { $project[] = $options['database'] . '-wal'; } + // Find all .git directories in the project and exclude them. We cannot do + // this with FileSystemInterface::scanDirectory() because it unconditionally + // excludes anything starting with a dot. + $finder = Finder::create() + ->in($this->pathLocator->getProjectRoot()) + ->directories() + ->name('.git') + ->ignoreVCS(FALSE) + ->ignoreDotFiles(FALSE); + + foreach ($finder as $git_directory) { + $project[] = $git_directory->getPathname(); + } + $this->excludeInWebRoot($event, $web); $this->excludeInProjectRoot($event, $project); } diff --git a/core/modules/package_manager/src/FileSyncerFactory.php b/core/modules/package_manager/src/FileSyncerFactory.php index ee96aa1e579cc07a93bbec1eee12a9aa8679c072..f93e31bfecabdb9d5a244f53590a32a68efb98ab 100644 --- a/core/modules/package_manager/src/FileSyncerFactory.php +++ b/core/modules/package_manager/src/FileSyncerFactory.php @@ -9,7 +9,7 @@ use Symfony\Component\Process\ExecutableFinder; /** - * A file syncer factory which returns file syncers according to configuration. + * A file syncer factory which creates a file syncer according to configuration. */ class FileSyncerFactory implements FileSyncerFactoryInterface { diff --git a/core/modules/package_manager/src/ProcessFactory.php b/core/modules/package_manager/src/ProcessFactory.php index 8a1ca6583a3be2350d2ba5e53162e758ed512df8..4eb5195cbf1659893a3dc9d2e7bccd0dd47e7640 100644 --- a/core/modules/package_manager/src/ProcessFactory.php +++ b/core/modules/package_manager/src/ProcessFactory.php @@ -10,8 +10,6 @@ /** * Defines a process factory which sets the COMPOSER_HOME environment variable. - * - * @todo Figure out how to do this in composer_stager. */ final class ProcessFactory implements ProcessFactoryInterface { diff --git a/core/modules/package_manager/src/Stage.php b/core/modules/package_manager/src/Stage.php index 4432089cd00f7d45e4ebe0271a7d1beee31bcedd..6d4678383085849e26e49e7c0fb4d27c2beb4545 100644 --- a/core/modules/package_manager/src/Stage.php +++ b/core/modules/package_manager/src/Stage.php @@ -61,21 +61,21 @@ class Stage { protected $pathLocator; /** - * The beginner service from Composer Stager. + * The beginner service. * * @var \PhpTuf\ComposerStager\Domain\BeginnerInterface */ protected $beginner; /** - * The stager service from Composer Stager. + * The stager service. * * @var \PhpTuf\ComposerStager\Domain\StagerInterface */ protected $stager; /** - * The committer service from Composer Stager. + * The committer service. * * @var \PhpTuf\ComposerStager\Domain\CommitterInterface */ @@ -117,11 +117,11 @@ class Stage { * @param \Drupal\package_manager\PathLocator $path_locator * The path locator service. * @param \PhpTuf\ComposerStager\Domain\BeginnerInterface $beginner - * The beginner service from Composer Stager. + * The beginner service. * @param \PhpTuf\ComposerStager\Domain\StagerInterface $stager - * The stager service from Composer Stager. + * The stager service. * @param \PhpTuf\ComposerStager\Domain\CommitterInterface $committer - * The committer service from Composer Stager. + * The committer service. * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher @@ -199,6 +199,9 @@ protected function setMetadata(string $key, $data): void { * performing other operations on it. Calling code should store this ID for * as long as the stage needs to exist. * + * @throws \Drupal\package_manager\Exception\StageException + * Thrown if a staging area already exists. + * * @see ::claim() */ public function create(): string { @@ -246,6 +249,8 @@ public function require(array $constraints, bool $dev = FALSE): void { $this->dispatch(new PreRequireEvent($this)); $dir = $this->getStageDirectory(); $this->stager->stage($command, $dir); + // @todo Limit the update command to only packages in $constraints in + // https://www.drupal.org/i/3257849. $this->stager->stage(['update', '--with-all-dependencies'], $dir); $this->dispatch(new PostRequireEvent($this)); } @@ -285,6 +290,9 @@ public function destroy(bool $force = FALSE): void { // Delete all directories in parent staging directory. $parent_stage_dir = static::getStagingRoot(); if (is_dir($parent_stage_dir)) { + // @todo Ensure that even if attempting to delete this directory throws an + // exception the stage is still marked as available in + // https://www.drupal.org/i/3258048. $this->fileSystem->deleteRecursive($parent_stage_dir, function (string $path): void { $this->fileSystem->chmod($path, 0777); }); @@ -324,6 +332,7 @@ protected function dispatch(StageEvent $event): void { } } catch (\Throwable $error) { + // @todo Simplify exception handling in https://www.drupal.org/i/3258056. // If we are not going to be able to create the staging area, mark it as // available. // @see ::create() @@ -407,12 +416,16 @@ final public function claim(string $unique_id): self { } /** - * Ensures that the current user or session owns the staging area. + * Validates the ownership of staging area. + * + * The stage is considered under valid ownership if it was created by current + * user or session, using the current class. * * @throws \LogicException * If ::claim() has not been previously called. * @throws \Drupal\package_manager\Exception\StageOwnershipException - * If the current user or session does not own the staging area. + * If the current user or session does not own the staging area, or it was + * created by a different class. */ final protected function checkOwnership(): void { if (empty($this->lock)) { diff --git a/core/modules/package_manager/src/EventSubscriber/ComposerExecutableValidator.php b/core/modules/package_manager/src/Validator/ComposerExecutableValidator.php similarity index 95% rename from core/modules/package_manager/src/EventSubscriber/ComposerExecutableValidator.php rename to core/modules/package_manager/src/Validator/ComposerExecutableValidator.php index f91e852930321300c2dd5b4067736165ff3dafc1..db516e5089a0b643015e0fbd4adb68a6d8cd8258 100644 --- a/core/modules/package_manager/src/EventSubscriber/ComposerExecutableValidator.php +++ b/core/modules/package_manager/src/Validator/ComposerExecutableValidator.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; @@ -12,7 +12,7 @@ use PhpTuf\ComposerStager\Exception\ExceptionInterface; /** - * Validates that the Composer executable can be found in the correct version. + * Validates the Composer executable is the correct version. */ class ComposerExecutableValidator implements PreOperationStageValidatorInterface, OutputCallbackInterface { diff --git a/core/modules/package_manager/src/EventSubscriber/ComposerSettingsValidator.php b/core/modules/package_manager/src/Validator/ComposerSettingsValidator.php similarity index 96% rename from core/modules/package_manager/src/EventSubscriber/ComposerSettingsValidator.php rename to core/modules/package_manager/src/Validator/ComposerSettingsValidator.php index 64c7249564e83520e06f7cc857311859ebaf640d..0ad1c7c254f7b92923b39d74b8c85567e351d3ca 100644 --- a/core/modules/package_manager/src/EventSubscriber/ComposerSettingsValidator.php +++ b/core/modules/package_manager/src/Validator/ComposerSettingsValidator.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; diff --git a/core/modules/package_manager/src/EventSubscriber/DiskSpaceValidator.php b/core/modules/package_manager/src/Validator/DiskSpaceValidator.php similarity index 95% rename from core/modules/package_manager/src/EventSubscriber/DiskSpaceValidator.php rename to core/modules/package_manager/src/Validator/DiskSpaceValidator.php index c54a653052092cb6f5f6de96292ac8178fea9a0b..7a0a97dc4e7f60703fbb64226a3459541b448e18 100644 --- a/core/modules/package_manager/src/EventSubscriber/DiskSpaceValidator.php +++ b/core/modules/package_manager/src/Validator/DiskSpaceValidator.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; @@ -11,7 +11,7 @@ use Drupal\package_manager\PathLocator; /** - * Validates that there is enough free disk space to do automatic updates. + * Validates that there is enough free disk space to do staging operations. */ class DiskSpaceValidator implements PreOperationStageValidatorInterface { @@ -103,7 +103,8 @@ public function validateStagePreOperation(PreOperationStageEvent $event): void { $vendor_path = $this->pathLocator->getVendorDirectory(); $messages = []; - // @todo Make this configurable. + // @todo Make this configurable or set to a different value in + // https://www.drupal.org/i/3166416. $minimum_mb = 1024; $minimum_bytes = Bytes::toNumber($minimum_mb . 'M'); diff --git a/core/modules/package_manager/src/EventSubscriber/LockFileValidator.php b/core/modules/package_manager/src/Validator/LockFileValidator.php similarity index 96% rename from core/modules/package_manager/src/EventSubscriber/LockFileValidator.php rename to core/modules/package_manager/src/Validator/LockFileValidator.php index dab3d8d298bbff2e71830ca55218b8cff4bfad8a..15a3a977d5dafcc716ae6218929e3a8a1b75b945 100644 --- a/core/modules/package_manager/src/EventSubscriber/LockFileValidator.php +++ b/core/modules/package_manager/src/Validator/LockFileValidator.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -109,7 +109,7 @@ public function validateStagePreOperation(PreOperationStageEvent $event): void { } // If we have both hashes, ensure they match. - if ($hash && $stored_hash && hash_equals($stored_hash, $hash) == FALSE) { + if ($hash && $stored_hash && !hash_equals($stored_hash, $hash)) { $error = $this->t('Stored lock file hash does not match the active lock file.'); } diff --git a/core/modules/package_manager/src/EventSubscriber/PendingUpdatesValidator.php b/core/modules/package_manager/src/Validator/PendingUpdatesValidator.php similarity index 97% rename from core/modules/package_manager/src/EventSubscriber/PendingUpdatesValidator.php rename to core/modules/package_manager/src/Validator/PendingUpdatesValidator.php index 53cbc7420633dc26bc0485072ae11b0ede02ce77..16062a98a6198d7ab4d2e4b0c94cef623671fc13 100644 --- a/core/modules/package_manager/src/EventSubscriber/PendingUpdatesValidator.php +++ b/core/modules/package_manager/src/Validator/PendingUpdatesValidator.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; diff --git a/core/modules/package_manager/src/EventSubscriber/PreOperationStageValidatorInterface.php b/core/modules/package_manager/src/Validator/PreOperationStageValidatorInterface.php similarity index 91% rename from core/modules/package_manager/src/EventSubscriber/PreOperationStageValidatorInterface.php rename to core/modules/package_manager/src/Validator/PreOperationStageValidatorInterface.php index a48ff5127f42a5e5ce0c5af75b4c4e4319479013..a30bfbcda53117347a12b55bde192303868b734c 100644 --- a/core/modules/package_manager/src/EventSubscriber/PreOperationStageValidatorInterface.php +++ b/core/modules/package_manager/src/Validator/PreOperationStageValidatorInterface.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\package_manager\Event\PreOperationStageEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; diff --git a/core/modules/package_manager/src/EventSubscriber/WritableFileSystemValidator.php b/core/modules/package_manager/src/Validator/WritableFileSystemValidator.php similarity index 97% rename from core/modules/package_manager/src/EventSubscriber/WritableFileSystemValidator.php rename to core/modules/package_manager/src/Validator/WritableFileSystemValidator.php index 95bc5d1104deac380383c2c28d996d6f928c1320..24ccc241867fa46634e5ea9c089124b57907f208 100644 --- a/core/modules/package_manager/src/EventSubscriber/WritableFileSystemValidator.php +++ b/core/modules/package_manager/src/Validator/WritableFileSystemValidator.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\package_manager\EventSubscriber; +namespace Drupal\package_manager\Validator; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreOperationStageEvent; diff --git a/core/modules/package_manager/tests/fixtures/fake_site/composer.json b/core/modules/package_manager/tests/fixtures/fake_site/composer.json deleted file mode 100644 index 0967ef424bce6791893e9a57bb952f80fd536e93..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/composer.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/core/modules/package_manager/tests/fixtures/fake_site/private/ignore.txt b/core/modules/package_manager/tests/fixtures/fake_site/private/ignore.txt deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/private/ignore.txt +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/services.yml b/core/modules/package_manager/tests/fixtures/fake_site/sites/default/services.yml deleted file mode 100644 index cbc4434e8f2bc888569a704746bf41606174d259..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/services.yml +++ /dev/null @@ -1,2 +0,0 @@ -# This file should never be staged. -must_not_be: 'empty' diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/settings.local.php b/core/modules/package_manager/tests/fixtures/fake_site/sites/default/settings.local.php deleted file mode 100644 index 15b43d28125cc4a2e30348dc76d972ce240443ac..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/settings.local.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php - -/** - * @file - * This file should never be staged. - */ diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/settings.php b/core/modules/package_manager/tests/fixtures/fake_site/sites/default/settings.php deleted file mode 100644 index 15b43d28125cc4a2e30348dc76d972ce240443ac..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/settings.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php - -/** - * @file - * This file should never be staged. - */ diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/stage.txt b/core/modules/package_manager/tests/fixtures/fake_site/sites/default/stage.txt deleted file mode 100644 index 0087269e33e50d1805db3c9ecf821660384c11bc..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/default/stage.txt +++ /dev/null @@ -1 +0,0 @@ -This file should be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-shm +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/db.sqlite-wal +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/files/ignore.txt b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/files/ignore.txt deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/files/ignore.txt +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/services.yml b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/services.yml deleted file mode 100644 index f408d89e28e978d7e5cc3605b5e35eb81d122e3b..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/services.yml +++ /dev/null @@ -1,2 +0,0 @@ -# This file should never be staged. -key: "value" diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/settings.local.php b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/settings.local.php deleted file mode 100644 index 15b43d28125cc4a2e30348dc76d972ce240443ac..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/settings.local.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php - -/** - * @file - * This file should never be staged. - */ diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/settings.php b/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/settings.php deleted file mode 100644 index 15b43d28125cc4a2e30348dc76d972ce240443ac..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/example.com/settings.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php - -/** - * @file - * This file should never be staged. - */ diff --git a/core/modules/package_manager/tests/fixtures/fake_site/sites/simpletest/ignore.txt b/core/modules/package_manager/tests/fixtures/fake_site/sites/simpletest/ignore.txt deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/sites/simpletest/ignore.txt +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/vendor/.htaccess b/core/modules/package_manager/tests/fixtures/fake_site/vendor/.htaccess deleted file mode 100644 index e11552b41d40377475700ab10cd3118257d93cc7..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/vendor/.htaccess +++ /dev/null @@ -1 +0,0 @@ -# This file should never be staged. diff --git a/core/modules/package_manager/tests/fixtures/fake_site/vendor/web.config b/core/modules/package_manager/tests/fixtures/fake_site/vendor/web.config deleted file mode 100644 index 08874eba8bb924527069b41e1195da4b6b69d1dd..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/fixtures/fake_site/vendor/web.config +++ /dev/null @@ -1 +0,0 @@ -This file should never be staged. diff --git a/core/modules/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php b/core/modules/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php index bc57b4fa75a7b65aff5a4fca754e66d48c5c8191..12b4fe415ca89cc14febdada697392017b38e5a0 100644 --- a/core/modules/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php +++ b/core/modules/package_manager/tests/src/Kernel/ComposerExecutableValidatorTest.php @@ -3,14 +3,14 @@ namespace Drupal\Tests\package_manager\Kernel; use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\EventSubscriber\ComposerExecutableValidator; +use Drupal\package_manager\Validator\ComposerExecutableValidator; use Drupal\package_manager\ValidationResult; use PhpTuf\ComposerStager\Exception\IOException; use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface; use Prophecy\Argument; /** - * @covers \Drupal\package_manager\EventSubscriber\ComposerExecutableValidator + * @covers \Drupal\package_manager\Validator\ComposerExecutableValidator * * @group package_manager */ @@ -120,7 +120,7 @@ public function testComposerVersionValidation(string $reported_version, array $e // $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\package_manager\EventSubscriber\ComposerExecutableValidator $validator */ + /** @var \Drupal\package_manager\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 diff --git a/core/modules/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php b/core/modules/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php index ff09f97428aeaa6ca03d7b51aad449faa78df02c..af6f12bde624b4aa748455f318d0b71ac8b8388f 100644 --- a/core/modules/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php +++ b/core/modules/package_manager/tests/src/Kernel/ComposerSettingsValidatorTest.php @@ -3,35 +3,16 @@ namespace Drupal\Tests\package_manager\Kernel; use Drupal\Component\Serialization\Json; -use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\package_manager\Exception\StageValidationException; -use Drupal\package_manager\PathLocator; use Drupal\package_manager\ValidationResult; -use org\bovigo\vfs\vfsStream; /** - * @covers \Drupal\package_manager\EventSubscriber\ComposerSettingsValidator + * @covers \Drupal\package_manager\Validator\ComposerSettingsValidator * * @group package_manager */ class ComposerSettingsValidatorTest extends PackageManagerKernelTestBase { - /** - * {@inheritdoc} - */ - protected function disableValidators(ContainerBuilder $container): void { - parent::disableValidators($container); - - // Disable the disk space validator, since it tries to inspect the file - // system in ways that vfsStream doesn't support, like calling stat() and - // disk_free_space(). - $container->removeDefinition('package_manager.validator.disk_space'); - - // Disable the lock file validator, since the mock file system we create in - // this test doesn't have any lock files to validate. - $container->removeDefinition('package_manager.validator.lock_file'); - } - /** * Data provider for ::testSecureHttpValidation(). * @@ -78,16 +59,10 @@ public function providerSecureHttpValidation(): array { * @dataProvider providerSecureHttpValidation */ public function testSecureHttpValidation(string $contents, array $expected_results): void { - $file = vfsStream::newFile('composer.json')->setContent($contents); - $this->vfsRoot->addChild($file); - - $active_dir = $this->vfsRoot->url(); - $locator = $this->prophesize(PathLocator::class); - $locator->getActiveDirectory()->willReturn($active_dir); - $locator->getProjectRoot()->willReturn($active_dir); - $locator->getWebRoot()->willReturn(''); - $locator->getVendorDirectory()->willReturn($active_dir); - $this->container->set('package_manager.path_locator', $locator->reveal()); + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getActiveDirectory(); + file_put_contents("$active_dir/composer.json", $contents); try { $this->createStage()->create(); diff --git a/core/modules/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php b/core/modules/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php index 3195b51d3a14164f46b2cae143bf0224dbe7dfb2..a972c7e8a417910106e55072b7842a82e0c06cf7 100644 --- a/core/modules/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php +++ b/core/modules/package_manager/tests/src/Kernel/DiskSpaceValidatorTest.php @@ -2,44 +2,17 @@ namespace Drupal\Tests\package_manager\Kernel; -use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\EventSubscriber\DiskSpaceValidator; use Drupal\package_manager\ValidationResult; use Drupal\Component\Utility\Bytes; /** - * @covers \Drupal\package_manager\EventSubscriber\DiskSpaceValidator + * @covers \Drupal\package_manager\Validator\DiskSpaceValidator * * @group package_manager */ class DiskSpaceValidatorTest extends PackageManagerKernelTestBase { - /** - * {@inheritdoc} - */ - public function register(ContainerBuilder $container) { - parent::register($container); - - // Replace the validator under test with a mocked version which can be - // rigged up to return specific values for various filesystem checks. - $container->getDefinition('package_manager.validator.disk_space') - ->setClass(TestDiskSpaceValidator::class); - } - - /** - * {@inheritdoc} - */ - protected function disableValidators(ContainerBuilder $container): void { - parent::disableValidators($container); - - // Disable the lock file and Composer settings validators, since in this - // test we are validating an imaginary file system which doesn't have any - // Composer files. - $container->removeDefinition('package_manager.validator.lock_file'); - $container->removeDefinition('package_manager.validator.composer_settings'); - } - /** * Data provider for ::testDiskSpaceValidation(). * @@ -47,17 +20,21 @@ protected function disableValidators(ContainerBuilder $container): void { * Sets of arguments to pass to the test method. */ public function providerDiskSpaceValidation(): array { - $root_insufficient = t('Drupal root filesystem "root" has insufficient space. There must be at least 1024 megabytes free.'); - $vendor_insufficient = t('Vendor filesystem "vendor" has insufficient space. There must be at least 1024 megabytes free.'); - $temp_insufficient = t('Directory "temp" has insufficient space. There must be at least 1024 megabytes free.'); + // These will be defined by ::createTestProject(). + $root = 'vfs://root/active'; + $vendor = "$root/vendor"; + + $root_insufficient = "Drupal root filesystem \"$root\" has insufficient space. There must be at least 1024 megabytes free."; + $vendor_insufficient = "Vendor filesystem \"$vendor\" has insufficient space. There must be at least 1024 megabytes free."; + $temp_insufficient = 'Directory "temp" has insufficient space. There must be at least 1024 megabytes free.'; $summary = t("There is not enough disk space to create a staging area."); return [ 'shared, vendor and temp sufficient, root insufficient' => [ TRUE, [ - 'root' => '1M', - 'vendor' => '2G', + $root => '1M', + $vendor => '2G', 'temp' => '4G', ], [ @@ -67,8 +44,8 @@ public function providerDiskSpaceValidation(): array { 'shared, root and vendor insufficient, temp sufficient' => [ TRUE, [ - 'root' => '1M', - 'vendor' => '2M', + $root => '1M', + $vendor => '2M', 'temp' => '2G', ], [ @@ -78,8 +55,8 @@ public function providerDiskSpaceValidation(): array { 'shared, vendor and root sufficient, temp insufficient' => [ TRUE, [ - 'root' => '2G', - 'vendor' => '4G', + $root => '2G', + $vendor => '4G', 'temp' => '1M', ], [ @@ -89,8 +66,8 @@ public function providerDiskSpaceValidation(): array { 'shared, root and temp insufficient, vendor sufficient' => [ TRUE, [ - 'root' => '1M', - 'vendor' => '2G', + $root => '1M', + $vendor => '2G', 'temp' => '2M', ], [ @@ -103,8 +80,8 @@ public function providerDiskSpaceValidation(): array { 'not shared, root insufficient, vendor and temp sufficient' => [ FALSE, [ - 'root' => '5M', - 'vendor' => '1G', + $root => '5M', + $vendor => '1G', 'temp' => '4G', ], [ @@ -114,8 +91,8 @@ public function providerDiskSpaceValidation(): array { 'not shared, vendor insufficient, root and temp sufficient' => [ FALSE, [ - 'root' => '2G', - 'vendor' => '10M', + $root => '2G', + $vendor => '10M', 'temp' => '4G', ], [ @@ -125,8 +102,8 @@ public function providerDiskSpaceValidation(): array { 'not shared, root and vendor sufficient, temp insufficient' => [ FALSE, [ - 'root' => '1G', - 'vendor' => '2G', + $root => '1G', + $vendor => '2G', 'temp' => '3M', ], [ @@ -136,8 +113,8 @@ public function providerDiskSpaceValidation(): array { 'not shared, root and vendor insufficient, temp sufficient' => [ FALSE, [ - 'root' => '500M', - 'vendor' => '75M', + $root => '500M', + $vendor => '75M', 'temp' => '2G', ], [ @@ -156,22 +133,17 @@ public function providerDiskSpaceValidation(): array { * @param bool $shared_disk * Whether the root and vendor directories are on the same logical disk. * @param array $free_space - * The free space that should be reported for various locations. The keys - * are the locations (only 'root', 'vendor', and 'temp' are supported), and - * the values are the space that should be reported, in a format that can be - * parsed by \Drupal\Component\Utility\Bytes::toNumber(). + * The free space that should be reported for various paths. The keys + * are the paths, and the values are the free space that should be reported, + * in a format that can be parsed by + * \Drupal\Component\Utility\Bytes::toNumber(). * @param \Drupal\package_manager\ValidationResult[] $expected_results * The expected validation results. * * @dataProvider providerDiskSpaceValidation */ public function testDiskSpaceValidation(bool $shared_disk, array $free_space, array $expected_results): void { - $path_locator = $this->prophesize('\Drupal\package_manager\PathLocator'); - $path_locator->getProjectRoot()->willReturn('root'); - $path_locator->getWebRoot()->willReturn(''); - $path_locator->getActiveDirectory()->willReturn('root'); - $path_locator->getVendorDirectory()->willReturn('vendor'); - $this->container->set('package_manager.path_locator', $path_locator->reveal()); + $this->createTestProject(); /** @var \Drupal\Tests\package_manager\Kernel\TestDiskSpaceValidator $validator */ $validator = $this->container->get('package_manager.validator.disk_space'); @@ -182,47 +154,3 @@ public function testDiskSpaceValidation(bool $shared_disk, array $free_space, ar } } - -/** - * A test version of the disk space validator. - */ -class TestDiskSpaceValidator extends DiskSpaceValidator { - - /** - * Whether the root and vendor directories are on the same logical disk. - * - * @var bool - */ - public $sharedDisk; - - /** - * The amount of free space, keyed by location. - * - * @var float[] - */ - public $freeSpace = []; - - /** - * {@inheritdoc} - */ - protected function stat(string $path): array { - return [ - 'dev' => $this->sharedDisk ? 'disk' : uniqid(), - ]; - } - - /** - * {@inheritdoc} - */ - protected function freeSpace(string $path): float { - return $this->freeSpace[$path]; - } - - /** - * {@inheritdoc} - */ - protected function temporaryDirectory(): string { - return 'temp'; - } - -} diff --git a/core/modules/package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php b/core/modules/package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php deleted file mode 100644 index 0489ad421300185aa4222b188dd7a3172fbefe5f..0000000000000000000000000000000000000000 --- a/core/modules/package_manager/tests/src/Kernel/ExcludedPathsSubscriberTest.php +++ /dev/null @@ -1,99 +0,0 @@ -<?php - -namespace Drupal\Tests\package_manager\Kernel; - -use Drupal\Core\Database\Connection; -use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber; - -/** - * @covers \Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber - * - * @group package_manager - */ -class ExcludedPathsSubscriberTest extends PackageManagerKernelTestBase { - - /** - * Data provider for ::testSqliteDatabaseExcluded(). - * - * @return array[] - * Sets of arguments to pass to the test method. - */ - public function providerSqliteDatabaseExcluded(): array { - $drupal_root = $this->getDrupalRoot(); - - return [ - 'relative path, in site directory' => [ - 'sites/example.com/db.sqlite', - [ - 'sites/example.com/db.sqlite', - 'sites/example.com/db.sqlite-shm', - 'sites/example.com/db.sqlite-wal', - ], - ], - 'relative path, at root' => [ - 'db.sqlite', - [ - 'db.sqlite', - 'db.sqlite-shm', - 'db.sqlite-wal', - ], - ], - 'absolute path, in site directory' => [ - $drupal_root . '/sites/example.com/db.sqlite', - [ - 'sites/example.com/db.sqlite', - 'sites/example.com/db.sqlite-shm', - 'sites/example.com/db.sqlite-wal', - ], - ], - 'absolute path, at root' => [ - $drupal_root . '/db.sqlite', - [ - 'db.sqlite', - 'db.sqlite-shm', - 'db.sqlite-wal', - ], - ], - ]; - } - - /** - * Tests that SQLite database paths are excluded from the staging area. - * - * The exclusion of SQLite databases from the staging area is functionally - * tested by \Drupal\Tests\package_manager\Functional\ExcludedPathsTest. The - * purpose of this test is to ensure that SQLite database paths are processed - * properly (e.g., converting an absolute path to a relative path) before - * being flagged for exclusion. - * - * @param string $database - * The path of the SQLite database, as set in the database connection - * options. - * @param string[] $expected_exclusions - * The database paths which should be flagged for exclusion. - * - * @dataProvider providerSqliteDatabaseExcluded - * - * @see \Drupal\Tests\package_manager\Functional\ExcludedPathsTest - */ - public function testSqliteDatabaseExcluded(string $database, array $expected_exclusions): void { - $connection = $this->prophesize(Connection::class); - $connection->driver()->willReturn('sqlite'); - $connection->getConnectionOptions()->willReturn(['database' => $database]); - - $subscriber = new ExcludedPathsSubscriber( - 'sites/default', - $this->container->get('package_manager.symfony_file_system'), - $this->container->get('stream_wrapper_manager'), - $connection->reveal(), - $this->container->get('package_manager.path_locator') - ); - - $event = new PreCreateEvent($this->createStage()); - $subscriber->ignoreCommonPaths($event); - // All of the expected exclusions should be flagged. - $this->assertEmpty(array_diff($expected_exclusions, $event->getExcludedPaths())); - } - -} diff --git a/core/modules/package_manager/tests/src/Kernel/ExcludedPathsTest.php b/core/modules/package_manager/tests/src/Kernel/ExcludedPathsTest.php index b9a8eadbe36a68e429f99ef60562c5b48319d4e7..776c0ff9a0b1737a4c9d78f5ebcbd3528ea682ec 100644 --- a/core/modules/package_manager/tests/src/Kernel/ExcludedPathsTest.php +++ b/core/modules/package_manager/tests/src/Kernel/ExcludedPathsTest.php @@ -3,10 +3,8 @@ namespace Drupal\Tests\package_manager\Kernel; use Drupal\Core\Database\Connection; -use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber; -use Drupal\package_manager\PathLocator; -use org\bovigo\vfs\vfsStream; /** * @covers \Drupal\package_manager\EventSubscriber\ExcludedPathsSubscriber @@ -16,153 +14,56 @@ class ExcludedPathsTest extends PackageManagerKernelTestBase { /** - * {@inheritdoc} + * The mocked SQLite database connection. + * + * @var \Drupal\Core\Database\Connection|\Prophecy\Prophecy\ObjectProphecy */ - protected function setUp(): void { - parent::setUp(); - - // Ensure that any staging directories created by TestStage are created - // in the virtual file system. - TestStage::$stagingRoot = $this->vfsRoot->url(); - - // We need to rebuild the container after setting a private file path, since - // the private stream wrapper is only registered if this setting is set. - // @see \Drupal\Core\CoreServiceProvider::register() - $this->setSetting('file_private_path', 'private'); - $kernel = $this->container->get('kernel'); - $kernel->rebuildContainer(); - $this->container = $kernel->getContainer(); - } + private $mockDatabase; /** * {@inheritdoc} */ - public function register(ContainerBuilder $container) { + protected function setUp(): void { + parent::setUp(); + // Normally, package_manager_bypass will disable all the actual staging // operations. In this case, we want to perform them so that we can be sure // that files are staged as expected. $this->setSetting('package_manager_bypass_stager', FALSE); + // The private stream wrapper is only registered if this setting is set. + // @see \Drupal\Core\CoreServiceProvider::register() + $this->setSetting('file_private_path', 'private'); - $container->getDefinition('package_manager.excluded_paths_subscriber') - ->setClass(TestExcludedPathsSubscriber::class); - - parent::register($container); - } - - /** - * {@inheritdoc} - */ - protected function disableValidators(ContainerBuilder $container): void { - parent::disableValidators($container); - - // Disable the disk space validator, since it tries to inspect the file - // system in ways that vfsStream doesn't support, like calling stat() and - // disk_free_space(). - $container->removeDefinition('package_manager.validator.disk_space'); + // Rebuild the container to make the new settings take effect. + $kernel = $this->container->get('kernel'); + $kernel->rebuildContainer(); + $this->container = $kernel->getContainer(); - // Disable the lock file and Composer settings validators, since in this - // test we have an imaginary file system without any Composer files. - $container->removeDefinition('package_manager.validator.lock_file'); + // Mock a SQLite database connection so we can test that the subscriber will + // exclude the database files. + $this->mockDatabase = $this->prophesize(Connection::class); + $this->mockDatabase->driver()->willReturn('sqlite'); } /** * Tests that certain paths are excluded from staging operations. */ public function testExcludedPaths(): void { - $site = [ - 'composer.json' => '{}', - 'private' => [ - 'ignore.txt' => 'This file should never be staged.', - ], - 'sites' => [ - 'default' => [ - 'services.yml' => <<<END -# This file should never be staged. -must_not_be: 'empty' -END, - 'settings.local.php' => <<<END -<?php - -/** - * @file - * This file should never be staged. - */ -END, - 'settings.php' => <<<END -<?php - -/** - * @file - * This file should never be staged. - */ -END, - 'stage.txt' => 'This file should be staged.', - ], - 'example.com' => [ - 'files' => [ - 'ignore.txt' => 'This file should never be staged.', - ], - 'db.sqlite' => 'This file should never be staged.', - 'db.sqlite-shm' => 'This file should never be staged.', - 'db.sqlite-wal' => 'This file should never be staged.', - 'services.yml' => <<<END -# This file should never be staged. -key: "value" -END, - 'settings.local.php' => <<<END -<?php - -/** - * @file - * This file should never be staged. - */ -END, - 'settings.php' => <<<END -<?php - -/** - * @file - * This file should never be staged. - */ -END, - ], - 'simpletest' => [ - 'ignore.txt' => 'This file should never be staged.', - ], - ], - 'vendor' => [ - '.htaccess' => '# This file should never be staged.', - 'web.config' => 'This file should never be staged.', - ], - ]; - vfsStream::create(['active' => $site], $this->vfsRoot); - - $active_dir = $this->vfsRoot->getChild('active')->url(); - - $path_locator = $this->prophesize(PathLocator::class); - $path_locator->getActiveDirectory()->willReturn($active_dir); - $path_locator->getProjectRoot()->willReturn($active_dir); - $path_locator->getWebRoot()->willReturn(''); - $path_locator->getVendorDirectory()->willReturn("$active_dir/vendor"); - $this->container->set('package_manager.path_locator', $path_locator->reveal()); + $this->createTestProject(); + $active_dir = $this->container->get('package_manager.path_locator') + ->getActiveDirectory(); $site_path = 'sites/example.com'; // Ensure that we are using directories within the fake site fixture for // public and private files. $this->setSetting('file_public_path', "$site_path/files"); - /** @var \Drupal\Tests\package_manager\Kernel\TestExcludedPathsSubscriber $subscriber */ - $subscriber = $this->container->get('package_manager.excluded_paths_subscriber'); - $subscriber->sitePath = $site_path; - // Mock a SQLite database connection to a file in the active directory. The // file should not be staged. - $database = $this->prophesize(Connection::class); - $database->driver()->willReturn('sqlite'); - $database->getConnectionOptions()->willReturn([ + $this->mockDatabase->getConnectionOptions()->willReturn([ 'database' => $site_path . '/db.sqlite', ]); - $subscriber->database = $database->reveal(); + $this->setUpSubscriber($site_path); $stage = $this->createStage(); $stage->create(); @@ -185,6 +86,9 @@ public function testExcludedPaths(): void { 'sites/default/settings.php', 'sites/default/settings.local.php', 'sites/default/services.yml', + // No git directories should be staged. + '.git/ignore.txt', + 'modules/example/.git/ignore.txt', ]; foreach ($ignore as $path) { $this->assertFileExists("$active_dir/$path"); @@ -192,6 +96,10 @@ public function testExcludedPaths(): void { } // A non-excluded file in the default site directory should be staged. $this->assertFileExists("$stage_dir/sites/default/stage.txt"); + // Regular module files should be staged. + $this->assertFileExists("$stage_dir/modules/example/example.info.yml"); + // Files that start with .git, but aren't actually .git, should be staged. + $this->assertFileExists("$stage_dir/.gitignore"); // A new file added to the staging area in an excluded directory, should not // be copied to the active directory. @@ -207,21 +115,94 @@ public function testExcludedPaths(): void { } } -} - -/** - * A test-only implementation of the excluded path event subscriber. - */ -class TestExcludedPathsSubscriber extends ExcludedPathsSubscriber { + /** + * Data provider for ::testSqliteDatabaseExcluded(). + * + * @return array[] + * Sets of arguments to pass to the test method. + */ + public function providerSqliteDatabaseExcluded(): array { + $drupal_root = $this->getDrupalRoot(); + + return [ + 'relative path, in site directory' => [ + 'sites/example.com/db.sqlite', + [ + 'sites/example.com/db.sqlite', + 'sites/example.com/db.sqlite-shm', + 'sites/example.com/db.sqlite-wal', + ], + ], + 'relative path, at root' => [ + 'db.sqlite', + [ + 'db.sqlite', + 'db.sqlite-shm', + 'db.sqlite-wal', + ], + ], + 'absolute path, in site directory' => [ + $drupal_root . '/sites/example.com/db.sqlite', + [ + 'sites/example.com/db.sqlite', + 'sites/example.com/db.sqlite-shm', + 'sites/example.com/db.sqlite-wal', + ], + ], + 'absolute path, at root' => [ + $drupal_root . '/db.sqlite', + [ + 'db.sqlite', + 'db.sqlite-shm', + 'db.sqlite-wal', + ], + ], + ]; + } /** - * {@inheritdoc} + * Tests that SQLite database paths are excluded from the staging area. + * + * The exclusion of SQLite databases from the staging area is functionally + * tested by \Drupal\Tests\package_manager\Functional\ExcludedPathsTest. The + * purpose of this test is to ensure that SQLite database paths are processed + * properly (e.g., converting an absolute path to a relative path) before + * being flagged for exclusion. + * + * @param string $database + * The path of the SQLite database, as set in the database connection + * options. + * @param string[] $expected_exclusions + * The database paths which should be flagged for exclusion. + * + * @dataProvider providerSqliteDatabaseExcluded */ - public $sitePath; + public function testSqliteDatabaseExcluded(string $database, array $expected_exclusions): void { + $this->mockDatabase->getConnectionOptions()->willReturn([ + 'database' => $database, + ]); + + $event = new PreCreateEvent($this->createStage()); + $this->setUpSubscriber(); + $this->container->get('package_manager.excluded_paths_subscriber')->ignoreCommonPaths($event); + // All of the expected exclusions should be flagged. + $this->assertEmpty(array_diff($expected_exclusions, $event->getExcludedPaths())); + } /** - * {@inheritdoc} + * Sets up the event subscriber with a mocked database and site path. + * + * @param string $site_path + * (optional) The site path. Defaults to 'sites/default'. */ - public $database; + private function setUpSubscriber(string $site_path = 'sites/default'): void { + $this->container->set('package_manager.excluded_paths_subscriber', new ExcludedPathsSubscriber( + $site_path, + $this->container->get('package_manager.symfony_file_system'), + $this->container->get('stream_wrapper_manager'), + $this->mockDatabase->reveal(), + $this->container->get('package_manager.path_locator') + )); + } } diff --git a/core/modules/package_manager/tests/src/Kernel/LockFileValidatorTest.php b/core/modules/package_manager/tests/src/Kernel/LockFileValidatorTest.php index c71a6d27e46af85c4cba3296a14554184ab09e98..c6cfeb535d4c043213216be339947822b2781aed 100644 --- a/core/modules/package_manager/tests/src/Kernel/LockFileValidatorTest.php +++ b/core/modules/package_manager/tests/src/Kernel/LockFileValidatorTest.php @@ -2,53 +2,34 @@ namespace Drupal\Tests\package_manager\Kernel; -use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreRequireEvent; -use Drupal\package_manager\EventSubscriber\LockFileValidator; -use Drupal\package_manager\PathLocator; +use Drupal\package_manager\Validator\LockFileValidator; use Drupal\package_manager\ValidationResult; -use org\bovigo\vfs\vfsStream; /** - * @coversDefaultClass \Drupal\package_manager\EventSubscriber\LockFileValidator + * @coversDefaultClass \Drupal\package_manager\Validator\LockFileValidator * * @group package_manager */ class LockFileValidatorTest extends PackageManagerKernelTestBase { /** - * {@inheritdoc} + * The path of the active directory in the virtual file system. + * + * @var string */ - protected function setUp(): void { - parent::setUp(); - - $vendor = vfsStream::newDirectory('vendor'); - $this->vfsRoot->addChild($vendor); - - $path_locator = $this->prophesize(PathLocator::class); - $path_locator->getActiveDirectory()->willReturn($this->vfsRoot->url()); - $path_locator->getProjectRoot()->willReturn($this->vfsRoot->url()); - $path_locator->getWebRoot()->willReturn(''); - $path_locator->getVendorDirectory()->willReturn($vendor->url()); - $this->container->set('package_manager.path_locator', $path_locator->reveal()); - } + private $activeDir; /** * {@inheritdoc} */ - protected function disableValidators(ContainerBuilder $container): void { - parent::disableValidators($container); - - // Disable the disk space validator, since it tries to inspect the file - // system in ways that vfsStream doesn't support, like calling stat() and - // disk_free_space(). - $container->removeDefinition('package_manager.validator.disk_space'); - - // Disable the Composer settings validator, since it tries to read Composer - // files that may not exist in this test. - $container->removeDefinition('package_manager.validator.composer_settings'); + protected function setUp(): void { + parent::setUp(); + $this->createTestProject(); + $this->activeDir = $this->container->get('package_manager.path_locator') + ->getActiveDirectory(); } /** @@ -57,6 +38,8 @@ protected function disableValidators(ContainerBuilder $container): void { * @covers ::storeHash */ public function testCreateWithNoLock(): void { + unlink($this->activeDir . '/composer.lock'); + $no_lock = ValidationResult::createError(['Could not hash the active lock file.']); $this->assertResults([$no_lock], PreCreateEvent::class); } @@ -68,12 +51,11 @@ public function testCreateWithNoLock(): void { * @covers ::deleteHash */ public function testCreateWithLock(): void { - $this->createActiveLockFile(); $this->assertResults([]); // Change the lock file to ensure the stored hash of the previous version // has been deleted. - $this->vfsRoot->getChild('composer.lock')->setContent('"changed"'); + file_put_contents($this->activeDir . '/composer.lock', 'changed'); $this->assertResults([]); } @@ -83,14 +65,12 @@ public function testCreateWithLock(): void { * @dataProvider providerValidateStageEvents */ public function testLockFileChanged(string $event_class): void { - $this->createActiveLockFile(); - // Add a listener with an extremely high priority to the same event that // should raise the validation error. Because the validator uses the default // priority of 0, this listener changes lock file before the validator // runs. $this->addListener($event_class, function () { - $this->vfsRoot->getChild('composer.lock')->setContent('"changed"'); + file_put_contents($this->activeDir . '/composer.lock', 'changed'); }); $result = ValidationResult::createError([ 'Stored lock file hash does not match the active lock file.', @@ -104,14 +84,12 @@ public function testLockFileChanged(string $event_class): void { * @dataProvider providerValidateStageEvents */ public function testLockFileDeleted(string $event_class): void { - $this->createActiveLockFile(); - // Add a listener with an extremely high priority to the same event that // should raise the validation error. Because the validator uses the default // priority of 0, this listener deletes lock file before the validator // runs. $this->addListener($event_class, function () { - $this->vfsRoot->removeChild('composer.lock'); + unlink($this->activeDir . '/composer.lock'); }); $result = ValidationResult::createError([ 'Could not hash the active lock file.', @@ -125,8 +103,6 @@ public function testLockFileDeleted(string $event_class): void { * @dataProvider providerValidateStageEvents */ public function testNoStoredHash(string $event_class): void { - $this->createActiveLockFile(); - $reflector = new \ReflectionClassConstant(LockFileValidator::class, 'STATE_KEY'); $state_key = $reflector->getValue(); @@ -160,14 +136,6 @@ public function providerValidateStageEvents(): array { ]; } - /** - * Creates a 'composer.lock' file in the active directory. - */ - private function createActiveLockFile(): void { - $lock_file = vfsStream::newFile('composer.lock')->setContent('{}'); - $this->vfsRoot->addChild($lock_file); - } - /** * Adds an event listener with the highest possible priority. * diff --git a/core/modules/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/core/modules/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php index e6e7b7504c069ddc200acb5dc51cacaff35c3941..7c770fe543ab76a735bbb95d5688774fc41553a8 100644 --- a/core/modules/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php +++ b/core/modules/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php @@ -5,10 +5,13 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\KernelTests\KernelTestBase; use Drupal\package_manager\Event\StageEvent; +use Drupal\package_manager\Validator\DiskSpaceValidator; use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageValidationException; +use Drupal\package_manager\PathLocator; use Drupal\package_manager\Stage; use Drupal\Tests\package_manager\Traits\ValidationTestTrait; +use org\bovigo\vfs\vfsStream; /** * Base class for kernel tests of Package Manager's functionality. @@ -119,6 +122,129 @@ protected function registerPostUpdateFunctions(): void { ->set('existing_updates', $updates); } + /** + * Creates a test project in a virtual file system. + * + * This will create two directories at the root of the virtual file system: + * 'active', which is the active directory containing a fake Drupal code base, + * and 'stage', which is the root directory used to stage changes. The path + * locator service will also be mocked so that it points to the test project. + */ + protected function createTestProject(): void { + $tree = [ + 'active' => [ + 'composer.json' => '{}', + 'composer.lock' => '{}', + '.git' => [ + 'ignore.txt' => 'This file should never be staged.', + ], + '.gitignore' => 'This file should be staged.', + 'private' => [ + 'ignore.txt' => 'This file should never be staged.', + ], + 'modules' => [ + 'example' => [ + 'example.info.yml' => 'This file should be staged.', + '.git' => [ + 'ignore.txt' => 'This file should never be staged.', + ], + ], + ], + 'sites' => [ + 'default' => [ + 'services.yml' => <<<END +# This file should never be staged. +must_not_be: 'empty' +END, + 'settings.local.php' => <<<END +<?php + +/** + * @file + * This file should never be staged. + */ +END, + 'settings.php' => <<<END +<?php + +/** + * @file + * This file should never be staged. + */ +END, + 'stage.txt' => 'This file should be staged.', + ], + 'example.com' => [ + 'files' => [ + 'ignore.txt' => 'This file should never be staged.', + ], + 'db.sqlite' => 'This file should never be staged.', + 'db.sqlite-shm' => 'This file should never be staged.', + 'db.sqlite-wal' => 'This file should never be staged.', + 'services.yml' => <<<END +# This file should never be staged. +key: "value" +END, + 'settings.local.php' => <<<END +<?php + +/** + * @file + * This file should never be staged. + */ +END, + 'settings.php' => <<<END +<?php + +/** + * @file + * This file should never be staged. + */ +END, + ], + 'simpletest' => [ + 'ignore.txt' => 'This file should never be staged.', + ], + ], + 'vendor' => [ + '.htaccess' => '# This file should never be staged.', + 'web.config' => 'This file should never be staged.', + ], + ], + 'stage' => [], + ]; + $root = vfsStream::create($tree, $this->vfsRoot)->url(); + $active_dir = "$root/active"; + TestStage::$stagingRoot = "$root/stage"; + + $path_locator = $this->prophesize(PathLocator::class); + $path_locator->getActiveDirectory()->willReturn($active_dir); + $path_locator->getProjectRoot()->willReturn($active_dir); + $path_locator->getWebRoot()->willReturn(''); + $path_locator->getVendorDirectory()->willReturn("$active_dir/vendor"); + + // We won't need the prophet anymore. + $path_locator = $path_locator->reveal(); + $this->container->set('package_manager.path_locator', $path_locator); + + // Since the path locator now points to a virtual file system, we need to + // replace the disk space validator with a test-only version that bypasses + // system calls, like disk_free_space() and stat(), which aren't supported + // by vfsStream. + $validator = new TestDiskSpaceValidator( + $this->container->get('package_manager.path_locator'), + $this->container->get('string_translation') + ); + // By default, the validator should report that the root, vendor, and + // temporary directories have basically infinite free space. + $validator->freeSpace = [ + $path_locator->getActiveDirectory() => PHP_INT_MAX, + $path_locator->getVendorDirectory() => PHP_INT_MAX, + $validator->temporaryDirectory() => PHP_INT_MAX, + ]; + $this->container->set('package_manager.validator.disk_space', $validator); + } + } /** @@ -156,3 +282,47 @@ protected function dispatch(StageEvent $event): void { } } + +/** + * A test version of the disk space validator to bypass system-level functions. + */ +class TestDiskSpaceValidator extends DiskSpaceValidator { + + /** + * Whether the root and vendor directories are on the same logical disk. + * + * @var bool + */ + public $sharedDisk = TRUE; + + /** + * The amount of free space, keyed by path. + * + * @var float[] + */ + public $freeSpace = []; + + /** + * {@inheritdoc} + */ + protected function stat(string $path): array { + return [ + 'dev' => $this->sharedDisk ? 'disk' : uniqid(), + ]; + } + + /** + * {@inheritdoc} + */ + protected function freeSpace(string $path): float { + return $this->freeSpace[$path]; + } + + /** + * {@inheritdoc} + */ + public function temporaryDirectory(): string { + return 'temp'; + } + +} diff --git a/core/modules/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php b/core/modules/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php index ea90029ca28aa001c035cb076bf868158b580dca..35605045ea2b3b4b50816f875400338e6ec8c85d 100644 --- a/core/modules/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php +++ b/core/modules/package_manager/tests/src/Kernel/PendingUpdatesValidatorTest.php @@ -6,7 +6,7 @@ use Drupal\package_manager\ValidationResult; /** - * @covers \Drupal\package_manager\EventSubscriber\PendingUpdatesValidator + * @covers \Drupal\package_manager\Validator\PendingUpdatesValidator * * @group package_manager */ diff --git a/core/modules/package_manager/tests/src/Kernel/StageEventsTest.php b/core/modules/package_manager/tests/src/Kernel/StageEventsTest.php index eb5f46c1b45bfd05691a3258960b5af4856082ef..8321d259ea454984a49942e1db021a5baa86a185 100644 --- a/core/modules/package_manager/tests/src/Kernel/StageEventsTest.php +++ b/core/modules/package_manager/tests/src/Kernel/StageEventsTest.php @@ -13,7 +13,6 @@ use Drupal\package_manager\Event\PreRequireEvent; use Drupal\package_manager\Event\StageEvent; use Drupal\package_manager\Exception\StageValidationException; -use Drupal\package_manager\Stage; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -44,16 +43,7 @@ class StageEventsTest extends PackageManagerKernelTestBase implements EventSubsc */ protected function setUp(): void { parent::setUp(); - - $this->stage = new Stage( - $this->container->get('package_manager.path_locator'), - $this->container->get('package_manager.beginner'), - $this->container->get('package_manager.stager'), - $this->container->get('package_manager.committer'), - $this->container->get('file_system'), - $this->container->get('event_dispatcher'), - $this->container->get('tempstore.shared') - ); + $this->stage = $this->createStage(); } /** diff --git a/core/modules/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php b/core/modules/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php index 21cf1b2007743ac6a29417d8dadaae6a0dc3d816..3686bd29af7fe3ca82e17c9297ee20407dfec6ea 100644 --- a/core/modules/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php +++ b/core/modules/package_manager/tests/src/Kernel/WritableFileSystemValidatorTest.php @@ -3,11 +3,9 @@ namespace Drupal\Tests\package_manager\Kernel; use Drupal\package_manager\Event\PreCreateEvent; -use Drupal\package_manager\EventSubscriber\WritableFileSystemValidator; +use Drupal\package_manager\Validator\WritableFileSystemValidator; use Drupal\package_manager\ValidationResult; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\package_manager\PathLocator; -use org\bovigo\vfs\vfsStream; /** * Unit tests the file system permissions validator. @@ -17,7 +15,7 @@ * * @see \Drupal\Tests\auto_updates\Build\CoreUpdateTest::assertReadOnlyFileSystemError() * - * @covers \Drupal\package_manager\EventSubscriber\WritableFileSystemValidator + * @covers \Drupal\package_manager\Validator\WritableFileSystemValidator * * @group package_manager */ @@ -39,16 +37,8 @@ public function register(ContainerBuilder $container) { * {@inheritdoc} */ protected function disableValidators(ContainerBuilder $container): void { - // Disable the disk space validator, since it tries to inspect the file - // system in ways that vfsStream doesn't support, like calling stat() and - // disk_free_space(). - $container->removeDefinition('package_manager.validator.disk_space'); - - // Disable the lock file and Composer settings validators, since in this - // test we are validating an imaginary file system which doesn't have any - // Composer files. - $container->removeDefinition('package_manager.validator.lock_file'); - $container->removeDefinition('package_manager.validator.composer_settings'); + // The parent method disables the validator we're testing, so we don't want + // to do anything here. } /** @@ -58,8 +48,9 @@ protected function disableValidators(ContainerBuilder $container): void { * Sets of arguments to pass to the test method. */ public function providerWritable(): array { - $root_error = t('The Drupal directory "vfs://root" is not writable.'); - $vendor_error = t('The vendor directory "vfs://root/vendor" is not writable.'); + // The root and vendor paths are defined by ::createTestProject(). + $root_error = 'The Drupal directory "vfs://root/active" is not writable.'; + $vendor_error = 'The vendor directory "vfs://root/active/vendor" is not writable.'; $summary = t('The file system is not writable.'); $writable_permission = 0777; $non_writable_permission = 0444; @@ -107,20 +98,16 @@ public function providerWritable(): array { * @dataProvider providerWritable */ public function testWritable(int $root_permissions, int $vendor_permissions, array $expected_results): void { - $root = vfsStream::setup('root', $root_permissions); - $vendor = vfsStream::newDirectory('vendor', $vendor_permissions); - $root->addChild($vendor); - - $path_locator = $this->prophesize(PathLocator::class); - $path_locator->getActiveDirectory()->willReturn($root->url()); - $path_locator->getProjectRoot()->willReturn($root->url()); - $path_locator->getWebRoot()->willReturn(''); - $path_locator->getVendorDirectory()->willReturn($vendor->url()); - $this->container->set('package_manager.path_locator', $path_locator->reveal()); + $this->createTestProject(); + // For reasons unclear, the built-in chmod() function doesn't seem to work + // when changing vendor permissions, so just call vfsStream's API directly. + $active_dir = $this->vfsRoot->getChild('active'); + $active_dir->chmod($root_permissions); + $active_dir->getChild('vendor')->chmod($vendor_permissions); /** @var \Drupal\Tests\package_manager\Kernel\TestWritableFileSystemValidator $validator */ $validator = $this->container->get('package_manager.validator.file_system'); - $validator->appRoot = $root->url(); + $validator->appRoot = $active_dir->url(); $this->assertResults($expected_results, PreCreateEvent::class); }