From 190a68c43bcef3cd650ef0a700f95e172d8bae72 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 17 Dec 2025 15:45:32 +0100 Subject: [PATCH 1/4] Avoid side effects in `FeatureContext` file --- src/Context/FeatureContext.php | 160 +++++++++++++++++---------------- 1 file changed, 81 insertions(+), 79 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 7cff1631..ba5e186b 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -282,9 +282,6 @@ private static function running_with_code_coverage() { return \in_array( $with_code_coverage, [ 'true', '1' ], true ); } - /** - * @AfterSuite - */ public static function merge_coverage_reports(): void { if ( ! self::running_with_code_coverage() ) { return; @@ -430,14 +427,14 @@ private static function get_process_env_variables(): array { // Ensure we're using the expected `wp` binary. $bin_path = self::get_bin_path(); - wp_cli_behat_env_debug( "WP-CLI binary path: {$bin_path}" ); + self::debug( "WP-CLI binary path: {$bin_path}" ); if ( ! file_exists( "{$bin_path}/wp" ) ) { - wp_cli_behat_env_debug( "WARNING: No file named 'wp' found in the provided/detected binary path." ); + self::debug( "WARNING: No file named 'wp' found in the provided/detected binary path." ); } if ( ! is_executable( "{$bin_path}/wp" ) ) { - wp_cli_behat_env_debug( "WARNING: File named 'wp' found in the provided/detected binary path is not executable." ); + self::debug( "WARNING: File named 'wp' found in the provided/detected binary path is not executable." ); } $path_separator = Utils\is_windows() ? ';' : ':'; @@ -502,9 +499,9 @@ private static function get_process_env_variables(): array { } // Dump environment for debugging purposes, but before adding the GitHub token. - wp_cli_behat_env_debug( 'Environment:' ); + self::debug( 'Environment:' ); foreach ( $env as $key => $value ) { - wp_cli_behat_env_debug( " [{$key}] => {$value}" ); + self::debug( " [{$key}] => {$value}" ); } $github_token = getenv( 'GITHUB_TOKEN' ); @@ -651,6 +648,8 @@ private static function cache_wp_files( $version = '' ): void { * @BeforeSuite */ public static function prepare( BeforeSuiteScope $scope ): void { + self::bootstrap_feature_context(); + // Test performance statistics - useful for detecting slow tests. self::$log_run_times = getenv( 'WP_CLI_TEST_LOG_RUN_TIMES' ); if ( false !== self::$log_run_times ) { @@ -679,6 +678,8 @@ public static function prepare( BeforeSuiteScope $scope ): void { * @AfterSuite */ public static function afterSuite( AfterSuiteScope $scope ): void { + self::bootstrap_feature_context(); + if ( self::$composer_local_repository ) { self::remove_dir( self::$composer_local_repository ); self::$composer_local_repository = null; @@ -687,6 +688,8 @@ public static function afterSuite( AfterSuiteScope $scope ): void { if ( self::$log_run_times ) { self::log_run_times_after_suite( $scope ); } + + self::merge_coverage_reports(); } /** @@ -802,6 +805,8 @@ public static function create_cache_dir(): string { * Every scenario gets its own context object. */ public function __construct() { + $this->bootstrap_feature_context(); + if ( getenv( 'WP_CLI_TEST_DBROOTUSER' ) ) { $this->variables['DB_ROOT_USER'] = getenv( 'WP_CLI_TEST_DBROOTUSER' ); } @@ -866,6 +871,74 @@ public function __construct() { $this->set_cache_dir(); } + /** + * @param string $message + */ + protected static function debug( $message ): void { // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed + if ( ! getenv( 'WP_CLI_TEST_DEBUG_BEHAT_ENV' ) ) { + return; + } + + echo "{$message}\n"; + } + + /** + * Load required support files as needed before heading into the Behat context. + */ + protected static function bootstrap_feature_context(): void { + $vendor_folder = self::get_vendor_dir(); + self::debug( "Vendor folder location: {$vendor_folder}" ); + + // Didn't manage to detect a valid vendor folder. + if ( empty( $vendor_folder ) ) { + return; + } + + // We assume the vendor folder is located in the project root folder. + $project_folder = dirname( $vendor_folder ); + + $framework_folder = self::get_framework_dir(); + self::debug( "Framework folder location: {$framework_folder}" ); + + // Load helper functionality that is needed for the tests. + require_once "{$framework_folder}/php/utils.php"; + require_once "{$framework_folder}/php/WP_CLI/Process.php"; + require_once "{$framework_folder}/php/WP_CLI/ProcessRun.php"; + + // Manually load Composer file includes by generating a config with require: + // statements for each file. + $project_composer = "{$project_folder}/composer.json"; + if ( ! file_exists( $project_composer ) ) { + return; + } + + $composer = json_decode( file_get_contents( $project_composer ) ); + if ( empty( $composer->autoload->files ) ) { + return; + } + + $contents = "require:\n"; + foreach ( $composer->autoload->files as $file ) { + $contents .= " - {$project_folder}/{$file}\n"; + } + + $temp_folder = sys_get_temp_dir() . '/wp-cli-package-test'; + if ( + ! is_dir( $temp_folder ) + && ! mkdir( $temp_folder ) + && ! is_dir( $temp_folder ) + ) { + return; + } + + $project_config = "{$temp_folder}/config.yml"; + file_put_contents( $project_config, $contents ); + putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); + + self::debug( "Project config file location: {$project_config}" ); + self::debug( "Project config:\n{$contents}" ); + } + /** * Replace standard {VARIABLE_NAME} variables and the special {INVOKE_WP_CLI_WITH_PHP_ARGS-args} and {WP_VERSION-version-latest} variables. * Note that standard variable names can only contain uppercase letters, digits and underscores and cannot begin with a digit. @@ -1766,74 +1839,3 @@ private static function log_proc_method_run_time( $key, $start_time ): void { ++self::$proc_method_run_times[ $key ][1]; } } - - -/** - * @param string $message - */ -function wp_cli_behat_env_debug( $message ): void { // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed - if ( ! getenv( 'WP_CLI_TEST_DEBUG_BEHAT_ENV' ) ) { - return; - } - - echo "{$message}\n"; -} - -/** - * Load required support files as needed before heading into the Behat context. - */ -function wpcli_bootstrap_behat_feature_context(): void { - $vendor_folder = FeatureContext::get_vendor_dir(); - wp_cli_behat_env_debug( "Vendor folder location: {$vendor_folder}" ); - - // Didn't manage to detect a valid vendor folder. - if ( empty( $vendor_folder ) ) { - return; - } - - // We assume the vendor folder is located in the project root folder. - $project_folder = dirname( $vendor_folder ); - - $framework_folder = FeatureContext::get_framework_dir(); - wp_cli_behat_env_debug( "Framework folder location: {$framework_folder}" ); - - // Load helper functionality that is needed for the tests. - require_once "{$framework_folder}/php/utils.php"; - require_once "{$framework_folder}/php/WP_CLI/Process.php"; - require_once "{$framework_folder}/php/WP_CLI/ProcessRun.php"; - - // Manually load Composer file includes by generating a config with require: - // statements for each file. - $project_composer = "{$project_folder}/composer.json"; - if ( ! file_exists( $project_composer ) ) { - return; - } - - $composer = json_decode( file_get_contents( $project_composer ) ); - if ( empty( $composer->autoload->files ) ) { - return; - } - - $contents = "require:\n"; - foreach ( $composer->autoload->files as $file ) { - $contents .= " - {$project_folder}/{$file}\n"; - } - - $temp_folder = sys_get_temp_dir() . '/wp-cli-package-test'; - if ( - ! is_dir( $temp_folder ) - && ! mkdir( $temp_folder ) - && ! is_dir( $temp_folder ) - ) { - return; - } - - $project_config = "{$temp_folder}/config.yml"; - file_put_contents( $project_config, $contents ); - putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); - - wp_cli_behat_env_debug( "Project config file location: {$project_config}" ); - wp_cli_behat_env_debug( "Project config:\n{$contents}" ); -} - -wpcli_bootstrap_behat_feature_context(); From b85f077399fc7863c460cd98b8ef9292023e3557 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 17 Dec 2025 15:45:49 +0100 Subject: [PATCH 2/4] Do not use deprecated `SnippetAcceptingContext` class --- bin/run-behat-tests | 2 +- src/Context/FeatureContext.php | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/bin/run-behat-tests b/bin/run-behat-tests index 37ace1ab..3ee00892 100755 --- a/bin/run-behat-tests +++ b/bin/run-behat-tests @@ -120,4 +120,4 @@ if [[ "${WP_CLI_TEST_COVERAGE}" == "true" ]] && vendor/bin/behat --help 2>/dev/n fi # Run the functional tests. -vendor/bin/behat --format progress "$BEHAT_TAGS" --strict "${BEHAT_EXTRA_ARGS[@]}" "$@" +vendor/bin/behat --snippets-for="WP_CLI\Tests\Context\FeatureContext" --format progress "$BEHAT_TAGS" --strict "${BEHAT_EXTRA_ARGS[@]}" "$@" diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index ba5e186b..315a6bba 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -2,7 +2,7 @@ namespace WP_CLI\Tests\Context; -use Behat\Behat\Context\SnippetAcceptingContext; +use Behat\Behat\Context\Context; use Behat\Behat\EventDispatcher\Event\OutlineTested; use Behat\Behat\Hook\Scope\AfterScenarioScope; use Behat\Behat\Hook\Scope\BeforeScenarioScope; @@ -13,7 +13,6 @@ use Behat\Behat\Hook\Scope\AfterFeatureScope; use Behat\Behat\Hook\Scope\BeforeFeatureScope; use Behat\Behat\Hook\Scope\BeforeStepScope; -use Behat\Testwork\Hook\Scope\HookScope; use SebastianBergmann\CodeCoverage\Report\Clover; use SebastianBergmann\CodeCoverage\Driver\Selector; use SebastianBergmann\CodeCoverage\Driver\Xdebug; @@ -21,7 +20,6 @@ use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\Environment\Runtime; use RuntimeException; -use WP_CLI; use DirectoryIterator; use WP_CLI\Process; use WP_CLI\ProcessRun; @@ -33,7 +31,7 @@ * * @phpstan-ignore class.implementsDeprecatedInterface */ -class FeatureContext implements SnippetAcceptingContext { +class FeatureContext implements Context { use GivenStepDefinitions; use ThenStepDefinitions; From ee1fc173db9b79194c7195411c1b2cdea97c86fe Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 17 Dec 2025 15:53:00 +0100 Subject: [PATCH 3/4] PHPStan fixes --- src/Context/FeatureContext.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 315a6bba..e4220f59 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -28,8 +28,6 @@ /** * Features context. - * - * @phpstan-ignore class.implementsDeprecatedInterface */ class FeatureContext implements Context { @@ -803,7 +801,7 @@ public static function create_cache_dir(): string { * Every scenario gets its own context object. */ public function __construct() { - $this->bootstrap_feature_context(); + self::bootstrap_feature_context(); if ( getenv( 'WP_CLI_TEST_DBROOTUSER' ) ) { $this->variables['DB_ROOT_USER'] = getenv( 'WP_CLI_TEST_DBROOTUSER' ); From 0044f24ad647ff2ccacd7720f8c91f63ead4c7fb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 17 Dec 2025 15:58:18 +0100 Subject: [PATCH 4/4] Call it only once --- src/Context/FeatureContext.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index e4220f59..c39620ff 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -674,8 +674,6 @@ public static function prepare( BeforeSuiteScope $scope ): void { * @AfterSuite */ public static function afterSuite( AfterSuiteScope $scope ): void { - self::bootstrap_feature_context(); - if ( self::$composer_local_repository ) { self::remove_dir( self::$composer_local_repository ); self::$composer_local_repository = null; @@ -801,8 +799,6 @@ public static function create_cache_dir(): string { * Every scenario gets its own context object. */ public function __construct() { - self::bootstrap_feature_context(); - if ( getenv( 'WP_CLI_TEST_DBROOTUSER' ) ) { $this->variables['DB_ROOT_USER'] = getenv( 'WP_CLI_TEST_DBROOTUSER' ); }