diff --git a/.gitignore b/.gitignore index 54f24c8..ff49419 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ node_modules/ vendor/ *.zip *.tar.gz +composer.lock +*.log diff --git a/.travis.yml b/.travis.yml index c14cdd0..6e593f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false dist: trusty language: php +php: 7.2 notifications: email: @@ -14,7 +15,6 @@ branches: cache: directories: - - vendor - $HOME/.composer/cache env: @@ -22,22 +22,6 @@ env: - PATH="$TRAVIS_BUILD_DIR/vendor/bin:$PATH" - WP_CLI_BIN_DIR="$TRAVIS_BUILD_DIR/vendor/bin" -matrix: - include: - - 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: @@ -49,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 || composer behat-rerun + +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/command.php b/admin-command.php similarity index 100% rename from command.php rename to admin-command.php diff --git a/bin/install-package-tests.sh b/bin/install-package-tests.sh deleted file mode 100755 index 2ff49dd..0000000 --- a/bin/install-package-tests.sh +++ /dev/null @@ -1,10 +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 -} - -install_db diff --git a/bin/test.sh b/bin/test.sh deleted file mode 100644 index bd3ae6e..0000000 --- a/bin/test.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -ex - -# Run the unit tests, if they exist -if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ] -then - phpunit -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 c69f315..e711fb2 100644 --- a/composer.json +++ b/composer.json @@ -1,27 +1,52 @@ { "name": "wp-cli/admin-command", - "description": "Open /wp-admin/ in a browser.", "type": "wp-cli-package", + "description": "Open /wp-admin/ in a browser.", "homepage": "https://github.com/wp-cli/admin-command", "license": "MIT", - "authors": [], - "minimum-stability": "dev", - "prefer-stable": true, - "autoload": { - "files": [ "command.php" ] - }, "require": { - "wp-cli/wp-cli": "*" + "wp-cli/wp-cli": "^2" }, "require-dev": { - "behat/behat": "~2.5" + "wp-cli/wp-cli-tests": "^2.0.7" + }, + "config": { + "process-timeout": 7200, + "sort-packages": true }, "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" }, "commands": [ "admin" ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "admin-command.php" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "behat": "run-behat-tests", + "behat-rerun": "rerun-behat-tests", + "lint": "run-linter-tests", + "phpcs": "run-phpcs-tests", + "phpunit": "run-php-unit-tests", + "prepare-tests": "install-package-tests", + "test": [ + "@lint", + "@phpcs", + "@phpunit", + "@behat" + ] + }, + "support": { + "issues": "https://github.com/wp-cli/admin-command/issues" } -} +} \ No newline at end of file diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php deleted file mode 100644 index b46de64..0000000 --- a/features/bootstrap/FeatureContext.php +++ /dev/null @@ -1,903 +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 install" 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 install" 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 install" 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 install" 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. 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' ); - $env = array( - 'PATH' => $bin_dir . ':' . $vendor_dir . ':' . 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 ( $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 {VARIABLE_NAME}. Note that variable names can only contain uppercase letters and underscores (no numbers). - */ - public function replace_variables( $str ) { - $ret = preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str ); - if ( false !== strpos( $str, '{WP_VERSION-' ) ) { - $ret = $this->_replace_wp_versions( $ret ); - } - return $ret; - } - - /** - * 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'] ) { - throw new RuntimeException( stream_get_contents( $pipes[2] ) ); - } 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' - ); - - $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' - ); - - $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 get_php_binary() { - if ( getenv( 'WP_CLI_PHP_USED' ) ) - return getenv( 'WP_CLI_PHP_USED' ); - - if ( getenv( 'WP_CLI_PHP' ) ) - return getenv( 'WP_CLI_PHP' ); - - if ( defined( 'PHP_BINARY' ) ) - return PHP_BINARY; - - return 'php'; - } - - public function start_php_server() { - $cmd = Utils\esc_cmd( '%s -S %s -t %s -c %s %s', - $this->get_php_binary(), - 'localhost:8080', - $this->variables['RUN_DIR'] . '/wordpress/', - 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 8032a24..0000000 --- a/features/bootstrap/Process.php +++ /dev/null @@ -1,116 +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 = proc_open( $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; - } -} diff --git a/features/bootstrap/ProcessRun.php b/features/bootstrap/ProcessRun.php deleted file mode 100644 index 96b4c80..0000000 --- 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 a37a064..0000000 --- a/features/bootstrap/support.php +++ /dev/null @@ -1,194 +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 fc8d0df..0000000 --- a/features/bootstrap/utils.php +++ /dev/null @@ -1,1093 +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 ) { - 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'; - else - $path = false; - - if ( $path ) - $path = realpath( $path ); - } - - return $path; -} - -function wp_version_compare( $since, $operator ) { - return version_compare( str_replace( array( '-src' ), '', $GLOBALS['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) - * @return string|bool Edited text, if file is saved from editor; false, if no change to file. - */ -function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { - - check_proc_available( 'launch_editor_for_input' ); - - $tmpdir = get_temp_dir(); - - do { - $tmpfile = basename( $filename ); - $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); - $tmpfile .= '-' . substr( md5( rand() ), 0, 6 ); - $tmpfile = $tmpdir . $tmpfile . '.tmp'; - $fp = @fopen( $tmpfile, 'x' ); - 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 ) { - if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) - $editor = 'notepad'; - else - $editor = 'vi'; - } - - $descriptorspec = array( STDIN, STDOUT, STDERR ); - $process = proc_open( "$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'] = intval( $extra ); - $assoc_args['protocol'] = 'tcp'; - } else if ( $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'] ) ) { - $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); - } - - $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( $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. - * @return cli\progress\Bar|WP_CLI\NoOp - */ -function make_progress_bar( $message, $count ) { - if ( \cli\Shell::isPiped() ) - return new \WP_CLI\NoOp; - - return new \cli\progress\Bar( $message, $count ); -} - -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'; - 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'] ) ){ - WP_CLI::error( "Cannot find SSL certificate." ); - } - } - - try { - $request = \Requests::request( $url, $headers, $data, $method, $options ); - return $request; - } 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 ) ) { - \WP_CLI::error( sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ) ); - } - // 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 ) { - \WP_CLI::error( sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ) ); - } - } -} - -/** - * 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'; - } else if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { - return 'minor'; - } else { - 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, '/\\' ) . '/'; -} - -/** - * 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. - if ( $try = sys_get_temp_dir() ) { - $temp = trailingslashit( $try ); - } elseif ( $try = ini_get( 'upload_tmp_dir' ) ) { - $temp = trailingslashit( $try ); - } else { - $temp = '/tmp/'; - } - - 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 ]; - } - } - 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. - */ -function report_batch_operation_results( $noun, $verb, $total, $successes, $failures ) { - $plural_noun = $noun . 's'; - $past_tense_verb = past_tense_verb( $verb ); - $past_tense_verb_upper = ucfirst( $past_tense_verb ); - if ( $failures ) { - if ( $successes ) { - WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}." ); - } else { - WP_CLI::error( "No {$plural_noun} {$past_tense_verb}." ); - } - } else { - if ( $successes ) { - WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}." ); - } 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)([\'"]?)(.+?)(? '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'; -} diff --git a/features/extra/no-mail.php b/features/extra/no-mail.php deleted file mode 100644 index de7a422..0000000 --- a/features/extra/no-mail.php +++ /dev/null @@ -1,7 +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$/', - function ( $world ) { - $world->install_wp(); - } -); - -$steps->Given( "/^a WP install in '([^\s]+)'$/", - function ( $world, $subdir ) { - $world->install_wp( $subdir ); - } -); - -$steps->Given( '/^a WP install with Composer$/', - function ( $world ) { - $world->install_wp_with_composer(); - } -); - -$steps->Given( "/^a WP install 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$/', - 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(); - } -); diff --git a/features/steps/then.php b/features/steps/then.php deleted file mode 100644 index aa1f3f6..0000000 --- a/features/steps/then.php +++ /dev/null @@ -1,218 +0,0 @@ -Then( '/^the return code should be (\d+)$/', - function ( $world, $return_code ) { - if ( $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( '/^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 afe3f7a..0000000 --- a/features/steps/when.php +++ /dev/null @@ -1,54 +0,0 @@ - 'run_check', - '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 ); - } -); - diff --git a/utils/behat-tags.php b/utils/behat-tags.php deleted file mode 100644 index ee51fc9..0000000 --- a/utils/behat-tags.php +++ /dev/null @@ -1,76 +0,0 @@ -' ) -); - -# Skip Github API tests by default because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 -$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 ); -} - diff --git a/wp-cli.yml b/wp-cli.yml index 2c97a7d..8226135 100644 --- a/wp-cli.yml +++ b/wp-cli.yml @@ -1,2 +1,2 @@ require: - - command.php + - admin-command.php