mirror of
https://ghproxy.net/https://github.com/bbpress/wp-cli-bbpress.git
synced 2025-10-04 05:51:30 +08:00
Behat files updated
This commit is contained in:
parent
bc22a47b14
commit
abf6b81edc
8 changed files with 1215 additions and 121 deletions
|
@ -19,7 +19,7 @@ if ( file_exists( __DIR__ . '/utils.php' ) ) {
|
|||
if ( ! empty( $composer->autoload->files ) ) {
|
||||
$contents = 'require:' . PHP_EOL;
|
||||
foreach( $composer->autoload->files as $file ) {
|
||||
$contents .= ' - ' . dirname( dirname( dirname( __FILE__ ) ) ) . '/' . $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';
|
||||
|
@ -32,7 +32,11 @@ if ( file_exists( __DIR__ . '/utils.php' ) ) {
|
|||
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';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,8 +44,36 @@ if ( file_exists( __DIR__ . '/utils.php' ) ) {
|
|||
*/
|
||||
class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
||||
|
||||
private static $cache_dir, $suite_cache_dir;
|
||||
/**
|
||||
* 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',
|
||||
|
@ -49,39 +81,79 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
'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 '<file>.<line>'. 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
|
||||
* @beforeSuite
|
||||
*/
|
||||
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" );
|
||||
$bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' );
|
||||
$vendor_dir = realpath( __DIR__ . '/../../vendor/bin' );
|
||||
$env = array(
|
||||
'PATH' => $bin_dir . ':' . getenv( 'PATH' ),
|
||||
'PATH' => $bin_dir . ':' . $vendor_dir . ':' . getenv( 'PATH' ),
|
||||
'BEHAT_RUN' => 1,
|
||||
'HOME' => '/tmp/wp-cli-home',
|
||||
'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
|
||||
/**
|
||||
* 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() {
|
||||
self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test core-download-cache';
|
||||
$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 ( getenv( 'WP_VERSION' ) ) {
|
||||
$cmd .= Utils\esc_cmd( ' --version=%s', getenv( 'WP_VERSION' ) );
|
||||
if ( $wp_version ) {
|
||||
$cmd .= Utils\esc_cmd( ' --version=%s', $wp_version );
|
||||
}
|
||||
Process::create( $cmd, null, self::get_process_env_variables() )->run_check();
|
||||
}
|
||||
|
@ -90,6 +162,11 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
* @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;
|
||||
|
@ -98,14 +175,26 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
$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::$suite_cache_dir ) {
|
||||
Process::create( Utils\esc_cmd( 'rm -r %s', self::$suite_cache_dir ), null, self::get_process_env_variables() )->run();
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,37 +202,58 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
* @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 ( isset( $this->variables['RUN_DIR'] ) ) {
|
||||
|
||||
if ( self::$run_dir ) {
|
||||
// remove altered WP install, unless there's an error
|
||||
if ( $event->getResult() < 4 ) {
|
||||
$this->proc( Utils\esc_cmd( 'rm -r %s', $this->variables['RUN_DIR'] ) )->run();
|
||||
self::remove_dir( self::$run_dir );
|
||||
}
|
||||
self::$run_dir = null;
|
||||
}
|
||||
|
||||
// Remove WP-CLI package directory
|
||||
// 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'] ) ) {
|
||||
$this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['PACKAGE_PATH'] ) )->run();
|
||||
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 ) {
|
||||
self::terminate_proc( $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( $proc ) {
|
||||
$status = proc_get_status( $proc );
|
||||
|
||||
$master_pid = $status['pid'];
|
||||
private static function terminate_proc( $master_pid ) {
|
||||
|
||||
$output = `ps -o ppid,pid,command | grep $master_pid`;
|
||||
|
||||
|
@ -153,31 +263,51 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
$child = $matches[2];
|
||||
|
||||
if ( $parent == $master_pid ) {
|
||||
if ( ! posix_kill( (int) $child, 9 ) ) {
|
||||
throw new RuntimeException( posix_strerror( posix_get_last_error() ) );
|
||||
}
|
||||
self::terminate_proc( $child );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! posix_kill( (int) $master_pid, 9 ) ) {
|
||||
throw new RuntimeException( posix_strerror( posix_get_last_error() ) );
|
||||
$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() {
|
||||
self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-suite-cache-", TRUE );
|
||||
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 it's own context object.
|
||||
* 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 );
|
||||
|
@ -191,10 +321,20 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace {VARIABLE_NAME}. Note that variable names can only contain uppercase letters and underscores (no numbers).
|
||||
*/
|
||||
public function replace_variables( $str ) {
|
||||
return preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $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];
|
||||
|
||||
|
@ -205,9 +345,60 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
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'] ) ) {
|
||||
$this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-run-", TRUE );
|
||||
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'] );
|
||||
}
|
||||
}
|
||||
|
@ -215,37 +406,89 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
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',
|
||||
__DIR__ . '/../../utils/make-phar.php',
|
||||
$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';
|
||||
$this->proc( Utils\esc_cmd( 'mkdir -p %s', $path ) )->run_check();
|
||||
if ( ! file_exists( $path ) ) {
|
||||
mkdir( $path );
|
||||
}
|
||||
$this->variables['CACHE_DIR'] = $path;
|
||||
}
|
||||
|
||||
private static function run_sql( $sql ) {
|
||||
Utils\run_mysql_command( 'mysql --no-defaults', array(
|
||||
'execute' => $sql,
|
||||
/**
|
||||
* 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( "CREATE DATABASE IF NOT EXISTS $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( "DROP DATABASE IF EXISTS $dbname" );
|
||||
self::run_sql( 'mysql --no-defaults', array( 'execute' => "DROP DATABASE IF EXISTS $dbname" ) );
|
||||
}
|
||||
|
||||
public function proc( $command, $assoc_args = array(), $path = '' ) {
|
||||
|
@ -293,6 +536,13 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
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.
|
||||
*/
|
||||
|
@ -313,26 +563,53 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
mkdir( $dest_dir );
|
||||
}
|
||||
|
||||
$this->proc( Utils\esc_cmd( "cp -r %s/* %s", self::$cache_dir, $dest_dir ) )->run_check();
|
||||
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 = '' ) {
|
||||
public function create_config( $subdir = '', $extra_php = false ) {
|
||||
$params = self::$db_settings;
|
||||
$params['dbprefix'] = $subdir ?: 'wp_';
|
||||
|
||||
// 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;
|
||||
$this->proc( 'wp core config', $params, $subdir )->run_check();
|
||||
|
||||
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(
|
||||
|
@ -343,7 +620,284 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
|||
'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=<output_to>[,<num_top_processes>][,<num_top_scenarios>]" 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 "<grandparent-dir> <feature-file>:<line-number>", 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]++;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,11 +6,46 @@ namespace WP_CLI;
|
|||
* Run a system process, and learn what happened.
|
||||
*/
|
||||
class Process {
|
||||
/**
|
||||
* @var string The full command to execute by the system.
|
||||
*/
|
||||
private $command;
|
||||
|
||||
/**
|
||||
* @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory).
|
||||
*/
|
||||
private $cwd;
|
||||
|
||||
/**
|
||||
* @var array Environment variables to set when running the command.
|
||||
*/
|
||||
private $env;
|
||||
|
||||
/**
|
||||
* @var array Descriptor spec for `proc_open()`.
|
||||
*/
|
||||
private static $descriptors = array(
|
||||
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;
|
||||
|
@ -22,8 +57,6 @@ class Process {
|
|||
return $proc;
|
||||
}
|
||||
|
||||
private $command, $cwd, $env;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
|
@ -32,15 +65,9 @@ class Process {
|
|||
* @return ProcessRun
|
||||
*/
|
||||
public function run() {
|
||||
$cwd = $this->cwd;
|
||||
$start_time = microtime( true );
|
||||
|
||||
$descriptors = array(
|
||||
0 => STDIN,
|
||||
1 => array( 'pipe', 'w' ),
|
||||
2 => array( 'pipe', 'w' ),
|
||||
);
|
||||
|
||||
$proc = proc_open( $this->command, $descriptors, $pipes, $cwd, $this->env );
|
||||
$proc = proc_open( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env );
|
||||
|
||||
$stdout = stream_get_contents( $pipes[1] );
|
||||
fclose( $pipes[1] );
|
||||
|
@ -48,13 +75,26 @@ class Process {
|
|||
$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' => proc_close( $proc ),
|
||||
'return_code' => $return_code,
|
||||
'command' => $this->command,
|
||||
'cwd' => $cwd,
|
||||
'env' => $this->env
|
||||
'cwd' => $this->cwd,
|
||||
'env' => $this->env,
|
||||
'run_time' => $run_time,
|
||||
) );
|
||||
}
|
||||
|
||||
|
@ -66,6 +106,7 @@ class Process {
|
|||
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 );
|
||||
}
|
||||
|
|
|
@ -6,6 +6,40 @@ namespace WP_CLI;
|
|||
* Results of an executed command.
|
||||
*/
|
||||
class ProcessRun {
|
||||
/**
|
||||
* @var string The full command executed by the system.
|
||||
*/
|
||||
public $command;
|
||||
|
||||
/**
|
||||
* @var string Captured output from the process' STDOUT.
|
||||
*/
|
||||
public $stdout;
|
||||
|
||||
/**
|
||||
* @var string Captured output from the process' STDERR.
|
||||
*/
|
||||
public $stderr;
|
||||
|
||||
/**
|
||||
* @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory).
|
||||
*/
|
||||
public $cwd;
|
||||
|
||||
/**
|
||||
* @var array Environment variables set for this process.
|
||||
*/
|
||||
public $env;
|
||||
|
||||
/**
|
||||
* @var int Exit code of the process.
|
||||
*/
|
||||
public $return_code;
|
||||
|
||||
/**
|
||||
* @var float The run time of the process.
|
||||
*/
|
||||
public $run_time;
|
||||
|
||||
/**
|
||||
* @var array $props Properties of executed command.
|
||||
|
@ -25,6 +59,7 @@ class ProcessRun {
|
|||
$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;
|
||||
|
|
|
@ -182,8 +182,8 @@ function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) {
|
|||
* @param[in] $expectedYaml the expected YAML string
|
||||
*/
|
||||
function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) {
|
||||
$actualValue = spyc_load( $actualYaml );
|
||||
$expectedValue = spyc_load( $expectedYaml );
|
||||
$actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml );
|
||||
$expectedValue = Mustangostang\Spyc::YAMLLoad( $expectedYaml );
|
||||
|
||||
if ( !$actualValue ) {
|
||||
return false;
|
||||
|
|
|
@ -10,8 +10,10 @@ use \WP_CLI;
|
|||
use \WP_CLI\Dispatcher;
|
||||
use \WP_CLI\Iterators\Transform;
|
||||
|
||||
const PHAR_STREAM_PREFIX = 'phar://';
|
||||
|
||||
function inside_phar() {
|
||||
return 0 === strpos( WP_CLI_ROOT, 'phar://' );
|
||||
return 0 === strpos( WP_CLI_ROOT, PHAR_STREAM_PREFIX );
|
||||
}
|
||||
|
||||
// Files that need to be read by external programs have to be extracted from the Phar archive.
|
||||
|
@ -35,7 +37,11 @@ function extract_from_phar( $path ) {
|
|||
|
||||
function load_dependencies() {
|
||||
if ( inside_phar() ) {
|
||||
if ( file_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) {
|
||||
require WP_CLI_ROOT . '/vendor/autoload.php';
|
||||
} elseif ( file_exists( dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php' ) ) {
|
||||
require dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -83,19 +89,6 @@ function load_command( $name ) {
|
|||
}
|
||||
}
|
||||
|
||||
function load_all_commands() {
|
||||
$cmd_dir = WP_CLI_ROOT . '/php/commands';
|
||||
|
||||
$iterator = new \DirectoryIterator( $cmd_dir );
|
||||
|
||||
foreach ( $iterator as $filename ) {
|
||||
if ( '.php' != substr( $filename, -4 ) )
|
||||
continue;
|
||||
|
||||
include_once "$cmd_dir/$filename";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like array_map(), except it returns a new iterator, instead of a modified array.
|
||||
*
|
||||
|
@ -193,11 +186,16 @@ function assoc_args_to_str( $assoc_args ) {
|
|||
$str = '';
|
||||
|
||||
foreach ( $assoc_args as $key => $value ) {
|
||||
if ( true === $value )
|
||||
if ( true === $value ) {
|
||||
$str .= " --$key";
|
||||
else
|
||||
} elseif( is_array( $value ) ) {
|
||||
foreach( $value as $_ => $v ) {
|
||||
$str .= assoc_args_to_str( array( $key => $v ) );
|
||||
}
|
||||
} else {
|
||||
$str .= " --$key=" . escapeshellarg( $value );
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
@ -343,6 +341,8 @@ function pick_fields( $item, $fields ) {
|
|||
*/
|
||||
function launch_editor_for_input( $input, $filename = 'WP-CLI' ) {
|
||||
|
||||
check_proc_available( 'launch_editor_for_input' );
|
||||
|
||||
$tmpdir = get_temp_dir();
|
||||
|
||||
do {
|
||||
|
@ -417,6 +417,8 @@ function mysql_host_to_cli_args( $raw_host ) {
|
|||
}
|
||||
|
||||
function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) {
|
||||
check_proc_available( 'run_mysql_command' );
|
||||
|
||||
if ( !$descriptors )
|
||||
$descriptors = array( STDIN, STDOUT, STDERR );
|
||||
|
||||
|
@ -430,7 +432,7 @@ function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) {
|
|||
$old_pass = getenv( 'MYSQL_PWD' );
|
||||
putenv( 'MYSQL_PWD=' . $pass );
|
||||
|
||||
$final_cmd = $cmd . assoc_args_to_str( $assoc_args );
|
||||
$final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args );
|
||||
|
||||
$proc = proc_open( $final_cmd, $descriptors, $pipes );
|
||||
if ( !$proc )
|
||||
|
@ -455,7 +457,7 @@ function mustache_render( $template_name, $data = array() ) {
|
|||
$template = file_get_contents( $template_name );
|
||||
|
||||
$m = new \Mustache_Engine( array(
|
||||
'escape' => function ( $val ) { return $val; }
|
||||
'escape' => function ( $val ) { return $val; },
|
||||
) );
|
||||
|
||||
return $m->render( $template, $data );
|
||||
|
@ -508,9 +510,11 @@ function parse_url( $url ) {
|
|||
|
||||
/**
|
||||
* Check if we're running in a Windows environment (cmd.exe).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_windows() {
|
||||
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
return false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -522,7 +526,7 @@ function is_windows() {
|
|||
function replace_path_consts( $source, $path ) {
|
||||
$replacements = array(
|
||||
'__FILE__' => "'$path'",
|
||||
'__DIR__' => "'" . dirname( $path ) . "'"
|
||||
'__DIR__' => "'" . dirname( $path ) . "'",
|
||||
);
|
||||
|
||||
$old = array_keys( $replacements );
|
||||
|
@ -559,7 +563,7 @@ function http_request( $method, $url, $data = null, $headers = array(), $options
|
|||
if ( inside_phar() ) {
|
||||
// cURL can't read Phar archives
|
||||
$options['verify'] = extract_from_phar(
|
||||
WP_CLI_ROOT . '/vendor' . $cert_path );
|
||||
WP_CLI_VENDOR_DIR . $cert_path );
|
||||
} else {
|
||||
foreach( get_vendor_paths() as $vendor_path ) {
|
||||
if ( file_exists( $vendor_path . $cert_path ) ) {
|
||||
|
@ -568,7 +572,7 @@ function http_request( $method, $url, $data = null, $headers = array(), $options
|
|||
}
|
||||
}
|
||||
if ( empty( $options['verify'] ) ){
|
||||
WP_CLI::error_log( "Cannot find SSL certificate." );
|
||||
WP_CLI::error( "Cannot find SSL certificate." );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,13 +580,17 @@ function http_request( $method, $url, $data = null, $headers = array(), $options
|
|||
$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( $ex->getMessage() );
|
||||
\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( $ex->getMessage() );
|
||||
\WP_CLI::error( sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -694,6 +702,37 @@ 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.
|
||||
*
|
||||
|
@ -705,17 +744,15 @@ function get_flag_value( $assoc_args, $flag, $default = null ) {
|
|||
function get_temp_dir() {
|
||||
static $temp = '';
|
||||
|
||||
$trailingslashit = function( $path ) {
|
||||
return rtrim( $path ) . '/';
|
||||
};
|
||||
if ( $temp ) {
|
||||
return $temp;
|
||||
}
|
||||
|
||||
if ( $temp )
|
||||
return $trailingslashit( $temp );
|
||||
|
||||
if ( function_exists( 'sys_get_temp_dir' ) ) {
|
||||
$temp = sys_get_temp_dir();
|
||||
} else if ( ini_get( 'upload_tmp_dir' ) ) {
|
||||
$temp = ini_get( 'upload_tmp_dir' );
|
||||
// `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/';
|
||||
}
|
||||
|
@ -724,7 +761,7 @@ function get_temp_dir() {
|
|||
\WP_CLI::warning( "Temp directory isn't writable: {$temp}" );
|
||||
}
|
||||
|
||||
return $trailingslashit( $temp );
|
||||
return $temp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -741,18 +778,24 @@ function get_temp_dir() {
|
|||
* @return mixed
|
||||
*/
|
||||
function parse_ssh_url( $url, $component = -1 ) {
|
||||
preg_match( '#^([^:/~]+)(:([\d]+))?((/|~)(.+))?$#', $url, $matches );
|
||||
preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches );
|
||||
$bits = array();
|
||||
foreach( array(
|
||||
1 => 'host',
|
||||
3 => 'port',
|
||||
4 => 'path',
|
||||
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:
|
||||
|
@ -778,11 +821,7 @@ function parse_ssh_url( $url, $component = -1 ) {
|
|||
*/
|
||||
function report_batch_operation_results( $noun, $verb, $total, $successes, $failures ) {
|
||||
$plural_noun = $noun . 's';
|
||||
if ( in_array( $verb, array( 'reset' ), true ) ) {
|
||||
$past_tense_verb = $verb;
|
||||
} else {
|
||||
$past_tense_verb = 'e' === substr( $verb, -1 ) ? $verb . 'd' : $verb . 'ed';
|
||||
}
|
||||
$past_tense_verb = past_tense_verb( $verb );
|
||||
$past_tense_verb_upper = ucfirst( $past_tense_verb );
|
||||
if ( $failures ) {
|
||||
if ( $successes ) {
|
||||
|
@ -823,3 +862,333 @@ function parse_str_to_argv( $arguments ) {
|
|||
}, $argv );
|
||||
return $argv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale-independent version of basename()
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $suffix
|
||||
* @return string
|
||||
*/
|
||||
function basename( $path, $suffix = '' ) {
|
||||
return urldecode( \basename( str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ), $suffix ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the output of the current script is a TTY or a pipe / redirect
|
||||
*
|
||||
* Returns true if STDOUT output is being redirected to a pipe or a file; false is
|
||||
* output is being sent directly to the terminal.
|
||||
*
|
||||
* If an env variable SHELL_PIPE exists, returned result depends it's
|
||||
* value. Strings like 1, 0, yes, no, that validate to booleans are accepted.
|
||||
*
|
||||
* To enable ASCII formatting even when shell is piped, use the
|
||||
* ENV variable SHELL_PIPE=0
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function isPiped() {
|
||||
$shellPipe = getenv('SHELL_PIPE');
|
||||
|
||||
if ($shellPipe !== false) {
|
||||
return filter_var($shellPipe, FILTER_VALIDATE_BOOLEAN);
|
||||
} else {
|
||||
return (function_exists('posix_isatty') && !posix_isatty(STDOUT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand within paths to their matching paths.
|
||||
*
|
||||
* Has no effect on paths which do not use glob patterns.
|
||||
*
|
||||
* @param string|array $paths Single path as a string, or an array of paths.
|
||||
* @param int $flags Optional. Flags to pass to glob. Defaults to GLOB_BRACE.
|
||||
*
|
||||
* @return array Expanded paths.
|
||||
*/
|
||||
function expand_globs( $paths, $flags = 'default' ) {
|
||||
// Compatibility for systems without GLOB_BRACE.
|
||||
$glob_func = 'glob';
|
||||
if ( 'default' === $flags ) {
|
||||
if ( ! defined( 'GLOB_BRACE' ) || getenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ) ) {
|
||||
$glob_func = 'WP_CLI\Utils\glob_brace';
|
||||
} else {
|
||||
$flags = GLOB_BRACE;
|
||||
}
|
||||
}
|
||||
|
||||
$expanded = array();
|
||||
|
||||
foreach ( (array) $paths as $path ) {
|
||||
$matching = array( $path );
|
||||
|
||||
if ( preg_match( '/[' . preg_quote( '*?[]{}!', '/' ) . ']/', $path ) ) {
|
||||
$matching = $glob_func( $path, $flags ) ?: array();
|
||||
}
|
||||
$expanded = array_merge( $expanded, $matching );
|
||||
}
|
||||
|
||||
return array_values( array_unique( $expanded ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a `glob()` with the `GLOB_BRACE` flag set. For systems (eg Alpine Linux) built against a libc library (eg https://www.musl-libc.org/) that lacks it.
|
||||
* Copied and adapted from Zend Framework's `Glob::fallbackGlob()` and Glob::nextBraceSub()`.
|
||||
*
|
||||
* Zend Framework (http://framework.zend.com/)
|
||||
*
|
||||
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
||||
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
||||
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||||
*
|
||||
* @param string $pattern Filename pattern.
|
||||
* @param void $dummy_flags Not used.
|
||||
*
|
||||
* @return array Array of paths.
|
||||
*/
|
||||
function glob_brace( $pattern, $dummy_flags = null ) {
|
||||
|
||||
static $next_brace_sub;
|
||||
if ( ! $next_brace_sub ) {
|
||||
// Find the end of the subpattern in a brace expression.
|
||||
$next_brace_sub = function ( $pattern, $current ) {
|
||||
$length = strlen( $pattern );
|
||||
$depth = 0;
|
||||
|
||||
while ( $current < $length ) {
|
||||
if ( '\\' === $pattern[ $current ] ) {
|
||||
if ( ++$current === $length ) {
|
||||
break;
|
||||
}
|
||||
$current++;
|
||||
} else {
|
||||
if ( ( '}' === $pattern[ $current ] && $depth-- === 0 ) || ( ',' === $pattern[ $current ] && 0 === $depth ) ) {
|
||||
break;
|
||||
} elseif ( '{' === $pattern[ $current++ ] ) {
|
||||
$depth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $current < $length ? $current : null;
|
||||
};
|
||||
}
|
||||
|
||||
$length = strlen( $pattern );
|
||||
|
||||
// Find first opening brace.
|
||||
for ( $begin = 0; $begin < $length; $begin++ ) {
|
||||
if ( '\\' === $pattern[ $begin ] ) {
|
||||
$begin++;
|
||||
} elseif ( '{' === $pattern[ $begin ] ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find comma or matching closing brace.
|
||||
if ( null === ( $next = $next_brace_sub( $pattern, $begin + 1 ) ) ) {
|
||||
return glob( $pattern );
|
||||
}
|
||||
|
||||
$rest = $next;
|
||||
|
||||
// Point `$rest` to matching closing brace.
|
||||
while ( '}' !== $pattern[ $rest ] ) {
|
||||
if ( null === ( $rest = $next_brace_sub( $pattern, $rest + 1 ) ) ) {
|
||||
return glob( $pattern );
|
||||
}
|
||||
}
|
||||
|
||||
$paths = array();
|
||||
$p = $begin + 1;
|
||||
|
||||
// For each comma-separated subpattern.
|
||||
do {
|
||||
$subpattern = substr( $pattern, 0, $begin )
|
||||
. substr( $pattern, $p, $next - $p )
|
||||
. substr( $pattern, $rest + 1 );
|
||||
|
||||
if ( ( $result = glob_brace( $subpattern ) ) ) {
|
||||
$paths = array_merge( $paths, $result );
|
||||
}
|
||||
|
||||
if ( '}' === $pattern[ $next ] ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$p = $next + 1;
|
||||
$next = $next_brace_sub( $pattern, $p );
|
||||
} while ( null !== $next );
|
||||
|
||||
return array_values( array_unique( $paths ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest suggestion for a mis-typed target term amongst a list of
|
||||
* options.
|
||||
*
|
||||
* Uses the Levenshtein algorithm to calculate the relative "distance" between
|
||||
* terms.
|
||||
*
|
||||
* If the "distance" to the closest term is higher than the threshold, an empty
|
||||
* string is returned.
|
||||
*
|
||||
* @param string $target Target term to get a suggestion for.
|
||||
* @param array $options Array with possible options.
|
||||
* @param int $threshold Threshold above which to return an empty string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_suggestion( $target, array $options, $threshold = 2 ) {
|
||||
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';
|
||||
}
|
||||
|
|
|
@ -10,6 +10,22 @@ $steps->Given( '/^an empty directory$/',
|
|||
}
|
||||
);
|
||||
|
||||
$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();
|
||||
|
@ -20,7 +36,10 @@ $steps->Given( '/^an? ([^\s]+) file:$/',
|
|||
function ( $world, $path, PyStringNode $content ) {
|
||||
$content = (string) $content . "\n";
|
||||
$full_path = $world->variables['RUN_DIR'] . "/$path";
|
||||
Process::create( \WP_CLI\utils\esc_cmd( 'mkdir -p %s', dirname( $full_path ) ) )->run_check();
|
||||
$dir = dirname( $full_path );
|
||||
if ( ! file_exists( $dir ) ) {
|
||||
mkdir( $dir, 0777, true /*recursive*/ );
|
||||
}
|
||||
file_put_contents( $full_path, $content );
|
||||
}
|
||||
);
|
||||
|
@ -62,6 +81,18 @@ $steps->Given( "/^a WP install in '([^\s]+)'$/",
|
|||
}
|
||||
);
|
||||
|
||||
$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();
|
||||
|
@ -109,7 +140,7 @@ $steps->Given( '/^download:$/',
|
|||
}
|
||||
);
|
||||
|
||||
$steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?as \{(\w+)\}$/',
|
||||
$steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/',
|
||||
function ( $world, $stream, $output_filter, $key ) {
|
||||
|
||||
$stream = strtolower( $stream );
|
||||
|
@ -127,12 +158,18 @@ $steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?as \{(\w+)\}$/',
|
|||
}
|
||||
);
|
||||
|
||||
$steps->Given( '/^a new Phar(?: with version "([^"]+)")$/',
|
||||
function ( $world, $version ) {
|
||||
$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 ) );
|
||||
|
@ -162,3 +199,15 @@ $steps->Given('/^a misconfigured WP_CONTENT_DIR constant directory$/',
|
|||
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();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -13,7 +13,6 @@ $steps->Then( '/^the return code should be (\d+)$/',
|
|||
|
||||
$steps->Then( '/^(STDOUT|STDERR) should (be|contain|not contain):$/',
|
||||
function ( $world, $stream, $action, PyStringNode $expected ) {
|
||||
|
||||
$stream = strtolower( $stream );
|
||||
|
||||
$expected = $world->replace_variables( (string) $expected );
|
||||
|
@ -145,6 +144,16 @@ $steps->Then( '/^(STDOUT|STDERR) should not be empty$/',
|
|||
}
|
||||
);
|
||||
|
||||
$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 );
|
||||
|
@ -162,12 +171,12 @@ $steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:|
|
|||
switch ( $action ) {
|
||||
case 'exist':
|
||||
if ( ! $test( $path ) ) {
|
||||
throw new Exception( $world->result );
|
||||
throw new Exception( "$path doesn't exist." );
|
||||
}
|
||||
break;
|
||||
case 'not exist':
|
||||
if ( $test( $path ) ) {
|
||||
throw new Exception( $world->result );
|
||||
throw new Exception( "$path exists." );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -199,3 +208,10 @@ $steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $ex
|
|||
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 );
|
||||
}
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
function version_tags( $prefix, $current, $operator = '<' ) {
|
||||
if ( ! $current )
|
||||
return;
|
||||
return array();
|
||||
|
||||
exec( "grep '@{$prefix}-[0-9\.]*' -h -o features/*.feature | uniq", $existing_tags );
|
||||
|
||||
|
@ -31,8 +31,15 @@ function version_tags( $prefix, $current, $operator = '<' ) {
|
|||
return $skip_tags;
|
||||
}
|
||||
|
||||
$wp_version_reqs = array();
|
||||
// Only apply @require-wp tags when WP_VERSION isn't 'latest' or 'nightly'
|
||||
// 'latest' and 'nightly' are expected to work with all features
|
||||
if ( ! in_array( getenv( 'WP_VERSION' ), array( 'latest', 'nightly', 'trunk' ), true ) ) {
|
||||
$wp_version_reqs = version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' );
|
||||
}
|
||||
|
||||
$skip_tags = array_merge(
|
||||
version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' ),
|
||||
$wp_version_reqs,
|
||||
version_tags( 'require-php', PHP_VERSION, '<' ),
|
||||
version_tags( 'less-than-php', PHP_VERSION, '>' )
|
||||
);
|
||||
|
@ -40,6 +47,29 @@ $skip_tags = array_merge(
|
|||
# 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 );
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue