Skip to content
Snippets Groups Projects
Unverified Commit 3cf29653 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3487826 by catch: package_manager kernel tests are slow

(cherry picked from commit fa75872d)
parent 42402ecb
Branches
Tags
23 merge requests!12079Issue #3523476 by matthiasm11: Add empty check on operator,!12024Fix: DocBlock comment for return value of Drupal\Core\Database\Connection::transactionManager(),!11974Draft: Issue #3495165 by catch, joeyroth, berdir, texas-bronius: Better warning...,!11934Issue #3520997: DefaultLazyPluginCollection unnecessarily instantiates plugins when sorting collection,!11887Issue #3520065: The migrate Row class API is incomplete,!11636Draft: Issue #3515643 by macsim: fieldNameExists method is inconsistent,!11515Issue #3480419 by mondrake, smustgrave, catch: Method...,!11380Issue #3490698 by catch, spokje: Bump MINIMUM_STABILITY back to 'stable' when...,!11281Use Drupal Core Leadership terminology in MAINTAINERS.txt,!11239Issue #3507548: Allow workspace changes listing to show all items, without a pager,!11238Fix issue #3051797,!11213Issue #3506743 by tomislav.matokovic: Increasing the color contrast for the navigation block title against the background of the navigation sidebar to at least 4.5:1,!11147Draft: Try to avoid manually setting required cache contexts,!11108Issue #3490298 by nicxvan: Profiles can be missed in OOP hooks,!11093Drupal on MongoDB 11.1.x,!11017Issue #3502540: Add date filter for moderated content.,!11009Issue #3486972 migrate feed icon,!10999Cleaning up Taxonomy hooks and updating baseline.,!10977Issue #3501457: Fix path used in a A11y Test Admin,!10881Issue #3489329 by mfb, casey: symfony/http-foundation commit 32310ff breaks PathValidator,!10570Issue #3494197: Convert Twig engine hooks,!10567Issue #3494154: Index is not added if entity doesn't support revisions,!10548Revert "Issue #3478621 by catch, longwave, nicxvan: Add filecache to OOP hook attribute parsing"
Pipeline #357434 canceled
Pipeline: drupal

