From 02300f9b796bfe0501f5de3498efb43073de2cc3 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 15:38:19 +0100 Subject: [PATCH 01/16] Try windows logging --- bin/run-behat-tests | 2 +- src/Context/FeatureContext.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/run-behat-tests b/bin/run-behat-tests index 78160971..c26ae7fe 100755 --- a/bin/run-behat-tests +++ b/bin/run-behat-tests @@ -134,7 +134,7 @@ if [[ "${WP_CLI_TEST_COVERAGE}" == "true" ]] && vendor/bin/behat --help 2>/dev/n fi # Run the functional tests. -FORMAT_ARGS=(--format progress) +FORMAT_ARGS=(--format pretty) for arg in "$@"; do if [[ "$arg" == "--format"* ]]; then FORMAT_ARGS=() diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 494d3454..df14bc18 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1299,6 +1299,9 @@ private static function run_sql( $sql_cmd, $assoc_args = [], $add_database = fal } $start_time = microtime( true ); + if ( Utils\is_windows() ) { + fwrite( STDERR, "DEBUG WINDOWS SQL: sql_cmd={$sql_cmd} assoc_args=" . json_encode($assoc_args) . "\n" ); + } $result = Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ), null, $send_to_shell ); if ( self::$log_run_times ) { self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); @@ -1371,6 +1374,10 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $env = self::get_process_env_variables(); + if ( Utils\is_windows() ) { + fwrite( STDERR, "DEBUG WINDOWS PROC: command={$command} path=" . ( $path ?: 'null' ) . "\n" ); + } + if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; } @@ -1407,6 +1414,7 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { */ public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { + fwrite( STDERR, "DEBUG WINDOWS BG_PROC: cmd={$cmd}\n" ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); From 7a2a75bf03a51578116242d2ba84fa743887305c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 18:50:14 +0100 Subject: [PATCH 02/16] inject T(E)MP var into sub-processes --- src/Context/FeatureContext.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index df14bc18..48baa990 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -462,6 +462,11 @@ private static function get_process_env_variables(): array { 'TEST_RUN_DIR' => self::$behat_run_dir, ]; + if ( Utils\is_windows() ) { + $env['TMP'] = getenv( 'TMP' ) ?: sys_get_temp_dir(); + $env['TEMP'] = getenv( 'TEMP' ) ?: sys_get_temp_dir(); + } + $env = array_merge( $_ENV, $env ); if ( self::running_with_code_coverage() ) { From a8ca70d61de496535cc8ec2731241577709c1de5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 18:53:57 +0100 Subject: [PATCH 03/16] realpath run_dir --- src/Context/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 48baa990..3a21cfc6 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1188,7 +1188,7 @@ private static function get_event_file( $scope, &$line ): ?string { */ public function create_run_dir(): void { if ( ! isset( $this->variables['RUN_DIR'] ) ) { - self::$run_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ); + self::$run_dir = realpath( sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ) ); $this->variables['RUN_DIR'] = self::$run_dir; mkdir( $this->variables['RUN_DIR'] ); } From 6b5c279907937693b3782ca449fca4967d8ec6e7 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 00:08:34 +0100 Subject: [PATCH 04/16] mkdir tweak --- src/Context/FeatureContext.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 3a21cfc6..d190dc68 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1188,9 +1188,10 @@ private static function get_event_file( $scope, &$line ): ?string { */ public function create_run_dir(): void { if ( ! isset( $this->variables['RUN_DIR'] ) ) { - self::$run_dir = realpath( sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ) ); + $temp_run_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ); + mkdir( $temp_run_dir ); + self::$run_dir = realpath( $temp_run_dir ); $this->variables['RUN_DIR'] = self::$run_dir; - mkdir( $this->variables['RUN_DIR'] ); } } From 262e616eeccfdb46c9391be51cf2afa0df8b57eb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 12:24:42 +0100 Subject: [PATCH 05/16] escape % to %% for all commands run on Windows This prevents cmd.exe from misinterpreting %...% as undefined environment variables and stripping them. --- src/Context/FeatureContext.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index d190dc68..a857e724 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1378,6 +1378,10 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $command .= Utils\assoc_args_to_str( $assoc_args ); } + if ( Utils\is_windows() ) { + $command = str_replace( '%', '%%', $command ); + } + $env = self::get_process_env_variables(); if ( Utils\is_windows() ) { @@ -1420,6 +1424,7 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { */ public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { + $cmd = str_replace( '%', '%%', $cmd ); fwrite( STDERR, "DEBUG WINDOWS BG_PROC: cmd={$cmd}\n" ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. From 2c0c130a64696fbf56229ae33471f3a80c014044 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 13:14:31 +0100 Subject: [PATCH 06/16] remove logging again --- src/Context/FeatureContext.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index a857e724..bbc348f1 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1305,9 +1305,6 @@ private static function run_sql( $sql_cmd, $assoc_args = [], $add_database = fal } $start_time = microtime( true ); - if ( Utils\is_windows() ) { - fwrite( STDERR, "DEBUG WINDOWS SQL: sql_cmd={$sql_cmd} assoc_args=" . json_encode($assoc_args) . "\n" ); - } $result = Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ), null, $send_to_shell ); if ( self::$log_run_times ) { self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); @@ -1384,10 +1381,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $env = self::get_process_env_variables(); - if ( Utils\is_windows() ) { - fwrite( STDERR, "DEBUG WINDOWS PROC: command={$command} path=" . ( $path ?: 'null' ) . "\n" ); - } - if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; } @@ -1425,7 +1418,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { $cmd = str_replace( '%', '%%', $cmd ); - fwrite( STDERR, "DEBUG WINDOWS BG_PROC: cmd={$cmd}\n" ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); From 509b57ddb22386f8058ee89ce88e9e15791d2f7d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 16:19:52 +0100 Subject: [PATCH 07/16] real path --- src/Context/GivenStepDefinitions.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Context/GivenStepDefinitions.php b/src/Context/GivenStepDefinitions.php index 3d88e70f..a3ce0684 100644 --- a/src/Context/GivenStepDefinitions.php +++ b/src/Context/GivenStepDefinitions.php @@ -54,6 +54,9 @@ public function given_a_specific_directory( $empty_or_nonexistent, $dir ): void $dir = preg_replace( '|^/private/var/|', '/var/', $dir ); $temp_dir = sys_get_temp_dir(); + if ( Utils\is_windows() ) { + $temp_dir = realpath( $temp_dir ) ?: $temp_dir; + } // Also check for temp dir prefixed with `/private` for Mac OS X. if ( 0 !== strpos( $dir, $temp_dir ) && 0 !== strpos( $dir, "/private{$temp_dir}" ) ) { From 44a832f223900f43682e206c602b1472011353d6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 23:44:21 +0100 Subject: [PATCH 08/16] Partial revert --- src/Context/FeatureContext.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index bbc348f1..bd877da1 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1190,7 +1190,7 @@ public function create_run_dir(): void { if ( ! isset( $this->variables['RUN_DIR'] ) ) { $temp_run_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ); mkdir( $temp_run_dir ); - self::$run_dir = realpath( $temp_run_dir ); + self::$run_dir = $temp_run_dir; $this->variables['RUN_DIR'] = self::$run_dir; } } @@ -1375,10 +1375,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $command .= Utils\assoc_args_to_str( $assoc_args ); } - if ( Utils\is_windows() ) { - $command = str_replace( '%', '%%', $command ); - } - $env = self::get_process_env_variables(); if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { @@ -1417,7 +1413,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { */ public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { - $cmd = str_replace( '%', '%%', $cmd ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); From 661f366f835261bcd427017359002c5714de5cb6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 12:56:53 +0200 Subject: [PATCH 09/16] undo --- src/Context/GivenStepDefinitions.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Context/GivenStepDefinitions.php b/src/Context/GivenStepDefinitions.php index a3ce0684..3d88e70f 100644 --- a/src/Context/GivenStepDefinitions.php +++ b/src/Context/GivenStepDefinitions.php @@ -54,9 +54,6 @@ public function given_a_specific_directory( $empty_or_nonexistent, $dir ): void $dir = preg_replace( '|^/private/var/|', '/var/', $dir ); $temp_dir = sys_get_temp_dir(); - if ( Utils\is_windows() ) { - $temp_dir = realpath( $temp_dir ) ?: $temp_dir; - } // Also check for temp dir prefixed with `/private` for Mac OS X. if ( 0 !== strpos( $dir, $temp_dir ) && 0 !== strpos( $dir, "/private{$temp_dir}" ) ) { From ecb21c08984c03ec3bdee0deb5f369ca16b879f3 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 15:47:05 +0200 Subject: [PATCH 10/16] revert for testing --- src/Context/FeatureContext.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index bd877da1..d04cd714 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -462,11 +462,6 @@ private static function get_process_env_variables(): array { 'TEST_RUN_DIR' => self::$behat_run_dir, ]; - if ( Utils\is_windows() ) { - $env['TMP'] = getenv( 'TMP' ) ?: sys_get_temp_dir(); - $env['TEMP'] = getenv( 'TEMP' ) ?: sys_get_temp_dir(); - } - $env = array_merge( $_ENV, $env ); if ( self::running_with_code_coverage() ) { From be25fa49df50b68f8d519863a288036d60cbccb6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 16:14:22 +0200 Subject: [PATCH 11/16] pass down differently --- src/Context/FeatureContext.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index d04cd714..513df540 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -464,6 +464,13 @@ private static function get_process_env_variables(): array { $env = array_merge( $_ENV, $env ); + foreach ( [ 'TEMP', 'TMP' ] as $key ) { + $value = getenv( $key ); + if ( false !== $value ) { + $env[ $key ] = $value; + } + } + if ( self::running_with_code_coverage() ) { $has_coverage_driver = ( new Runtime() )->hasXdebug() || ( new Runtime() )->hasPCOV(); From 63834f93d7a3ff27a67fab8be69ae0638da20f93 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 16:30:51 +0200 Subject: [PATCH 12/16] add test --- features/testing.feature | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/testing.feature b/features/testing.feature index 8705258c..5336f946 100644 --- a/features/testing.feature +++ b/features/testing.feature @@ -125,3 +125,11 @@ Feature: Test that WP-CLI loads. This should only run on MySQL or MariaDB """ + Scenario: Verify sys_get_temp_dir() in sub-process + Given a WP install + When I run `wp eval 'echo sys_get_temp_dir();'` + Then STDOUT should not be: + """ + C:\Windows + """ + From 2f15de81ed35871ca14c9e7d0c6bcba4442301d0 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 16:33:31 +0200 Subject: [PATCH 13/16] add windir and systemroot --- src/Context/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 513df540..ad2a5990 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -464,7 +464,7 @@ private static function get_process_env_variables(): array { $env = array_merge( $_ENV, $env ); - foreach ( [ 'TEMP', 'TMP' ] as $key ) { + foreach ( [ 'TEMP', 'TMP', 'SystemRoot', 'windir' ] as $key ) { $value = getenv( $key ); if ( false !== $value ) { $env[ $key ] = $value; From 8d0e2ce47bcd1a74e168d547bbe451fdb07c723f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 17:02:37 +0200 Subject: [PATCH 14/16] fix matcher --- features/testing.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/testing.feature b/features/testing.feature index 5336f946..ae5a078d 100644 --- a/features/testing.feature +++ b/features/testing.feature @@ -128,7 +128,7 @@ Feature: Test that WP-CLI loads. Scenario: Verify sys_get_temp_dir() in sub-process Given a WP install When I run `wp eval 'echo sys_get_temp_dir();'` - Then STDOUT should not be: + Then STDOUT should not contain: """ C:\Windows """ From 4da0cd2c9a0f3c40afc7533c756c114198fdbd97 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 17:17:58 +0200 Subject: [PATCH 15/16] more resilient downloading --- src/Context/FeatureContext.php | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index ad2a5990..8cee7714 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -690,7 +690,7 @@ private static function configure_sqlite( $dir ): void { private static function cache_wp_files( $version = '' ): void { $wp_version = $version ?: getenv( 'WP_VERSION' ); $wp_version_suffix = $wp_version ? "-$wp_version" : ''; - self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; + $cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; self::$sqlite_cache_dir = sys_get_temp_dir() . '/wp-cli-test-sqlite-integration-cache'; if ( 'sqlite' === getenv( 'WP_CLI_TEST_DBTYPE' ) ) { @@ -706,15 +706,34 @@ private static function cache_wp_files( $version = '' ): void { } } - if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) { + if ( is_readable( $cache_dir . '/wp-includes/version.php' ) ) { + self::$cache_dir = $cache_dir; return; } - $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir ); + $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', $cache_dir ); if ( $wp_version ) { $cmd .= Utils\esc_cmd( ' --version=%s', $wp_version ); } - Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); + + $max_retries = 3; + $retry_count = 0; + $completed = false; + + // This is in addition to the retry logic inside Utils\http_request(). + while ( $retry_count < $max_retries && ! $completed ) { + try { + Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); + $completed = true; + } catch ( \Exception $e ) { + ++$retry_count; + if ( $retry_count >= $max_retries ) { + throw $e; + } + } + } + + self::$cache_dir = $cache_dir; } /** @@ -1533,7 +1552,7 @@ public static function copy_dir( $src_dir, $dest_dir ): void { * @var \SplFileInfo $item */ foreach ( $iterator as $item ) { - $dest_path = $dest_dir . '/' . $iterator->getSubPathname(); + $dest_path = rtrim( $dest_dir, '/\\' ) . DIRECTORY_SEPARATOR . $iterator->getSubPathname(); if ( $item->isDir() ) { if ( ! is_dir( $dest_path ) ) { mkdir( $dest_path, 0777, true ); @@ -1573,7 +1592,7 @@ public function download_wp( $subdir = '', $version = '' ): void { echo "WordPress {$result->stdout}\n"; } - $dest_dir = $this->variables['RUN_DIR'] . "/$subdir"; + $dest_dir = rtrim( $this->variables['RUN_DIR'], '/\\' ) . ( $subdir ? DIRECTORY_SEPARATOR . $subdir : '' ); if ( $subdir ) { mkdir( $dest_dir, 0777, true /*recursive*/ ); From 7927152eded691cc94a647ba83537cb2e7f894a6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 20:19:25 +0200 Subject: [PATCH 16/16] undo --- bin/run-behat-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/run-behat-tests b/bin/run-behat-tests index c26ae7fe..78160971 100755 --- a/bin/run-behat-tests +++ b/bin/run-behat-tests @@ -134,7 +134,7 @@ if [[ "${WP_CLI_TEST_COVERAGE}" == "true" ]] && vendor/bin/behat --help 2>/dev/n fi # Run the functional tests. -FORMAT_ARGS=(--format pretty) +FORMAT_ARGS=(--format progress) for arg in "$@"; do if [[ "$arg" == "--format"* ]]; then FORMAT_ARGS=()