From 0a7ae5d2b042a016dbd674a4db9a25adb5ee2934 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sat, 4 Aug 2018 14:47:40 +0200 Subject: [PATCH 1/9] Adapt package for framework v2 --- .travis.yml | 58 +- bin/install-package-tests.sh | 11 - bin/test.sh | 17 - composer.json | 19 +- features/bootstrap/FeatureContext.php | 942 ---------------- features/bootstrap/Process.php | 136 --- features/bootstrap/ProcessRun.php | 68 -- features/bootstrap/support.php | 200 ---- features/bootstrap/utils.php | 1481 ------------------------- features/extra/no-mail.php | 7 - utils/behat-tags.php | 85 -- 11 files changed, 50 insertions(+), 2974 deletions(-) delete mode 100755 bin/install-package-tests.sh delete mode 100755 bin/test.sh delete mode 100644 features/bootstrap/FeatureContext.php delete mode 100644 features/bootstrap/Process.php delete mode 100644 features/bootstrap/ProcessRun.php delete mode 100644 features/bootstrap/support.php delete mode 100644 features/bootstrap/utils.php delete mode 100644 features/extra/no-mail.php delete mode 100644 utils/behat-tags.php diff --git a/.travis.yml b/.travis.yml index 63e41a156..775eeb89d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false dist: trusty language: php +php: 7.2 notifications: email: @@ -21,24 +22,6 @@ env: - PATH="$TRAVIS_BUILD_DIR/vendor/bin:$PATH" - WP_CLI_BIN_DIR="$TRAVIS_BUILD_DIR/vendor/bin" -matrix: - include: - - php: 7.2 - env: WP_VERSION=latest - - php: 7.1 - env: WP_VERSION=latest - - php: 7.0 - env: WP_VERSION=latest - - php: 5.6 - env: WP_VERSION=latest - - php: 5.6 - env: WP_VERSION=3.7.11 - - php: 5.6 - env: WP_VERSION=trunk - - php: 5.3 - dist: precise - env: WP_VERSION=latest - before_install: - | # Remove Xdebug for a huge performance increase: @@ -50,14 +33,41 @@ before_install: - | # Raise PHP memory limit to 2048MB echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + - composer validate install: - - composer require wp-cli/wp-cli:dev-master - composer install - - bash bin/install-package-tests.sh - -before_script: - - composer validate + - composer prepare-tests script: - - bash bin/test.sh + - composer phpunit + - composer behat + +jobs: + include: + - stage: sniff + script: + - composer lint + - composer phpcs + env: BUILD=sniff + - stage: test + php: 7.2 + env: WP_VERSION=latest + - stage: test + php: 7.1 + env: WP_VERSION=latest + - stage: test + php: 7.0 + env: WP_VERSION=latest + - stage: test + php: 5.6 + env: WP_VERSION=latest + - stage: test + php: 5.6 + env: WP_VERSION=3.7.11 + - stage: test + php: 5.6 + env: WP_VERSION=trunk + - stage: test + php: 5.4 + env: WP_VERSION=latest diff --git a/bin/install-package-tests.sh b/bin/install-package-tests.sh deleted file mode 100755 index d672d9a50..000000000 --- a/bin/install-package-tests.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -ex - -install_db() { - mysql -e 'CREATE DATABASE IF NOT EXISTS wp_cli_test;' -uroot - mysql -e 'GRANT ALL PRIVILEGES ON wp_cli_test.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "password1"' -uroot - mysql -e 'GRANT ALL PRIVILEGES ON wp_cli_test_scaffold.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "password1"' -uroot -} - -install_db diff --git a/bin/test.sh b/bin/test.sh deleted file mode 100755 index 84e2460b8..000000000 --- a/bin/test.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -ex - -# Run the unit tests, if they exist -if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ] -then - phpunit -fi - -if [ $WP_VERSION = "latest" ]; then - export WP_VERSION=$(curl -s https://api.wordpress.org/core/version-check/1.7/ | jq -r ".offers[0].current") -fi - -# Run the functional tests -BEHAT_TAGS=$(php utils/behat-tags.php) -behat --format progress $BEHAT_TAGS --strict diff --git a/composer.json b/composer.json index 3e5a170a2..0c73d262b 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,11 @@ "files": [ "package-command.php" ] }, "require": { - "composer/composer": "^1.2.0" + "composer/composer": "^1.2.0", + "wp-cli/wp-cli": "^2" }, "require-dev": { - "behat/behat": "~2.5", - "wp-cli/wp-cli": "^1.5" + "wp-cli/wp-cli-tests": "^0" }, "extra": { "branch-alias": { @@ -42,5 +42,18 @@ "package update", "package uninstall" ] + }, + "scripts": { + "lint": "run-linter-tests", + "phpcs": "run-phpcs-tests", + "phpunit": "run-php-unit-tests", + "behat": "run-behat-tests", + "prepare-tests": "install-package-tests", + "test": [ + "@lint", + "@phpcs", + "@phpunit", + "@behat" + ] } } diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php deleted file mode 100644 index 6f88ed1bb..000000000 --- a/features/bootstrap/FeatureContext.php +++ /dev/null @@ -1,942 +0,0 @@ -autoload->files ) ) { - $contents = 'require:' . PHP_EOL; - foreach( $composer->autoload->files as $file ) { - $contents .= ' - ' . dirname( dirname( dirname( __FILE__ ) ) ) . '/' . $file . PHP_EOL; - } - @mkdir( sys_get_temp_dir() . '/wp-cli-package-test/' ); - $project_config = sys_get_temp_dir() . '/wp-cli-package-test/config.yml'; - file_put_contents( $project_config, $contents ); - putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); - } - } -// Inside WP-CLI -} else { - require_once __DIR__ . '/../../php/utils.php'; - require_once __DIR__ . '/../../php/WP_CLI/Process.php'; - require_once __DIR__ . '/../../php/WP_CLI/ProcessRun.php'; - if ( file_exists( __DIR__ . '/../../vendor/autoload.php' ) ) { - require_once __DIR__ . '/../../vendor/autoload.php'; - } else if ( file_exists( __DIR__ . '/../../../../autoload.php' ) ) { - require_once __DIR__ . '/../../../../autoload.php'; - } -} - -/** - * Features context. - */ -class FeatureContext extends BehatContext implements ClosuredContextInterface { - - /** - * The current working directory for scenarios that have a "Given a WP installation" or "Given an empty directory" step. Variable RUN_DIR. Lives until the end of the scenario. - */ - private static $run_dir; - - /** - * Where WordPress core is downloaded to for caching, and which is copied to RUN_DIR during a "Given a WP installation" step. Lives until manually deleted. - */ - private static $cache_dir; - - /** - * The directory that holds the install cache, and which is copied to RUN_DIR during a "Given a WP installation" step. Recreated on each suite run. - */ - private static $install_cache_dir; - - /** - * The directory that the WP-CLI cache (WP_CLI_CACHE_DIR, normally "$HOME/.wp-cli/cache") is set to on a "Given an empty cache" step. - * Variable SUITE_CACHE_DIR. Lives until the end of the scenario (or until another "Given an empty cache" step within the scenario). - */ - private static $suite_cache_dir; - - /** - * Where the current WP-CLI source repository is copied to for Composer-based tests with a "Given a dependency on current wp-cli" step. - * Variable COMPOSER_LOCAL_REPOSITORY. Lives until the end of the suite. - */ - private static $composer_local_repository; - - /** - * The test database settings. All but `dbname` can be set via environment variables. The database is dropped at the start of each scenario and created on a "Given a WP installation" step. - */ - private static $db_settings = array( - 'dbname' => 'wp_cli_test', - 'dbuser' => 'wp_cli_test', - 'dbpass' => 'password1', - 'dbhost' => '127.0.0.1', - ); - - /** - * Array of background process ids started by the current scenario. Used to terminate them at the end of the scenario. - */ - private $running_procs = array(); - - /** - * Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest. - * Some are step-dependent: RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. One is set on use: INVOKE_WP_CLI_WITH_PHP_ARGS-args. - * Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario. - */ - public $variables = array(); - - /** - * The current feature file and scenario line number as '.'. Used in RUN_DIR and SUITE_CACHE_DIR directory names. Set at the start of each scenario. - */ - private static $temp_dir_infix; - - /** - * Settings and variables for WP_CLI_TEST_LOG_RUN_TIMES run time logging. - */ - private static $log_run_times; // Whether to log run times - WP_CLI_TEST_LOG_RUN_TIMES env var. Set on `@BeforeScenario'. - private static $suite_start_time; // When the suite started, set on `@BeforeScenario'. - private static $output_to; // Where to output log - stdout|error_log. Set on `@BeforeSuite`. - private static $num_top_processes; // Number of processes/methods to output by longest run times. Set on `@BeforeSuite`. - private static $num_top_scenarios; // Number of scenarios to output by longest run times. Set on `@BeforeSuite`. - - private static $scenario_run_times = array(); // Scenario run times (top `self::$num_top_scenarios` only). - private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`. - private static $proc_method_run_times = array(); // Array of run time info for proc methods, keyed by method name and arg, each a 2-element array containing run time and run count. - - /** - * Get the environment variables required for launched `wp` processes - */ - private static function get_process_env_variables() { - // Ensure we're using the expected `wp` binary - $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' ); - $vendor_dir = realpath( __DIR__ . '/../../vendor/bin' ); - $path_separator = Utils\is_windows() ? ';' : ':'; - $env = array( - 'PATH' => $bin_dir . $path_separator . $vendor_dir . $path_separator . getenv( 'PATH' ), - 'BEHAT_RUN' => 1, - 'HOME' => sys_get_temp_dir() . '/wp-cli-home', - ); - if ( $config_path = getenv( 'WP_CLI_CONFIG_PATH' ) ) { - $env['WP_CLI_CONFIG_PATH'] = $config_path; - } - if ( $term = getenv( 'TERM' ) ) { - $env['TERM'] = $term; - } - if ( $php_args = getenv( 'WP_CLI_PHP_ARGS' ) ) { - $env['WP_CLI_PHP_ARGS'] = $php_args; - } - if ( $php_used = getenv( 'WP_CLI_PHP_USED' ) ) { - $env['WP_CLI_PHP_USED'] = $php_used; - } - if ( $php = getenv( 'WP_CLI_PHP' ) ) { - $env['WP_CLI_PHP'] = $php; - } - if ( $travis_build_dir = getenv( 'TRAVIS_BUILD_DIR' ) ) { - $env['TRAVIS_BUILD_DIR'] = $travis_build_dir; - } - if ( $github_token = getenv( 'GITHUB_TOKEN' ) ) { - $env['GITHUB_TOKEN'] = $github_token; - } - return $env; - } - - /** - * We cache the results of `wp core download` to improve test performance. - * Ideally, we'd cache at the HTTP layer for more reliable tests. - */ - private static function cache_wp_files() { - $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; - self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; - - if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) - return; - - $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir ); - if ( $wp_version ) { - $cmd .= Utils\esc_cmd( ' --version=%s', $wp_version ); - } - Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); - } - - /** - * @BeforeSuite - */ - public static function prepare( SuiteEvent $event ) { - // Test performance statistics - useful for detecting slow tests. - if ( self::$log_run_times = getenv( 'WP_CLI_TEST_LOG_RUN_TIMES' ) ) { - self::log_run_times_before_suite( $event ); - } - - $result = Process::create( 'wp cli info', null, self::get_process_env_variables() )->run_check(); - echo PHP_EOL; - echo $result->stdout; - echo PHP_EOL; - self::cache_wp_files(); - $result = Process::create( Utils\esc_cmd( 'wp core version --path=%s', self::$cache_dir ) , null, self::get_process_env_variables() )->run_check(); - echo 'WordPress ' . $result->stdout; - echo PHP_EOL; - - // Remove install cache if any (not setting the static var). - $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; - $install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; - if ( file_exists( $install_cache_dir ) ) { - self::remove_dir( $install_cache_dir ); - } - } - - /** - * @AfterSuite - */ - public static function afterSuite( SuiteEvent $event ) { - if ( self::$composer_local_repository ) { - self::remove_dir( self::$composer_local_repository ); - self::$composer_local_repository = null; - } - - if ( self::$log_run_times ) { - self::log_run_times_after_suite( $event ); - } - } - - /** - * @BeforeScenario - */ - public function beforeScenario( $event ) { - if ( self::$log_run_times ) { - self::log_run_times_before_scenario( $event ); - } - - $this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' ); - - // Used in the names of the RUN_DIR and SUITE_CACHE_DIR directories. - self::$temp_dir_infix = null; - if ( $file = self::get_event_file( $event, $line ) ) { - self::$temp_dir_infix = basename( $file ) . '.' . $line; - } - } - - /** - * @AfterScenario - */ - public function afterScenario( $event ) { - - if ( self::$run_dir ) { - // remove altered WP install, unless there's an error - if ( $event->getResult() < 4 ) { - self::remove_dir( self::$run_dir ); - } - self::$run_dir = null; - } - - // Remove WP-CLI package directory if any. Set to `wp package path` by package-command and scaffold-package-command features, and by cli-info.feature. - if ( isset( $this->variables['PACKAGE_PATH'] ) ) { - self::remove_dir( $this->variables['PACKAGE_PATH'] ); - } - - // Remove SUITE_CACHE_DIR if any. - if ( self::$suite_cache_dir ) { - self::remove_dir( self::$suite_cache_dir ); - self::$suite_cache_dir = null; - } - - // Remove any background processes. - foreach ( $this->running_procs as $proc ) { - $status = proc_get_status( $proc ); - self::terminate_proc( $status['pid'] ); - } - - if ( self::$log_run_times ) { - self::log_run_times_after_scenario( $event ); - } - } - - /** - * Terminate a process and any of its children. - */ - private static function terminate_proc( $master_pid ) { - - $output = `ps -o ppid,pid,command | grep $master_pid`; - - foreach ( explode( PHP_EOL, $output ) as $line ) { - if ( preg_match( '/^\s*(\d+)\s+(\d+)/', $line, $matches ) ) { - $parent = $matches[1]; - $child = $matches[2]; - - if ( $parent == $master_pid ) { - self::terminate_proc( $child ); - } - } - } - - if ( ! posix_kill( (int) $master_pid, 9 ) ) { - $errno = posix_get_last_error(); - // Ignore "No such process" error as that's what we want. - if ( 3 /*ESRCH*/ !== $errno ) { - throw new RuntimeException( posix_strerror( $errno ) ); - } - } - } - - /** - * Create a temporary WP_CLI_CACHE_DIR. Exposed as SUITE_CACHE_DIR in "Given an empty cache" step. - */ - public static function create_cache_dir() { - if ( self::$suite_cache_dir ) { - self::remove_dir( self::$suite_cache_dir ); - } - self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-suite-cache-' . self::$temp_dir_infix . '-', TRUE ); - mkdir( self::$suite_cache_dir ); - return self::$suite_cache_dir; - } - - /** - * Initializes context. - * Every scenario gets its own context object. - * - * @param array $parameters context parameters (set them up through behat.yml) - */ - public function __construct( array $parameters ) { - if ( getenv( 'WP_CLI_TEST_DBUSER' ) ) { - self::$db_settings['dbuser'] = getenv( 'WP_CLI_TEST_DBUSER' ); - } - - if ( false !== getenv( 'WP_CLI_TEST_DBPASS' ) ) { - self::$db_settings['dbpass'] = getenv( 'WP_CLI_TEST_DBPASS' ); - } - - if ( getenv( 'WP_CLI_TEST_DBHOST' ) ) { - self::$db_settings['dbhost'] = getenv( 'WP_CLI_TEST_DBHOST' ); - } - - $this->drop_db(); - $this->set_cache_dir(); - $this->variables['CORE_CONFIG_SETTINGS'] = Utils\assoc_args_to_str( self::$db_settings ); - } - - public function getStepDefinitionResources() { - return glob( __DIR__ . '/../steps/*.php' ); - } - - public function getHookDefinitionResources() { - return array(); - } - - /** - * 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. - */ - public function replace_variables( $str ) { - if ( false !== strpos( $str, '{INVOKE_WP_CLI_WITH_PHP_ARGS-' ) ) { - $str = $this->replace_invoke_wp_cli_with_php_args( $str ); - } - $str = preg_replace_callback( '/\{([A-Z_][A-Z_0-9]*)\}/', array( $this, 'replace_var' ), $str ); - if ( false !== strpos( $str, '{WP_VERSION-' ) ) { - $str = $this->replace_wp_versions( $str ); - } - return $str; - } - - /** - * Substitute {INVOKE_WP_CLI_WITH_PHP_ARGS-args} variables. - */ - private function replace_invoke_wp_cli_with_php_args( $str ) { - static $phar_path = null, $shell_path = null; - - if ( null === $phar_path ) { - $phar_path = false; - $phar_begin = '#!/usr/bin/env php'; - $phar_begin_len = strlen( $phar_begin ); - if ( ( $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ) && file_exists( $bin_dir . '/wp' ) && $phar_begin === file_get_contents( $bin_dir . '/wp', false, null, 0, $phar_begin_len ) ) { - $phar_path = $bin_dir . '/wp'; - } else { - $src_dir = dirname( dirname( __DIR__ ) ); - $bin_path = $src_dir . '/bin/wp'; - $vendor_bin_path = $src_dir . '/vendor/bin/wp'; - if ( file_exists( $bin_path ) && is_executable( $bin_path ) ) { - $shell_path = $bin_path; - } elseif ( file_exists( $vendor_bin_path ) && is_executable( $vendor_bin_path ) ) { - $shell_path = $vendor_bin_path; - } else { - $shell_path = 'wp'; - } - } - } - - $str = preg_replace_callback( '/{INVOKE_WP_CLI_WITH_PHP_ARGS-([^}]*)}/', function ( $matches ) use ( $phar_path, $shell_path ) { - return $phar_path ? "php {$matches[1]} {$phar_path}" : ( 'WP_CLI_PHP_ARGS=' . escapeshellarg( $matches[1] ) . ' ' . $shell_path ); - }, $str ); - - return $str; - } - - /** - * Replace variables callback. - */ - private function replace_var( $matches ) { - $cmd = $matches[0]; - - foreach ( array_slice( $matches, 1 ) as $key ) { - $cmd = str_replace( '{' . $key . '}', $this->variables[ $key ], $cmd ); - } - - return $cmd; - } - - /** - * Substitute {WP_VERSION-version-latest} variables. - */ - private function replace_wp_versions( $str ) { - static $wp_versions = null; - if ( null === $wp_versions ) { - $wp_versions = array(); - - $response = Requests::get( 'https://api.wordpress.org/core/version-check/1.7/', null, array( 'timeout' => 30 ) ); - if ( 200 === $response->status_code && ( $body = json_decode( $response->body ) ) && is_object( $body ) && isset( $body->offers ) && is_array( $body->offers ) ) { - // Latest version alias. - $wp_versions["{WP_VERSION-latest}"] = count( $body->offers ) ? $body->offers[0]->version : ''; - foreach ( $body->offers as $offer ) { - $sub_ver = preg_replace( '/(^[0-9]+\.[0-9]+)\.[0-9]+$/', '$1', $offer->version ); - $sub_ver_key = "{WP_VERSION-{$sub_ver}-latest}"; - - $main_ver = preg_replace( '/(^[0-9]+)\.[0-9]+$/', '$1', $sub_ver ); - $main_ver_key = "{WP_VERSION-{$main_ver}-latest}"; - - if ( ! isset( $wp_versions[ $main_ver_key ] ) ) { - $wp_versions[ $main_ver_key ] = $offer->version; - } - if ( ! isset( $wp_versions[ $sub_ver_key ] ) ) { - $wp_versions[ $sub_ver_key ] = $offer->version; - } - } - } - } - return strtr( $str, $wp_versions ); - } - - /** - * Get the file and line number for the current behat event. - */ - private static function get_event_file( $event, &$line ) { - if ( method_exists( $event, 'getScenario' ) ) { - $scenario_feature = $event->getScenario(); - } elseif ( method_exists( $event, 'getFeature' ) ) { - $scenario_feature = $event->getFeature(); - } elseif ( method_exists( $event, 'getOutline' ) ) { - $scenario_feature = $event->getOutline(); - } else { - return null; - } - $line = $scenario_feature->getLine(); - return $scenario_feature->getFile(); - } - - /** - * Create the RUN_DIR directory, unless already set for this scenario. - */ - public function create_run_dir() { - if ( !isset( $this->variables['RUN_DIR'] ) ) { - self::$run_dir = $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', TRUE ); - mkdir( $this->variables['RUN_DIR'] ); - } - } - - public function build_phar( $version = 'same' ) { - $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( "wp-cli-build-", TRUE ) . '.phar'; - - // Test running against a package installed as a WP-CLI dependency - // WP-CLI installed as a project dependency - $make_phar_path = __DIR__ . '/../../../../../utils/make-phar.php'; - if ( ! file_exists( $make_phar_path ) ) { - // Test running against WP-CLI proper - $make_phar_path = __DIR__ . '/../../utils/make-phar.php'; - if ( ! file_exists( $make_phar_path ) ) { - // WP-CLI as a dependency of this project - $make_phar_path = __DIR__ . '/../../vendor/wp-cli/wp-cli/utils/make-phar.php'; - } - } - - $this->proc( Utils\esc_cmd( - 'php -dphar.readonly=0 %1$s %2$s --version=%3$s && chmod +x %2$s', - $make_phar_path, - $this->variables['PHAR_PATH'], - $version - ) )->run_check(); - } - - public function download_phar( $version = 'same' ) { - if ( 'same' === $version ) { - $version = WP_CLI_VERSION; - } - - $download_url = sprintf( - 'https://github.com/wp-cli/wp-cli/releases/download/v%1$s/wp-cli-%1$s.phar', - $version - ); - - $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' - . uniqid( 'wp-cli-download-', true ) - . '.phar'; - - Process::create( Utils\esc_cmd( - 'curl -sSfL %1$s > %2$s && chmod +x %2$s', - $download_url, - $this->variables['PHAR_PATH'] - ) )->run_check(); - } - - /** - * CACHE_DIR is a cache for downloaded test data such as images. Lives until manually deleted. - */ - private function set_cache_dir() { - $path = sys_get_temp_dir() . '/wp-cli-test-cache'; - if ( ! file_exists( $path ) ) { - mkdir( $path ); - } - $this->variables['CACHE_DIR'] = $path; - } - - /** - * Run a MySQL command with `$db_settings`. - * - * @param string $sql_cmd Command to run. - * @param array $assoc_args Optional. Associative array of options. Default empty. - * @param bool $add_database Optional. Whether to add dbname to the $sql_cmd. Default false. - */ - private static function run_sql( $sql_cmd, $assoc_args = array(), $add_database = false ) { - $default_assoc_args = array( - 'host' => self::$db_settings['dbhost'], - 'user' => self::$db_settings['dbuser'], - 'pass' => self::$db_settings['dbpass'], - ); - if ( $add_database ) { - $sql_cmd .= ' ' . escapeshellarg( self::$db_settings['dbname'] ); - } - $start_time = microtime( true ); - Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ) ); - if ( self::$log_run_times ) { - self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); - } - } - - public function create_db() { - $dbname = self::$db_settings['dbname']; - self::run_sql( 'mysql --no-defaults', array( 'execute' => "CREATE DATABASE IF NOT EXISTS $dbname" ) ); - } - - public function drop_db() { - $dbname = self::$db_settings['dbname']; - self::run_sql( 'mysql --no-defaults', array( 'execute' => "DROP DATABASE IF EXISTS $dbname" ) ); - } - - public function proc( $command, $assoc_args = array(), $path = '' ) { - if ( !empty( $assoc_args ) ) - $command .= Utils\assoc_args_to_str( $assoc_args ); - - $env = self::get_process_env_variables(); - if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { - $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; - } - - if ( isset( $this->variables['RUN_DIR'] ) ) { - $cwd = "{$this->variables['RUN_DIR']}/{$path}"; - } else { - $cwd = null; - } - - return Process::create( $command, $cwd, $env ); - } - - /** - * Start a background process. Will automatically be closed when the tests finish. - */ - public function background_proc( $cmd ) { - $descriptors = array( - 0 => STDIN, - 1 => array( 'pipe', 'w' ), - 2 => array( 'pipe', 'w' ), - ); - - $proc = proc_open( $cmd, $descriptors, $pipes, $this->variables['RUN_DIR'], self::get_process_env_variables() ); - - sleep(1); - - $status = proc_get_status( $proc ); - - if ( !$status['running'] ) { - $stderr = is_resource( $pipes[2] ) ? ( ': ' . stream_get_contents( $pipes[2] ) ) : ''; - throw new RuntimeException( sprintf( "Failed to start background process '%s'%s.", $cmd, $stderr ) ); - } else { - $this->running_procs[] = $proc; - } - } - - public function move_files( $src, $dest ) { - rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" ); - } - - /** - * Remove a directory (recursive). - */ - public static function remove_dir( $dir ) { - Process::create( Utils\esc_cmd( 'rm -rf %s', $dir ) )->run_check(); - } - - /** - * Copy a directory (recursive). Destination directory must exist. - */ - public static function copy_dir( $src_dir, $dest_dir ) { - Process::create( Utils\esc_cmd( "cp -r %s/* %s", $src_dir, $dest_dir ) )->run_check(); - } - - public function add_line_to_wp_config( &$wp_config_code, $line ) { - $token = "/* That's all, stop editing!"; - - $wp_config_code = str_replace( $token, "$line\n\n$token", $wp_config_code ); - } - - public function download_wp( $subdir = '' ) { - $dest_dir = $this->variables['RUN_DIR'] . "/$subdir"; - - if ( $subdir ) { - mkdir( $dest_dir ); - } - - self::copy_dir( self::$cache_dir, $dest_dir ); - - // disable emailing - mkdir( $dest_dir . '/wp-content/mu-plugins' ); - copy( __DIR__ . '/../extra/no-mail.php', $dest_dir . '/wp-content/mu-plugins/no-mail.php' ); - } - - public function create_config( $subdir = '', $extra_php = false ) { - $params = self::$db_settings; - - // Replaces all characters that are not alphanumeric or an underscore into an underscore. - $params['dbprefix'] = $subdir ? preg_replace( '#[^a-zA-Z\_0-9]#', '_', $subdir ) : 'wp_'; - - $params['skip-salts'] = true; - - if( false !== $extra_php ) { - $params['extra-php'] = $extra_php; - } - - $config_cache_path = ''; - if ( self::$install_cache_dir ) { - $config_cache_path = self::$install_cache_dir . '/config_' . md5( implode( ':', $params ) . ':subdir=' . $subdir ); - $run_dir = '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR']; - } - - if ( $config_cache_path && file_exists( $config_cache_path ) ) { - copy( $config_cache_path, $run_dir . '/wp-config.php' ); - } else { - $this->proc( 'wp config create', $params, $subdir )->run_check(); - if ( $config_cache_path && file_exists( $run_dir . '/wp-config.php' ) ) { - copy( $run_dir . '/wp-config.php', $config_cache_path ); - } - } - } - - public function install_wp( $subdir = '' ) { - $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; - self::$install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; - if ( ! file_exists( self::$install_cache_dir ) ) { - mkdir( self::$install_cache_dir ); - } - - $subdir = $this->replace_variables( $subdir ); - - $this->create_db(); - $this->create_run_dir(); - $this->download_wp( $subdir ); - $this->create_config( $subdir ); - - $install_args = array( - 'url' => 'http://example.com', - 'title' => 'WP CLI Site', - 'admin_user' => 'admin', - 'admin_email' => 'admin@example.com', - 'admin_password' => 'password1', - 'skip-email' => true, - ); - - $install_cache_path = ''; - if ( self::$install_cache_dir ) { - $install_cache_path = self::$install_cache_dir . '/install_' . md5( implode( ':', $install_args ) . ':subdir=' . $subdir ); - $run_dir = '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR']; - } - - if ( $install_cache_path && file_exists( $install_cache_path ) ) { - self::copy_dir( $install_cache_path, $run_dir ); - self::run_sql( 'mysql --no-defaults', array( 'execute' => "source {$install_cache_path}.sql" ), true /*add_database*/ ); - } else { - $this->proc( 'wp core install', $install_args, $subdir )->run_check(); - if ( $install_cache_path ) { - mkdir( $install_cache_path ); - self::dir_diff_copy( $run_dir, self::$cache_dir, $install_cache_path ); - self::run_sql( 'mysqldump --no-defaults', array( 'result-file' => "{$install_cache_path}.sql" ), true /*add_database*/ ); - } - } - } - - public function install_wp_with_composer( $vendor_directory = 'vendor' ) { - $this->create_run_dir(); - $this->create_db(); - - $yml_path = $this->variables['RUN_DIR'] . "/wp-cli.yml"; - file_put_contents( $yml_path, 'path: wordpress' ); - - $this->composer_command( 'init --name="wp-cli/composer-test" --type="project" --no-interaction' ); - $this->composer_command( 'config vendor-dir ' . $vendor_directory ); - $this->composer_command( 'require johnpbloch/wordpress --optimize-autoloader --no-interaction' ); - - $config_extra_php = "require_once dirname(__DIR__) . '/" . $vendor_directory . "/autoload.php';"; - $this->create_config( 'wordpress', $config_extra_php ); - - $install_args = array( - 'url' => 'http://localhost:8080', - 'title' => 'WP CLI Site with both WordPress and wp-cli as Composer dependencies', - 'admin_user' => 'admin', - 'admin_email' => 'admin@example.com', - 'admin_password' => 'password1', - 'skip-email' => true, - ); - - $this->proc( 'wp core install', $install_args )->run_check(); - } - - public function composer_add_wp_cli_local_repository() { - if ( ! self::$composer_local_repository ) { - self::$composer_local_repository = sys_get_temp_dir() . '/' . uniqid( "wp-cli-composer-local-", TRUE ); - mkdir( self::$composer_local_repository ); - - $env = self::get_process_env_variables(); - $src = isset( $env['TRAVIS_BUILD_DIR'] ) ? $env['TRAVIS_BUILD_DIR'] : realpath( __DIR__ . '/../../' ); - - self::copy_dir( $src, self::$composer_local_repository . '/' ); - self::remove_dir( self::$composer_local_repository . '/.git' ); - self::remove_dir( self::$composer_local_repository . '/vendor' ); - } - $dest = self::$composer_local_repository . '/'; - $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false}}'" ); - $this->variables['COMPOSER_LOCAL_REPOSITORY'] = self::$composer_local_repository; - } - - public function composer_require_current_wp_cli() { - $this->composer_add_wp_cli_local_repository(); - $this->composer_command( 'require wp-cli/wp-cli:dev-master --optimize-autoloader --no-interaction' ); - } - - public function start_php_server( $subdir = '' ) { - $dir = $this->variables['RUN_DIR'] . '/'; - if ( $subdir ) { - $dir .= trim( $subdir, '/' ) . '/'; - } - $cmd = Utils\esc_cmd( '%s -S %s -t %s -c %s %s', - Utils\get_php_binary(), - 'localhost:8080', - $dir, - get_cfg_var( 'cfg_file_path' ), - $this->variables['RUN_DIR'] . '/vendor/wp-cli/server-command/router.php' - ); - $this->background_proc( $cmd ); - } - - private function composer_command($cmd) { - if ( !isset( $this->variables['COMPOSER_PATH'] ) ) { - $this->variables['COMPOSER_PATH'] = exec('which composer'); - } - $this->proc( $this->variables['COMPOSER_PATH'] . ' ' . $cmd )->run_check(); - } - - /** - * Initialize run time logging. - */ - private static function log_run_times_before_suite( $event ) { - self::$suite_start_time = microtime( true ); - - Process::$log_run_times = true; - - $travis = getenv( 'TRAVIS' ); - - // Default output settings. - self::$output_to = 'stdout'; - self::$num_top_processes = $travis ? 10 : 40; - self::$num_top_scenarios = $travis ? 10 : 20; - - // Allow setting of above with "WP_CLI_TEST_LOG_RUN_TIMES=[,][,]" formatted env var. - if ( preg_match( '/^(stdout|error_log)?(,[0-9]+)?(,[0-9]+)?$/i', self::$log_run_times, $matches ) ) { - if ( isset( $matches[1] ) ) { - self::$output_to = strtolower( $matches[1] ); - } - if ( isset( $matches[2] ) ) { - self::$num_top_processes = max( (int) substr( $matches[2], 1 ), 1 ); - } - if ( isset( $matches[3] ) ) { - self::$num_top_scenarios = max( (int) substr( $matches[3], 1 ), 1 ); - } - } - } - - /** - * Record the start time of the scenario into the `$scenario_run_times` array. - */ - private static function log_run_times_before_scenario( $event ) { - if ( $scenario_key = self::get_scenario_key( $event ) ) { - self::$scenario_run_times[ $scenario_key ] = -microtime( true ); - } - } - - /** - * Save the run time of the scenario into the `$scenario_run_times` array. Only the top `self::$num_top_scenarios` are kept. - */ - private static function log_run_times_after_scenario( $event ) { - if ( $scenario_key = self::get_scenario_key( $event ) ) { - self::$scenario_run_times[ $scenario_key ] += microtime( true ); - self::$scenario_count++; - if ( count( self::$scenario_run_times ) > self::$num_top_scenarios ) { - arsort( self::$scenario_run_times ); - array_pop( self::$scenario_run_times ); - } - } - } - - /** - * Copy files in updated directory that are not in source directory to copy directory. ("Incremental backup".) - * Note: does not deal with changed files (ie does not compare file contents for changes), for speed reasons. - * - * @param string $upd_dir The directory to search looking for files/directories not in `$src_dir`. - * @param string $src_dir The directory to be compared to `$upd_dir`. - * @param string $cop_dir Where to copy any files/directories in `$upd_dir` but not in `$src_dir` to. - */ - private static function dir_diff_copy( $upd_dir, $src_dir, $cop_dir ) { - if ( false === ( $files = scandir( $upd_dir ) ) ) { - $error = error_get_last(); - throw new \RuntimeException( sprintf( "Failed to open updated directory '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_dir, $error['message'] ) ); - } - foreach ( array_diff( $files, array( '.', '..' ) ) as $file ) { - $upd_file = $upd_dir . '/' . $file; - $src_file = $src_dir . '/' . $file; - $cop_file = $cop_dir . '/' . $file; - if ( ! file_exists( $src_file ) ) { - if ( is_dir( $upd_file ) ) { - if ( ! file_exists( $cop_file ) && ! mkdir( $cop_file, 0777, true /*recursive*/ ) ) { - $error = error_get_last(); - throw new \RuntimeException( sprintf( "Failed to create copy directory '%s': %s. " . __FILE__ . ':' . __LINE__, $cop_file, $error['message'] ) ); - } - self::copy_dir( $upd_file, $cop_file ); - } else { - if ( ! copy( $upd_file, $cop_file ) ) { - $error = error_get_last(); - throw new \RuntimeException( sprintf( "Failed to copy '%s' to '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_file, $cop_file, $error['message'] ) ); - } - } - } elseif ( is_dir( $upd_file ) ) { - self::dir_diff_copy( $upd_file, $src_file, $cop_file ); - } - } - } - - /** - * Get the scenario key used for `$scenario_run_times` array. - * Format " :", eg "core-command core-update.feature:221". - */ - private static function get_scenario_key( $event ) { - $scenario_key = ''; - if ( $file = self::get_event_file( $event, $line ) ) { - $scenario_grandparent = Utils\basename( dirname( dirname( $file ) ) ); - $scenario_key = $scenario_grandparent . ' ' . Utils\basename( $file ) . ':' . $line; - } - return $scenario_key; - } - - /** - * Print out stats on the run times of processes and scenarios. - */ - private static function log_run_times_after_suite( $event ) { - - $suite = ''; - if ( self::$scenario_run_times ) { - // Grandparent directory is first part of key. - $keys = array_keys( self::$scenario_run_times ); - $suite = substr( $keys[0], 0, strpos( $keys[0], ' ' ) ); - } - - $run_from = Utils\basename( dirname( dirname( __DIR__ ) ) ); - - // Format same as Behat, if have minutes. - $fmt = function ( $time ) { - $mins = floor( $time / 60 ); - return round( $time, 3 ) . ( $mins ? ( ' (' . $mins . 'm' . round( $time - ( $mins * 60 ), 3 ) . 's)' ) : '' ); - }; - - $time = microtime( true ) - self::$suite_start_time; - - $log = PHP_EOL . str_repeat( '(', 80 ) . PHP_EOL; - - // Process and proc method run times. - $run_times = array_merge( Process::$run_times, self::$proc_method_run_times ); - - list( $ptime, $calls ) = array_reduce( $run_times, function ( $carry, $item ) { - return array( $carry[0] + $item[0], $carry[1] + $item[1] ); - }, array( 0, 0 ) ); - - $overhead = $time - $ptime; - $pct = round( ( $overhead / $time ) * 100 ); - $unique = count( $run_times ); - - $log .= sprintf( - PHP_EOL . "Total process run time %s (tests %s, overhead %.3f %d%%), calls %d (%d unique) for '%s' run from '%s'" . PHP_EOL, - $fmt( $ptime ), $fmt( $time ), $overhead, $pct, $calls, $unique, $suite, $run_from - ); - - uasort( $run_times, function ( $a, $b ) { - return $a[0] === $b[0] ? 0 : ( $a[0] < $b[0] ? 1 : -1 ); // Reverse sort. - } ); - - $tops = array_slice( $run_times, 0, self::$num_top_processes, true ); - - $log .= PHP_EOL . "Top " . self::$num_top_processes . " process run times for '$suite'"; - $log .= PHP_EOL . implode( PHP_EOL, array_map( function ( $k, $v, $i ) { - return sprintf( ' %3d. %7.3f %3d %s', $i + 1, round( $v[0], 3 ), $v[1], $k ); - }, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; - - // Scenario run times. - arsort( self::$scenario_run_times ); - - $tops = array_slice( self::$scenario_run_times, 0, self::$num_top_scenarios, true ); - - $log .= PHP_EOL . "Top " . self::$num_top_scenarios . " (of " . self::$scenario_count . ") scenario run times for '$suite'"; - $log .= PHP_EOL . implode( PHP_EOL, array_map( function ( $k, $v, $i ) { - return sprintf( ' %3d. %7.3f %s', $i + 1, round( $v, 3 ), substr( $k, strpos( $k, ' ' ) + 1 ) ); - }, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; - - $log .= PHP_EOL . str_repeat( ')', 80 ); - - if ( 'error_log' === self::$output_to ) { - error_log( $log ); - } else { - echo PHP_EOL . $log; - } - } - - /** - * Log the run time of a proc method (one that doesn't use Process but does (use a function that does) a `proc_open()`). - */ - private static function log_proc_method_run_time( $key, $start_time ) { - $run_time = microtime( true ) - $start_time; - if ( ! isset( self::$proc_method_run_times[ $key ] ) ) { - self::$proc_method_run_times[ $key ] = array( 0, 0 ); - } - self::$proc_method_run_times[ $key ][0] += $run_time; - self::$proc_method_run_times[ $key ][1]++; - } - -} diff --git a/features/bootstrap/Process.php b/features/bootstrap/Process.php deleted file mode 100644 index 584f679b9..000000000 --- a/features/bootstrap/Process.php +++ /dev/null @@ -1,136 +0,0 @@ - STDIN, - 1 => array( 'pipe', 'w' ), - 2 => array( 'pipe', 'w' ), - ); - - /** - * @var bool Whether to log run time info or not. - */ - public static $log_run_times = false; - - /** - * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count. - */ - public static $run_times = array(); - - /** - * @param string $command Command to execute. - * @param string $cwd Directory to execute the command in. - * @param array $env Environment variables to set when running the command. - * - * @return Process - */ - public static function create( $command, $cwd = null, $env = array() ) { - $proc = new self; - - $proc->command = $command; - $proc->cwd = $cwd; - $proc->env = $env; - - return $proc; - } - - private function __construct() {} - - /** - * Run the command. - * - * @return ProcessRun - */ - public function run() { - $start_time = microtime( true ); - - $proc = Utils\proc_open_compat( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env ); - - $stdout = stream_get_contents( $pipes[1] ); - fclose( $pipes[1] ); - - $stderr = stream_get_contents( $pipes[2] ); - fclose( $pipes[2] ); - - $return_code = proc_close( $proc ); - - $run_time = microtime( true ) - $start_time; - - if ( self::$log_run_times ) { - if ( ! isset( self::$run_times[ $this->command ] ) ) { - self::$run_times[ $this->command ] = array( 0, 0 ); - } - self::$run_times[ $this->command ][0] += $run_time; - self::$run_times[ $this->command ][1]++; - } - - return new ProcessRun( - array( - 'stdout' => $stdout, - 'stderr' => $stderr, - 'return_code' => $return_code, - 'command' => $this->command, - 'cwd' => $this->cwd, - 'env' => $this->env, - 'run_time' => $run_time, - ) - ); - } - - /** - * Run the command, but throw an Exception on error. - * - * @return ProcessRun - */ - public function run_check() { - $r = $this->run(); - - // $r->STDERR is incorrect, but kept incorrect for backwards-compat - if ( $r->return_code || ! empty( $r->STDERR ) ) { - throw new \RuntimeException( $r ); - } - - return $r; - } - - /** - * Run the command, but throw an Exception on error. - * Same as `run_check()` above, but checks the correct stderr. - * - * @return ProcessRun - */ - public function run_check_stderr() { - $r = $this->run(); - - if ( $r->return_code || ! empty( $r->stderr ) ) { - throw new \RuntimeException( $r ); - } - - return $r; - } -} diff --git a/features/bootstrap/ProcessRun.php b/features/bootstrap/ProcessRun.php deleted file mode 100644 index 96b4c80b6..000000000 --- a/features/bootstrap/ProcessRun.php +++ /dev/null @@ -1,68 +0,0 @@ - $value ) { - $this->$key = $value; - } - } - - /** - * Return properties of executed command as a string. - * - * @return string - */ - public function __toString() { - $out = "$ $this->command\n"; - $out .= "$this->stdout\n$this->stderr"; - $out .= "cwd: $this->cwd\n"; - $out .= "run time: $this->run_time\n"; - $out .= "exit status: $this->return_code"; - - return $out; - } - -} diff --git a/features/bootstrap/support.php b/features/bootstrap/support.php deleted file mode 100644 index 6aa17c6c3..000000000 --- a/features/bootstrap/support.php +++ /dev/null @@ -1,200 +0,0 @@ - $value ) { - if ( ! compareContents( $value, $actual->$name ) ) - return false; - } - } else if ( is_array( $expected ) ) { - foreach ( $expected as $key => $value ) { - if ( ! compareContents( $value, $actual[$key] ) ) - return false; - } - } else { - return $expected === $actual; - } - - return true; -} - -/** - * Compare two strings containing JSON to ensure that @a $actualJson contains at - * least what the JSON string @a $expectedJson contains. - * - * @return whether or not @a $actualJson contains @a $expectedJson - * @retval true @a $actualJson contains @a $expectedJson - * @retval false @a $actualJson does not contain @a $expectedJson - * - * @param[in] $actualJson the JSON string to be tested - * @param[in] $expectedJson the expected JSON string - * - * Examples: - * expected: {'a':1,'array':[1,3,5]} - * - * 1 ) - * actual: {'a':1,'b':2,'c':3,'array':[1,2,3,4,5]} - * return: true - * - * 2 ) - * actual: {'b':2,'c':3,'array':[1,2,3,4,5]} - * return: false - * element 'a' is missing from the root object - * - * 3 ) - * actual: {'a':0,'b':2,'c':3,'array':[1,2,3,4,5]} - * return: false - * the value of element 'a' is not 1 - * - * 4 ) - * actual: {'a':1,'b':2,'c':3,'array':[1,2,4,5]} - * return: false - * the contents of 'array' does not include 3 - */ -function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { - $actualValue = json_decode( $actualJson ); - $expectedValue = json_decode( $expectedJson ); - - if ( !$actualValue ) { - return false; - } - - return compareContents( $expectedValue, $actualValue ); -} - -/** - * Compare two strings to confirm $actualCSV contains $expectedCSV - * Both strings are expected to have headers for their CSVs. - * $actualCSV must match all data rows in $expectedCSV - * - * @param string A CSV string - * @param array A nested array of values - * @return bool Whether $actualCSV contains $expectedCSV - */ -function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { - $actualCSV = array_map( 'str_getcsv', explode( PHP_EOL, $actualCSV ) ); - - if ( empty( $actualCSV ) ) - return false; - - // Each sample must have headers - $actualHeaders = array_values( array_shift( $actualCSV ) ); - $expectedHeaders = array_values( array_shift( $expectedCSV ) ); - - // Each expectedCSV must exist somewhere in actualCSV in the proper column - $expectedResult = 0; - foreach ( $expectedCSV as $expected_row ) { - $expected_row = array_combine( $expectedHeaders, $expected_row ); - foreach ( $actualCSV as $actual_row ) { - - if ( count( $actualHeaders ) != count( $actual_row ) ) - continue; - - $actual_row = array_intersect_key( array_combine( $actualHeaders, $actual_row ), $expected_row ); - if ( $actual_row == $expected_row ) - $expectedResult++; - } - } - - return $expectedResult >= count( $expectedCSV ); -} - -/** - * Compare two strings containing YAML to ensure that @a $actualYaml contains at - * least what the YAML string @a $expectedYaml contains. - * - * @return whether or not @a $actualYaml contains @a $expectedJson - * @retval true @a $actualYaml contains @a $expectedJson - * @retval false @a $actualYaml does not contain @a $expectedJson - * - * @param[in] $actualYaml the YAML string to be tested - * @param[in] $expectedYaml the expected YAML string - */ -function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) { - $actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml ); - $expectedValue = Mustangostang\Spyc::YAMLLoad( $expectedYaml ); - - if ( !$actualValue ) { - return false; - } - - return compareContents( $expectedValue, $actualValue ); -} - diff --git a/features/bootstrap/utils.php b/features/bootstrap/utils.php deleted file mode 100644 index 8033dcb7c..000000000 --- a/features/bootstrap/utils.php +++ /dev/null @@ -1,1481 +0,0 @@ -config ) && ! empty( $composer->config->{'vendor-dir'} ) ) { - array_unshift( $vendor_paths, WP_CLI_ROOT . '/../../../' . $composer->config->{'vendor-dir'} ); - } - } - return $vendor_paths; -} - -// Using require() directly inside a class grants access to private methods to the loaded code -function load_file( $path ) { - require_once $path; -} - -function load_command( $name ) { - $path = WP_CLI_ROOT . "/php/commands/$name.php"; - - if ( is_readable( $path ) ) { - include_once $path; - } -} - -/** - * Like array_map(), except it returns a new iterator, instead of a modified array. - * - * Example: - * - * $arr = array('Football', 'Socker'); - * - * $it = iterator_map($arr, 'strtolower', function($val) { - * return str_replace('foo', 'bar', $val); - * }); - * - * foreach ( $it as $val ) { - * var_dump($val); - * } - * - * @param array|object Either a plain array or another iterator - * @param callback The function to apply to an element - * @return object An iterator that applies the given callback(s) - */ -function iterator_map( $it, $fn ) { - if ( is_array( $it ) ) { - $it = new \ArrayIterator( $it ); - } - - if ( ! method_exists( $it, 'add_transform' ) ) { - $it = new Transform( $it ); - } - - foreach ( array_slice( func_get_args(), 1 ) as $fn ) { - $it->add_transform( $fn ); - } - - return $it; -} - -/** - * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true - * @param string|array The files (or file) to search for - * @param string|null The directory to start searching from; defaults to CWD - * @param callable Function which is passed the current dir each time a directory level is traversed - * @return null|string Null if the file was not found - */ -function find_file_upward( $files, $dir = null, $stop_check = null ) { - $files = (array) $files; - if ( is_null( $dir ) ) { - $dir = getcwd(); - } - while ( is_readable( $dir ) ) { - // Stop walking up when the supplied callable returns true being passed the $dir - if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { - return null; - } - - foreach ( $files as $file ) { - $path = $dir . DIRECTORY_SEPARATOR . $file; - if ( file_exists( $path ) ) { - return $path; - } - } - - $parent_dir = dirname( $dir ); - if ( empty( $parent_dir ) || $parent_dir === $dir ) { - break; - } - $dir = $parent_dir; - } - return null; -} - -function is_path_absolute( $path ) { - // Windows - if ( isset( $path[1] ) && ':' === $path[1] ) { - return true; - } - - return '/' === $path[0]; -} - -/** - * Composes positional arguments into a command string. - * - * @param array - * @return string - */ -function args_to_str( $args ) { - return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); -} - -/** - * Composes associative arguments into a command string. - * - * @param array - * @return string - */ -function assoc_args_to_str( $assoc_args ) { - $str = ''; - - foreach ( $assoc_args as $key => $value ) { - if ( true === $value ) { - $str .= " --$key"; - } elseif ( is_array( $value ) ) { - foreach ( $value as $_ => $v ) { - $str .= assoc_args_to_str( - array( - $key => $v, - ) - ); - } - } else { - $str .= " --$key=" . escapeshellarg( $value ); - } - } - - return $str; -} - -/** - * Given a template string and an arbitrary number of arguments, - * returns the final command, with the parameters escaped. - */ -function esc_cmd( $cmd ) { - if ( func_num_args() < 2 ) { - trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); - } - - $args = func_get_args(); - - $cmd = array_shift( $args ); - - return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) ); -} - -function locate_wp_config() { - static $path; - - if ( null === $path ) { - $path = false; - - if ( file_exists( ABSPATH . 'wp-config.php' ) ) { - $path = ABSPATH . 'wp-config.php'; - } elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) ) { - $path = ABSPATH . '../wp-config.php'; - } - - if ( $path ) { - $path = realpath( $path ); - } - } - - return $path; -} - -function wp_version_compare( $since, $operator ) { - $wp_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); - $since = str_replace( '-src', '', $since ); - return version_compare( $wp_version, $since, $operator ); -} - -/** - * Render a collection of items as an ASCII table, JSON, CSV, YAML, list of ids, or count. - * - * Given a collection of items with a consistent data structure: - * - * ``` - * $items = array( - * array( - * 'key' => 'foo', - * 'value' => 'bar', - * ) - * ); - * ``` - * - * Render `$items` as an ASCII table: - * - * ``` - * WP_CLI\Utils\format_items( 'table', $items, array( 'key', 'value' ) ); - * - * # +-----+-------+ - * # | key | value | - * # +-----+-------+ - * # | foo | bar | - * # +-----+-------+ - * ``` - * - * Or render `$items` as YAML: - * - * ``` - * WP_CLI\Utils\format_items( 'yaml', $items, array( 'key', 'value' ) ); - * - * # --- - * # - - * # key: foo - * # value: bar - * ``` - * - * @access public - * @category Output - * - * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count' - * @param array $items An array of items to output. - * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. - * @return null - */ -function format_items( $format, $items, $fields ) { - $assoc_args = compact( 'format', 'fields' ); - $formatter = new \WP_CLI\Formatter( $assoc_args ); - $formatter->display_items( $items ); -} - -/** - * Write data as CSV to a given file. - * - * @access public - * - * @param resource $fd File descriptor - * @param array $rows Array of rows to output - * @param array $headers List of CSV columns (optional) - */ -function write_csv( $fd, $rows, $headers = array() ) { - if ( ! empty( $headers ) ) { - fputcsv( $fd, $headers ); - } - - foreach ( $rows as $row ) { - if ( ! empty( $headers ) ) { - $row = pick_fields( $row, $headers ); - } - - fputcsv( $fd, array_values( $row ) ); - } -} - -/** - * Pick fields from an associative array or object. - * - * @param array|object Associative array or object to pick fields from - * @param array List of fields to pick - * @return array - */ -function pick_fields( $item, $fields ) { - $item = (object) $item; - - $values = array(); - - foreach ( $fields as $field ) { - $values[ $field ] = isset( $item->$field ) ? $item->$field : null; - } - - return $values; -} - -/** - * Launch system's $EDITOR for the user to edit some text. - * - * @access public - * @category Input - * - * @param string $content Some form of text to edit (e.g. post content) - * @param string $title Title to display in the editor. - * @param string $ext Extension to use with the temp file. - * @return string|bool Edited text, if file is saved from editor; false, if no change to file. - */ -function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) { - - check_proc_available( 'launch_editor_for_input' ); - - $tmpdir = get_temp_dir(); - - do { - $tmpfile = basename( $title ); - $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); - $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); - $tmpfile = $tmpdir . $tmpfile . '.' . $ext; - $fp = fopen( $tmpfile, 'xb' ); - if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { - $tmpfile = ''; - continue; - } - if ( $fp ) { - fclose( $fp ); - } - } while ( ! $tmpfile ); - - if ( ! $tmpfile ) { - \WP_CLI::error( 'Error creating temporary file.' ); - } - - $output = ''; - file_put_contents( $tmpfile, $input ); - - $editor = getenv( 'EDITOR' ); - if ( ! $editor ) { - $editor = is_windows() ? 'notepad' : 'vi'; - } - - $descriptorspec = array( STDIN, STDOUT, STDERR ); - $process = proc_open_compat( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); - $r = proc_close( $process ); - if ( $r ) { - exit( $r ); - } - - $output = file_get_contents( $tmpfile ); - - unlink( $tmpfile ); - - if ( $output === $input ) { - return false; - } - - return $output; -} - -/** - * @param string MySQL host string, as defined in wp-config.php - * @return array - */ -function mysql_host_to_cli_args( $raw_host ) { - $assoc_args = array(); - - $host_parts = explode( ':', $raw_host ); - if ( count( $host_parts ) == 2 ) { - list( $assoc_args['host'], $extra ) = $host_parts; - $extra = trim( $extra ); - if ( is_numeric( $extra ) ) { - $assoc_args['port'] = (int) $extra; - $assoc_args['protocol'] = 'tcp'; - } elseif ( '' !== $extra ) { - $assoc_args['socket'] = $extra; - } - } else { - $assoc_args['host'] = $raw_host; - } - - return $assoc_args; -} - -function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { - check_proc_available( 'run_mysql_command' ); - - if ( ! $descriptors ) { - $descriptors = array( STDIN, STDOUT, STDERR ); - } - - if ( isset( $assoc_args['host'] ) ) { - //@codingStandardsIgnoreStart - $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); - //@codingStandardsIgnoreEnd - } - - $pass = $assoc_args['pass']; - unset( $assoc_args['pass'] ); - - $old_pass = getenv( 'MYSQL_PWD' ); - putenv( 'MYSQL_PWD=' . $pass ); - - $final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args ); - - $proc = proc_open_compat( $final_cmd, $descriptors, $pipes ); - if ( ! $proc ) { - exit( 1 ); - } - - $r = proc_close( $proc ); - - putenv( 'MYSQL_PWD=' . $old_pass ); - - if ( $r ) { - exit( $r ); - } -} - -/** - * Render PHP or other types of files using Mustache templates. - * - * IMPORTANT: Automatic HTML escaping is disabled! - */ -function mustache_render( $template_name, $data = array() ) { - if ( ! file_exists( $template_name ) ) { - $template_name = WP_CLI_ROOT . "/templates/$template_name"; - } - - $template = file_get_contents( $template_name ); - - $m = new \Mustache_Engine( - array( - 'escape' => function ( $val ) { - return $val; }, - ) - ); - - return $m->render( $template, $data ); -} - -/** - * Create a progress bar to display percent completion of a given operation. - * - * Progress bar is written to STDOUT, and disabled when command is piped. Progress - * advances with `$progress->tick()`, and completes with `$progress->finish()`. - * Process bar also indicates elapsed time and expected total time. - * - * ``` - * # `wp user generate` ticks progress bar each time a new user is created. - * # - * # $ wp user generate --count=500 - * # Generating users 22 % [=======> ] 0:05 / 0:23 - * - * $progress = \WP_CLI\Utils\make_progress_bar( 'Generating users', $count ); - * for ( $i = 0; $i < $count; $i++ ) { - * // uses wp_insert_user() to insert the user - * $progress->tick(); - * } - * $progress->finish(); - * ``` - * - * @access public - * @category Output - * - * @param string $message Text to display before the progress bar. - * @param integer $count Total number of ticks to be performed. - * @param int $interval Optional. The interval in milliseconds between updates. Default 100. - * @return cli\progress\Bar|WP_CLI\NoOp - */ -function make_progress_bar( $message, $count, $interval = 100 ) { - if ( \cli\Shell::isPiped() ) { - return new \WP_CLI\NoOp; - } - - return new \cli\progress\Bar( $message, $count, $interval ); -} - -function parse_url( $url ) { - $url_parts = \parse_url( $url ); - - if ( ! isset( $url_parts['scheme'] ) ) { - $url_parts = parse_url( 'http://' . $url ); - } - - return $url_parts; -} - -/** - * Check if we're running in a Windows environment (cmd.exe). - * - * @return bool - */ -function is_windows() { - return false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN'; -} - -/** - * Replace magic constants in some PHP source code. - * - * @param string $source The PHP code to manipulate. - * @param string $path The path to use instead of the magic constants - */ -function replace_path_consts( $source, $path ) { - $replacements = array( - '__FILE__' => "'$path'", - '__DIR__' => "'" . dirname( $path ) . "'", - ); - - $old = array_keys( $replacements ); - $new = array_values( $replacements ); - - return str_replace( $old, $new, $source ); -} - -/** - * Make a HTTP request to a remote URL. - * - * Wraps the Requests HTTP library to ensure every request includes a cert. - * - * ``` - * # `wp core download` verifies the hash for a downloaded WordPress archive - * - * $md5_response = Utils\http_request( 'GET', $download_url . '.md5' ); - * if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { - * WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})" ); - * } - * ``` - * - * @access public - * - * @param string $method HTTP method (GET, POST, DELETE, etc.) - * @param string $url URL to make the HTTP request to. - * @param array $headers Add specific headers to the request. - * @param array $options - * @return object - */ -function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { - - $cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; - $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; - if ( inside_phar() ) { - // cURL can't read Phar archives - $options['verify'] = extract_from_phar( - WP_CLI_VENDOR_DIR . $cert_path - ); - } else { - foreach ( get_vendor_paths() as $vendor_path ) { - if ( file_exists( $vendor_path . $cert_path ) ) { - $options['verify'] = $vendor_path . $cert_path; - break; - } - } - if ( empty( $options['verify'] ) ) { - $error_msg = 'Cannot find SSL certificate.'; - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - throw new \RuntimeException( $error_msg ); - } - } - - try { - return \Requests::request( $url, $headers, $data, $method, $options ); - } catch ( \Requests_Exception $ex ) { - // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. - if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ), true ) ) { - $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - throw new \RuntimeException( $error_msg, null, $ex ); - } - // Handle SSL certificate issues gracefully - \WP_CLI::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) ); - $options['verify'] = false; - try { - return \Requests::request( $url, $headers, $data, $method, $options ); - } catch ( \Requests_Exception $ex ) { - $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - throw new \RuntimeException( $error_msg, null, $ex ); - } - } -} - -/** - * Increments a version string using the "x.y.z-pre" format - * - * Can increment the major, minor or patch number by one - * If $new_version == "same" the version string is not changed - * If $new_version is not a known keyword, it will be used as the new version string directly - * - * @param string $current_version - * @param string $new_version - * @return string - */ -function increment_version( $current_version, $new_version ) { - // split version assuming the format is x.y.z-pre - $current_version = explode( '-', $current_version, 2 ); - $current_version[0] = explode( '.', $current_version[0] ); - - switch ( $new_version ) { - case 'same': - // do nothing - break; - - case 'patch': - $current_version[0][2]++; - - $current_version = array( $current_version[0] ); // drop possible pre-release info - break; - - case 'minor': - $current_version[0][1]++; - $current_version[0][2] = 0; - - $current_version = array( $current_version[0] ); // drop possible pre-release info - break; - - case 'major': - $current_version[0][0]++; - $current_version[0][1] = 0; - $current_version[0][2] = 0; - - $current_version = array( $current_version[0] ); // drop possible pre-release info - break; - - default: // not a keyword - $current_version = array( array( $new_version ) ); - break; - } - - // reconstruct version string - $current_version[0] = implode( '.', $current_version[0] ); - $current_version = implode( '-', $current_version ); - - return $current_version; -} - -/** - * Compare two version strings to get the named semantic version. - * - * @access public - * - * @param string $new_version - * @param string $original_version - * @return string $name 'major', 'minor', 'patch' - */ -function get_named_sem_ver( $new_version, $original_version ) { - - if ( ! Comparator::greaterThan( $new_version, $original_version ) ) { - return ''; - } - - $parts = explode( '-', $original_version ); - $bits = explode( '.', $parts[0] ); - $major = $bits[0]; - if ( isset( $bits[1] ) ) { - $minor = $bits[1]; - } - if ( isset( $bits[2] ) ) { - $patch = $bits[2]; - } - - if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { - return 'patch'; - } - - if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { - return 'minor'; - } - - return 'major'; -} - -/** - * Return the flag value or, if it's not set, the $default value. - * - * Because flags can be negated (e.g. --no-quiet to negate --quiet), this - * function provides a safer alternative to using - * `isset( $assoc_args['quiet'] )` or similar. - * - * @access public - * @category Input - * - * @param array $assoc_args Arguments array. - * @param string $flag Flag to get the value. - * @param mixed $default Default value for the flag. Default: NULL - * @return mixed - */ -function get_flag_value( $assoc_args, $flag, $default = null ) { - return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; -} - -/** - * Get the home directory. - * - * @access public - * @category System - * - * @return string - */ -function get_home_dir() { - $home = getenv( 'HOME' ); - if ( ! $home ) { - // In Windows $HOME may not be defined - $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); - } - - return rtrim( $home, '/\\' ); -} - -/** - * Appends a trailing slash. - * - * @access public - * @category System - * - * @param string $string What to add the trailing slash to. - * @return string String with trailing slash added. - */ -function trailingslashit( $string ) { - return rtrim( $string, '/\\' ) . '/'; -} - -/** - * Normalize a filesystem path. - * - * On Windows systems, replaces backslashes with forward slashes - * and forces upper-case drive letters. - * Allows for two leading slashes for Windows network shares, but - * ensures that all other duplicate slashes are reduced to a single one. - * Ensures upper-case drive letters on Windows systems. - * - * @access public - * @category System - * - * @param string $path Path to normalize. - * @return string Normalized path. - */ -function normalize_path( $path ) { - $path = str_replace( '\\', '/', $path ); - $path = preg_replace( '|(?<=.)/+|', '/', $path ); - if ( ':' === substr( $path, 1, 1 ) ) { - $path = ucfirst( $path ); - } - return $path; -} - - -/** - * Convert Windows EOLs to *nix. - * - * @param string $str String to convert. - * @return string String with carriage return / newline pairs reduced to newlines. - */ -function normalize_eols( $str ) { - return str_replace( "\r\n", "\n", $str ); -} - -/** - * Get the system's temp directory. Warns user if it isn't writable. - * - * @access public - * @category System - * - * @return string - */ -function get_temp_dir() { - static $temp = ''; - - if ( $temp ) { - return $temp; - } - - // `sys_get_temp_dir()` introduced PHP 5.2.1. Will always return something. - $temp = trailingslashit( sys_get_temp_dir() ); - - if ( ! is_writable( $temp ) ) { - \WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); - } - - return $temp; -} - -/** - * Parse a SSH url for its host, port, and path. - * - * Similar to parse_url(), but adds support for defined SSH aliases. - * - * ``` - * host OR host/path/to/wordpress OR host:port/path/to/wordpress - * ``` - * - * @access public - * - * @return mixed - */ -function parse_ssh_url( $url, $component = -1 ) { - preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); - $bits = array(); - foreach ( array( - 2 => 'scheme', - 4 => 'user', - 5 => 'host', - 7 => 'port', - 8 => 'path', - ) as $i => $key ) { - if ( ! empty( $matches[ $i ] ) ) { - $bits[ $key ] = $matches[ $i ]; - } - } - - // Find the hostname from `vagrant ssh-config` automatically. - if ( preg_match( '/^vagrant:?/', $url ) ) { - if ( 'vagrant' === $bits['host'] && empty( $bits['scheme'] ) ) { - $ssh_config = shell_exec( 'vagrant ssh-config 2>/dev/null' ); - if ( preg_match( '/Host\s(.+)/', $ssh_config, $matches ) ) { - $bits['scheme'] = 'vagrant'; - $bits['host'] = $matches[1]; - } - } - } - - switch ( $component ) { - case PHP_URL_SCHEME: - return isset( $bits['scheme'] ) ? $bits['scheme'] : null; - case PHP_URL_USER: - return isset( $bits['user'] ) ? $bits['user'] : null; - case PHP_URL_HOST: - return isset( $bits['host'] ) ? $bits['host'] : null; - case PHP_URL_PATH: - return isset( $bits['path'] ) ? $bits['path'] : null; - case PHP_URL_PORT: - return isset( $bits['port'] ) ? $bits['port'] : null; - default: - return $bits; - } -} - -/** - * Report the results of the same operation against multiple resources. - * - * @access public - * @category Input - * - * @param string $noun Resource being affected (e.g. plugin) - * @param string $verb Type of action happening to the noun (e.g. activate) - * @param integer $total Total number of resource being affected. - * @param integer $successes Number of successful operations. - * @param integer $failures Number of failures. - * @param null|integer $skips Optional. Number of skipped operations. Default null (don't show skips). - */ -function report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips = null ) { - $plural_noun = $noun . 's'; - $past_tense_verb = past_tense_verb( $verb ); - $past_tense_verb_upper = ucfirst( $past_tense_verb ); - if ( $failures ) { - $failed_skipped_message = null === $skips ? '' : " ({$failures} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')'; - if ( $successes ) { - WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}{$failed_skipped_message}." ); - } else { - WP_CLI::error( "No {$plural_noun} {$past_tense_verb}{$failed_skipped_message}." ); - } - } else { - $skipped_message = $skips ? " ({$skips} skipped)" : ''; - if ( $successes || $skips ) { - WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}{$skipped_message}." ); - } else { - $message = $total > 1 ? ucfirst( $plural_noun ) : ucfirst( $noun ); - WP_CLI::success( "{$message} already {$past_tense_verb}." ); - } - } -} - -/** - * Parse a string of command line arguments into an $argv-esqe variable. - * - * @access public - * @category Input - * - * @param string $arguments - * @return array - */ -function parse_str_to_argv( $arguments ) { - preg_match_all( '/(?<=^|\s)([\'"]?)(.+?)(? 'create', - 'check' => 'check-update', - 'capability' => 'cap', - 'clear' => 'flush', - 'decrement' => 'decr', - 'del' => 'delete', - 'directory' => 'dir', - 'exec' => 'eval', - 'exec-file' => 'eval-file', - 'increment' => 'incr', - 'language' => 'locale', - 'lang' => 'locale', - 'new' => 'create', - 'number' => 'count', - 'remove' => 'delete', - 'regen' => 'regenerate', - 'rep' => 'replace', - 'repl' => 'replace', - 'trash' => 'delete', - 'v' => 'version', - ); - - if ( array_key_exists( $target, $suggestion_map ) && in_array( $suggestion_map[ $target ], $options, true ) ) { - return $suggestion_map[ $target ]; - } - - if ( empty( $options ) ) { - return ''; - } - foreach ( $options as $option ) { - $distance = levenshtein( $option, $target ); - $levenshtein[ $option ] = $distance; - } - - // Sort known command strings by distance to user entry. - asort( $levenshtein ); - - // Fetch the closest command string. - reset( $levenshtein ); - $suggestion = key( $levenshtein ); - - // Only return a suggestion if below a given threshold. - return $levenshtein[ $suggestion ] <= $threshold && $suggestion !== $target - ? (string) $suggestion - : ''; -} - -/** - * Get a Phar-safe version of a path. - * - * For paths inside a Phar, this strips the outer filesystem's location to - * reduce the path to what it needs to be within the Phar archive. - * - * Use the __FILE__ or __DIR__ constants as a starting point. - * - * @param string $path An absolute path that might be within a Phar. - * - * @return string A Phar-safe version of the path. - */ -function phar_safe_path( $path ) { - - if ( ! inside_phar() ) { - return $path; - } - - return str_replace( - PHAR_STREAM_PREFIX . WP_CLI_PHAR_PATH . '/', - PHAR_STREAM_PREFIX, - $path - ); -} - -/** - * Check whether a given Command object is part of the bundled set of - * commands. - * - * This function accepts both a fully qualified class name as a string as - * well as an object that extends `WP_CLI\Dispatcher\CompositeCommand`. - * - * @param \WP_CLI\Dispatcher\CompositeCommand|string $command - * - * @return bool - */ -function is_bundled_command( $command ) { - static $classes; - - if ( null === $classes ) { - $classes = array(); - $class_map = WP_CLI_VENDOR_DIR . '/composer/autoload_commands_classmap.php'; - if ( file_exists( WP_CLI_VENDOR_DIR . '/composer/' ) ) { - $classes = include $class_map; - } - } - - if ( is_object( $command ) ) { - $command = get_class( $command ); - } - - return is_string( $command ) - ? array_key_exists( $command, $classes ) - : false; -} - -/** - * Maybe prefix command string with "/usr/bin/env". - * Removes (if there) if Windows, adds (if not there) if not. - * - * @param string $command - * - * @return string - */ -function force_env_on_nix_systems( $command ) { - $env_prefix = '/usr/bin/env '; - $env_prefix_len = strlen( $env_prefix ); - if ( is_windows() ) { - if ( 0 === strncmp( $command, $env_prefix, $env_prefix_len ) ) { - $command = substr( $command, $env_prefix_len ); - } - } else { - if ( 0 !== strncmp( $command, $env_prefix, $env_prefix_len ) ) { - $command = $env_prefix . $command; - } - } - return $command; -} - -/** - * Check that `proc_open()` and `proc_close()` haven't been disabled. - * - * @param string $context Optional. If set will appear in error message. Default null. - * @param bool $return Optional. If set will return false rather than error out. Default false. - * - * @return bool - */ -function check_proc_available( $context = null, $return = false ) { - if ( ! function_exists( 'proc_open' ) || ! function_exists( 'proc_close' ) ) { - if ( $return ) { - return false; - } - $msg = 'The PHP functions `proc_open()` and/or `proc_close()` are disabled. Please check your PHP ini directive `disable_functions` or suhosin settings.'; - if ( $context ) { - WP_CLI::error( sprintf( "Cannot do '%s': %s", $context, $msg ) ); - } else { - WP_CLI::error( $msg ); - } - } - return true; -} - -/** - * Returns past tense of verb, with limited accuracy. Only regular verbs catered for, apart from "reset". - * - * @param string $verb Verb to return past tense of. - * - * @return string - */ -function past_tense_verb( $verb ) { - static $irregular = array( - 'reset' => 'reset', - ); - if ( isset( $irregular[ $verb ] ) ) { - return $irregular[ $verb ]; - } - $last = substr( $verb, -1 ); - if ( 'e' === $last ) { - $verb = substr( $verb, 0, -1 ); - } elseif ( 'y' === $last && ! preg_match( '/[aeiou]y$/', $verb ) ) { - $verb = substr( $verb, 0, -1 ) . 'i'; - } elseif ( preg_match( '/^[^aeiou]*[aeiou][^aeiouhwxy]$/', $verb ) ) { - // Rule of thumb that most (all?) one-voweled regular verbs ending in vowel + consonant (excluding "h", "w", "x", "y") double their final consonant - misses many cases (eg "submit"). - $verb .= $last; - } - return $verb . 'ed'; -} - -/** - * Get the path to the PHP binary used when executing WP-CLI. - * - * Environment values permit specific binaries to be indicated. - * - * @access public - * @category System - * - * @return string - */ -function get_php_binary() { - if ( $wp_cli_php_used = getenv( 'WP_CLI_PHP_USED' ) ) { - return $wp_cli_php_used; - } - - if ( $wp_cli_php = getenv( 'WP_CLI_PHP' ) ) { - return $wp_cli_php; - } - - // Available since PHP 5.4. - if ( defined( 'PHP_BINARY' ) ) { - // @codingStandardsIgnoreLine - return PHP_BINARY; - } - - // @codingStandardsIgnoreLine - if ( @is_executable( PHP_BINDIR . '/php' ) ) { - return PHP_BINDIR . '/php'; - } - - // @codingStandardsIgnoreLine - if ( is_windows() && @is_executable( PHP_BINDIR . '/php.exe' ) ) { - return PHP_BINDIR . '/php.exe'; - } - - return 'php'; -} - -/** - * Windows compatible `proc_open()`. - * Works around bug in PHP, and also deals with *nix-like `ENV_VAR=blah cmd` environment variable prefixes. - * - * @access public - * - * @param string $command Command to execute. - * @param array $descriptorspec Indexed array of descriptor numbers and their values. - * @param array &$pipes Indexed array of file pointers that correspond to PHP's end of any pipes that are created. - * @param string $cwd Initial working directory for the command. - * @param array $env Array of environment variables. - * @param array $other_options Array of additional options (Windows only). - * - * @return string Command stripped of any environment variable settings. - */ -function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = null, $other_options = null ) { - if ( is_windows() ) { - // Need to encompass the whole command in double quotes - PHP bug https://bugs.php.net/bug.php?id=49139 - $cmd = '"' . _proc_open_compat_win_env( $cmd, $env ) . '"'; - } - return proc_open( $cmd, $descriptorspec, $pipes, $cwd, $env, $other_options ); -} - -/** - * For use by `proc_open_compat()` only. Separated out for ease of testing. Windows only. - * Turns *nix-like `ENV_VAR=blah command` environment variable prefixes into stripped `cmd` with prefixed environment variables added to passed in environment array. - * - * @access private - * - * @param string $command Command to execute. - * @param array &$env Array of existing environment variables. Will be modified if any settings in command. - * - * @return string Command stripped of any environment variable settings. - */ -function _proc_open_compat_win_env( $cmd, &$env ) { - if ( false !== strpos( $cmd, '=' ) ) { - while ( preg_match( '/^([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|[^ ]*) /', $cmd, $matches ) ) { - $cmd = substr( $cmd, strlen( $matches[0] ) ); - if ( null === $env ) { - $env = array(); - } - $env[ $matches[1] ] = isset( $matches[2][0] ) && '"' === $matches[2][0] ? substr( $matches[2], 1, -1 ) : $matches[2]; - } - } - return $cmd; -} - -/** - * First half of escaping for LIKE special characters % and _ before preparing for MySQL. - * - * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security. - * - * Copied from core "wp-includes/wp-db.php". Avoids dependency on WP 4.4 wpdb. - * - * @access public - * - * @param string $text The raw text to be escaped. The input typed by the user should have no - * extra or deleted slashes. - * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() - * or real_escape next. - */ -function esc_like( $text ) { - return addcslashes( $text, '_%\\' ); -} - -/** - * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names. - * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html - * - * @param string|array $idents A single identifier or an array of identifiers. - * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings. - */ -function esc_sql_ident( $idents ) { - $backtick = function ( $v ) { - // Escape any backticks in the identifier by doubling. - return '`' . str_replace( '`', '``', $v ) . '`'; - }; - if ( is_string( $idents ) ) { - return $backtick( $idents ); - } - return array_map( $backtick, $idents ); -} - -/** - * Check whether a given string is a valid JSON representation. - * - * @param string $argument String to evaluate. - * @param bool $ignore_scalars Optional. Whether to ignore scalar values. - * Defaults to true. - * - * @return bool Whether the provided string is a valid JSON representation. - */ -function is_json( $argument, $ignore_scalars = true ) { - if ( ! is_string( $argument ) || '' === $argument ) { - return false; - } - - if ( $ignore_scalars && ! in_array( $argument[0], array( '{', '[' ), true ) ) { - return false; - } - - json_decode( $argument, $assoc = true ); - - return json_last_error() === JSON_ERROR_NONE; -} - -/** - * Parse known shell arrays included in the $assoc_args array. - * - * @param array $assoc_args Associative array of arguments. - * @param array $array_arguments Array of argument keys that should receive an - * array through the shell. - * - * @return array - */ -function parse_shell_arrays( $assoc_args, $array_arguments ) { - if ( empty( $assoc_args ) || empty( $array_arguments ) ) { - return $assoc_args; - } - - foreach ( $array_arguments as $key ) { - if ( array_key_exists( $key, $assoc_args ) && is_json( $assoc_args[ $key ] ) ) { - $assoc_args[ $key ] = json_decode( $assoc_args[ $key ], $assoc = true ); - } - } - - return $assoc_args; -} diff --git a/features/extra/no-mail.php b/features/extra/no-mail.php deleted file mode 100644 index de7a42272..000000000 --- a/features/extra/no-mail.php +++ /dev/null @@ -1,7 +0,0 @@ -=' ) - ); -} else { - // But make sure @less-than-wp tags always exist for those special cases. (Note: @less-than-wp-latest etc won't work and shouldn't be used). - $wp_version_reqs = array_merge( $wp_version_reqs, version_tags( 'less-than-wp', '9999', '>=' ) ); -} - -$skip_tags = array_merge( - $wp_version_reqs, - version_tags( 'require-php', PHP_VERSION, '<' ), - version_tags( 'less-than-php', PHP_VERSION, '>=' ) // Note: this was '>' prior to WP-CLI 1.5.0 but the change is unlikely to cause BC issues as usually compared against major.minor only. -); - -# Skip Github API tests if `GITHUB_TOKEN` not available because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 -if ( ! getenv( 'GITHUB_TOKEN' ) ) { - $skip_tags[] = '@github-api'; -} - -# Skip tests known to be broken. -$skip_tags[] = '@broken'; - -# Require PHP extension, eg 'imagick'. -function extension_tags() { - $extension_tags = array(); - exec( "grep '@require-extension-[A-Za-z_]*' -h -o features/*.feature | uniq", $extension_tags ); - - $skip_tags = array(); - - $substr_start = strlen( '@require-extension-' ); - foreach ( $extension_tags as $tag ) { - $extension = substr( $tag, $substr_start ); - if ( ! extension_loaded( $extension ) ) { - $skip_tags[] = $tag; - } - } - - return $skip_tags; -} - -$skip_tags = array_merge( $skip_tags, extension_tags() ); - -if ( !empty( $skip_tags ) ) { - echo '--tags=~' . implode( '&&~', $skip_tags ); -} - From b089b591ebbb7e5c52b9a70a3bb12d1d0757ec90 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sat, 4 Aug 2018 15:23:45 +0200 Subject: [PATCH 2/9] Switch PHP 5.4 env back to precise dist --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 775eeb89d..3707aad21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,4 +70,5 @@ jobs: env: WP_VERSION=trunk - stage: test php: 5.4 + dist: precise env: WP_VERSION=latest From 2c1e0e9f586c684278cbd66ec14806ce4efe574e Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sat, 4 Aug 2018 16:25:31 +0200 Subject: [PATCH 3/9] Normalize composer.json file --- composer.json | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 0c73d262b..0cbd841fb 100644 --- a/composer.json +++ b/composer.json @@ -1,11 +1,8 @@ { "name": "wp-cli/package-command", - "description": "Lists, installs, and removes WP-CLI packages.", "type": "wp-cli-package", + "description": "Lists, installs, and removes WP-CLI packages.", "homepage": "https://github.com/wp-cli/package-command", - "support": { - "issues": "https://github.com/wp-cli/package-command/issues" - }, "license": "MIT", "authors": [ { @@ -14,14 +11,6 @@ "homepage": "https://runcommand.io" } ], - "minimum-stability": "dev", - "prefer-stable": true, - "autoload": { - "psr-4": { - "": "src/" - }, - "files": [ "package-command.php" ] - }, "require": { "composer/composer": "^1.2.0", "wp-cli/wp-cli": "^2" @@ -29,6 +18,9 @@ "require-dev": { "wp-cli/wp-cli-tests": "^0" }, + "config": { + "sort-packages": true + }, "extra": { "branch-alias": { "dev-master": "1.x-dev" @@ -43,11 +35,21 @@ "package uninstall" ] }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "package-command.php" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, "scripts": { + "behat": "run-behat-tests", "lint": "run-linter-tests", "phpcs": "run-phpcs-tests", "phpunit": "run-php-unit-tests", - "behat": "run-behat-tests", "prepare-tests": "install-package-tests", "test": [ "@lint", @@ -55,5 +57,8 @@ "@phpunit", "@behat" ] + }, + "support": { + "issues": "https://github.com/wp-cli/package-command/issues" } } From b7ee9fe4fccaeac4577300080b122e52edac51c1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 4 Aug 2018 16:31:19 +0200 Subject: [PATCH 4/9] Add missing dev dependency --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0cbd841fb..ed3755867 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "wp-cli/wp-cli": "^2" }, "require-dev": { - "wp-cli/wp-cli-tests": "^0" + "wp-cli/wp-cli-tests": "^0", + "wp-cli/scaffold-command": "^1.2" }, "config": { "sort-packages": true From 65994b92238907811bbab58235ec1f7fd220c9f6 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sat, 4 Aug 2018 16:40:22 +0200 Subject: [PATCH 5/9] Remove left-over Behat step definitions --- features/steps/given.php | 219 ------------------------------------ features/steps/then.php | 237 --------------------------------------- features/steps/when.php | 54 --------- 3 files changed, 510 deletions(-) delete mode 100644 features/steps/given.php delete mode 100644 features/steps/then.php delete mode 100644 features/steps/when.php diff --git a/features/steps/given.php b/features/steps/given.php deleted file mode 100644 index 3e501c2c7..000000000 --- a/features/steps/given.php +++ /dev/null @@ -1,219 +0,0 @@ -Given( '/^an empty directory$/', - function ( $world ) { - $world->create_run_dir(); - } -); - -$steps->Given( '/^an? (empty|non-existent) ([^\s]+) directory$/', - function ( $world, $empty_or_nonexistent, $dir ) { - $dir = $world->replace_variables( $dir ); - if ( ! WP_CLI\Utils\is_path_absolute( $dir ) ) { - $dir = $world->variables['RUN_DIR'] . "/$dir"; - } - if ( 0 !== strpos( $dir, sys_get_temp_dir() ) ) { - throw new RuntimeException( sprintf( "Attempted to delete directory '%s' that is not in the temp directory '%s'. " . __FILE__ . ':' . __LINE__, $dir, sys_get_temp_dir() ) ); - } - $world->remove_dir( $dir ); - if ( 'empty' === $empty_or_nonexistent ) { - mkdir( $dir, 0777, true /*recursive*/ ); - } - } -); - -$steps->Given( '/^an empty cache/', - function ( $world ) { - $world->variables['SUITE_CACHE_DIR'] = FeatureContext::create_cache_dir(); - } -); - -$steps->Given( '/^an? ([^\s]+) file:$/', - function ( $world, $path, PyStringNode $content ) { - $content = (string) $content . "\n"; - $full_path = $world->variables['RUN_DIR'] . "/$path"; - $dir = dirname( $full_path ); - if ( ! file_exists( $dir ) ) { - mkdir( $dir, 0777, true /*recursive*/ ); - } - file_put_contents( $full_path, $content ); - } -); - -$steps->Given( '/^"([^"]+)" replaced with "([^"]+)" in the ([^\s]+) file$/', function( $world, $search, $replace, $path ) { - $full_path = $world->variables['RUN_DIR'] . "/$path"; - $contents = file_get_contents( $full_path ); - $contents = str_replace( $search, $replace, $contents ); - file_put_contents( $full_path, $contents ); -}); - -$steps->Given( '/^WP files$/', - function ( $world ) { - $world->download_wp(); - } -); - -$steps->Given( '/^wp-config\.php$/', - function ( $world ) { - $world->create_config(); - } -); - -$steps->Given( '/^a database$/', - function ( $world ) { - $world->create_db(); - } -); - -$steps->Given( '/^a WP (install|installation)$/', - function ( $world ) { - $world->install_wp(); - } -); - -$steps->Given( "/^a WP (install|installation) in '([^\s]+)'$/", - function ( $world, $_, $subdir ) { - $world->install_wp( $subdir ); - } -); - -$steps->Given( '/^a WP (install|installation) with Composer$/', - function ( $world ) { - $world->install_wp_with_composer(); - } -); - -$steps->Given( "/^a WP (install|installation) with Composer and a custom vendor directory '([^\s]+)'$/", - function ( $world, $_, $vendor_directory ) { - $world->install_wp_with_composer( $vendor_directory ); - } -); - -$steps->Given( '/^a WP multisite (subdirectory|subdomain)?\s?(install|installation)$/', - function ( $world, $type = 'subdirectory' ) { - $world->install_wp(); - $subdomains = ! empty( $type ) && 'subdomain' === $type ? 1 : 0; - $world->proc( 'wp core install-network', array( 'title' => 'WP CLI Network', 'subdomains' => $subdomains ) )->run_check(); - } -); - -$steps->Given( '/^these installed and active plugins:$/', - function( $world, $stream ) { - $plugins = implode( ' ', array_map( 'trim', explode( PHP_EOL, (string)$stream ) ) ); - $world->proc( "wp plugin install $plugins --activate" )->run_check(); - } -); - -$steps->Given( '/^a custom wp-content directory$/', - function ( $world ) { - $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; - - $wp_config_code = file_get_contents( $wp_config_path ); - - $world->move_files( 'wp-content', 'my-content' ); - $world->add_line_to_wp_config( $wp_config_code, - "define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/my-content' );" ); - - $world->move_files( 'my-content/plugins', 'my-plugins' ); - $world->add_line_to_wp_config( $wp_config_code, - "define( 'WP_PLUGIN_DIR', __DIR__ . '/my-plugins' );" ); - - file_put_contents( $wp_config_path, $wp_config_code ); - } -); - -$steps->Given( '/^download:$/', - function ( $world, TableNode $table ) { - foreach ( $table->getHash() as $row ) { - $path = $world->replace_variables( $row['path'] ); - if ( file_exists( $path ) ) { - // assume it's the same file and skip re-download - continue; - } - - Process::create( \WP_CLI\Utils\esc_cmd( 'curl -sSL %s > %s', $row['url'], $path ) )->run_check(); - } - } -); - -$steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/', - function ( $world, $stream, $output_filter, $key ) { - - $stream = strtolower( $stream ); - - if ( $output_filter ) { - $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; - if ( false !== preg_match( $output_filter, $world->result->$stream, $matches ) ) - $output = array_pop( $matches ); - else - $output = ''; - } else { - $output = $world->result->$stream; - } - $world->variables[ $key ] = trim( $output, "\n" ); - } -); - -$steps->Given( '/^a new Phar with (?:the same version|version "([^"]+)")$/', - function ( $world, $version = 'same' ) { - $world->build_phar( $version ); - } -); - -$steps->Given( '/^a downloaded Phar with (?:the same version|version "([^"]+)")$/', - function ( $world, $version = 'same' ) { - $world->download_phar( $version ); - } -); - -$steps->Given( '/^save the (.+) file ([\'].+[^\'])?as \{(\w+)\}$/', - function ( $world, $filepath, $output_filter, $key ) { - $full_file = file_get_contents( $world->replace_variables( $filepath ) ); - - if ( $output_filter ) { - $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; - if ( false !== preg_match( $output_filter, $full_file, $matches ) ) - $output = array_pop( $matches ); - else - $output = ''; - } else { - $output = $full_file; - } - $world->variables[ $key ] = trim( $output, "\n" ); - } -); - -$steps->Given('/^a misconfigured WP_CONTENT_DIR constant directory$/', - function($world) { - $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; - - $wp_config_code = file_get_contents( $wp_config_path ); - - $world->add_line_to_wp_config( $wp_config_code, - "define( 'WP_CONTENT_DIR', '' );" ); - - file_put_contents( $wp_config_path, $wp_config_code ); - } -); - -$steps->Given( '/^a dependency on current wp-cli$/', - function ( $world ) { - $world->composer_require_current_wp_cli(); - } -); - -$steps->Given( '/^a PHP built-in web server$/', - function ( $world ) { - $world->start_php_server(); - } -); - -$steps->Given( "/^a PHP built-in web server to serve '([^\s]+)'$/", - function ( $world, $subdir ) { - $world->start_php_server( $subdir ); - } -); diff --git a/features/steps/then.php b/features/steps/then.php deleted file mode 100644 index 21589e737..000000000 --- a/features/steps/then.php +++ /dev/null @@ -1,237 +0,0 @@ -Then( '/^the return code should( not)? be (\d+)$/', - function ( $world, $not, $return_code ) { - if ( ( ! $not && $return_code != $world->result->return_code ) || ( $not && $return_code == $world->result->return_code ) ) { - throw new RuntimeException( $world->result ); - } - } -); - -$steps->Then( '/^(STDOUT|STDERR) should (be|contain|not contain):$/', - function ( $world, $stream, $action, PyStringNode $expected ) { - - $stream = strtolower( $stream ); - - $expected = $world->replace_variables( (string) $expected ); - - checkString( $world->result->$stream, $expected, $action, $world->result ); - } -); - -$steps->Then( '/^(STDOUT|STDERR) should be a number$/', - function ( $world, $stream ) { - - $stream = strtolower( $stream ); - - assertNumeric( trim( $world->result->$stream, "\n" ) ); - } -); - -$steps->Then( '/^(STDOUT|STDERR) should not be a number$/', - function ( $world, $stream ) { - - $stream = strtolower( $stream ); - - assertNotNumeric( trim( $world->result->$stream, "\n" ) ); - } -); - -$steps->Then( '/^STDOUT should be a table containing rows:$/', - function ( $world, TableNode $expected ) { - $output = $world->result->stdout; - $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); - - $expected_rows = array(); - foreach ( $expected->getRows() as $row ) { - $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); - } - - compareTables( $expected_rows, $actual_rows, $output ); - } -); - -$steps->Then( '/^STDOUT should end with a table containing rows:$/', - function ( $world, TableNode $expected ) { - $output = $world->result->stdout; - $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); - - $expected_rows = array(); - foreach ( $expected->getRows() as $row ) { - $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); - } - - $start = array_search( $expected_rows[0], $actual_rows ); - - if ( false === $start ) - throw new \Exception( $world->result ); - - compareTables( $expected_rows, array_slice( $actual_rows, $start ), $output ); - } -); - -$steps->Then( '/^STDOUT should be JSON containing:$/', - function ( $world, PyStringNode $expected ) { - $output = $world->result->stdout; - $expected = $world->replace_variables( (string) $expected ); - - if ( !checkThatJsonStringContainsJsonString( $output, $expected ) ) { - throw new \Exception( $world->result ); - } -}); - -$steps->Then( '/^STDOUT should be a JSON array containing:$/', - function ( $world, PyStringNode $expected ) { - $output = $world->result->stdout; - $expected = $world->replace_variables( (string) $expected ); - - $actualValues = json_decode( $output ); - $expectedValues = json_decode( $expected ); - - $missing = array_diff( $expectedValues, $actualValues ); - if ( !empty( $missing ) ) { - throw new \Exception( $world->result ); - } -}); - -$steps->Then( '/^STDOUT should be CSV containing:$/', - function ( $world, TableNode $expected ) { - $output = $world->result->stdout; - - $expected_rows = $expected->getRows(); - foreach ( $expected as &$row ) { - foreach ( $row as &$value ) { - $value = $world->replace_variables( $value ); - } - } - - if ( ! checkThatCsvStringContainsValues( $output, $expected_rows ) ) - throw new \Exception( $world->result ); - } -); - -$steps->Then( '/^STDOUT should be YAML containing:$/', - function ( $world, PyStringNode $expected ) { - $output = $world->result->stdout; - $expected = $world->replace_variables( (string) $expected ); - - if ( !checkThatYamlStringContainsYamlString( $output, $expected ) ) { - throw new \Exception( $world->result ); - } -}); - -$steps->Then( '/^(STDOUT|STDERR) should be empty$/', - function ( $world, $stream ) { - - $stream = strtolower( $stream ); - - if ( !empty( $world->result->$stream ) ) { - throw new \Exception( $world->result ); - } - } -); - -$steps->Then( '/^(STDOUT|STDERR) should not be empty$/', - function ( $world, $stream ) { - - $stream = strtolower( $stream ); - - if ( '' === rtrim( $world->result->$stream, "\n" ) ) { - throw new Exception( $world->result ); - } - } -); - -$steps->Then( '/^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|!=|<>) ([+\w.{}-]+)$/', - function ( $world, $stream, $operator, $goal_ver ) { - $goal_ver = $world->replace_variables( $goal_ver ); - $stream = strtolower( $stream ); - if ( false === version_compare( trim( $world->result->$stream, "\n" ), $goal_ver, $operator ) ) { - throw new Exception( $world->result ); - } - } -); - -$steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:|not contain:)$/', - function ( $world, $path, $type, $action, $expected = null ) { - $path = $world->replace_variables( $path ); - - // If it's a relative path, make it relative to the current test dir - if ( '/' !== $path[0] ) - $path = $world->variables['RUN_DIR'] . "/$path"; - - if ( 'file' == $type ) { - $test = 'file_exists'; - } else if ( 'directory' == $type ) { - $test = 'is_dir'; - } - - switch ( $action ) { - case 'exist': - if ( ! $test( $path ) ) { - throw new Exception( "$path doesn't exist." ); - } - break; - case 'not exist': - if ( $test( $path ) ) { - throw new Exception( "$path exists." ); - } - break; - default: - if ( ! $test( $path ) ) { - throw new Exception( "$path doesn't exist." ); - } - $action = substr( $action, 0, -1 ); - $expected = $world->replace_variables( (string) $expected ); - if ( 'file' == $type ) { - $contents = file_get_contents( $path ); - } else if ( 'directory' == $type ) { - $files = glob( rtrim( $path, '/' ) . '/*' ); - foreach( $files as &$file ) { - $file = str_replace( $path . '/', '', $file ); - } - $contents = implode( PHP_EOL, $files ); - } - checkString( $contents, $expected, $action ); - } - } -); - -$steps->Then( '/^the contents of the (.+) file should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', - function ( $world, $path, $expected ) { - $path = $world->replace_variables( $path ); - // If it's a relative path, make it relative to the current test dir - if ( '/' !== $path[0] ) { - $path = $world->variables['RUN_DIR'] . "/$path"; - } - $contents = file_get_contents( $path ); - assertRegExp( $expected, $contents ); - } -); - -$steps->Then( '/^(STDOUT|STDERR) should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', - function ( $world, $stream, $expected ) { - $stream = strtolower( $stream ); - assertRegExp( $expected, $world->result->$stream ); - } -); - -$steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) { - if ( 'be sent' === $expected ) { - assertNotEquals( 0, $world->email_sends ); - } else if ( 'not be sent' === $expected ) { - assertEquals( 0, $world->email_sends ); - } else { - throw new Exception( 'Invalid expectation' ); - } -}); - -$steps->Then( '/^the HTTP status code should be (\d+)$/', - function ( $world, $return_code ) { - $response = \Requests::request( 'http://localhost:8080' ); - assertEquals( $return_code, $response->status_code ); - } -); diff --git a/features/steps/when.php b/features/steps/when.php deleted file mode 100644 index d23aa0e66..000000000 --- a/features/steps/when.php +++ /dev/null @@ -1,54 +0,0 @@ - 'run_check_stderr', - 'try' => 'run' - ); - $method = $map[ $mode ]; - - return $proc->$method(); -} - -function capture_email_sends( $stdout ) { - $stdout = preg_replace( '#WP-CLI test suite: Sent email to.+\n?#', '', $stdout, -1, $email_sends ); - return array( $stdout, $email_sends ); -} - -$steps->When( '/^I launch in the background `([^`]+)`$/', - function ( $world, $cmd ) { - $world->background_proc( $cmd ); - } -); - -$steps->When( '/^I (run|try) `([^`]+)`$/', - function ( $world, $mode, $cmd ) { - $cmd = $world->replace_variables( $cmd ); - $world->result = invoke_proc( $world->proc( $cmd ), $mode ); - list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); - } -); - -$steps->When( "/^I (run|try) `([^`]+)` from '([^\s]+)'$/", - function ( $world, $mode, $cmd, $subdir ) { - $cmd = $world->replace_variables( $cmd ); - $world->result = invoke_proc( $world->proc( $cmd, array(), $subdir ), $mode ); - list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); - } -); - -$steps->When( '/^I (run|try) the previous command again$/', - function ( $world, $mode ) { - if ( !isset( $world->result ) ) - throw new \Exception( 'No previous command.' ); - - $proc = Process::create( $world->result->command, $world->result->cwd, $world->result->env ); - $world->result = invoke_proc( $proc, $mode ); - list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); - } -); - From 7982ad2f831ab14e0593a631fb3a7db6f8b2667d Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sat, 4 Aug 2018 18:05:59 +0200 Subject: [PATCH 6/9] Add higher timeout config value --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ed3755867..6e24a4360 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "wp-cli/scaffold-command": "^1.2" }, "config": { - "sort-packages": true + "sort-packages": true, + "process-timeout": 7200 }, "extra": { "branch-alias": { From f7a50375204c84f704367ed42452ba6326507d2d Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sat, 4 Aug 2018 18:47:59 +0200 Subject: [PATCH 7/9] Remove tests that require `make-phar.php` These need to be moved into `wp-cli/wp-cli-bundle`. See https://github.com/wp-cli/wp-cli-bundle/issues/2 --- features/package-install.feature | 132 ------------------------------- 1 file changed, 132 deletions(-) diff --git a/features/package-install.feature b/features/package-install.feature index 7440eb4a3..4d991c74b 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -1041,138 +1041,6 @@ Feature: Install WP-CLI packages wp-cli/community-command """ - Scenario: Install a package requiring a WP-CLI version that doesn't match - Given an empty directory - And a new Phar with version "0.23.0" - And a path-command/command.php file: - """ - 'before_wp_load' ) ); - """ - And a path-command/composer.json file: - """ - { - "name": "wp-cli/community-command", - "description": "A demo community command.", - "license": "MIT", - "minimum-stability": "dev", - "autoload": { - "files": [ "command.php" ] - }, - "require": { - "wp-cli/wp-cli": ">=0.24.0" - }, - "require-dev": { - "behat/behat": "~2.5" - } - } - """ - - When I try `{PHAR_PATH} package install path-command` - Then STDOUT should contain: - """ - wp-cli/community-command dev-master requires wp-cli/wp-cli >=0.24.0 - """ - And STDERR should contain: - """ - Error: Package installation failed - """ - And the return code should be 1 - - When I run `cat {PACKAGE_PATH}composer.json` - Then STDOUT should contain: - """ - "version": "0.23.0", - """ - - Scenario: Install a package requiring a WP-CLI version that does match - Given an empty directory - And a new Phar with version "0.23.0" - And a path-command/command.php file: - """ - 'before_wp_load' ) ); - """ - And a path-command/composer.json file: - """ - { - "name": "wp-cli/community-command", - "description": "A demo community command.", - "license": "MIT", - "minimum-stability": "dev", - "autoload": { - "files": [ "command.php" ] - }, - "require": { - "wp-cli/wp-cli": ">=0.22.0" - }, - "require-dev": { - "behat/behat": "~2.5" - } - } - """ - - # Allow for composer/ca-bundle using `openssl_x509_parse()` which throws PHP warnings on old versions of PHP. - When I try `{PHAR_PATH} package install path-command` - Then STDOUT should contain: - """ - Success: Package installed. - """ - And the return code should be 0 - - When I run `cat {PACKAGE_PATH}composer.json` - Then STDOUT should contain: - """ - "version": "0.23.0", - """ - - Scenario: Install a package requiring a WP-CLI alpha version that does match - Given an empty directory - And a new Phar with version "0.23.0-alpha-90ecad6" - And a path-command/command.php file: - """ - 'before_wp_load' ) ); - """ - And a path-command/composer.json file: - """ - { - "name": "wp-cli/community-command", - "description": "A demo community command.", - "license": "MIT", - "minimum-stability": "dev", - "autoload": { - "files": [ "command.php" ] - }, - "require": { - "wp-cli/wp-cli": ">=0.22.0" - }, - "require-dev": { - "behat/behat": "~2.5" - } - } - """ - - # Allow for composer/ca-bundle using `openssl_x509_parse()` which throws PHP warnings on old versions of PHP. - When I try `{PHAR_PATH} package install path-command` - Then STDOUT should contain: - """ - Success: Package installed. - """ - And the return code should be 0 - - When I run `cat {PACKAGE_PATH}composer.json` - Then STDOUT should contain: - """ - "version": "0.23.0-alpha", - """ - Scenario: Try to install bad packages Given an empty directory And a package-dir/composer.json file: From 2eebd3e68ca92e6e4f6e8e2847fa4ded8bd237d5 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sun, 5 Aug 2018 09:00:21 +0200 Subject: [PATCH 8/9] Remove v2 version hack --- src/Package_Command.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 019681104..1fb54d30c 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -859,12 +859,6 @@ private function get_composer_json_path() { private static function get_wp_cli_version_composer() { preg_match( '#^[0-9\.]+(-(alpha|beta)[^-]{0,})?#', WP_CLI_VERSION, $matches ); $version = isset( $matches[0] ) ? $matches[0] : ''; - if ( 0 === strpos( $version, '2' ) ) { - // Fake a v1.x.x version for v2.x.x framework for now, as the - // command package are not yet accepting v2.x.x to meet their - // dependency requirements. - $version = '1.99.99-alpha'; - } return $version; } From 6e090d61cc863aa9ab3ff8060b6e74e384ada1c8 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Sun, 5 Aug 2018 10:48:45 +0200 Subject: [PATCH 9/9] Fix package update test by switching to a new package --- features/package-update.feature | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/features/package-update.feature b/features/package-update.feature index c70d69d9f..6c7435c1b 100644 --- a/features/package-update.feature +++ b/features/package-update.feature @@ -28,10 +28,10 @@ Feature: Update WP-CLI packages Scenario: Update a package with an update available Given an empty directory - When I run `wp package install wp-cli/scaffold-package-command:0.3.0` + When I run `wp package install wp-cli-test/updateable-package:v1.0.0` Then STDOUT should contain: """ - Installing package wp-cli/scaffold-package-command (0.3.0) + Installing package wp-cli-test/updateable-package (v1.0.0) """ And STDOUT should contain: """ @@ -41,13 +41,13 @@ Feature: Update WP-CLI packages When I run `cat {PACKAGE_PATH}/composer.json` Then STDOUT should contain: """ - "wp-cli/scaffold-package-command": "0.3.0" + "wp-cli-test/updateable-package": "v1.0.0" """ - When I run `wp help scaffold package` + When I run `wp help updateable-package v1` Then STDOUT should contain: """ - wp scaffold package + wp updateable-package v1 """ When I run `wp package update` @@ -62,22 +62,22 @@ Feature: Update WP-CLI packages When I run `wp package list --fields=name,update` Then STDOUT should be a table containing rows: - | name | update | - | wp-cli/scaffold-package-command | available | + | name | update | + | wp-cli-test/updateable-package | available | - When I run `sed -i.bak s/0.3.0/\>=0.3.0/g {PACKAGE_PATH}/composer.json` + When I run `sed -i.bak s/v1.0.0/\>=1.0.0/g {PACKAGE_PATH}/composer.json` Then the return code should be 0 When I run `cat {PACKAGE_PATH}/composer.json` Then STDOUT should contain: """ - "wp-cli/scaffold-package-command": ">=0.3.0" + "wp-cli-test/updateable-package": ">=1.0.0" """ When I run `wp package list --fields=name,update` Then STDOUT should be a table containing rows: - | name | update | - | wp-cli/scaffold-package-command | available | + | name | update | + | wp-cli-test/updateable-package | available | When I run `wp package update` Then STDOUT should contain: @@ -95,13 +95,13 @@ Feature: Update WP-CLI packages When I run `wp package list --fields=name,update` Then STDOUT should be a table containing rows: - | name | update | - | wp-cli/scaffold-package-command | none | + | name | update | + | wp-cli-test/updateable-package | none | When I run `wp package update` Then STDOUT should contain: """ - Package operations: 0 installs, 0 updates, 0 removals + Nothing to install or update """ And STDOUT should contain: """