#357436

    Showing
    with 749 additions and 466 deletions
    ......@@ -182,7 +182,7 @@ variables:
    '⚙️️ PHPUnit Kernel':
    <<: [ *with-composer, *run-tests, *default-job-settings ]
    parallel: 4
    parallel: 5
    variables:
    TESTSUITE: PHPUnit-Kernel
    KUBERNETES_CPU_REQUEST: "8"
    ......
    ......@@ -14,12 +14,13 @@ final class ActiveFixtureManipulator extends FixtureManipulator {
    /**
    * {@inheritdoc}
    */
    public function commitChanges(?string $dir = NULL): void {
    public function commitChanges(?string $dir = NULL, bool $validate_composer = FALSE): self {
    if ($dir) {
    throw new \UnexpectedValueException("$dir cannot be specific for a ActiveFixtureManipulator instance");
    }
    $dir = \Drupal::service(PathLocator::class)->getProjectRoot();
    parent::doCommitChanges($dir);
    return $this;
    }
    }
    ......@@ -303,8 +303,10 @@ public function setCorePackageVersion(string $version): self {
    *
    * @param array $additional_config
    * The configuration to add.
    * @param bool $update_lock
    * Whether to run composer update --lock. Defaults to FALSE.
    */
    public function addConfig(array $additional_config): self {
    public function addConfig(array $additional_config, bool $update_lock = FALSE): self {
    if (empty($additional_config)) {
    throw new \InvalidArgumentException('No config to add.');
    }
    ......@@ -330,17 +332,23 @@ public function addConfig(array $additional_config): self {
    $command[] = $value;
    $this->runComposerCommand($command);
    }
    $this->runComposerCommand(['update', '--lock']);
    if ($update_lock) {
    $this->runComposerCommand(['update', '--lock']);
    }
    return $this;
    }
    /**
    * Commits the changes to the directory.
    *
    * @param string $dir
    * The directory to commit the changes to.
    */
    public function commitChanges(string $dir): void {
    public function commitChanges(string $dir): self {
    $this->doCommitChanges($dir);
    $this->committed = TRUE;
    return $this;
    }
    /**
    ......@@ -367,7 +375,11 @@ final protected function doCommitChanges(string $dir): void {
    }
    $this->committed = TRUE;
    $this->committingChanges = FALSE;
    $this->validateComposer();
    }
    public function updateLock(): self {
    $this->runComposerCommand(['update', '--lock']);
    return $this;
    }
    /**
    ......@@ -623,8 +635,12 @@ private function addRepository(array $package): string {
    /**
    * Sets up the path repos at absolute paths.
    *
    * @param bool $composer_refresh
    * Whether to run composer update --lock && composer install. Defaults to
    * FALSE.
    */
    public function setUpRepos(): void {
    public function setUpRepos($composer_refresh = FALSE): void {
    $fs = new SymfonyFileSystem();
    $path_repo_base = \Drupal::state()->get(self::PATH_REPO_STATE_KEY);
    if (empty($path_repo_base)) {
    ......@@ -638,8 +654,10 @@ public function setUpRepos(): void {
    // repos at the absolute path.
    $composer_json = file_get_contents($this->dir . '/packages.json');
    assert(file_put_contents($this->dir . '/packages.json', str_replace('../path_repos/', "$path_repo_base/", $composer_json)) !== FALSE);
    $this->runComposerCommand(['update', '--lock']);
    $this->runComposerCommand(['install']);
    if ($composer_refresh) {
    $this->runComposerCommand(['update', '--lock']);
    $this->runComposerCommand(['install']);
    }
    }
    }
    ......@@ -61,7 +61,7 @@ public function begin(PathInterface $activeDir, PathInterface $stagingDir, ?Path
    /**
    * {@inheritdoc}
    */
    public function commitChanges(string $dir): void {
    public function commitChanges(string $dir, bool $validate_composer = FALSE): self {
    throw new \BadMethodCallException('::commitChanges() should not be called directly in StageFixtureManipulator().');
    }
    ......
    ......@@ -25,7 +25,7 @@ public function testPreCreate(): void {
    "drupal/dummy_scaffolding",
    "drupal/dummy_scaffolding_2",
    ],
    ])->commitChanges();
    ])->commitChanges()->updateLock();
    $result = ValidationResult::createError(
    [
    ......@@ -47,7 +47,7 @@ public function testPreApply(): void {
    'extra.drupal-scaffold.allowed-packages' => [
    "drupal/dummy_scaffolding",
    ],
    ]);
    ], TRUE);
    $result = ValidationResult::createError(
    [
    ......
    ......@@ -204,6 +204,7 @@ public function testErrorDuringPreApply(int $in_active, int $in_stage, array $ex
    }
    if ($in_active !== static::ABSENT) {
    $active_manipulator->commitChanges();
    $active_manipulator->updateLock();
    }
    // Simulate in stage.
    ......
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    /**
    * @covers \Drupal\package_manager\Validator\ComposerPluginsValidator
    * @group package_manager
    * @group #slow
    * @internal
    */
    class ComposerPluginsValidatorComplexInvalidTest extends ComposerPluginsValidatorTestBase {
    /**
    * Tests composer plugins are validated during pre-create.
    *
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests composer plugins are validated during pre-apply.
    *
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests additional composer plugins can be trusted during pre-create.
    *
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationAfterTrustingDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationAfterTrustingDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests additional composer plugins can be trusted during pre-apply.
    *
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationAfterTrustingDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationAfterTrustingDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results);
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Drupal\fixture_manipulator\ActiveFixtureManipulator;
    use Drupal\package_manager\Event\PreApplyEvent;
    use Drupal\package_manager\Event\PreCreateEvent;
    use Drupal\package_manager\Exception\StageEventException;
    use Drupal\package_manager\ValidationResult;
    /**
    * @covers \Drupal\package_manager\Validator\ComposerPluginsValidator
    * @group package_manager
    * @internal
    */
    class ComposerPluginsValidatorInsecureTest extends PackageManagerKernelTestBase {
    /**
    * Tests `config.allow-plugins: true` fails validation during pre-create.
    */
    public function testInsecureConfigurationFailsValidationPreCreate(): void {
    $active_manipulator = new ActiveFixtureManipulator();
    $active_manipulator->addConfig(['allow-plugins' => TRUE]);
    $active_manipulator->commitChanges();
    $expected_results = [
    ValidationResult::createError(
    [
    new TranslatableMarkup('All composer plugins are allowed because <code>config.allow-plugins</code> is configured to <code>true</code>. This is an unacceptable security risk.'),
    ],
    ),
    ];
    $this->assertStatusCheckResults($expected_results);
    $this->assertResults($expected_results, PreCreateEvent::class);
    }
    /**
    * Tests `config.allow-plugins: true` fails validation during pre-apply.
    */
    public function testInsecureConfigurationFailsValidationPreApply(): void {
    $stage_manipulator = $this->getStageFixtureManipulator();
    $stage_manipulator->addConfig(['allow-plugins' => TRUE]);
    $expected_results = [
    ValidationResult::createError(
    [
    new TranslatableMarkup('All composer plugins are allowed because <code>config.allow-plugins</code> is configured to <code>true</code>. This is an unacceptable security risk.'),
    ],
    ),
    ];
    $this->assertResults($expected_results, PreApplyEvent::class);
    }
    /**
    * Tests adding a plugin that's not allowed by the allow-plugins config.
    *
    * The exception that this test looks for is not necessarily triggered by
    * ComposerPluginsValidator; Composer will exit with an error if there is an
    * installed plugin that is not allowed by the `allow-plugins` config. In
    * practice, this means that whichever validator is the first one to do a
    * Composer operation (via ComposerInspector) will get the exception -- it
    * may or may not be ComposerPluginsValidator.
    *
    * This test is here to ensure that Composer's behavior remains consistent,
    * even if we're not explicitly testing ComposerPluginsValidator here.
    */
    public function testAddDisallowedPlugin(): void {
    $this->getStageFixtureManipulator()
    ->addPackage([
    'name' => 'composer/plugin-c',
    'version' => '16.4',
    'type' => 'composer-plugin',
    'require' => ['composer-plugin-api' => '*'],
    'extra' => ['class' => 'AnyClass'],
    ]);
    $expected_message = "composer/plugin-c contains a Composer plugin which is blocked by your allow-plugins config.";
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    try {
    // We are trying to add package plugin-c but not allowing it in config,
    // so we expect the operation to fail on PreApplyEvent.
    $stage->apply();
    }
    catch (StageEventException $e) {
    // Processing is required because the error message we get from Composer
    // contains multiple white spaces at the start or end of line.
    $this->assertStringContainsString($expected_message, preg_replace('/\s\s+/', '', $e->getMessage()));
    $this->assertInstanceOf(PreApplyEvent::class, $e->event);
    }
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    /**
    * @covers \Drupal\package_manager\Validator\ComposerPluginsValidator
    * @group package_manager
    * @group #slow
    * @internal
    */
    class ComposerPluginsValidatorSimpleInvalidTest extends ComposerPluginsValidatorTestBase {
    /**
    * Tests composer plugins are validated during pre-create.
    *
    * @dataProvider providerSimpleInvalidCases
    */
    public function testValidationDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests composer plugins are validated during pre-apply.
    *
    * @dataProvider providerSimpleInvalidCases
    */
    public function testValidationDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests additional composer plugins can be trusted during pre-create.
    *
    * @dataProvider providerSimpleInvalidCases
    */
    public function testValidationAfterTrustingDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationAfterTrustingDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests additional composer plugins can be trusted during pre-apply.
    *
    * @dataProvider providerSimpleInvalidCases
    */
    public function testValidationAfterTrustingDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationAfterTrustingDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results);
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    /**
    * @covers \Drupal\package_manager\Validator\ComposerPluginsValidator
    * @group #slow
    * @group package_manager
    * @internal
    */
    class ComposerPluginsValidatorSimpleValidTest extends ComposerPluginsValidatorTestBase {
    /**
    * Tests composer plugins are validated during pre-create.
    *
    * @dataProvider providerSimpleValidCases
    */
    public function testValidationDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results);
    }
    /**
    * Tests composer plugins are validated during pre-apply.
    *
    * @dataProvider providerSimpleValidCases
    */
    public function testValidationDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $this->doTestValidationDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results);
    }
    }
    ......@@ -9,60 +9,18 @@
    use Drupal\fixture_manipulator\ActiveFixtureManipulator;
    use Drupal\package_manager\Event\PreApplyEvent;
    use Drupal\package_manager\Event\PreCreateEvent;
    use Drupal\package_manager\Exception\StageEventException;
    use Drupal\package_manager\ValidationResult;
    /**
    * @covers \Drupal\package_manager\Validator\ComposerPluginsValidator
    * @group package_manager
    * @internal
    */
    class ComposerPluginsValidatorTest extends PackageManagerKernelTestBase {
    /**
    * Tests `config.allow-plugins: true` fails validation during pre-create.
    */
    public function testInsecureConfigurationFailsValidationPreCreate(): void {
    $active_manipulator = new ActiveFixtureManipulator();
    $active_manipulator->addConfig(['allow-plugins' => TRUE]);
    $active_manipulator->commitChanges();
    $expected_results = [
    ValidationResult::createError(
    [
    new TranslatableMarkup('All composer plugins are allowed because <code>config.allow-plugins</code> is configured to <code>true</code>. This is an unacceptable security risk.'),
    ],
    ),
    ];
    $this->assertStatusCheckResults($expected_results);
    $this->assertResults($expected_results, PreCreateEvent::class);
    }
    /**
    * Tests `config.allow-plugins: true` fails validation during pre-apply.
    */
    public function testInsecureConfigurationFailsValidationPreApply(): void {
    $stage_manipulator = $this->getStageFixtureManipulator();
    $stage_manipulator->addConfig(['allow-plugins' => TRUE]);
    $expected_results = [
    ValidationResult::createError(
    [
    new TranslatableMarkup('All composer plugins are allowed because <code>config.allow-plugins</code> is configured to <code>true</code>. This is an unacceptable security risk.'),
    ],
    ),
    ];
    $this->assertResults($expected_results, PreApplyEvent::class);
    }
    class ComposerPluginsValidatorTestBase extends PackageManagerKernelTestBase {
    /**
    * Tests composer plugins are validated during pre-create.
    *
    * @dataProvider providerSimpleValidCases
    * @dataProvider providerSimpleInvalidCases
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    protected function doTestValidationDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $active_manipulator = new ActiveFixtureManipulator();
    if ($composer_config_to_add) {
    $active_manipulator->addConfig($composer_config_to_add);
    ......@@ -78,12 +36,8 @@ public function testValidationDuringPreCreate(array $composer_config_to_add, arr
    /**
    * Tests composer plugins are validated during pre-apply.
    *
    * @dataProvider providerSimpleValidCases
    * @dataProvider providerSimpleInvalidCases
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    protected function doTestValidationDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $stage_manipulator = $this->getStageFixtureManipulator();
    if ($composer_config_to_add) {
    $stage_manipulator->addConfig($composer_config_to_add);
    ......@@ -102,53 +56,10 @@ public function testValidationDuringPreApply(array $composer_config_to_add, arra
    $this->assertResults($expected_results, PreApplyEvent::class);
    }
    /**
    * Tests adding a plugin that's not allowed by the allow-plugins config.
    *
    * The exception that this test looks for is not necessarily triggered by
    * ComposerPluginsValidator; Composer will exit with an error if there is an
    * installed plugin that is not allowed by the `allow-plugins` config. In
    * practice, this means that whichever validator is the first one to do a
    * Composer operation (via ComposerInspector) will get the exception -- it
    * may or may not be ComposerPluginsValidator.
    *
    * This test is here to ensure that Composer's behavior remains consistent,
    * even if we're not explicitly testing ComposerPluginsValidator here.
    */
    public function testAddDisallowedPlugin(): void {
    $this->getStageFixtureManipulator()
    ->addPackage([
    'name' => 'composer/plugin-c',
    'version' => '16.4',
    'type' => 'composer-plugin',
    'require' => ['composer-plugin-api' => '*'],
    'extra' => ['class' => 'AnyClass'],
    ]);
    $expected_message = "composer/plugin-c contains a Composer plugin which is blocked by your allow-plugins config.";
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    try {
    // We are trying to add package plugin-c but not allowing it in config,
    // so we expect the operation to fail on PreApplyEvent.
    $stage->apply();
    }
    catch (StageEventException $e) {
    // Processing is required because the error message we get from Composer
    // contains multiple white spaces at the start or end of line.
    $this->assertStringContainsString($expected_message, preg_replace('/\s\s+/', '', $e->getMessage()));
    $this->assertInstanceOf(PreApplyEvent::class, $e->event);
    }
    }
    /**
    * Tests additional composer plugins can be trusted during pre-create.
    *
    * @dataProvider providerSimpleInvalidCases
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationAfterTrustingDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    protected function doTestValidationAfterTrustingDuringPreCreate(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $expected_results_without_composer_plugin_violations = array_filter(
    $expected_results,
    fn (ValidationResult $v) => !$v->summary || !str_contains(strtolower($v->summary->getUntranslatedString()), 'unsupported composer plugin'),
    ......@@ -161,16 +72,13 @@ public function testValidationAfterTrustingDuringPreCreate(array $composer_confi
    // Reuse the test logic that does not trust additional packages, but with
    // updated expected results.
    $this->testValidationDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results_without_composer_plugin_violations);
    $this->doTestValidationDuringPreCreate($composer_config_to_add, $packages_to_add, $expected_results_without_composer_plugin_violations);
    }
    /**
    * Tests additional composer plugins can be trusted during pre-apply.
    *
    * @dataProvider providerSimpleInvalidCases
    * @dataProvider providerComplexInvalidCases
    */
    public function testValidationAfterTrustingDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    protected function doTestValidationAfterTrustingDuringPreApply(array $composer_config_to_add, array $packages_to_add, array $expected_results): void {
    $expected_results_without_composer_plugin_violations = array_filter(
    $expected_results,
    fn (ValidationResult $v) => !$v->summary || !str_contains(strtolower($v->summary->getUntranslatedString()), 'unsupported composer plugin'),
    ......@@ -183,7 +91,7 @@ public function testValidationAfterTrustingDuringPreApply(array $composer_config
    // Reuse the test logic that does not trust additional packages, but with
    // updated expected results.
    $this->testValidationDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results_without_composer_plugin_violations);
    $this->doTestValidationDuringPreApply($composer_config_to_add, $packages_to_add, $expected_results_without_composer_plugin_violations);
    }
    /**
    ......
    ......@@ -98,7 +98,7 @@ public static function providerComposerSettingsValidation(): array {
    * @dataProvider providerComposerSettingsValidation
    */
    public function testComposerSettingsValidation(array $config, array $expected_results): void {
    (new ActiveFixtureManipulator())->addConfig($config)->commitChanges();
    (new ActiveFixtureManipulator())->addConfig($config)->commitChanges()->updateLock();
    $this->assertStatusCheckResults($expected_results);
    $this->assertResults($expected_results, PreCreateEvent::class);
    }
    ......@@ -114,7 +114,7 @@ public function testComposerSettingsValidation(array $config, array $expected_re
    * @dataProvider providerComposerSettingsValidation
    */
    public function testComposerSettingsValidationDuringPreApply(array $config, array $expected_results): void {
    $this->getStageFixtureManipulator()->addConfig($config);
    $this->getStageFixtureManipulator()->addConfig($config, TRUE);
    $this->assertResults($expected_results, PreApplyEvent::class);
    }
    ......
    ......@@ -17,6 +17,7 @@
    /**
    * @coversDefaultClass \Drupal\package_manager\Validator\PhpTufValidator
    * @group package_manager
    * @group #slow
    * @internal
    */
    class PhpTufValidatorTest extends PackageManagerKernelTestBase {
    ......@@ -49,7 +50,8 @@ protected function setUp(): void {
    'class' => 'PhpTufComposerPlugin',
    ],
    ])
    ->commitChanges();
    ->commitChanges()
    ->updateLock();
    }
    /**
    ......@@ -66,7 +68,8 @@ public function testPluginInstalledAndConfiguredProperly(): void {
    public function testPluginNotInstalledInProjectRoot(): void {
    (new ActiveFixtureManipulator())
    ->removePackage(PhpTufValidator::PLUGIN_NAME)
    ->commitChanges();
    ->commitChanges()
    ->updateLock();
    $messages = [
    t('The <code>php-tuf/composer-integration</code> plugin is not installed.'),
    ......@@ -190,7 +193,7 @@ public static function providerInvalidConfigurationInStage(): \Generator {
    * @dataProvider providerInvalidConfiguration
    */
    public function testInvalidConfigurationInProjectRoot(array $config, array $expected_messages): void {
    (new ActiveFixtureManipulator())->addConfig($config)->commitChanges();
    (new ActiveFixtureManipulator())->addConfig($config)->commitChanges()->updateLock();
    $result = ValidationResult::createError($expected_messages, t('The active directory is not protected by PHP-TUF, which is required to use Package Manager securely.'));
    $this->assertStatusCheckResults([$result]);
    ......@@ -213,7 +216,8 @@ public function testInvalidConfigurationInStage(array $config, array $expected_m
    $listener = function (PreRequireEvent|PreApplyEvent $event) use ($config): void {
    (new FixtureManipulator())
    ->addConfig($config)
    ->commitChanges($event->stage->getStageDirectory());
    ->commitChanges($event->stage->getStageDirectory())
    ->updateLock();
    };
    $this->addEventTestListener($listener, $event_class);
    ......
    ......@@ -4,20 +4,12 @@
    namespace Drupal\Tests\package_manager\Kernel;
    use Drupal\Component\Datetime\Time;
    use Drupal\Core\DependencyInjection\ContainerBuilder;
    use Drupal\Core\Extension\ModuleUninstallValidatorException;
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Drupal\package_manager\Event\CollectPathsToExcludeEvent;
    use Drupal\package_manager\Event\PostApplyEvent;
    use Drupal\package_manager\Event\PostCreateEvent;
    use Drupal\package_manager\Event\PostRequireEvent;
    use Drupal\package_manager\Event\PreApplyEvent;
    use Drupal\package_manager\Event\PreCreateEvent;
    use Drupal\package_manager\Event\PreRequireEvent;
    use Drupal\package_manager\Event\StageEvent;
    use Drupal\package_manager\Exception\ApplyFailedException;
    use Drupal\package_manager\Exception\StageEventException;
    use Drupal\package_manager\Exception\StageException;
    use Drupal\package_manager\Exception\StageFailureMarkerException;
    use Drupal\package_manager\FailureMarker;
    ......@@ -26,21 +18,14 @@
    use Drupal\package_manager_bypass\LoggingBeginner;
    use Drupal\package_manager_bypass\LoggingCommitter;
    use Drupal\package_manager_bypass\NoOpStager;
    use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber;
    use PhpTuf\ComposerStager\API\Core\BeginnerInterface;
    use PhpTuf\ComposerStager\API\Core\CommitterInterface;
    use PhpTuf\ComposerStager\API\Core\StagerInterface;
    use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
    use PhpTuf\ComposerStager\API\Exception\InvalidArgumentException;
    use PhpTuf\ComposerStager\API\Exception\PreconditionException;
    use PhpTuf\ComposerStager\API\Precondition\Service\PreconditionInterface;
    use Psr\Log\LogLevel;
    use ColinODell\PsrTestLogger\TestLogger;
    /**
    * @coversDefaultClass \Drupal\package_manager\StageBase
    * @covers \Drupal\package_manager\PackageManagerUninstallValidator
    * @group package_manager
    * @group #slow
    * @internal
    */
    class StageBaseTest extends PackageManagerKernelTestBase {
    ......@@ -56,66 +41,12 @@ class StageBaseTest extends PackageManagerKernelTestBase {
    public function register(ContainerBuilder $container): void {
    parent::register($container);
    $container->getDefinition('datetime.time')
    ->setClass(TestTime::class);
    // Since this test adds arbitrary event listeners that aren't services, we
    // need to ensure they will persist even if the container is rebuilt when
    // staged changes are applied.
    $container->getDefinition('event_dispatcher')->addTag('persist');
    }
    /**
    * Data provider for testLoggedOnError().
    *
    * @return string[][]
    * The test cases.
    */
    public static function providerLoggedOnError(): array {
    return [
    [PreCreateEvent::class],
    [PostCreateEvent::class],
    [PreRequireEvent::class],
    [PostRequireEvent::class],
    [PreApplyEvent::class],
    [PostApplyEvent::class],
    ];
    }
    /**
    * @covers \Drupal\package_manager\StageBase::dispatch
    *
    * @dataProvider providerLoggedOnError
    *
    * @param string $event_class
    * The event class to throw an exception on.
    */
    public function testLoggedOnError(string $event_class): void {
    $exception = new \Exception("This should be logged!");
    TestSubscriber::setException($exception, $event_class);
    $stage = $this->createStage();
    $logger = new TestLogger();
    $stage->setLogger($logger);
    try {
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    $stage->apply();
    $stage->postApply();
    $this->fail('Expected an exception to be thrown, but none was.');
    }
    catch (StageEventException $e) {
    $this->assertInstanceOf($event_class, $e->event);
    $predicate = function (array $record) use ($e): bool {
    $context = $record['context'];
    return $context['@message'] === $e->getMessage() && str_contains($context['@backtrace_string'], 'testLoggedOnError');
    };
    $this->assertTrue($logger->hasRecordThatPasses($predicate, LogLevel::ERROR));
    }
    }
    /**
    * @covers ::getMetadata
    * @covers ::setMetadata
    ......@@ -200,147 +131,6 @@ public function testUncreatedGetStageDirectory(): void {
    $this->createStage()->getStageDirectory();
    }
    /**
    * Data provider for testDestroyDuringApply().
    *
    * @return mixed[][]
    * The test cases.
    */
    public static function providerDestroyDuringApply(): array {
    $error_message_while_being_applied = 'Cannot destroy the stage directory while it is being applied to the active directory.';
    return [
    'force destroy on pre-apply, fresh' => [
    PreApplyEvent::class,
    TRUE,
    1,
    $error_message_while_being_applied,
    ],
    'destroy on pre-apply, fresh' => [
    PreApplyEvent::class,
    FALSE,
    1,
    $error_message_while_being_applied,
    ],
    'force destroy on pre-apply, stale' => [
    PreApplyEvent::class,
    TRUE,
    7200,
    'Stage directory does not exist',
    ],
    'destroy on pre-apply, stale' => [
    PreApplyEvent::class,
    FALSE,
    7200,
    'Stage directory does not exist',
    ],
    'force destroy on post-apply, fresh' => [
    PostApplyEvent::class,
    TRUE,
    1,
    $error_message_while_being_applied,
    ],
    'destroy on post-apply, fresh' => [
    PostApplyEvent::class,
    FALSE,
    1,
    $error_message_while_being_applied,
    ],
    'force destroy on post-apply, stale' => [
    PostApplyEvent::class,
    TRUE,
    7200,
    NULL,
    ],
    'destroy on post-apply, stale' => [
    PostApplyEvent::class,
    FALSE,
    7200,
    NULL,
    ],
    ];
    }
    /**
    * Tests destroying a stage while applying it.
    *
    * @param string $event_class
    * The event class for which to attempt to destroy the stage.
    * @param bool $force
    * Whether the stage should be force destroyed.
    * @param int $time_offset
    * How many simulated seconds should have elapsed between the PreApplyEvent
    * being dispatched and the attempt to destroy the stage.
    * @param string|null $expected_exception_message
    * The expected exception message string if an exception is expected, or
    * NULL if no exception message was expected.
    *
    * @dataProvider providerDestroyDuringApply
    */
    public function testDestroyDuringApply(string $event_class, bool $force, int $time_offset, ?string $expected_exception_message): void {
    $listener = function (StageEvent $event) use ($force, $time_offset): void {
    // Simulate that a certain amount of time has passed since we started
    // applying staged changes. After a point, it should be possible to
    // destroy the stage even if it hasn't finished.
    TestTime::$offset = $time_offset;
    // No real-life event subscriber should try to destroy the stage while
    // handling another event. The only reason we're doing it here is to
    // simulate an attempt to destroy the stage while it's being applied, for
    // testing purposes.
    $event->stage->destroy($force);
    LoggingCommitter::setException(
    PreconditionException::class,
    $this->createMock(PreconditionInterface::class),
    $this->createComposeStagerMessage('Stage directory does not exist'),
    );
    };
    $this->addEventTestListener($listener, $event_class, 0);
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['ext-json:*']);
    if ($expected_exception_message) {
    $this->expectException(StageException::class);
    $this->expectExceptionMessage($expected_exception_message);
    }
    $stage->apply();
    // If the stage was successfully destroyed by the event handler (i.e., the
    // stage has been applying for too long and is therefore considered stale),
    // the postApply() method should fail because the stage is not claimed.
    if ($stage->isAvailable()) {
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessage('Stage must be claimed before performing any operations on it.');
    }
    $stage->postApply();
    }
    /**
    * Test uninstalling any module while the staged changes are being applied.
    */
    public function testUninstallModuleDuringApply(): void {
    $listener = function (PreApplyEvent $event): void {
    $this->assertTrue($event->stage->isApplying());
    // Trying to uninstall any module while the stage is being applied should
    // result in a module uninstall validation error.
    try {
    $this->container->get('module_installer')
    ->uninstall(['package_manager_bypass']);
    $this->fail('Expected an exception to be thrown while uninstalling a module.');
    }
    catch (ModuleUninstallValidatorException $e) {
    $this->assertStringContainsString('Modules cannot be uninstalled while Package Manager is applying staged changes to the active code base.', $e->getMessage());
    }
    };
    $this->addEventTestListener($listener);
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['ext-json:*']);
    $stage->apply();
    }
    /**
    * Tests that Composer Stager is invoked with a long timeout.
    */
    ......@@ -376,92 +166,6 @@ public function testTimeouts(): void {
    }
    }
    /**
    * Data provider for testCommitException().
    *
    * @return \string[][]
    * The test cases.
    */
    public static function providerCommitException(): array {
    return [
    'RuntimeException to ApplyFailedException' => [
    'RuntimeException',
    ApplyFailedException::class,
    ],
    'InvalidArgumentException' => [
    InvalidArgumentException::class,
    StageException::class,
    ],
    'PreconditionException' => [
    PreconditionException::class,
    StageException::class,
    ],
    'Exception' => [
    'Exception',
    ApplyFailedException::class,
    ],
    ];
    }
    /**
    * Tests exception handling during calls to Composer Stager commit.
    *
    * @param string $thrown_class
    * The throwable class that should be thrown by Composer Stager.
    * @param string $expected_class
    * The expected exception class, if different from $thrown_class.
    *
    * @dataProvider providerCommitException
    */
    public function testCommitException(string $thrown_class, string $expected_class): void {
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    $throwable_arguments = [
    'A very bad thing happened',
    123,
    ];
    // Composer Stager's exception messages are usually translatable, so they
    // need to be wrapped by a TranslatableMessage object.
    if (is_subclass_of($thrown_class, ExceptionInterface::class)) {
    $throwable_arguments[0] = $this->createComposeStagerMessage($throwable_arguments[0]);
    }
    // PreconditionException requires a preconditions object.
    if ($thrown_class === PreconditionException::class) {
    array_unshift($throwable_arguments, $this->createMock(PreconditionInterface::class));
    }
    LoggingCommitter::setException($thrown_class, ...$throwable_arguments);
    try {
    $stage->apply();
    $this->fail('Expected an exception.');
    }
    catch (\Throwable $exception) {
    $this->assertInstanceOf($expected_class, $exception);
    $this->assertSame(123, $exception->getCode());
    // This needs to be done because we always use the message from
    // \Drupal\package_manager\Stage::getFailureMarkerMessage() when throwing
    // ApplyFailedException.
    if ($expected_class == ApplyFailedException::class) {
    $this->assertMatchesRegularExpression("/^Staged changes failed to apply, and the site is in an indeterminate state. It is strongly recommended to restore the code and database from a backup. Caused by $thrown_class, with this message: A very bad thing happened\nBacktrace:\n#0 .*/", $exception->getMessage());
    }
    else {
    $this->assertSame('A very bad thing happened', $exception->getMessage());
    }
    $failure_marker = $this->container->get(FailureMarker::class);
    if ($exception instanceof ApplyFailedException) {
    $this->assertFileExists($failure_marker->getPath());
    $this->assertFalse($stage->isApplying());
    }
    else {
    $failure_marker->assertNotExists();
    }
    }
    }
    /**
    * Tests that if a stage fails to apply, another stage cannot be created.
    */
    ......@@ -622,36 +326,6 @@ public function testTempStoreMessageExpired(): void {
    $stage->claim($stage_id);
    }
    /**
    * Tests running apply and post-apply in the same request.
    */
    public function testApplyAndPostApplyInSameRequest(): void {
    $stage = $this->createStage();
    $logger = new TestLogger();
    $stage->setLogger($logger);
    $warning_message = 'Post-apply tasks are running in the same request during which staged changes were applied to the active code base. This can result in unpredictable behavior.';
    // Run apply and post-apply in the same request (i.e., the same request
    // time), and ensure the warning is logged.
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    $stage->apply();
    $stage->postApply();
    $this->assertTrue($logger->hasRecord($warning_message, LogLevel::WARNING));
    $stage->destroy();
    $logger->reset();
    $stage->create();
    $stage->require(['drupal/core:9.8.2']);
    $stage->apply();
    // Simulate post-apply taking place in another request by simulating a
    // request time 30 seconds after apply started.
    TestTime::$offset = 30;
    $stage->postApply();
    $this->assertFalse($logger->hasRecord($warning_message, LogLevel::WARNING));
    }
    /**
    * Data provider for ::testFailureDuringComposerStagerOperations().
    *
    ......@@ -808,24 +482,3 @@ public function testStageDirectoryDeletedDuringCron(): void {
    }
    }
    /**
    * A test-only implementation of the time service.
    */
    class TestTime extends Time {
    /**
    * An offset to add to the request time.
    *
    * @var int
    */
    public static $offset = 0;
    /**
    * {@inheritdoc}
    */
    public function getRequestTime() {
    return parent::getRequestTime() + static::$offset;
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    use Drupal\Core\DependencyInjection\ContainerBuilder;
    use Drupal\package_manager\Exception\ApplyFailedException;
    use Drupal\package_manager\Exception\StageException;
    use Drupal\package_manager\FailureMarker;
    use Drupal\package_manager_bypass\LoggingCommitter;
    use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
    use PhpTuf\ComposerStager\API\Exception\InvalidArgumentException;
    use PhpTuf\ComposerStager\API\Exception\PreconditionException;
    use PhpTuf\ComposerStager\API\Precondition\Service\PreconditionInterface;
    /**
    * @coversDefaultClass \Drupal\package_manager\StageBase
    * @covers \Drupal\package_manager\PackageManagerUninstallValidator
    * @group package_manager
    * @internal
    */
    class StageCommitExceptionTest extends PackageManagerKernelTestBase {
    /**
    * {@inheritdoc}
    */
    protected static $modules = ['package_manager_test_validation'];
    /**
    * {@inheritdoc}
    */
    public function register(ContainerBuilder $container): void {
    parent::register($container);
    // Since this test adds arbitrary event listeners that aren't services, we
    // need to ensure they will persist even if the container is rebuilt when
    // staged changes are applied.
    $container->getDefinition('event_dispatcher')->addTag('persist');
    }
    /**
    * Data provider for testCommitException().
    *
    * @return \string[][]
    * The test cases.
    */
    public static function providerCommitException(): array {
    return [
    'RuntimeException to ApplyFailedException' => [
    'RuntimeException',
    ApplyFailedException::class,
    ],
    'InvalidArgumentException' => [
    InvalidArgumentException::class,
    StageException::class,
    ],
    'PreconditionException' => [
    PreconditionException::class,
    StageException::class,
    ],
    'Exception' => [
    'Exception',
    ApplyFailedException::class,
    ],
    ];
    }
    /**
    * Tests exception handling during calls to Composer Stager commit.
    *
    * @param string $thrown_class
    * The throwable class that should be thrown by Composer Stager.
    * @param string $expected_class
    * The expected exception class, if different from $thrown_class.
    *
    * @dataProvider providerCommitException
    */
    public function testCommitException(string $thrown_class, string $expected_class): void {
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    $throwable_arguments = [
    'A very bad thing happened',
    123,
    ];
    // Composer Stager's exception messages are usually translatable, so they
    // need to be wrapped by a TranslatableMessage object.
    if (is_subclass_of($thrown_class, ExceptionInterface::class)) {
    $throwable_arguments[0] = $this->createComposeStagerMessage($throwable_arguments[0]);
    }
    // PreconditionException requires a preconditions object.
    if ($thrown_class === PreconditionException::class) {
    array_unshift($throwable_arguments, $this->createMock(PreconditionInterface::class));
    }
    LoggingCommitter::setException($thrown_class, ...$throwable_arguments);
    try {
    $stage->apply();
    $this->fail('Expected an exception.');
    }
    catch (\Throwable $exception) {
    $this->assertInstanceOf($expected_class, $exception);
    $this->assertSame(123, $exception->getCode());
    // This needs to be done because we always use the message from
    // \Drupal\package_manager\Stage::getFailureMarkerMessage() when throwing
    // ApplyFailedException.
    if ($expected_class == ApplyFailedException::class) {
    $this->assertMatchesRegularExpression("/^Staged changes failed to apply, and the site is in an indeterminate state. It is strongly recommended to restore the code and database from a backup. Caused by $thrown_class, with this message: A very bad thing happened\nBacktrace:\n#0 .*/", $exception->getMessage());
    }
    else {
    $this->assertSame('A very bad thing happened', $exception->getMessage());
    }
    $failure_marker = $this->container->get(FailureMarker::class);
    if ($exception instanceof ApplyFailedException) {
    $this->assertFileExists($failure_marker->getPath());
    $this->assertFalse($stage->isApplying());
    }
    else {
    $failure_marker->assertNotExists();
    }
    }
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    use Drupal\Component\Datetime\Time;
    use Drupal\Core\DependencyInjection\ContainerBuilder;
    use Drupal\Core\Extension\ModuleUninstallValidatorException;
    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\LoggingCommitter;
    use PhpTuf\ComposerStager\API\Exception\PreconditionException;
    use PhpTuf\ComposerStager\API\Precondition\Service\PreconditionInterface;
    use Psr\Log\LogLevel;
    use ColinODell\PsrTestLogger\TestLogger;
    /**
    * @coversDefaultClass \Drupal\package_manager\StageBase
    * @covers \Drupal\package_manager\PackageManagerUninstallValidator
    * @group package_manager
    * @group #slow
    * @internal
    */
    class StageConflictTest extends PackageManagerKernelTestBase {
    /**
    * {@inheritdoc}
    */
    protected static $modules = ['package_manager_test_validation'];
    /**
    * {@inheritdoc}
    */
    public function register(ContainerBuilder $container): void {
    parent::register($container);
    $container->getDefinition('datetime.time')
    ->setClass(TestTime::class);
    // Since this test adds arbitrary event listeners that aren't services, we
    // need to ensure they will persist even if the container is rebuilt when
    // staged changes are applied.
    $container->getDefinition('event_dispatcher')->addTag('persist');
    }
    /**
    * Data provider for testDestroyDuringApply().
    *
    * @return mixed[][]
    * The test cases.
    */
    public static function providerDestroyDuringApply(): array {
    $error_message_while_being_applied = 'Cannot destroy the stage directory while it is being applied to the active directory.';
    return [
    'force destroy on pre-apply, fresh' => [
    PreApplyEvent::class,
    TRUE,
    1,
    $error_message_while_being_applied,
    ],
    'destroy on pre-apply, fresh' => [
    PreApplyEvent::class,
    FALSE,
    1,
    $error_message_while_being_applied,
    ],
    'force destroy on pre-apply, stale' => [
    PreApplyEvent::class,
    TRUE,
    7200,
    'Stage directory does not exist',
    ],
    'destroy on pre-apply, stale' => [
    PreApplyEvent::class,
    FALSE,
    7200,
    'Stage directory does not exist',
    ],
    'force destroy on post-apply, fresh' => [
    PostApplyEvent::class,
    TRUE,
    1,
    $error_message_while_being_applied,
    ],
    'destroy on post-apply, fresh' => [
    PostApplyEvent::class,
    FALSE,
    1,
    $error_message_while_being_applied,
    ],
    'force destroy on post-apply, stale' => [
    PostApplyEvent::class,
    TRUE,
    7200,
    NULL,
    ],
    'destroy on post-apply, stale' => [
    PostApplyEvent::class,
    FALSE,
    7200,
    NULL,
    ],
    ];
    }
    /**
    * Tests destroying a stage while applying it.
    *
    * @param string $event_class
    * The event class for which to attempt to destroy the stage.
    * @param bool $force
    * Whether the stage should be force destroyed.
    * @param int $time_offset
    * How many simulated seconds should have elapsed between the PreApplyEvent
    * being dispatched and the attempt to destroy the stage.
    * @param string|null $expected_exception_message
    * The expected exception message string if an exception is expected, or
    * NULL if no exception message was expected.
    *
    * @dataProvider providerDestroyDuringApply
    */
    public function testDestroyDuringApply(string $event_class, bool $force, int $time_offset, ?string $expected_exception_message): void {
    $listener = function (StageEvent $event) use ($force, $time_offset): void {
    // Simulate that a certain amount of time has passed since we started
    // applying staged changes. After a point, it should be possible to
    // destroy the stage even if it hasn't finished.
    TestTime::$offset = $time_offset;
    // No real-life event subscriber should try to destroy the stage while
    // handling another event. The only reason we're doing it here is to
    // simulate an attempt to destroy the stage while it's being applied, for
    // testing purposes.
    $event->stage->destroy($force);
    LoggingCommitter::setException(
    PreconditionException::class,
    $this->createMock(PreconditionInterface::class),
    $this->createComposeStagerMessage('Stage directory does not exist'),
    );
    };
    $this->addEventTestListener($listener, $event_class, 0);
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['ext-json:*']);
    if ($expected_exception_message) {
    $this->expectException(StageException::class);
    $this->expectExceptionMessage($expected_exception_message);
    }
    $stage->apply();
    // If the stage was successfully destroyed by the event handler (i.e., the
    // stage has been applying for too long and is therefore considered stale),
    // the postApply() method should fail because the stage is not claimed.
    if ($stage->isAvailable()) {
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessage('Stage must be claimed before performing any operations on it.');
    }
    $stage->postApply();
    }
    /**
    * Tests running apply and post-apply in the same request.
    */
    public function testApplyAndPostApplyInSameRequest(): void {
    $stage = $this->createStage();
    $logger = new TestLogger();
    $stage->setLogger($logger);
    $warning_message = 'Post-apply tasks are running in the same request during which staged changes were applied to the active code base. This can result in unpredictable behavior.';
    // Run apply and post-apply in the same request (i.e., the same request
    // time), and ensure the warning is logged.
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    $stage->apply();
    $stage->postApply();
    $this->assertTrue($logger->hasRecord($warning_message, LogLevel::WARNING));
    $stage->destroy();
    $logger->reset();
    $stage->create();
    $stage->require(['drupal/core:9.8.2']);
    $stage->apply();
    // Simulate post-apply taking place in another request by simulating a
    // request time 30 seconds after apply started.
    TestTime::$offset = 30;
    $stage->postApply();
    $this->assertFalse($logger->hasRecord($warning_message, LogLevel::WARNING));
    }
    /**
    * Test uninstalling any module while the staged changes are being applied.
    */
    public function testUninstallModuleDuringApply(): void {
    $listener = function (PreApplyEvent $event): void {
    $this->assertTrue($event->stage->isApplying());
    // Trying to uninstall any module while the stage is being applied should
    // result in a module uninstall validation error.
    try {
    $this->container->get('module_installer')
    ->uninstall(['package_manager_bypass']);
    $this->fail('Expected an exception to be thrown while uninstalling a module.');
    }
    catch (ModuleUninstallValidatorException $e) {
    $this->assertStringContainsString('Modules cannot be uninstalled while Package Manager is applying staged changes to the active code base.', $e->getMessage());
    }
    };
    $this->addEventTestListener($listener);
    $stage = $this->createStage();
    $stage->create();
    $stage->require(['ext-json:*']);
    $stage->apply();
    }
    }
    /**
    * A test-only implementation of the time service.
    */
    class TestTime extends Time {
    /**
    * An offset to add to the request time.
    *
    * @var int
    */
    public static $offset = 0;
    /**
    * {@inheritdoc}
    */
    public function getRequestTime() {
    return parent::getRequestTime() + static::$offset;
    }
    }
    <?php
    declare(strict_types=1);
    namespace Drupal\Tests\package_manager\Kernel;
    use Drupal\Core\DependencyInjection\ContainerBuilder;
    use Drupal\package_manager\Event\PostApplyEvent;
    use Drupal\package_manager\Event\PostCreateEvent;
    use Drupal\package_manager\Event\PostRequireEvent;
    use Drupal\package_manager\Event\PreApplyEvent;
    use Drupal\package_manager\Event\PreCreateEvent;
    use Drupal\package_manager\Event\PreRequireEvent;
    use Drupal\package_manager\Exception\StageEventException;
    use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber;
    use Psr\Log\LogLevel;
    use ColinODell\PsrTestLogger\TestLogger;
    /**
    * @coversDefaultClass \Drupal\package_manager\StageBase
    * @covers \Drupal\package_manager\PackageManagerUninstallValidator
    * @group package_manager
    * @internal
    */
    class StageLoggedOnErrorTest extends PackageManagerKernelTestBase {
    /**
    * {@inheritdoc}
    */
    protected static $modules = ['package_manager_test_validation'];
    /**
    * {@inheritdoc}
    */
    public function register(ContainerBuilder $container): void {
    parent::register($container);
    // Since this test adds arbitrary event listeners that aren't services, we
    // need to ensure they will persist even if the container is rebuilt when
    // staged changes are applied.
    $container->getDefinition('event_dispatcher')->addTag('persist');
    }
    /**
    * Data provider for testLoggedOnError().
    *
    * @return string[][]
    * The test cases.
    */
    public static function providerLoggedOnError(): array {
    return [
    [PreCreateEvent::class],
    [PostCreateEvent::class],
    [PreRequireEvent::class],
    [PostRequireEvent::class],
    [PreApplyEvent::class],
    [PostApplyEvent::class],
    ];
    }
    /**
    * @covers \Drupal\package_manager\StageBase::dispatch
    *
    * @dataProvider providerLoggedOnError
    *
    * @param string $event_class
    * The event class to throw an exception on.
    */
    public function testLoggedOnError(string $event_class): void {
    $exception = new \Exception("This should be logged!");
    TestSubscriber::setException($exception, $event_class);
    $stage = $this->createStage();
    $logger = new TestLogger();
    $stage->setLogger($logger);
    try {
    $stage->create();
    $stage->require(['drupal/core:9.8.1']);
    $stage->apply();
    $stage->postApply();
    $this->fail('Expected an exception to be thrown, but none was.');
    }
    catch (StageEventException $e) {
    $this->assertInstanceOf($event_class, $e->event);
    $predicate = function (array $record) use ($e): bool {
    $context = $record['context'];
    return $context['@message'] === $e->getMessage() && str_contains($context['@backtrace_string'], 'testLoggedOnError');
    };
    $this->assertTrue($logger->hasRecordThatPasses($predicate, LogLevel::ERROR));
    }
    }
    }
    ......@@ -11,6 +11,7 @@
    /**
    * @coversDefaultClass \Drupal\package_manager\Validator\SupportedReleaseValidator
    * @group #slow
    * @group package_manager
    * @internal
    */
    ......
    ......@@ -69,7 +69,8 @@ private function setInstallerPaths(array $installer_paths, string $directory): v
    ->addConfig([
    'extra.installer-paths' => $installer_paths + $existing_installer_paths,
    ])
    ->commitChanges($directory);
    ->commitChanges($directory)
    ->updateLock();
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please register or to comment