From 78229e50e91bfd0a908ebd77066bd5056012cabb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 18 Sep 2026 15:00:00 +0000 Subject: [PATCH 1/2] Install WordPress from roots/wordpress-full in Composer-based tests The `Given a WP installation with Composer` step pulled WordPress from johnpbloch/wordpress-core, which repackages each release into its own repository. Tags 7.1.1 and 7.0.5 were published without a `wp-includes` directory, so the step produced a tree that WP-CLI does not recognise as a WordPress installation and every scenario building on it failed. roots/wordpress-full points its dist at the official WordPress.org release zip instead of a repackaged copy, so it is published as soon as a release ships and cannot drift from the canonical archive. Its package type is `wordpress-core` and roots/wordpress-core-installer honours the same `extra.wordpress-install-dir` key, making this a drop-in swap. The `-full` variant is used rather than the roots/wordpress metapackage because the latter resolves to roots/wordpress-no-content, which omits the default themes and plugins that johnpbloch/wordpress-core shipped. Refs https://github.com/johnpbloch/wordpress-core/issues/40 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RTrBbJsJA4S2nkL4oRaNQS --- composer.json | 4 ++-- src/Context/FeatureContext.php | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 36ab0de3..6ebd9875 100644 --- a/composer.json +++ b/composer.json @@ -38,8 +38,8 @@ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, - "johnpbloch/wordpress-core-installer": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "roots/wordpress-core-installer": true }, "sort-packages": true, "lock": false diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index a1485248..4cfcd250 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -2044,7 +2044,10 @@ public function install_wp_with_composer( $vendor_directory = 'vendor' ): void { // Allow for all Composer plugins to run to avoid warnings. $this->composer_command( 'config --no-plugins allow-plugins true' ); - $this->composer_command( 'require johnpbloch/wordpress-core-installer johnpbloch/wordpress-core --optimize-autoloader' ); + // The roots packages link directly to the official WordPress.org release zips, + // so they are published immediately and cannot suffer from repackaging issues. + // The "-full" variant ships the default themes and plugins, roots/wordpress does not. + $this->composer_command( 'require roots/wordpress-core-installer roots/wordpress-full --optimize-autoloader' ); // Disable WP Cron by default to avoid bogus HTTP requests in CLI context. $config_extra_php = "if ( defined( 'DISABLE_WP_CRON' ) === false ) { define( 'DISABLE_WP_CRON', true ); }\n"; From c0e4e1604adefc46c23de0c91e31d94d22eb1a7c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 18 Sep 2026 15:32:08 +0000 Subject: [PATCH 2/2] Cache the SQLite plugins in the Composer install path too `install_wp_with_composer()` copies from `self::$sqlite_cache_dir` and `self::$sqlite_object_cache_dir`, but those statics are only populated by `cache_wp_files()`, which that path never calls. A Composer scenario that runs first in a SQLite suite therefore died on an empty source directory: RecursiveDirectoryIterator::__construct(): Argument #1 ($directory) must not be empty A full suite run hides this, because an earlier `Given a WP installation` has already set the statics by the time the Composer scenarios run. Extract the plugin caching into `cache_sqlite_plugins()` and call it from both paths, so either can be the first step of a scenario. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RTrBbJsJA4S2nkL4oRaNQS --- src/Context/FeatureContext.php | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 4cfcd250..8a980c71 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -908,15 +908,12 @@ private static function find_wp_root( $dir ): ?string { } /** - * We cache the results of `wp core download` to improve test performance. - * Ideally, we'd cache at the HTTP layer for more reliable tests. + * We cache the SQLite plugins so that they only need to be downloaded once per machine. * - * @param string $version + * Every code path that installs WordPress has to call this before using the cache directories, + * as any of them can be the first step of a scenario. */ - private static function cache_wp_files( $version = '' ): void { - $core_zip = $version ? null : self::get_core_zip(); - $wp_version = $version ?: getenv( 'WP_VERSION' ); - $cache_dir = self::get_core_cache_dir( $version ); + private static function cache_sqlite_plugins(): void { self::$sqlite_cache_dir = sys_get_temp_dir() . '/wp-cli-test-sqlite-integration-cache'; if ( 'sqlite' === getenv( 'WP_CLI_TEST_DBTYPE' ) ) { @@ -931,6 +928,20 @@ private static function cache_wp_files( $version = '' ): void { self::download_sqlite_object_cache_plugin( self::$sqlite_object_cache_dir ); } } + } + + /** + * We cache the results of `wp core download` to improve test performance. + * Ideally, we'd cache at the HTTP layer for more reliable tests. + * + * @param string $version + */ + private static function cache_wp_files( $version = '' ): void { + $core_zip = $version ? null : self::get_core_zip(); + $wp_version = $version ?: getenv( 'WP_VERSION' ); + $cache_dir = self::get_core_cache_dir( $version ); + + self::cache_sqlite_plugins(); if ( is_readable( $cache_dir . '/wp-includes/version.php' ) ) { self::$cache_dir = $cache_dir; @@ -2035,6 +2046,10 @@ public function install_wp_with_composer( $vendor_directory = 'vendor' ): void { $this->create_run_dir(); $this->create_db(); + // Unlike download_wp(), this path never goes through cache_wp_files(), + // so the SQLite plugins have to be cached here. + self::cache_sqlite_plugins(); + $yml_path = $this->variables['RUN_DIR'] . '/wp-cli.yml'; file_put_contents( $yml_path, 'path: WordPress' );