diff --git a/automatic_updates_extensions/src/ExtensionUpdater.php b/automatic_updates_extensions/src/ExtensionUpdater.php index 6e532e75d7a230beefd9649c29fc8f07efd75f27..7ee83c8b340218fc194231daf5ff5eb0e5da2bdc 100644 --- a/automatic_updates_extensions/src/ExtensionUpdater.php +++ b/automatic_updates_extensions/src/ExtensionUpdater.php @@ -51,7 +51,7 @@ class ExtensionUpdater extends Stage { $this->tempStore->set(static::TEMPSTORE_METADATA_KEY, [ 'packages' => $package_versions, ]); - return parent::create(); + return $this->create(); } /** diff --git a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php index 34598ecf423584ec187ad553d561743370bb3da5..c40ef7a63a1c4022db402f82c30df90244601931 100644 --- a/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php +++ b/automatic_updates_extensions/tests/src/Kernel/Validator/PackagesInstalledWithComposerValidatorTest.php @@ -16,57 +16,60 @@ use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsK */ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase { - /** - * The active directory in the virtual file system. - * - * @var string - */ - private $activeDir; - /** * {@inheritdoc} */ protected function setUp(): void { - // In this test, we don't focus on validating that the updated projects are - // secure and supported. Therefore, we need to disable the update release - // validator that validates updated projects are secure and supported. + // In this test, we don't care whether the updated projects are secure and + // supported. $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release'; - // In this test, we don't focus on validating that the updated projects are - // only themes or modules. Therefore, we need to disable the update packages - // type validator. + // We also don't care if the updated projects are themes and modules only. $this->disableValidators[] = 'automatic_updates_extensions.validator.packages_type'; parent::setUp(); - $this->activeDir = $this->container->get('package_manager.path_locator') + + $active_dir = $this->container->get('package_manager.path_locator') ->getProjectRoot(); + + $installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json'; + $this->assertFileIsReadable($installed); + copy($installed, $active_dir . '/vendor/composer/installed.json'); } /** * Data provider for testPreCreateException(). * - * @return array + * @return array[] * Test cases for testPreCreateException(). */ public function providerPreCreateException(): array { + $summary = t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'); + return [ - 'module not installed via composer' => [ + 'module not installed via Composer' => [ [ 'new_module' => '9.8.0', ], - [ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_module'], $summary), + ], ], - 'theme not installed via composer' => [ + 'theme not installed via Composer' => [ [ 'new_theme' => '9.8.0', ], - [ValidationResult::createError(['new_theme'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_theme'], $summary), + ], ], - 'profile not installed via composer' => [ + 'profile not installed via Composer' => [ [ 'new_profile' => '9.8.0', ], - [ValidationResult::createError(['new_profile'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_profile'], $summary), + ], ], - 'module_theme_profile_dependency_not_installed_via_composer' => [ + 'module, theme, profile, and library not installed via Composer' => [ [ 'new_module' => '9.8.0', 'new_theme' => '9.8.0', @@ -74,12 +77,10 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi 'new_dependency' => '9.8.0', ], [ - ValidationResult::createError( - ['new_module', 'new_theme', 'new_profile', 'new_dependency'], - t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:')), + ValidationResult::createError(['new_module', 'new_theme', 'new_profile', 'new_dependency'], $summary), ], ], - 'module_theme_profile_installed_via_composer' => [ + 'module, theme, and profile installed via Composer' => [ [ 'existing_module' => '9.8.1', 'existing_theme' => '9.8.1', @@ -87,18 +88,20 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi ], [], ], - 'existing module installed and new module not installed via composer' => [ + 'existing module installed and new module not installed via Composer' => [ [ 'existing_module' => '9.8.1', 'new_module' => '9.8.0', ], - [ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_module'], $summary), + ], ], ]; } /** - * Tests the packages installed with composer during pre-create. + * Tests the packages installed with Composer during pre-create. * * @param array $projects * The projects to install. @@ -108,11 +111,6 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi * @dataProvider providerPreCreateException */ public function testPreCreateException(array $projects, array $expected_results): void { - // Path of `active.installed.json` file. It will be used as the virtual - // project's active `vendor/composer/installed.json` file. - $active_installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json'; - $this->assertFileIsReadable($active_installed); - copy($active_installed, "$this->activeDir/vendor/composer/installed.json"); $this->assertUpdateResults($projects, $expected_results, PreCreateEvent::class); } @@ -123,32 +121,38 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi * Test cases for testPreApplyException(). */ public function providerPreApplyException(): array { + $summary = t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'); $fixtures_folder = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator'; + return [ - 'module not installed via composer' => [ + 'module not installed via Composer' => [ "$fixtures_folder/module_not_installed.staged.installed.json", - [ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_module'], $summary), + ], ], - 'theme not installed via composer' => [ + 'theme not installed via Composer' => [ "$fixtures_folder/theme_not_installed.staged.installed.json", - [ValidationResult::createError(['new_theme'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_theme'], $summary), + ], ], - 'profile not installed via composer' => [ + 'profile not installed via Composer' => [ "$fixtures_folder/profile_not_installed.staged.installed.json", - [ValidationResult::createError(['new_profile'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))], + [ + ValidationResult::createError(['new_profile'], $summary), + ], ], - // Dependency drupal/new_dependency of type 'drupal-library' will not show - // up in the error because it is not one of the covered types - // ('drupal-module', 'drupal-theme' or 'drupal-profile'). Module - // new_module1 will also not show up as it's name doesn't start with - // 'drupal/'. + // The `drupal/new_dependency` package won't show up in the error because + // its type is `drupal-library`, and the validator only considers the + // `drupal-module`, `drupal-theme`, and `drupal-profile` package types. + // The `not-drupal/new_module1` package won't show up either, even though + // its type is `drupal-module`, because it doesn't start with `drupal/`. // @see \Drupal\automatic_updates_extensions\Validator\PackagesInstalledWithComposerValidator - 'module_theme_profile_dependency_not_installed_via_composer' => [ + 'module, theme, and profile not installed via Composer' => [ "$fixtures_folder/module_theme_profile_dependency_not_installed.staged.installed.json", [ - ValidationResult::createError( - ['new_module', 'new_theme', 'new_profile'], - t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:')), + ValidationResult::createError(['new_module', 'new_theme', 'new_profile'], $summary), ], ], ]; @@ -166,18 +170,14 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi * @dataProvider providerPreApplyException */ public function testPreApplyException(string $staged_installed, array $expected_results): void { - // Path of `active.installed.json` file. It will be used as the virtual - // project's active `vendor/composer/installed.json` file. - $active_installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json'; - $this->assertFileIsReadable($active_installed); $this->assertFileIsReadable($staged_installed); - copy($active_installed, "$this->activeDir/vendor/composer/installed.json"); + $listener = function (PreApplyEvent $event) use ($staged_installed): void { $stage_dir = $event->getStage()->getStageDirectory(); copy($staged_installed, $stage_dir . "/vendor/composer/installed.json"); }; $this->container->get('event_dispatcher')->addListener(PreApplyEvent::class, $listener, 1000); - $this->assertUpdateResults([], $expected_results, PreApplyEvent::class); + $this->assertUpdateResults(['my_module' => '9.8.1'], $expected_results, PreApplyEvent::class); } } diff --git a/package_manager/tests/modules/package_manager_bypass/src/Beginner.php b/package_manager/tests/modules/package_manager_bypass/src/Beginner.php index 6509c0fcf1873ab5857ceb92eea2c1ad7c77f308..c746f1322ee0fa4585ee5dfec0bf12653fa76248 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/Beginner.php +++ b/package_manager/tests/modules/package_manager_bypass/src/Beginner.php @@ -11,13 +11,14 @@ use PhpTuf\ComposerStager\Domain\Value\PathList\PathListInterface; /** * Defines an update beginner which doesn't do anything. */ -class Beginner extends InvocationRecorderBase implements BeginnerInterface { +class Beginner extends BypassedStagerServiceBase implements BeginnerInterface { /** * {@inheritdoc} */ public function begin(PathInterface $activeDir, PathInterface $stagingDir, ?PathListInterface $exclusions = NULL, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = ProcessRunnerInterface::DEFAULT_TIMEOUT): void { $this->saveInvocationArguments($activeDir, $stagingDir, $exclusions, $timeout); + $this->copyFixtureFilesTo($stagingDir); } } diff --git a/package_manager/tests/modules/package_manager_bypass/src/BypassedStagerServiceBase.php b/package_manager/tests/modules/package_manager_bypass/src/BypassedStagerServiceBase.php new file mode 100644 index 0000000000000000000000000000000000000000..53015287233e66a1abb5b07709535ca683c3e098 --- /dev/null +++ b/package_manager/tests/modules/package_manager_bypass/src/BypassedStagerServiceBase.php @@ -0,0 +1,97 @@ +<?php + +namespace Drupal\package_manager_bypass; + +use Drupal\Core\State\StateInterface; +use PhpTuf\ComposerStager\Domain\Value\Path\PathInterface; +use Symfony\Component\Filesystem\Filesystem; + +/** + * Records information about method invocations. + * + * This can be used by functional tests to ensure that the bypassed Composer + * Stager services were called as expected. Kernel and unit tests should use + * regular mocks instead. + */ +abstract class BypassedStagerServiceBase { + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * The Symfony file system service. + * + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $fileSystem; + + /** + * Constructs an InvocationRecorderBase object. + * + * @param \Drupal\Core\State\StateInterface $state + * The state service. + * @param \Symfony\Component\Filesystem\Filesystem $file_system + * The Symfony file system service. + */ + public function __construct(StateInterface $state, Filesystem $file_system) { + $this->state = $state; + $this->fileSystem = $file_system; + } + + /** + * Sets a path to be mirrored into a destination by the main class method. + * + * @param string|null $path + * A path to mirror into a destination directory when the main class method + * is called, or NULL to disable. + * + * @see ::copyFixtureFilesTo() + */ + public static function setFixturePath(?string $path): void { + \Drupal::state()->set(static::class . ' fixture', $path); + } + + /** + * If a fixture path has been set, mirrors it to the given path. + * + * @param \PhpTuf\ComposerStager\Domain\Value\Path\PathInterface $destination + * The path to which the fixture files should be mirrored. + */ + protected function copyFixtureFilesTo(PathInterface $destination): void { + $fixturePath = $this->state->get(static::class . ' fixture'); + + if ($fixturePath && is_dir($fixturePath)) { + $this->fileSystem->mirror($fixturePath, $destination->resolve(), NULL, [ + 'override' => TRUE, + 'delete' => TRUE, + ]); + } + } + + /** + * Returns the arguments from every invocation of the main class method. + * + * @return array[] + * The arguments from every invocation of the main class method. + */ + public function getInvocationArguments(): array { + return $this->state->get(static::class . ' arguments', []); + } + + /** + * Records the arguments from an invocation of the main class method. + * + * @param mixed ...$arguments + * The arguments that the main class method was called with. + */ + protected function saveInvocationArguments(...$arguments): void { + $invocations = $this->getInvocationArguments(); + $invocations[] = $arguments; + $this->state->set(static::class . ' arguments', $invocations); + } + +} diff --git a/package_manager/tests/modules/package_manager_bypass/src/Committer.php b/package_manager/tests/modules/package_manager_bypass/src/Committer.php index ea528824a9c4537e4966e664d072c53175572eb3..eb8f50ce640df9b36cd3dcb633251d2175f5f8d5 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/Committer.php +++ b/package_manager/tests/modules/package_manager_bypass/src/Committer.php @@ -11,13 +11,24 @@ use PhpTuf\ComposerStager\Domain\Value\PathList\PathListInterface; /** * Defines an update committer which doesn't do any actual committing. */ -class Committer extends InvocationRecorderBase implements CommitterInterface { +class Committer extends BypassedStagerServiceBase implements CommitterInterface { /** * {@inheritdoc} */ public function commit(PathInterface $stagingDir, PathInterface $activeDir, ?PathListInterface $exclusions = NULL, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = ProcessRunnerInterface::DEFAULT_TIMEOUT): void { $this->saveInvocationArguments($stagingDir, $activeDir, $exclusions, $timeout); + $this->copyFixtureFilesTo($activeDir); + } + + /** + * {@inheritdoc} + */ + public static function setFixturePath(?string $path): void { + // We haven't yet encountered a situation where we need the committer to + // copy fixture files to the active directory, but when we do, go ahead and + // remove this entire method. + throw new \BadMethodCallException('This is not implemented yet.'); } } diff --git a/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php b/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php deleted file mode 100644 index edc1075a9aaf37fbf3f34835a1ea8a58df2d9bc6..0000000000000000000000000000000000000000 --- a/package_manager/tests/modules/package_manager_bypass/src/InvocationRecorderBase.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -namespace Drupal\package_manager_bypass; - -/** - * Records information about method invocations. - * - * This can be used by functional tests to ensure that the bypassed Composer - * Stager services were called as expected. Kernel and unit tests should use - * regular mocks instead. - */ -abstract class InvocationRecorderBase { - - /** - * Returns the arguments from every invocation of the main class method. - * - * @return array[] - * The arguments from every invocation of the main class method. - */ - public function getInvocationArguments(): array { - return \Drupal::state()->get(static::class, []); - } - - /** - * Records the arguments from an invocation of the main class method. - * - * @param mixed ...$arguments - * The arguments that the main class method was called with. - */ - protected function saveInvocationArguments(...$arguments): void { - $invocations = $this->getInvocationArguments(); - $invocations[] = $arguments; - \Drupal::state()->set(static::class, $invocations); - } - -} diff --git a/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php b/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php index d62221fd1fb361da803384a83475340215e32072..51a0d5d66e73ec0b5ea80dc7b8079fb837596814 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php +++ b/package_manager/tests/modules/package_manager_bypass/src/PackageManagerBypassServiceProvider.php @@ -5,6 +5,7 @@ namespace Drupal\package_manager_bypass; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderBase; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Filesystem\Filesystem; /** * Defines services to bypass Package Manager's core functionality. @@ -17,21 +18,18 @@ class PackageManagerBypassServiceProvider extends ServiceProviderBase { public function alter(ContainerBuilder $container) { parent::alter($container); - $container->getDefinition('package_manager.beginner') - ->setClass(Beginner::class) - ->setArguments([]); - $container->getDefinition('package_manager.stager') - ->setClass(Stager::class) - ->setArguments([]); - - $container->register('package_manager_bypass.committer') - ->setClass(Committer::class) - ->setPublic(FALSE) - ->setDecoratedService('package_manager.committer') - ->setArguments([ - new Reference('package_manager_bypass.committer.inner'), - ]) - ->setProperty('_serviceId', 'package_manager.committer'); + $services = [ + 'package_manager.beginner' => Beginner::class, + 'package_manager.stager' => Stager::class, + 'package_manager.committer' => Committer::class, + ]; + $arguments = [ + new Reference('state'), + new Reference(Filesystem::class), + ]; + foreach ($services as $id => $class) { + $container->getDefinition($id)->setClass($class)->setArguments($arguments); + } } } diff --git a/package_manager/tests/modules/package_manager_bypass/src/Stager.php b/package_manager/tests/modules/package_manager_bypass/src/Stager.php index 38a67fb3e7e6e21591691040393831efdbe75084..b6adfaf835b6bd714b702f47316795c35f83652a 100644 --- a/package_manager/tests/modules/package_manager_bypass/src/Stager.php +++ b/package_manager/tests/modules/package_manager_bypass/src/Stager.php @@ -2,6 +2,7 @@ namespace Drupal\package_manager_bypass; +use Composer\Json\JsonFile; use PhpTuf\ComposerStager\Domain\Core\Stager\StagerInterface; use PhpTuf\ComposerStager\Domain\Service\ProcessOutputCallback\ProcessOutputCallbackInterface; use PhpTuf\ComposerStager\Domain\Service\ProcessRunner\ProcessRunnerInterface; @@ -10,13 +11,36 @@ use PhpTuf\ComposerStager\Domain\Value\Path\PathInterface; /** * Defines an update stager which doesn't actually do anything. */ -class Stager extends InvocationRecorderBase implements StagerInterface { +class Stager extends BypassedStagerServiceBase implements StagerInterface { /** * {@inheritdoc} */ public function stage(array $composerCommand, PathInterface $activeDir, PathInterface $stagingDir, ?ProcessOutputCallbackInterface $callback = NULL, ?int $timeout = ProcessRunnerInterface::DEFAULT_TIMEOUT): void { $this->saveInvocationArguments($composerCommand, $stagingDir); + $this->copyFixtureFilesTo($stagingDir); + + // If desired, simulate a change to the lock file (e.g., as a result of + // running `composer update`). + $lockFile = new JsonFile($stagingDir->resolve() . '/composer.lock'); + $changeLockFile = $this->state->get(static::class . ' lock', TRUE); + + if ($changeLockFile && $lockFile->exists()) { + $data = $lockFile->read(); + $data['_time'] = microtime(); + $lockFile->write($data); + } + } + + /** + * Sets whether or not ::stage() should simulate a change in the lock file. + * + * @param bool $value + * (optional) Whether or not to simulate a change in the lock file when + * ::stage() is called. Defaults to TRUE. + */ + public static function setLockFileShouldChange(bool $value = TRUE): void { + \Drupal::state()->set(static::class . ' lock', $value); } } diff --git a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.info.yml b/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.info.yml deleted file mode 100644 index aaea8bb04c56bfe89613cf558d1b26ca64c361ee..0000000000000000000000000000000000000000 --- a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.info.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: 'Package Manager Test Fixture' -description: 'Provides a mechanism for functional tests to stage fixture files.' -type: module -package: Testing -dependencies: - - automatic_updates:package_manager diff --git a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.services.yml b/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.services.yml deleted file mode 100644 index 394a1bb9d254e50d3fe97bae6904e100503dd2ac..0000000000000000000000000000000000000000 --- a/package_manager/tests/modules/package_manager_test_fixture/package_manager_test_fixture.services.yml +++ /dev/null @@ -1,8 +0,0 @@ -services: - package_manager_test_fixture.stager: - class: Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager - arguments: - - '@state' - - '@Symfony\Component\Filesystem\Filesystem' - tags: - - { name: event_subscriber } diff --git a/package_manager/tests/modules/package_manager_test_fixture/src/EventSubscriber/FixtureStager.php b/package_manager/tests/modules/package_manager_test_fixture/src/EventSubscriber/FixtureStager.php deleted file mode 100644 index fd5ecb712492fea746fbf5d1c204e279d9b1038e..0000000000000000000000000000000000000000 --- a/package_manager/tests/modules/package_manager_test_fixture/src/EventSubscriber/FixtureStager.php +++ /dev/null @@ -1,106 +0,0 @@ -<?php - -namespace Drupal\package_manager_test_fixture\EventSubscriber; - -use Drupal\Core\State\StateInterface; -use Drupal\package_manager\Event\PostRequireEvent; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Filesystem\Filesystem; - -/** - * Defines an event subscriber which copies certain files into the staging area. - * - * This is most useful in conjunction with package_manager_bypass, which quietly - * turns all Composer Stager operations into no-ops. In such cases, no staging - * area will be physically created, but if a test needs to simulate certain - * conditions in a staging area without actually staging the active code base, - * this event subscriber is the way to do it. - */ -class FixtureStager implements EventSubscriberInterface { - - /** - * The state service. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - - /** - * The Symfony file system service. - * - * @var \Symfony\Component\Filesystem\Filesystem - */ - protected $fileSystem; - - /** - * Constructs a FixtureStager. - * - * @param \Drupal\Core\State\StateInterface $state - * The state service. - * @param \Symfony\Component\Filesystem\Filesystem $file_system - * The Symfony file system service. - */ - public function __construct(StateInterface $state, Filesystem $file_system) { - $this->state = $state; - $this->fileSystem = $file_system; - } - - /** - * Copies files from a fixture into the staging area. - * - * Tests which use this functionality are responsible for cleaning up the - * staging area. - * - * @param \Drupal\package_manager\Event\PostRequireEvent $event - * The event object. - * - * @see \Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase::tearDown() - */ - public function copyFilesFromFixture(PostRequireEvent $event): void { - [$fixturePath, $changeLock] = $this->state->get(static::class); - - if ($fixturePath && is_dir($fixturePath)) { - $destination = $event->getStage()->getStageDirectory(); - - $this->fileSystem->mirror($fixturePath, $destination, NULL, [ - 'override' => TRUE, - 'delete' => TRUE, - ]); - - // Modify the lock file in the staging area, to simulate that a package - // was added, updated, or removed. Otherwise, tests must remember to - // disable the lock file validator. - // @see \Drupal\package_manager\Validator\LockFileValidator - $lock = $destination . '/composer.lock'; - if ($changeLock && file_exists($lock)) { - $data = file_get_contents($lock); - $data = json_decode($data); - $data->_time = microtime(); - file_put_contents($lock, json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); - } - } - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - return [ - PostRequireEvent::class => 'copyFilesFromFixture', - ]; - } - - /** - * Sets the path of the fixture to copy into the staging area. - * - * @param string $path - * The path of the fixture to copy into the staging area. - * @param bool $change_lock - * (optional) Whether to change the lock file, in order to simulate the - * addition, updating, or removal of a package. Defaults to TRUE. - */ - public static function setFixturePath(string $path, bool $change_lock = TRUE): void { - \Drupal::state()->set(static::class, [$path, $change_lock]); - } - -} diff --git a/package_manager/tests/src/Kernel/LockFileValidatorTest.php b/package_manager/tests/src/Kernel/LockFileValidatorTest.php index 803ca066ab8bbdfafd88b22311881b16876cf00d..02905e82758cb1a7591fb845dffc48ecb0aa8835 100644 --- a/package_manager/tests/src/Kernel/LockFileValidatorTest.php +++ b/package_manager/tests/src/Kernel/LockFileValidatorTest.php @@ -7,7 +7,7 @@ use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreRequireEvent; use Drupal\package_manager\Validator\LockFileValidator; use Drupal\package_manager\ValidationResult; -use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager; +use Drupal\package_manager_bypass\Stager; /** * @coversDefaultClass \Drupal\package_manager\Validator\LockFileValidator @@ -123,10 +123,8 @@ class LockFileValidatorTest extends PackageManagerKernelTestBase { * Tests validation when the staged and active lock files are identical. */ public function testApplyWithNoChange(): void { - // Ensure the lock file is not changed when the active directory is copied - // into the virtual staging area. - // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager - FixtureStager::setFixturePath($this->activeDir, FALSE); + // Leave the staged lock file alone. + Stager::setLockFileShouldChange(FALSE); $result = ValidationResult::createError([ 'There are no pending Composer operations.', diff --git a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php index 3d42bf13bb4832f8fb03ae04ba0b8d8f1740b8c8..281ce40da27cb21b1ded340a9784132ee4ddab0c 100644 --- a/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php +++ b/package_manager/tests/src/Kernel/PackageManagerKernelTestBase.php @@ -10,7 +10,7 @@ use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\PathLocator; use Drupal\package_manager\Stage; -use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager; +use Drupal\package_manager_bypass\Beginner; use Drupal\Tests\package_manager\Traits\ValidationTestTrait; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStreamDirectory; @@ -34,7 +34,6 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase { protected static $modules = [ 'package_manager', 'package_manager_bypass', - 'package_manager_test_fixture', ]; /** @@ -213,9 +212,8 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase { $active_dir = $active_dir->url(); $path_locator = $this->mockPathLocator($active_dir); - // Ensure that the active directory is copied into the virtual staging area, - // even if Package Manager's operations are bypassed. - FixtureStager::setFixturePath($active_dir); + // Ensure the active directory will be copied into the virtual staging area. + Beginner::setFixturePath($active_dir); // 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 @@ -289,11 +287,23 @@ trait TestStageTrait { */ public function __construct(...$arguments) { parent::__construct(...$arguments); - $mirror = new \ReflectionClass(Stage::class); - $this->tempStore->set($mirror->getConstant('TEMPSTORE_STAGING_ROOT_KEY'), PackageManagerKernelTestBase::$testStagingRoot); $this->pathFactory = new TestPathFactory(); } + /** + * {@inheritdoc} + */ + public function create(?int $timeout = 300): string { + // Ensure that tests which need to successively create multiple stages are + // always using the same staging root, since the stored value may be deleted + // if the stage encounters an error during pre-create. + // @see \Drupal\package_manager\Stage::markAsAvailable() + $constant = new \ReflectionClassConstant(Stage::class, 'TEMPSTORE_STAGING_ROOT_KEY'); + $this->tempStore->set($constant->getValue(), PackageManagerKernelTestBase::$testStagingRoot); + + return parent::create($timeout); + } + /** * {@inheritdoc} */ diff --git a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php index 24ca0cb409f4e6505f9fa7f04b1537eacee8ac10..36007e400db4b7749cce6b785ce5f8ec9d122bf3 100644 --- a/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php +++ b/package_manager/tests/src/Kernel/PathExcluder/GitExcluderTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\package_manager\Kernel\PathExcluder; +use Drupal\package_manager_bypass\Beginner; use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase; /** @@ -37,6 +38,11 @@ class GitExcluderTest extends PackageManagerKernelTestBase { mkdir($unreadable_dir, 0000); $this->assertDirectoryIsNotReadable($unreadable_dir); + // Don't mirror the active directory into the virtual staging area, since + // the active directory contains an unreadable directory which will cause + // an exception. + Beginner::setFixturePath(NULL); + $this->createStage()->create(); } diff --git a/package_manager/tests/src/Kernel/StageOwnershipTest.php b/package_manager/tests/src/Kernel/StageOwnershipTest.php index 21011a7578fe58bef8e4d54ee931dab2701f8d6a..ccb4073793dcaa3e8eef3472b4ca3bcca127aea0 100644 --- a/package_manager/tests/src/Kernel/StageOwnershipTest.php +++ b/package_manager/tests/src/Kernel/StageOwnershipTest.php @@ -8,6 +8,7 @@ use Drupal\package_manager\Event\PostDestroyEvent; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Exception\StageException; use Drupal\package_manager\Exception\StageOwnershipException; +use Drupal\package_manager_bypass\Stager; use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber; use Drupal\Tests\user\Traits\UserCreationTrait; use Psr\Log\Test\TestLogger; @@ -180,6 +181,12 @@ class StageOwnershipTest extends PackageManagerKernelTestBase { 'postApply' => [], 'destroy' => [], ]; + // Since we deliberately don't call create() on the stages we create as + // we loop through the life cycle methods, ensure that the active directory + // is mirrored into the staging area when a package is required. + $active_dir = $this->container->get('package_manager.path_locator') + ->getProjectRoot(); + Stager::setFixturePath($active_dir); foreach ($callbacks as $method => $arguments) { // Create a new stage instance for each method. $this->createStage()->claim($stage_id)->$method(...$arguments); diff --git a/package_manager/tests/src/Kernel/StageTest.php b/package_manager/tests/src/Kernel/StageTest.php index 76c5d8a86c9314962968c9cf29355cc1fc9f9f11..ac3e5c104d623437353ab34cbe184838415e991a 100644 --- a/package_manager/tests/src/Kernel/StageTest.php +++ b/package_manager/tests/src/Kernel/StageTest.php @@ -11,6 +11,7 @@ use Drupal\package_manager\Event\PostApplyEvent; use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\StageEvent; use Drupal\package_manager\Exception\StageException; +use Drupal\package_manager_bypass\Beginner; /** * @coversDefaultClass \Drupal\package_manager\Stage @@ -30,10 +31,6 @@ class StageTest extends PackageManagerKernelTestBase { * {@inheritdoc} */ protected function setUp(): void { - // Disable the symlink validator, since this test doesn't use a virtual - // project, but the running code base may have symlinks that don't affect - // the test. - $this->disableValidators[] = 'package_manager.validator.symlink'; parent::setUp(); $this->installConfig('system'); @@ -70,6 +67,9 @@ class StageTest extends PackageManagerKernelTestBase { // Even though we're using a virtual project, we want to test what happens // when we aren't. static::$testStagingRoot = NULL; + // Don't mirror the active directory from the virtual project into the + // real file system. + Beginner::setFixturePath(NULL); $stage = $this->createStage(); $id = $stage->create(); @@ -191,6 +191,7 @@ class StageTest extends PackageManagerKernelTestBase { $stage = $this->createStage(); $stage->create(); + $stage->require(['ext-json:*']); if ($expect_exception) { $this->expectException(StageException::class); $this->expectExceptionMessage('Cannot destroy the staging area while it is being applied to the active directory.'); @@ -230,6 +231,7 @@ class StageTest extends PackageManagerKernelTestBase { $stage = $this->createStage(); $stage->create(); + $stage->require(['ext-json:*']); $stage->apply(); } @@ -239,6 +241,7 @@ class StageTest extends PackageManagerKernelTestBase { public function testTimeouts(): void { $stage = $this->createStage(); $stage->create(420); + $stage->require(['ext-json:*']); $stage->apply(); $timeouts = [ diff --git a/package_manager/tests/src/Kernel/SymlinkValidatorTest.php b/package_manager/tests/src/Kernel/SymlinkValidatorTest.php index 5e462dd444207ee917327bd0e45dd8bb96305109..14b7f0592d5798ebfb7facf8d43f16fce2aa17b5 100644 --- a/package_manager/tests/src/Kernel/SymlinkValidatorTest.php +++ b/package_manager/tests/src/Kernel/SymlinkValidatorTest.php @@ -56,10 +56,6 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase { $stage = $this->createStage(); $stage->create(); - // Simulate updating a package. This will copy the active directory into - // the (virtual) staging area. - // @see ::createVirtualProject() - // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture() $stage->require(['composer/semver:^3']); // @see \Drupal\Tests\package_manager\Kernel\TestSymlinkValidator::isLink() @@ -89,10 +85,6 @@ class SymlinkValidatorTest extends PackageManagerKernelTestBase { $stage = $this->createStage(); $stage->create(); - // Simulate updating a package. This will copy the active directory into - // the (virtual) staging area. - // @see ::createVirtualProject() - // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture() $stage->require(['composer/semver:^3']); $active_dir = $this->container->get('package_manager.path_locator') diff --git a/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php b/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php index 3d80b39d74211adfa1726c2d53f460d3f52fd430..0c1f93fde723c9c379ab1d831c1ac2a90be2264f 100644 --- a/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php +++ b/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php @@ -14,18 +14,18 @@ trait PackageManagerBypassTestTrait { * The expected number of times an update was staged. */ private function assertUpdateStagedTimes(int $attempted_times): void { - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $beginner */ + /** @var \Drupal\package_manager_bypass\BypassedStagerServiceBase $beginner */ $beginner = $this->container->get('package_manager.beginner'); $this->assertCount($attempted_times, $beginner->getInvocationArguments()); - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */ + /** @var \Drupal\package_manager_bypass\BypassedStagerServiceBase $stager */ $stager = $this->container->get('package_manager.stager'); // If an update was attempted, then there will be two calls to the stager: // one to change the constraints in composer.json, and another to actually // update the installed dependencies. $this->assertCount($attempted_times * 2, $stager->getInvocationArguments()); - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $committer */ + /** @var \Drupal\package_manager_bypass\BypassedStagerServiceBase $committer */ $committer = $this->container->get('package_manager.committer'); $this->assertEmpty($committer->getInvocationArguments()); } diff --git a/tests/src/Functional/AvailableUpdatesReportTest.php b/tests/src/Functional/AvailableUpdatesReportTest.php index 2052512d4bc88e687afaac86667f8fb62c12a270..327a3fe351f9857a0895ba46b7a1113719b604ff 100644 --- a/tests/src/Functional/AvailableUpdatesReportTest.php +++ b/tests/src/Functional/AvailableUpdatesReportTest.php @@ -23,7 +23,6 @@ class AvailableUpdatesReportTest extends AutomaticUpdatesFunctionalTestBase { 'block', 'automatic_updates', 'automatic_updates_test', - 'package_manager_test_fixture', ]; /** diff --git a/tests/src/Functional/ReadinessValidationTest.php b/tests/src/Functional/ReadinessValidationTest.php index 942d28f22be0b87c488879446a026900df4b5cd1..ef351dee02bf33165fc486cd1d36aa0a4e8f430f 100644 --- a/tests/src/Functional/ReadinessValidationTest.php +++ b/tests/src/Functional/ReadinessValidationTest.php @@ -8,7 +8,7 @@ use Drupal\automatic_updates_test\Datetime\TestTime; use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1; use Drupal\automatic_updates_test2\EventSubscriber\TestSubscriber2; use Drupal\Core\Url; -use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager; +use Drupal\package_manager_bypass\Stager; use Drupal\system\SystemManager; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; use Drupal\Tests\Traits\Core\CronRunTrait; @@ -399,7 +399,6 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { $this->container->get('module_installer')->install([ 'automatic_updates', 'automatic_updates_test', - 'package_manager_test_fixture', ]); // Because all actual staging operations are bypassed by // package_manager_bypass (enabled by the parent class), disable these @@ -423,7 +422,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { // readiness check (without storing the results), and the checker is no // longer raising an error. $this->drupalGet('/admin/modules/automatic-update'); - FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); + Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); $assert_session->buttonExists('Update'); // Ensure that the previous results are still displayed on another admin // page, to confirm that the updater form is not discarding the previous diff --git a/tests/src/Functional/UpdateLockTest.php b/tests/src/Functional/UpdateLockTest.php index df5d5ad8ed952468362bd8ffc271df41acb2ea1e..3b0df121b482407415616967164432dd8600b477 100644 --- a/tests/src/Functional/UpdateLockTest.php +++ b/tests/src/Functional/UpdateLockTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\automatic_updates\Functional; -use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager; +use Drupal\package_manager_bypass\Stager; /** * Tests that only one Automatic Update operation can be performed at a time. @@ -22,7 +22,6 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase { protected static $modules = [ 'automatic_updates', 'automatic_updates_test', - 'package_manager_test_fixture', ]; /** @@ -53,7 +52,7 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase { // We should be able to get partway through an update without issue. $this->drupalLogin($user_1); $this->drupalGet('/admin/modules/automatic-update'); - FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); + Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); $page->pressButton('Update'); $this->checkForMetaRefresh(); $this->assertUpdateReady('9.8.1'); diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php index 3cb2c6f8aff3ecab29eaf58a8fd3f090cabcd6b6..809beccce8ed9349fe40385c807f71eb38252d40 100644 --- a/tests/src/Functional/UpdaterFormTest.php +++ b/tests/src/Functional/UpdaterFormTest.php @@ -9,7 +9,7 @@ use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\ValidationResult; use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1; -use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager; +use Drupal\package_manager_bypass\Stager; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; use Drupal\Tests\package_manager\Traits\PackageManagerBypassTestTrait; @@ -35,7 +35,6 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { 'block', 'automatic_updates', 'automatic_updates_test', - 'package_manager_test_fixture', ]; /** @@ -336,7 +335,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $this->checkForUpdates(); $this->drupalGet('/admin/modules/automatic-update'); - FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); + Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); $page->pressButton('Update to 9.8.1'); $this->checkForMetaRefresh(); $this->assertUpdateStagedTimes(1); @@ -471,7 +470,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $page = $this->getSession()->getPage(); $this->drupalGet('/admin/modules/automatic-update'); - FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); + Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); // The warning should be visible. $assert_session = $this->assertSession(); $assert_session->pageTextContains(reset($messages)); @@ -572,7 +571,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $cached_message = $this->setAndAssertCachedMessage(); $this->drupalGet($update_form_url); - FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); + Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); $assert_session->pageTextNotContains($cached_message); $page->pressButton('Update to 9.8.1'); $this->checkForMetaRefresh(); @@ -601,7 +600,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $page = $this->getSession()->getPage(); $this->drupalGet('/admin/modules/automatic-update'); - FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); + Stager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1'); $page->pressButton('Update to 9.8.1'); $this->checkForMetaRefresh(); $this->assertUpdateStagedTimes(1); diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php index 115f024f87bde49478fa0d39e17d9c1ca6cd2892..7133ec51d72d863560802ea7a6e4ed3f0026829e 100644 --- a/tests/src/Kernel/CronUpdaterTest.php +++ b/tests/src/Kernel/CronUpdaterTest.php @@ -54,15 +54,6 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { * {@inheritdoc} */ protected function setUp(): void { - // Because package_manager_bypass is enabled, a staging directory will not - // actually exist. Therefore, we need to disable these validators because - // they attempt to compare the active and stage directories. - $this->disableValidators[] = 'automatic_updates.validator.staged_database_updates'; - $this->disableValidators[] = 'automatic_updates.staged_projects_validator'; - $this->disableValidators[] = 'automatic_updates.validator.scaffold_file_permissions'; - // Since staging operations are bypassed, ignore any symbolic links in the - // running code base. - $this->disableValidators[] = 'package_manager.validator.symlink'; parent::setUp(); $this->logger = new TestLogger(); diff --git a/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php index 8f4e0530c8b6f01e61c3a5be00c480e5430e32bf..396fb0cd5e82510bc227771f543f79d2d424e7b4 100644 --- a/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/ScaffoldFilePermissionsValidatorTest.php @@ -296,10 +296,6 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas touch($this->activeDir . '/sites/default/deleted.txt'); touch($this->activeDir . '/foo.txt'); - // Simulate updating Drupal core. This will copy the active directory into - // the (virtual) staging area. - // @see ::createVirtualProject() - // @see \Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager::copyFilesFromFixture() $updater = $this->container->get('automatic_updates.updater'); $updater->begin(['drupal' => '9.8.1']); $updater->stage();