mirror of
https://hk.gh-proxy.com/https://github.com/wp-cli/profile-command.git
synced 2025-08-18 06:11:29 +08:00
Initial commit
This commit is contained in:
commit
717ea43c89
19 changed files with 2031 additions and 0 deletions
21
.editorconfig
Normal file
21
.editorconfig
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# This file is for unifying the coding style for different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
# WordPress Coding Standards
|
||||||
|
# https://make.wordpress.org/core/handbook/coding-standards/
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[{.jshintrc,*.json,*.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[{*.txt,wp-config-sample.php}]
|
||||||
|
end_of_line = crlf
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.DS_Store
|
||||||
|
composer.lock
|
||||||
|
wp-cli.local.yml
|
||||||
|
node_modules/
|
||||||
|
vendor/
|
29
.travis.yml
Normal file
29
.travis.yml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: php
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
on_success: never
|
||||||
|
on_failure: change
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
php:
|
||||||
|
- 5.3
|
||||||
|
- 5.6
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- composer
|
||||||
|
- $HOME/.composer/cache
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- WP_CLI_BIN_DIR=/tmp/wp-cli-phar
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- bash bin/install-package-tests.sh
|
||||||
|
|
||||||
|
script: ./vendor/bin/behat
|
53
README.md
Normal file
53
README.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
runcommand/profile
|
||||||
|
==================
|
||||||
|
|
||||||
|
Profile the performance of a request to WordPress.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/runcommand/profile)
|
||||||
|
|
||||||
|
Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing)
|
||||||
|
|
||||||
|
## Using
|
||||||
|
|
||||||
|
~~~
|
||||||
|
wp profile
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Installing this package requires WP-CLI v0.23.0 or greater. Update to the latest stable release with `wp cli update`.
|
||||||
|
|
||||||
|
Once you've done so, you can install this package with `wp package install runcommand/profile`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We appreciate you taking the initiative to contribute to this project.
|
||||||
|
|
||||||
|
Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation.
|
||||||
|
|
||||||
|
### Reporting a bug
|
||||||
|
|
||||||
|
Think you’ve found a bug? We’d love for you to help us get it fixed.
|
||||||
|
|
||||||
|
Before you create a new issue, you should [search existing issues](https://github.com/runcommand/profile/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version.
|
||||||
|
|
||||||
|
Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/runcommand/profile/issues/new) with the following:
|
||||||
|
|
||||||
|
1. What you were doing (e.g. "When I run `wp post list`").
|
||||||
|
2. What you saw (e.g. "I see a fatal about a class being undefined.").
|
||||||
|
3. What you expected to see (e.g. "I expected to see the list of posts.")
|
||||||
|
|
||||||
|
Include as much detail as you can, and clear steps to reproduce if possible.
|
||||||
|
|
||||||
|
### Creating a pull request
|
||||||
|
|
||||||
|
Want to contribute a new feature? Please first [open a new issue](https://github.com/runcommand/profile/issues/new) to discuss whether the feature is a good fit for the project.
|
||||||
|
|
||||||
|
Once you've decided to commit the time to seeing your pull request through, please follow our guidelines for creating a pull request to make sure it's a pleasant experience:
|
||||||
|
|
||||||
|
1. Create a feature branch for each contribution.
|
||||||
|
2. Submit your pull request early for feedback.
|
||||||
|
3. Include functional tests with your changes. [Read the WP-CLI documentation](https://wp-cli.org/docs/pull-requests/#functional-tests) for an introduction.
|
||||||
|
4. Follow the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/).
|
||||||
|
|
||||||
|
*This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.*
|
40
bin/install-package-tests.sh
Executable file
40
bin/install-package-tests.sh
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
PACKAGE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../ && pwd )"
|
||||||
|
|
||||||
|
download() {
|
||||||
|
if [ `which curl` ]; then
|
||||||
|
curl -s "$1" > "$2";
|
||||||
|
elif [ `which wget` ]; then
|
||||||
|
wget -nv -O "$2" "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_wp_cli() {
|
||||||
|
|
||||||
|
# the Behat test suite will pick up the executable found in $WP_CLI_BIN_DIR
|
||||||
|
mkdir -p $WP_CLI_BIN_DIR
|
||||||
|
download https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar $WP_CLI_BIN_DIR/wp
|
||||||
|
chmod +x $WP_CLI_BIN_DIR/wp
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
download_behat() {
|
||||||
|
|
||||||
|
cd $PACKAGE_DIR
|
||||||
|
download https://getcomposer.org/installer installer
|
||||||
|
php installer
|
||||||
|
php composer.phar require --dev behat/behat='~2.5'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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_wp_cli
|
||||||
|
download_behat
|
||||||
|
install_db
|
6
command.php
Normal file
6
command.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if ( class_exists( 'WP_CLI' ) ) {
|
||||||
|
require_once dirname( __FILE__ ) . '/inc/class-profile-command.php';
|
||||||
|
WP_CLI::add_command( 'profile', 'Profile_Command' );
|
||||||
|
}
|
22
composer.json
Normal file
22
composer.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "runcommand/profile",
|
||||||
|
"description": "Profile the performance of a request to WordPress.",
|
||||||
|
"homepage": "https://runcommand.io/wp/profile/",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [],
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"autoload": {
|
||||||
|
"files": [ "command.php" ]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"wp-cli/wp-cli": "~0.23.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"behat/behat": "~2.5"
|
||||||
|
},
|
||||||
|
"extras": {
|
||||||
|
"commands": [
|
||||||
|
"profile"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
329
features/bootstrap/FeatureContext.php
Normal file
329
features/bootstrap/FeatureContext.php
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Behat\Behat\Context\ClosuredContextInterface,
|
||||||
|
Behat\Behat\Context\TranslatedContextInterface,
|
||||||
|
Behat\Behat\Context\BehatContext,
|
||||||
|
Behat\Behat\Event\SuiteEvent;
|
||||||
|
|
||||||
|
use \WP_CLI\Process;
|
||||||
|
use \WP_CLI\Utils;
|
||||||
|
|
||||||
|
// Inside a community package
|
||||||
|
if ( file_exists( __DIR__ . '/utils.php' ) ) {
|
||||||
|
require_once __DIR__ . '/utils.php';
|
||||||
|
require_once __DIR__ . '/Process.php';
|
||||||
|
$project_composer = dirname( dirname( dirname( __FILE__ ) ) ) . '/composer.json';
|
||||||
|
if ( file_exists( $project_composer ) ) {
|
||||||
|
$composer = json_decode( file_get_contents( $project_composer ) );
|
||||||
|
if ( ! empty( $composer->autoload->files ) ) {
|
||||||
|
$contents = 'require:' . PHP_EOL;
|
||||||
|
foreach( $composer->autoload->files as $file ) {
|
||||||
|
$contents .= ' - ' . dirname( dirname( dirname( __FILE__ ) ) ) . '/' . $file;
|
||||||
|
}
|
||||||
|
@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__ . '/../../vendor/autoload.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Features context.
|
||||||
|
*/
|
||||||
|
class FeatureContext extends BehatContext implements ClosuredContextInterface {
|
||||||
|
|
||||||
|
private static $cache_dir, $suite_cache_dir;
|
||||||
|
|
||||||
|
private static $db_settings = array(
|
||||||
|
'dbname' => 'wp_cli_test',
|
||||||
|
'dbuser' => 'wp_cli_test',
|
||||||
|
'dbpass' => 'password1',
|
||||||
|
'dbhost' => '127.0.0.1',
|
||||||
|
);
|
||||||
|
|
||||||
|
private $running_procs = array();
|
||||||
|
|
||||||
|
public $variables = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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" );
|
||||||
|
$env = array(
|
||||||
|
'PATH' => $bin_dir . ':' . getenv( 'PATH' ),
|
||||||
|
'BEHAT_RUN' => 1,
|
||||||
|
'HOME' => '/tmp/wp-cli-home',
|
||||||
|
);
|
||||||
|
if ( $config_path = getenv( 'WP_CLI_CONFIG_PATH' ) ) {
|
||||||
|
$env['WP_CLI_CONFIG_PATH'] = $config_path;
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test core-download-cache';
|
||||||
|
|
||||||
|
if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
$cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir );
|
||||||
|
Process::create( $cmd, null, self::get_process_env_variables() )->run_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeSuite
|
||||||
|
*/
|
||||||
|
public static function prepare( SuiteEvent $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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeScenario
|
||||||
|
*/
|
||||||
|
public function beforeScenario( $event ) {
|
||||||
|
$this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @AfterScenario
|
||||||
|
*/
|
||||||
|
public function afterScenario( $event ) {
|
||||||
|
if ( isset( $this->variables['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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $this->running_procs as $proc ) {
|
||||||
|
self::terminate_proc( $proc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate a process and any of its children.
|
||||||
|
*/
|
||||||
|
private static function terminate_proc( $proc ) {
|
||||||
|
$status = proc_get_status( $proc );
|
||||||
|
|
||||||
|
$master_pid = $status['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 ) {
|
||||||
|
if ( ! posix_kill( (int) $child, 9 ) ) {
|
||||||
|
throw new RuntimeException( posix_strerror( posix_get_last_error() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! posix_kill( (int) $master_pid, 9 ) ) {
|
||||||
|
throw new RuntimeException( posix_strerror( posix_get_last_error() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create_cache_dir() {
|
||||||
|
self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-suite-cache-", TRUE );
|
||||||
|
mkdir( self::$suite_cache_dir );
|
||||||
|
return self::$suite_cache_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes context.
|
||||||
|
* Every scenario gets it's own context object.
|
||||||
|
*
|
||||||
|
* @param array $parameters context parameters (set them up through behat.yml)
|
||||||
|
*/
|
||||||
|
public function __construct( array $parameters ) {
|
||||||
|
$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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function replace_variables( $str ) {
|
||||||
|
return preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str );
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
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';
|
||||||
|
|
||||||
|
$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',
|
||||||
|
$this->variables['PHAR_PATH'],
|
||||||
|
$version
|
||||||
|
) )->run_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
$this->variables['CACHE_DIR'] = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function run_sql( $sql ) {
|
||||||
|
Utils\run_mysql_command( 'mysql --no-defaults', array(
|
||||||
|
'execute' => $sql,
|
||||||
|
'host' => self::$db_settings['dbhost'],
|
||||||
|
'user' => self::$db_settings['dbuser'],
|
||||||
|
'pass' => self::$db_settings['dbpass'],
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create_db() {
|
||||||
|
$dbname = self::$db_settings['dbname'];
|
||||||
|
self::run_sql( "CREATE DATABASE IF NOT EXISTS $dbname" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function drop_db() {
|
||||||
|
$dbname = self::$db_settings['dbname'];
|
||||||
|
self::run_sql( "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" );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->proc( Utils\esc_cmd( "cp -r %s/* %s", self::$cache_dir, $dest_dir ) )->run_check();
|
||||||
|
|
||||||
|
// 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 = '' ) {
|
||||||
|
$params = self::$db_settings;
|
||||||
|
$params['dbprefix'] = $subdir ?: 'wp_';
|
||||||
|
|
||||||
|
$params['skip-salts'] = true;
|
||||||
|
$this->proc( 'wp core config', $params, $subdir )->run_check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install_wp( $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'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->proc( 'wp core install', $install_args, $subdir )->run_check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
105
features/bootstrap/Process.php
Normal file
105
features/bootstrap/Process.php
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WP_CLI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a system process, and learn what happened.
|
||||||
|
*/
|
||||||
|
class Process {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
public static function create( $command, $cwd = null, $env = array() ) {
|
||||||
|
$proc = new self;
|
||||||
|
|
||||||
|
$proc->command = $command;
|
||||||
|
$proc->cwd = $cwd;
|
||||||
|
$proc->env = $env;
|
||||||
|
|
||||||
|
return $proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private $command, $cwd, $env;
|
||||||
|
|
||||||
|
private function __construct() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the command.
|
||||||
|
*
|
||||||
|
* @return ProcessRun
|
||||||
|
*/
|
||||||
|
public function run() {
|
||||||
|
$cwd = $this->cwd;
|
||||||
|
|
||||||
|
$descriptors = array(
|
||||||
|
0 => STDIN,
|
||||||
|
1 => array( 'pipe', 'w' ),
|
||||||
|
2 => array( 'pipe', 'w' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$proc = proc_open( $this->command, $descriptors, $pipes, $cwd, $this->env );
|
||||||
|
|
||||||
|
$stdout = stream_get_contents( $pipes[1] );
|
||||||
|
fclose( $pipes[1] );
|
||||||
|
|
||||||
|
$stderr = stream_get_contents( $pipes[2] );
|
||||||
|
fclose( $pipes[2] );
|
||||||
|
|
||||||
|
return new ProcessRun( array(
|
||||||
|
'stdout' => $stdout,
|
||||||
|
'stderr' => $stderr,
|
||||||
|
'return_code' => proc_close( $proc ),
|
||||||
|
'command' => $this->command,
|
||||||
|
'cwd' => $cwd,
|
||||||
|
'env' => $this->env
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the command, but throw an Exception on error.
|
||||||
|
*
|
||||||
|
* @return ProcessRun
|
||||||
|
*/
|
||||||
|
public function run_check() {
|
||||||
|
$r = $this->run();
|
||||||
|
|
||||||
|
if ( $r->return_code || !empty( $r->STDERR ) ) {
|
||||||
|
throw new \RuntimeException( $r );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Results of an executed command.
|
||||||
|
*/
|
||||||
|
class ProcessRun {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $props Properties of executed command.
|
||||||
|
*/
|
||||||
|
public function __construct( $props ) {
|
||||||
|
foreach ( $props as $key => $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 .= "exit status: $this->return_code";
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
188
features/bootstrap/support.php
Normal file
188
features/bootstrap/support.php
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Utility functions used by Behat steps
|
||||||
|
|
||||||
|
function assertEquals( $expected, $actual ) {
|
||||||
|
if ( $expected != $actual ) {
|
||||||
|
throw new Exception( "Actual value: " . var_export( $actual, true ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNumeric( $actual ) {
|
||||||
|
if ( !is_numeric( $actual ) ) {
|
||||||
|
throw new Exception( "Actual value: " . var_export( $actual, true ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNotNumeric( $actual ) {
|
||||||
|
if ( is_numeric( $actual ) ) {
|
||||||
|
throw new Exception( "Actual value: " . var_export( $actual, true ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkString( $output, $expected, $action, $message = false ) {
|
||||||
|
switch ( $action ) {
|
||||||
|
|
||||||
|
case 'be':
|
||||||
|
$r = $expected === rtrim( $output, "\n" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'contain':
|
||||||
|
$r = false !== strpos( $output, $expected );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'not contain':
|
||||||
|
$r = false === strpos( $output, $expected );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Behat\Behat\Exception\PendingException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !$r ) {
|
||||||
|
if ( false === $message )
|
||||||
|
$message = $output;
|
||||||
|
throw new Exception( $message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareTables( $expected_rows, $actual_rows, $output ) {
|
||||||
|
// the first row is the header and must be present
|
||||||
|
if ( $expected_rows[0] != $actual_rows[0] ) {
|
||||||
|
throw new \Exception( $output );
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $actual_rows[0] );
|
||||||
|
unset( $expected_rows[0] );
|
||||||
|
|
||||||
|
$missing_rows = array_diff( $expected_rows, $actual_rows );
|
||||||
|
if ( !empty( $missing_rows ) ) {
|
||||||
|
throw new \Exception( $output );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareContents( $expected, $actual ) {
|
||||||
|
if ( gettype( $expected ) != gettype( $actual ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_object( $expected ) ) {
|
||||||
|
foreach ( get_object_vars( $expected ) as $name => $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 = spyc_load( $actualYaml );
|
||||||
|
$expectedValue = spyc_load( $expectedYaml );
|
||||||
|
|
||||||
|
if ( !$actualValue ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareContents( $expectedValue, $actualValue );
|
||||||
|
}
|
||||||
|
|
757
features/bootstrap/utils.php
Normal file
757
features/bootstrap/utils.php
Normal file
|
@ -0,0 +1,757 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Utilities that do NOT depend on WordPress code.
|
||||||
|
|
||||||
|
namespace WP_CLI\Utils;
|
||||||
|
|
||||||
|
use \Composer\Semver\Comparator;
|
||||||
|
use \Composer\Semver\Semver;
|
||||||
|
use \WP_CLI\Dispatcher;
|
||||||
|
use \WP_CLI\Iterators\Transform;
|
||||||
|
|
||||||
|
function inside_phar() {
|
||||||
|
return 0 === strpos( WP_CLI_ROOT, 'phar://' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files that need to be read by external programs have to be extracted from the Phar archive.
|
||||||
|
function extract_from_phar( $path ) {
|
||||||
|
if ( ! inside_phar() ) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fname = basename( $path );
|
||||||
|
|
||||||
|
$tmp_path = get_temp_dir() . "wp-cli-$fname";
|
||||||
|
|
||||||
|
copy( $path, $tmp_path );
|
||||||
|
|
||||||
|
register_shutdown_function( function() use ( $tmp_path ) {
|
||||||
|
@unlink( $tmp_path );
|
||||||
|
} );
|
||||||
|
|
||||||
|
return $tmp_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_dependencies() {
|
||||||
|
if ( inside_phar() ) {
|
||||||
|
require WP_CLI_ROOT . '/vendor/autoload.php';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$has_autoload = false;
|
||||||
|
|
||||||
|
foreach ( get_vendor_paths() as $vendor_path ) {
|
||||||
|
if ( file_exists( $vendor_path . '/autoload.php' ) ) {
|
||||||
|
require $vendor_path . '/autoload.php';
|
||||||
|
$has_autoload = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !$has_autoload ) {
|
||||||
|
fputs( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" );
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_vendor_paths() {
|
||||||
|
$vendor_paths = array(
|
||||||
|
WP_CLI_ROOT . '/../../../vendor', // part of a larger project / installed via Composer (preferred)
|
||||||
|
WP_CLI_ROOT . '/vendor', // top-level project / installed as Git clone
|
||||||
|
);
|
||||||
|
$maybe_composer_json = WP_CLI_ROOT . '/../../../composer.json';
|
||||||
|
if ( file_exists( $maybe_composer_json ) && is_readable( $maybe_composer_json ) ) {
|
||||||
|
$composer = json_decode( file_get_contents( $maybe_composer_json ) );
|
||||||
|
if ( ! empty( $composer->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* 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";
|
||||||
|
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' ) {
|
||||||
|
|
||||||
|
$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 ) {
|
||||||
|
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 = $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).
|
||||||
|
*/
|
||||||
|
function is_windows() {
|
||||||
|
return 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_ROOT . '/vendor' . $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_log( "Cannot find SSL certificate." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$request = \Requests::request( $url, $headers, $data, $method, $options );
|
||||||
|
return $request;
|
||||||
|
} catch( \Requests_Exception $ex ) {
|
||||||
|
// Handle SSL certificate issues gracefully
|
||||||
|
\WP_CLI::warning( $ex->getMessage() );
|
||||||
|
$options['verify'] = false;
|
||||||
|
try {
|
||||||
|
return \Requests::request( $url, $headers, $data, $method, $options );
|
||||||
|
} catch( \Requests_Exception $ex ) {
|
||||||
|
\WP_CLI::error( $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 );
|
||||||
|
list( $major, $minor, $patch ) = explode( '.', $parts[0] );
|
||||||
|
|
||||||
|
if ( 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 system's temp directory. Warns user if it isn't writable.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @category System
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function get_temp_dir() {
|
||||||
|
static $temp = '';
|
||||||
|
|
||||||
|
$trailingslashit = function( $path ) {
|
||||||
|
return rtrim( $path ) . '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
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' );
|
||||||
|
} else {
|
||||||
|
$temp = '/tmp/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! @is_writable( $temp ) ) {
|
||||||
|
WP_CLI::warning( "Temp directory isn't writable: {$temp}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $trailingslashit( $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( '#^([^:/~]+)(:([\d]+))?((/|~)(.+))?$#', $url, $matches );
|
||||||
|
$bits = array();
|
||||||
|
foreach( array(
|
||||||
|
1 => 'host',
|
||||||
|
3 => 'port',
|
||||||
|
4 => 'path',
|
||||||
|
) as $i => $key ) {
|
||||||
|
if ( ! empty( $matches[ $i ] ) ) {
|
||||||
|
$bits[ $key ] = $matches[ $i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch ( $component ) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
6
features/extra/no-mail.php
Normal file
6
features/extra/no-mail.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function wp_mail() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
10
features/load-wp-cli.feature
Normal file
10
features/load-wp-cli.feature
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Feature: Test that WP-CLI loads.
|
||||||
|
|
||||||
|
Scenario: WP-CLI loads for your tests
|
||||||
|
Given a WP install
|
||||||
|
|
||||||
|
When I run `wp eval 'echo "Hello world.";'`
|
||||||
|
Then STDOUT should contain:
|
||||||
|
"""
|
||||||
|
Hello world.
|
||||||
|
"""
|
157
features/steps/given.php
Normal file
157
features/steps/given.php
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\PyStringNode,
|
||||||
|
Behat\Gherkin\Node\TableNode,
|
||||||
|
WP_CLI\Process;
|
||||||
|
|
||||||
|
$steps->Given( '/^an empty directory$/',
|
||||||
|
function ( $world ) {
|
||||||
|
$world->create_run_dir();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$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";
|
||||||
|
Process::create( \WP_CLI\utils\esc_cmd( 'mkdir -p %s', dirname( $full_path ) ) )->run_check();
|
||||||
|
file_put_contents( $full_path, $content );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$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 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) ([\'].+[^\'])?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 version "([^"]+)")$/',
|
||||||
|
function ( $world, $version ) {
|
||||||
|
$world->build_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 );
|
||||||
|
}
|
||||||
|
);
|
192
features/steps/then.php
Normal file
192
features/steps/then.php
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\PyStringNode,
|
||||||
|
Behat\Gherkin\Node\TableNode;
|
||||||
|
|
||||||
|
$steps->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( '/^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( $world->result );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'not exist':
|
||||||
|
if ( $test( $path ) ) {
|
||||||
|
throw new Exception( $world->result );
|
||||||
|
}
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
46
features/steps/when.php
Normal file
46
features/steps/when.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\PyStringNode,
|
||||||
|
Behat\Gherkin\Node\TableNode,
|
||||||
|
WP_CLI\Process;
|
||||||
|
|
||||||
|
function invoke_proc( $proc, $mode ) {
|
||||||
|
$map = array(
|
||||||
|
'run' => 'run_check',
|
||||||
|
'try' => 'run'
|
||||||
|
);
|
||||||
|
$method = $map[ $mode ];
|
||||||
|
|
||||||
|
return $proc->$method();
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$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 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$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 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
17
inc/class-profile-command.php
Normal file
17
inc/class-profile-command.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile the performance of a request to WordPress.
|
||||||
|
*/
|
||||||
|
class Profile_Command {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile the performance of a request to WordPress.
|
||||||
|
*
|
||||||
|
* @when before_wp_load
|
||||||
|
*/
|
||||||
|
public function __invoke() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
utils/behat-tags.php
Normal file
46
utils/behat-tags.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Generate a list of tags to skip during the test run.
|
||||||
|
*
|
||||||
|
* Require a minimum version of WordPress:
|
||||||
|
*
|
||||||
|
* @require-wp-4.0
|
||||||
|
* Scenario: Core translation CRUD
|
||||||
|
*
|
||||||
|
* Then use in bash script:
|
||||||
|
*
|
||||||
|
* BEHAT_TAGS=$(php behat-tags.php)
|
||||||
|
* vendor/bin/behat --format progress $BEHAT_TAGS
|
||||||
|
*/
|
||||||
|
|
||||||
|
function version_tags( $prefix, $current, $operator = '<' ) {
|
||||||
|
if ( ! $current )
|
||||||
|
return;
|
||||||
|
|
||||||
|
exec( "grep '@{$prefix}-[0-9\.]*' -h -o features/*.feature | uniq", $existing_tags );
|
||||||
|
|
||||||
|
$skip_tags = array();
|
||||||
|
|
||||||
|
foreach ( $existing_tags as $tag ) {
|
||||||
|
$compare = str_replace( "@{$prefix}-", '', $tag );
|
||||||
|
if ( version_compare( $current, $compare, $operator ) ) {
|
||||||
|
$skip_tags[] = $tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $skip_tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
$skip_tags = array_merge(
|
||||||
|
version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' ),
|
||||||
|
version_tags( 'require-php', PHP_VERSION, '<' ),
|
||||||
|
version_tags( 'less-than-php', PHP_VERSION, '>' )
|
||||||
|
);
|
||||||
|
|
||||||
|
# Skip Github API tests by default because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612
|
||||||
|
$skip_tags[] = '@github-api';
|
||||||
|
|
||||||
|
if ( !empty( $skip_tags ) ) {
|
||||||
|
echo '--tags=~' . implode( '&&~', $skip_tags );
|
||||||
|
}
|
||||||
|
|
2
wp-cli.yml
Normal file
2
wp-cli.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
require:
|
||||||
|
- command.php
|
Loading…
Add table
Add a link
Reference in a new issue