Merge pull request #151 from wojsmol/tov2

Move command over to new v2 structure
This commit is contained in:
Alain Schlesser 2018-09-25 20:29:59 +02:00 committed by GitHub
commit dadade7f9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 339 additions and 2119 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ node_modules/
vendor/ vendor/
*.zip *.zip
*.tar.gz *.tar.gz
*.log

View file

@ -1,6 +1,8 @@
sudo: false sudo: false
dist: trusty
language: php language: php
php: 7.2
notifications: notifications:
email: email:
@ -13,28 +15,56 @@ branches:
cache: cache:
directories: directories:
- vendor
- $HOME/.composer/cache - $HOME/.composer/cache
env: env:
global: global:
- WP_CLI_BIN_DIR=/tmp/wp-cli-phar - PATH="$TRAVIS_BUILD_DIR/vendor/bin:$PATH"
- WP_CLI_BIN_DIR="$TRAVIS_BUILD_DIR/vendor/bin"
matrix: before_install:
include: - |
- php: 7.1 # Remove Xdebug for a huge performance increase:
env: WP_VERSION=latest if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then
- php: 7.0 phpenv config-rm xdebug.ini
env: WP_VERSION=latest else
- php: 5.6 echo "xdebug.ini does not exist"
env: WP_VERSION=latest fi
- php: 5.6 - |
env: WP_VERSION=trunk # Raise PHP memory limit to 2048MB
- php: 5.4 echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
env: WP_VERSION=latest
before_script:
- composer validate - composer validate
- bash bin/install-package-tests.sh
script: ./bin/test.sh install:
- composer install
- composer prepare-tests
script:
- composer phpunit
- composer behat || composer behat-rerun
jobs:
include:
- stage: sniff
script:
- composer lint
- composer phpcs
env: BUILD=sniff
- stage: test
php: 7.2
env: WP_VERSION=latest
- stage: test
php: 7.1
env: WP_VERSION=latest
- stage: test
php: 7.0
env: WP_VERSION=latest
- stage: test
php: 5.6
env: WP_VERSION=latest
- stage: test
php: 5.6
env: WP_VERSION=trunk
- stage: test
php: 5.4
env: WP_VERSION=latest

View file

@ -1,40 +0,0 @@
#!/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

View file

@ -1,8 +0,0 @@
#!/bin/bash
set -ex
BEHAT_TAGS=$(php utils/behat-tags.php)
# Run the functional tests
vendor/bin/behat --format progress $BEHAT_TAGS --strict

View file

@ -1,25 +1,24 @@
{ {
"name": "wp-cli/profile-command", "name": "wp-cli/profile-command",
"description": "Quickly identify what's slow with WordPress.",
"type": "wp-cli-package", "type": "wp-cli-package",
"description": "Quickly identify what's slow with WordPress.",
"homepage": "https://runcommand.io/wp/profile/", "homepage": "https://runcommand.io/wp/profile/",
"license": "MIT", "license": "MIT",
"authors": [], "authors": [],
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"files": [ "command.php" ]
},
"require": { "require": {
"php": ">=5.4", "php": ">=5.4",
"wp-cli/wp-cli": "*" "wp-cli/wp-cli": "^2"
}, },
"require-dev": { "require-dev": {
"behat/behat": "~2.5" "wp-cli/wp-cli-tests": "^2.0.7"
},
"config": {
"process-timeout": 7200,
"sort-packages": true
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.x-dev" "dev-master": "2.x-dev"
}, },
"commands": [ "commands": [
"profile stage", "profile stage",
@ -38,5 +37,26 @@
"post": "bin/readme/overview-body.md" "post": "bin/readme/overview-body.md"
} }
} }
},
"autoload": {
"files": [
"command.php"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"behat": "run-behat-tests",
"behat-rerun": "rerun-behat-tests",
"lint": "run-linter-tests",
"phpcs": "run-phpcs-tests",
"phpunit": "run-php-unit-tests",
"prepare-tests": "install-package-tests",
"test": [
"@lint",
"@phpcs",
"@phpunit",
"@behat"
]
} }
} }

View file

@ -1,342 +0,0 @@
<?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';
require_once __DIR__ . '/ProcessRun.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__ . '/../../php/WP_CLI/ProcessRun.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 );
if ( getenv( 'WP_VERSION' ) ) {
$cmd .= Utils\esc_cmd( ' --version=%s', getenv( 'WP_VERSION' ) );
}
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();
$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;
}
/**
* @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();
}
}
// Remove WP-CLI package directory
if ( isset( $this->variables['PACKAGE_PATH'] ) ) {
$this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['PACKAGE_PATH'] ) )->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();
}
}

View file

@ -1,75 +0,0 @@
<?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;
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace WP_CLI;
/**
* 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;
}
}

View file

@ -1,194 +0,0 @@
<?php
// Utility functions used by Behat steps
function assertEquals( $expected, $actual ) {
if ( $expected != $actual ) {
throw new Exception( "Actual value: " . var_export( $actual, true ) );
}
}
function assertNotEquals( $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 );
}

View file

@ -1,764 +0,0 @@
<?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 );
$bits = explode( '.', $parts[0] );
$major = $bits[0];
if ( isset( $bits[1] ) ) {
$minor = $bits[1];
}
if ( isset( $bits[2] ) ) {
$patch = $bits[2];
}
if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) {
return 'patch';
} else if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) {
return 'minor';
} else {
return 'major';
}
}
/**
* Return the flag value or, if it's not set, the $default value.
*
* Because flags can be negated (e.g. --no-quiet to negate --quiet), this
* function provides a safer alternative to using
* `isset( $assoc_args['quiet'] )` or similar.
*
* @access public
* @category Input
*
* @param array $assoc_args Arguments array.
* @param string $flag Flag to get the value.
* @param mixed $default Default value for the flag. Default: NULL
* @return mixed
*/
function get_flag_value( $assoc_args, $flag, $default = null ) {
return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default;
}
/**
* Get the 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;
}
}

View file

@ -1,7 +0,0 @@
<?php
function wp_mail( $to ) {
// Log for testing purposes
WP_CLI::log( "WP-CLI test suite: Sent email to {$to}." );
}

View file

@ -3,12 +3,12 @@ Feature: Profile a specific hook
Scenario: Profile all hooks when a specific hook isn't specified Scenario: Profile all hooks when a specific hook isn't specified
Given a WP install Given a WP install
When I run `wp profile hook --fields=hook,callback_count` When I run `wp profile hook --fields=hook`
Then STDOUT should be a table containing rows: Then STDOUT should be a table containing rows:
| hook | callback_count | | hook |
| plugins_loaded | 3 | | plugins_loaded |
| init | 11 | | init |
| template_redirect | 7 | | template_redirect |
And STDERR should be empty And STDERR should be empty
Scenario: Profile all callbacks when --all flag is used Scenario: Profile all callbacks when --all flag is used
@ -116,7 +116,7 @@ Feature: Profile a specific hook
}); });
""" """
When I run `wp profile hook init` When I try `wp profile hook init`
Then STDERR should be: Then STDERR should be:
""" """
Warning: Called 1 Warning: Called 1

View file

@ -1,157 +0,0 @@
<?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 );
}
);

View file

@ -1,201 +0,0 @@
<?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 );
}
}
);
$steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) {
if ( 'be sent' === $expected ) {
assertNotEquals( 0, $world->email_sends );
} else if ( 'not be sent' === $expected ) {
assertEquals( 0, $world->email_sends );
} else {
throw new Exception( 'Invalid expectation' );
}
});

View file

@ -1,54 +0,0 @@
<?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();
}
function capture_email_sends( $stdout ) {
$stdout = preg_replace( '#WP-CLI test suite: Sent email to.+\n?#', '', $stdout, -1, $email_sends );
return array( $stdout, $email_sends );
}
$steps->When( '/^I launch in the background `([^`]+)`$/',
function ( $world, $cmd ) {
$world->background_proc( $cmd );
}
);
$steps->When( '/^I (run|try) `([^`]+)`$/',
function ( $world, $mode, $cmd ) {
$cmd = $world->replace_variables( $cmd );
$world->result = invoke_proc( $world->proc( $cmd ), $mode );
list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout );
}
);
$steps->When( "/^I (run|try) `([^`]+)` from '([^\s]+)'$/",
function ( $world, $mode, $cmd, $subdir ) {
$cmd = $world->replace_variables( $cmd );
$world->result = invoke_proc( $world->proc( $cmd, array(), $subdir ), $mode );
list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout );
}
);
$steps->When( '/^I (run|try) the previous command again$/',
function ( $world, $mode ) {
if ( !isset( $world->result ) )
throw new \Exception( 'No previous command.' );
$proc = Process::create( $world->result->command, $world->result->cwd, $world->result->env );
$world->result = invoke_proc( $proc, $mode );
list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout );
}
);

View file

@ -96,7 +96,7 @@ class Command {
public function stage( $args, $assoc_args ) { public function stage( $args, $assoc_args ) {
global $wpdb; global $wpdb;
$focus = Utils\get_flag_value( $assoc_args, 'all', isset( $args[0] ) ? $args[0] : null ); $focus = Utils\get_flag_value( $assoc_args, 'all', isset( $args[0] ) ? $args[0] : null );
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' ); $order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null ); $orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
@ -110,7 +110,7 @@ class Command {
$profiler->run(); $profiler->run();
if ( $focus ) { if ( $focus ) {
$base = array( $base = array(
'hook', 'hook',
'callback_count', 'callback_count',
); );
@ -125,7 +125,7 @@ class Command {
'request_count', 'request_count',
); );
} else { } else {
$base = array( $base = array(
'stage', 'stage',
); );
$metrics = array( $metrics = array(
@ -141,9 +141,9 @@ class Command {
'request_count', 'request_count',
); );
} }
$fields = array_merge( $base, $metrics ); $fields = array_merge( $base, $metrics );
$formatter = new Formatter( $assoc_args, $fields ); $formatter = new Formatter( $assoc_args, $fields );
$loggers = $profiler->get_loggers(); $loggers = $profiler->get_loggers();
if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) { if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
$loggers = self::shine_spotlight( $loggers, $metrics ); $loggers = self::shine_spotlight( $loggers, $metrics );
} }
@ -221,7 +221,7 @@ class Command {
} else { } else {
$base = array( 'hook', 'callback_count' ); $base = array( 'hook', 'callback_count' );
} }
$metrics = array( $metrics = array(
'time', 'time',
'query_time', 'query_time',
'query_count', 'query_count',
@ -231,9 +231,9 @@ class Command {
'request_time', 'request_time',
'request_count', 'request_count',
); );
$fields = array_merge( $base, $metrics ); $fields = array_merge( $base, $metrics );
$formatter = new Formatter( $assoc_args, $fields ); $formatter = new Formatter( $assoc_args, $fields );
$loggers = $profiler->get_loggers(); $loggers = $profiler->get_loggers();
if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) { if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
$loggers = self::shine_spotlight( $loggers, $metrics ); $loggers = self::shine_spotlight( $loggers, $metrics );
} }
@ -289,9 +289,14 @@ class Command {
$order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' ); $order = Utils\get_flag_value( $assoc_args, 'order', 'ASC' );
$orderby = Utils\get_flag_value( $assoc_args, 'orderby', null ); $orderby = Utils\get_flag_value( $assoc_args, 'orderby', null );
self::profile_eval_ish( $assoc_args, function() use ( $statement ) { self::profile_eval_ish(
eval( $statement ); $assoc_args,
}, $order, $orderby ); function() use ( $statement ) {
eval( $statement ); // phpcs:ignore Squiz.PHP.Eval.Discouraged -- no other way oround here
},
$order,
$orderby
);
} }
/** /**
@ -348,22 +353,27 @@ class Command {
WP_CLI::error( "'$file' does not exist." ); WP_CLI::error( "'$file' does not exist." );
} }
self::profile_eval_ish( $assoc_args, function() use ( $file ) { self::profile_eval_ish(
self::include_file( $file ); $assoc_args,
}, $order, $orderby ); function() use ( $file ) {
self::include_file( $file );
},
$order,
$orderby
);
} }
/** /**
* Profile an eval or eval-file statement. * Profile an eval or eval-file statement.
*/ */
private static function profile_eval_ish( $assoc_args, $profile_callback ) { private static function profile_eval_ish( $assoc_args, $profile_callback, $order = 'ASC', $orderby = null ) {
$hook = Utils\get_flag_value( $assoc_args, 'hook' ); $hook = Utils\get_flag_value( $assoc_args, 'hook' );
$type = $focus = false; $type = $focus = false;
$fields = array(); $fields = array();
if ( $hook ) { if ( $hook ) {
$type = 'hook'; $type = 'hook';
if ( true !== $hook ) { if ( true !== $hook ) {
$focus = $hook; $focus = $hook;
$fields[] = 'callback'; $fields[] = 'callback';
$fields[] = 'location'; $fields[] = 'location';
} else { } else {
@ -382,16 +392,19 @@ class Command {
$logger->stop(); $logger->stop();
$loggers = array( $logger ); $loggers = array( $logger );
} }
$fields = array_merge( $fields, array( $fields = array_merge(
'time', $fields,
'query_time', array(
'query_count', 'time',
'cache_ratio', 'query_time',
'cache_hits', 'query_count',
'cache_misses', 'cache_ratio',
'request_time', 'cache_hits',
'request_count', 'cache_misses',
) ); 'request_time',
'request_count',
)
);
$formatter = new Formatter( $assoc_args, $fields ); $formatter = new Formatter( $assoc_args, $fields );
$formatter->display_items( $loggers, false, $order, $orderby ); $formatter->display_items( $loggers, false, $order, $orderby );
} }
@ -414,9 +427,9 @@ class Command {
*/ */
private static function shine_spotlight( $loggers, $metrics ) { private static function shine_spotlight( $loggers, $metrics ) {
foreach( $loggers as $k => $logger ) { foreach ( $loggers as $k => $logger ) {
$non_zero = false; $non_zero = false;
foreach( $metrics as $metric ) { foreach ( $metrics as $metric ) {
switch ( $metric ) { switch ( $metric ) {
// 100% cache ratio is fine by us // 100% cache ratio is fine by us
case 'cache_ratio': case 'cache_ratio':

View file

@ -14,7 +14,7 @@ class Formatter {
$format_args = array( $format_args = array(
'format' => 'table', 'format' => 'table',
'fields' => $fields, 'fields' => $fields,
'field' => null 'field' => null,
); );
foreach ( array( 'format', 'fields', 'field' ) as $key ) { foreach ( array( 'format', 'fields', 'field' ) as $key ) {
@ -33,7 +33,7 @@ class Formatter {
$format_args['fields'] = array_map( 'trim', $format_args['fields'] ); $format_args['fields'] = array_map( 'trim', $format_args['fields'] );
$this->args = $format_args; $this->args = $format_args;
$this->formatter = new \WP_CLI\Formatter( $assoc_args, $fields, $prefix ); $this->formatter = new \WP_CLI\Formatter( $assoc_args, $fields, $prefix );
} }
@ -61,7 +61,7 @@ class Formatter {
$b = number_format( $b, 4 ); $b = number_format( $b, 4 );
if ( 0 === $a - $b ) { if ( 0 === $a - $b ) {
return 0; return 0;
} else if ( $a - $b < 0 ) { } elseif ( $a - $b < 0 ) {
return -1; return -1;
} else { } else {
return 1; return 1;
@ -90,21 +90,24 @@ class Formatter {
} }
if ( $orderby ) { if ( $orderby ) {
usort( $items, function( $a, $b ) use ( $order, $orderby ) { usort(
list( $first, $second ) = 'ASC' === $order ? array( $a, $b ) : array( $b, $a ); $items,
function( $a, $b ) use ( $order, $orderby ) {
list( $first, $second ) = 'ASC' === $order ? array( $a, $b ) : array( $b, $a );
if ( is_numeric( $first->$orderby ) && is_numeric( $second->$orderby ) ) { if ( is_numeric( $first->$orderby ) && is_numeric( $second->$orderby ) ) {
return $this->compare_float( $first->$orderby, $second->$orderby ); return $this->compare_float( $first->$orderby, $second->$orderby );
}
return strcmp( $first->$orderby, $second->$orderby );
} }
);
return strcmp( $first->$orderby, $second->$orderby );
});
} }
$location_index = array_search( 'location', $fields ); $location_index = array_search( 'location', $fields );
foreach ( $items as $item ) { foreach ( $items as $item ) {
$values = array_values( \WP_CLI\Utils\pick_fields( $item, $fields ) ); $values = array_values( \WP_CLI\Utils\pick_fields( $item, $fields ) );
foreach( $values as $i => $value ) { foreach ( $values as $i => $value ) {
if ( ! is_null( $this->total_cell_index ) && $this->total_cell_index === $i ) { if ( ! is_null( $this->total_cell_index ) && $this->total_cell_index === $i ) {
continue; continue;
} }
@ -135,7 +138,7 @@ class Formatter {
$table->addRow( $values ); $table->addRow( $values );
} }
if ( $include_total ) { if ( $include_total ) {
foreach( $totals as $i => $value ) { foreach ( $totals as $i => $value ) {
if ( null === $value ) { if ( null === $value ) {
continue; continue;
} }
@ -153,7 +156,7 @@ class Formatter {
$table->setFooters( $totals ); $table->setFooters( $totals );
} }
foreach( $table->getDisplayLines() as $line ) { foreach ( $table->getDisplayLines() as $line ) {
\WP_CLI::line( $line ); \WP_CLI::line( $line );
} }

View file

@ -4,29 +4,29 @@ namespace runcommand\Profile;
class Logger { class Logger {
public $time = 0; public $time = 0;
public $query_count = 0; public $query_count = 0;
public $query_time = 0; public $query_time = 0;
public $cache_hits = 0; public $cache_hits = 0;
public $cache_misses = 0; public $cache_misses = 0;
public $cache_ratio = null; public $cache_ratio = null;
public $hook_count = 0; public $hook_count = 0;
public $hook_time = 0; public $hook_time = 0;
public $request_count = 0; public $request_count = 0;
public $request_time = 0; public $request_time = 0;
private $start_time = null; private $start_time = null;
private $query_offset = null; private $query_offset = null;
private $cache_hit_offset = null; private $cache_hit_offset = null;
private $cache_miss_offset = null; private $cache_miss_offset = null;
private $hook_start_time = null; private $hook_start_time = null;
private $hook_depth = 0; private $hook_depth = 0;
private $request_start_time = null; private $request_start_time = null;
public static $active_loggers = array(); public static $active_loggers = array();
public function __construct( $definition = array() ) { public function __construct( $definition = array() ) {
foreach( $definition as $k => $v ) { foreach ( $definition as $k => $v ) {
$this->$k = $v; $this->$k = $v;
} }
} }
@ -36,12 +36,12 @@ class Logger {
*/ */
public function start() { public function start() {
global $wpdb, $wp_object_cache; global $wpdb, $wp_object_cache;
$this->start_time = microtime( true ); $this->start_time = microtime( true );
$this->query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0; $this->query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0;
if ( false === ( $key = array_search( $this, self::$active_loggers ) ) ) { if ( false === ( $key = array_search( $this, self::$active_loggers ) ) ) {
self::$active_loggers[] = $this; self::$active_loggers[] = $this;
} }
$this->cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $this->cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0;
$this->cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; $this->cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0;
} }
@ -61,7 +61,7 @@ class Logger {
if ( ! is_null( $this->start_time ) ) { if ( ! is_null( $this->start_time ) ) {
$this->time += microtime( true ) - $this->start_time; $this->time += microtime( true ) - $this->start_time;
} }
if ( ! is_null( $this->query_offset ) && isset( $wpdb ) ) { if ( ! is_null( $this->query_offset ) && isset( $wpdb ) && ! empty( $wpdb->queries ) ) {
for ( $i = $this->query_offset; $i < count( $wpdb->queries ); $i++ ) { for ( $i = $this->query_offset; $i < count( $wpdb->queries ); $i++ ) {
$this->query_time += $wpdb->queries[ $i ][1]; $this->query_time += $wpdb->queries[ $i ][1];
$this->query_count++; $this->query_count++;
@ -69,20 +69,20 @@ class Logger {
} }
if ( ! is_null( $this->cache_hit_offset ) && ! is_null( $this->cache_miss_offset ) && isset( $wp_object_cache ) ) { if ( ! is_null( $this->cache_hit_offset ) && ! is_null( $this->cache_miss_offset ) && isset( $wp_object_cache ) ) {
$cache_hits = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $cache_hits = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0;
$cache_misses = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; $cache_misses = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0;
$this->cache_hits = $cache_hits - $this->cache_hit_offset; $this->cache_hits = $cache_hits - $this->cache_hit_offset;
$this->cache_misses = $cache_misses - $this->cache_miss_offset; $this->cache_misses = $cache_misses - $this->cache_miss_offset;
$cache_total = $this->cache_hits + $this->cache_misses; $cache_total = $this->cache_hits + $this->cache_misses;
if ( $cache_total ) { if ( $cache_total ) {
$ratio = ( $this->cache_hits / $cache_total ) * 100; $ratio = ( $this->cache_hits / $cache_total ) * 100;
$this->cache_ratio = round( $ratio, 2 ) . '%'; $this->cache_ratio = round( $ratio, 2 ) . '%';
} }
} }
$this->start_time = null; $this->start_time = null;
$this->query_offset = null; $this->query_offset = null;
$this->cache_hit_offset = null; $this->cache_hit_offset = null;
$this->cache_miss_offset = null; $this->cache_miss_offset = null;
if ( false !== ( $key = array_search( $this, self::$active_loggers ) ) ) { if ( false !== ( $key = array_search( $this, self::$active_loggers ) ) ) {
unset( self::$active_loggers[ $key ] ); unset( self::$active_loggers[ $key ] );

View file

@ -8,9 +8,9 @@ class Profiler {
private $type; private $type;
private $focus; private $focus;
private $loggers = array(); private $loggers = array();
private $stage_hooks = array( private $stage_hooks = array(
'bootstrap' => array( 'bootstrap' => array(
'muplugins_loaded', 'muplugins_loaded',
'plugins_loaded', 'plugins_loaded',
'setup_theme', 'setup_theme',
@ -18,14 +18,14 @@ class Profiler {
'init', 'init',
'wp_loaded', 'wp_loaded',
), ),
'main_query' => array( 'main_query' => array(
'parse_request', 'parse_request',
'send_headers', 'send_headers',
'pre_get_posts', 'pre_get_posts',
'the_posts', 'the_posts',
'wp', 'wp',
), ),
'template' => array( 'template' => array(
'template_redirect', 'template_redirect',
'template_include', 'template_include',
'wp_head', 'wp_head',
@ -34,26 +34,26 @@ class Profiler {
'wp_footer', 'wp_footer',
), ),
); );
private $current_stage_hooks = array(); private $current_stage_hooks = array();
private $running_hook = null; private $running_hook = null;
private $previous_filter = null; private $previous_filter = null;
private $previous_filter_callbacks = null; private $previous_filter_callbacks = null;
private $filter_depth = 0; private $filter_depth = 0;
private $tick_callback = null; private $tick_callback = null;
private $tick_location = null; private $tick_location = null;
private $tick_start_time = null; private $tick_start_time = null;
private $tick_query_offset = null; private $tick_query_offset = null;
private $tick_cache_hit_offset = null; private $tick_cache_hit_offset = null;
private $tick_cache_miss_offset = null; private $tick_cache_miss_offset = null;
public function __construct( $type, $focus ) { public function __construct( $type, $focus ) {
$this->type = $type; $this->type = $type;
$this->focus = $focus; $this->focus = $focus;
} }
public function get_loggers() { public function get_loggers() {
foreach( $this->loggers as $i => $logger ) { foreach ( $this->loggers as $i => $logger ) {
if ( is_array( $logger ) ) { if ( is_array( $logger ) ) {
$this->loggers[ $i ] = $logger = new Logger( $logger ); $this->loggers[ $i ] = $logger = new Logger( $logger );
} }
@ -62,10 +62,10 @@ class Profiler {
} }
if ( ! isset( $logger->location ) ) { if ( ! isset( $logger->location ) ) {
list( $name, $location ) = self::get_name_location_from_callback( $logger->callback ); list( $name, $location ) = self::get_name_location_from_callback( $logger->callback );
$logger->callback = $name; $logger->callback = $name;
$logger->location = $location; $logger->location = $location;
} }
$logger->location = self::get_short_location( $logger->location ); $logger->location = self::get_short_location( $logger->location );
$this->loggers[ $i ] = $logger; $this->loggers[ $i ] = $logger;
} }
return $this->loggers; return $this->loggers;
@ -75,29 +75,35 @@ class Profiler {
* Run the profiler against WordPress * Run the profiler against WordPress
*/ */
public function run() { public function run() {
WP_CLI::add_wp_hook( 'muplugins_loaded', function(){ WP_CLI::add_wp_hook(
if ( $url = WP_CLI::get_runner()->config['url'] ) { 'muplugins_loaded',
WP_CLI::set_url( trailingslashit( $url ) ); function() {
} else { if ( $url = WP_CLI::get_runner()->config['url'] ) {
WP_CLI::set_url( home_url( '/' ) ); WP_CLI::set_url( trailingslashit( $url ) );
} else {
WP_CLI::set_url( home_url( '/' ) );
}
} }
}); );
WP_CLI::add_hook( 'after_wp_config_load', function() { WP_CLI::add_hook(
if ( defined( 'SAVEQUERIES' ) && ! SAVEQUERIES ) { 'after_wp_config_load',
WP_CLI::error( "'SAVEQUERIES' is defined as false, and must be true. Please check your wp-config.php" ); function() {
if ( defined( 'SAVEQUERIES' ) && ! SAVEQUERIES ) {
WP_CLI::error( "'SAVEQUERIES' is defined as false, and must be true. Please check your wp-config.php" );
}
if ( ! defined( 'SAVEQUERIES' ) ) {
define( 'SAVEQUERIES', true );
}
} }
if ( ! defined( 'SAVEQUERIES' ) ) { );
define( 'SAVEQUERIES', true );
}
});
if ( 'hook' === $this->type if ( 'hook' === $this->type
&& ':before' === substr( $this->focus, -7, 7 ) ) { && ':before' === substr( $this->focus, -7, 7 ) ) {
$stage_hooks = array(); $stage_hooks = array();
foreach( $this->stage_hooks as $hooks ) { foreach ( $this->stage_hooks as $hooks ) {
$stage_hooks = array_merge( $stage_hooks, $hooks ); $stage_hooks = array_merge( $stage_hooks, $hooks );
} }
$end_hook = substr( $this->focus, 0, -7 ); $end_hook = substr( $this->focus, 0, -7 );
$key = array_search( $end_hook, $stage_hooks ); $key = array_search( $end_hook, $stage_hooks );
if ( isset( $stage_hooks[ $key - 1 ] ) ) { if ( isset( $stage_hooks[ $key - 1 ] ) ) {
$start_hook = $stage_hooks[ $key - 1 ]; $start_hook = $stage_hooks[ $key - 1 ];
WP_CLI::add_wp_hook( $start_hook, array( $this, 'wp_tick_profile_begin' ), 9999 ); WP_CLI::add_wp_hook( $start_hook, array( $this, 'wp_tick_profile_begin' ), 9999 );
@ -105,7 +111,7 @@ class Profiler {
WP_CLI::add_hook( 'after_wp_config_load', array( $this, 'wp_tick_profile_begin' ) ); WP_CLI::add_hook( 'after_wp_config_load', array( $this, 'wp_tick_profile_begin' ) );
} }
WP_CLI::add_wp_hook( $end_hook, array( $this, 'wp_tick_profile_end' ), -9999 ); WP_CLI::add_wp_hook( $end_hook, array( $this, 'wp_tick_profile_end' ), -9999 );
} else if ( 'hook' === $this->type } elseif ( 'hook' === $this->type
&& ':after' === substr( $this->focus, -6, 6 ) ) { && ':after' === substr( $this->focus, -6, 6 ) ) {
$start_hook = substr( $this->focus, 0, -6 ); $start_hook = substr( $this->focus, 0, -6 );
WP_CLI::add_wp_hook( $start_hook, array( $this, 'wp_tick_profile_begin' ), 9999 ); WP_CLI::add_wp_hook( $start_hook, array( $this, 'wp_tick_profile_begin' ), 9999 );
@ -123,24 +129,24 @@ class Profiler {
public function wp_tick_profile_begin( $value = null ) { public function wp_tick_profile_begin( $value = null ) {
if ( version_compare( PHP_VERSION, '7.0.0' ) >= 0 ) { if ( version_compare( PHP_VERSION, '7.0.0' ) >= 0 ) {
WP_CLI::error( "Profiling intermediate hooks is broken in PHP 7, see https://bugs.php.net/bug.php?id=72966" ); WP_CLI::error( 'Profiling intermediate hooks is broken in PHP 7, see https://bugs.php.net/bug.php?id=72966' );
} }
// Disable opcode optimizers. These "optimize" calls out of the stack // Disable opcode optimizers. These "optimize" calls out of the stack
// and hide calls from the tick handler and backtraces. // and hide calls from the tick handler and backtraces.
// Copied from P3 Profiler // Copied from P3 Profiler
if ( extension_loaded( 'xcache' ) ) { if ( extension_loaded( 'xcache' ) ) {
@ini_set( 'xcache.optimizer', false ); @ini_set( 'xcache.optimizer', false ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- ini_set can be disabled on server.
} elseif ( extension_loaded( 'apc' ) ) { } elseif ( extension_loaded( 'apc' ) ) {
@ini_set( 'apc.optimization', 0 ); @ini_set( 'apc.optimization', 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- ini_set can be disabled on server.
apc_clear_cache(); apc_clear_cache();
} elseif ( extension_loaded( 'eaccelerator' ) ) { } elseif ( extension_loaded( 'eaccelerator' ) ) {
@ini_set( 'eaccelerator.optimizer', 0 ); @ini_set( 'eaccelerator.optimizer', 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- ini_set can be disabled on server.
if ( function_exists( 'eaccelerator_optimizer' ) ) { if ( function_exists( 'eaccelerator_optimizer' ) ) {
@eaccelerator_optimizer( false ); @eaccelerator_optimizer( false ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- disabling eaccelerator on runtime can faild
} }
} elseif ( extension_loaded( 'Zend Optimizer+' ) ) { } elseif ( extension_loaded( 'Zend Optimizer+' ) ) {
@ini_set( 'zend_optimizerplus.optimization_level', 0 ); @ini_set( 'zend_optimizerplus.optimization_level', 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- ini_set can be disabled on server.
} }
register_tick_function( array( $this, 'handle_function_tick' ) ); register_tick_function( array( $this, 'handle_function_tick' ) );
@ -162,7 +168,7 @@ class Profiler {
*/ */
public function wp_hook_begin() { public function wp_hook_begin() {
foreach( Logger::$active_loggers as $logger ) { foreach ( Logger::$active_loggers as $logger ) {
$logger->start_hook_timer(); $logger->start_hook_timer();
} }
@ -174,13 +180,18 @@ class Profiler {
$this->loggers[ $pseudo_hook ]->stop(); $this->loggers[ $pseudo_hook ]->stop();
} }
$callback_count = 0; $callback_count = 0;
$callbacks = self::get_filter_callbacks( $current_filter ); $callbacks = self::get_filter_callbacks( $current_filter );
if ( false !== $callbacks ) { if ( false !== $callbacks ) {
foreach( $callbacks as $priority => $cbs ) { foreach ( $callbacks as $priority => $cbs ) {
$callback_count += count( $cbs ); $callback_count += count( $cbs );
} }
} }
$this->loggers[ $current_filter ] = new Logger( array( 'hook' => $current_filter, 'callback_count' => $callback_count ) ); $this->loggers[ $current_filter ] = new Logger(
array(
'hook' => $current_filter,
'callback_count' => $callback_count,
)
);
$this->loggers[ $current_filter ]->start(); $this->loggers[ $current_filter ]->start();
} }
@ -210,24 +221,26 @@ class Profiler {
if ( false === $callbacks ) { if ( false === $callbacks ) {
return; return;
} }
$this->previous_filter = $current_filter; $this->previous_filter = $current_filter;
$this->previous_filter_callbacks = $callbacks; $this->previous_filter_callbacks = $callbacks;
foreach( $callbacks as $priority => $priority_callbacks ) { foreach ( $callbacks as $priority => $priority_callbacks ) {
foreach( $priority_callbacks as $i => $the_ ) { foreach ( $priority_callbacks as $i => $the_ ) {
$callbacks[ $priority ][ $i ] = array( $callbacks[ $priority ][ $i ] = array(
'function' => function() use( $the_, $i ) { 'function' => function() use ( $the_, $i ) {
if ( ! isset( $this->loggers[ $i ] ) ) { if ( ! isset( $this->loggers[ $i ] ) ) {
$this->loggers[ $i ] = new Logger( array( $this->loggers[ $i ] = new Logger(
'callback' => $the_['function'], array(
) ); 'callback' => $the_['function'],
)
);
} }
$this->loggers[ $i ]->start(); $this->loggers[ $i ]->start();
$value = call_user_func_array( $the_['function'], func_get_args() ); $value = call_user_func_array( $the_['function'], func_get_args() );
$this->loggers[ $i ]->stop(); $this->loggers[ $i ]->stop();
return $value; return $value;
}, },
'accepted_args' => $the_['accepted_args'], 'accepted_args' => $the_['accepted_args'],
); );
} }
} }
@ -239,7 +252,7 @@ class Profiler {
*/ */
public function wp_hook_end( $filter_value = null ) { public function wp_hook_end( $filter_value = null ) {
foreach( Logger::$active_loggers as $logger ) { foreach ( Logger::$active_loggers as $logger ) {
$logger->stop_hook_timer(); $logger->stop_hook_timer();
} }
@ -252,7 +265,7 @@ class Profiler {
if ( false !== $key && isset( $this->current_stage_hooks[ $key + 1 ] ) ) { if ( false !== $key && isset( $this->current_stage_hooks[ $key + 1 ] ) ) {
$pseudo_hook = "{$this->current_stage_hooks[$key+1]}:before"; $pseudo_hook = "{$this->current_stage_hooks[$key+1]}:before";
} else { } else {
$pseudo_hook = "{$this->current_stage_hooks[$key]}:after";; $pseudo_hook = "{$this->current_stage_hooks[$key]}:after";
$this->running_hook = $pseudo_hook; $this->running_hook = $pseudo_hook;
} }
$this->loggers[ $pseudo_hook ] = new Logger( array( 'hook' => $pseudo_hook ) ); $this->loggers[ $pseudo_hook ] = new Logger( array( 'hook' => $pseudo_hook ) );
@ -277,14 +290,14 @@ class Profiler {
$callback_hash = md5( serialize( $this->tick_callback . $this->tick_location ) ); $callback_hash = md5( serialize( $this->tick_callback . $this->tick_location ) );
if ( ! isset( $this->loggers[ $callback_hash ] ) ) { if ( ! isset( $this->loggers[ $callback_hash ] ) ) {
$this->loggers[ $callback_hash ] = array( $this->loggers[ $callback_hash ] = array(
'callback' => $this->tick_callback, 'callback' => $this->tick_callback,
'location' => $this->tick_location, 'location' => $this->tick_location,
'time' => 0, 'time' => 0,
'query_time' => 0, 'query_time' => 0,
'query_count' => 0, 'query_count' => 0,
'cache_hits' => 0, 'cache_hits' => 0,
'cache_misses' => 0, 'cache_misses' => 0,
'cache_ratio' => null, 'cache_ratio' => null,
); );
} }
@ -298,9 +311,9 @@ class Profiler {
} }
if ( isset( $wp_object_cache ) ) { if ( isset( $wp_object_cache ) ) {
$hits = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $hits = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0;
$misses = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; $misses = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0;
$this->loggers[ $callback_hash ]['cache_hits'] = ( $hits - $this->tick_cache_hit_offset ) + $this->loggers[ $callback_hash ]['cache_hits']; $this->loggers[ $callback_hash ]['cache_hits'] = ( $hits - $this->tick_cache_hit_offset ) + $this->loggers[ $callback_hash ]['cache_hits'];
$this->loggers[ $callback_hash ]['cache_misses'] = ( $misses - $this->tick_cache_miss_offset ) + $this->loggers[ $callback_hash ]['cache_misses']; $this->loggers[ $callback_hash ]['cache_misses'] = ( $misses - $this->tick_cache_miss_offset ) + $this->loggers[ $callback_hash ]['cache_misses'];
$total = $this->loggers[ $callback_hash ]['cache_hits'] + $this->loggers[ $callback_hash ]['cache_misses']; $total = $this->loggers[ $callback_hash ]['cache_hits'] + $this->loggers[ $callback_hash ]['cache_misses'];
if ( $total ) { if ( $total ) {
@ -310,7 +323,7 @@ class Profiler {
} }
} }
$bt = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT, 2 ); $bt = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT, 2 );
$frame = $bt[0]; $frame = $bt[0];
if ( isset( $bt[1] ) ) { if ( isset( $bt[1] ) ) {
$frame = $bt[1]; $frame = $bt[1];
@ -319,11 +332,11 @@ class Profiler {
$callback = $location = ''; $callback = $location = '';
if ( in_array( strtolower( $frame['function'] ), array( 'include', 'require', 'include_once', 'require_once' ) ) ) { if ( in_array( strtolower( $frame['function'] ), array( 'include', 'require', 'include_once', 'require_once' ) ) ) {
$callback = $frame['function'] . " '" . $frame['args'][0] . "'"; $callback = $frame['function'] . " '" . $frame['args'][0] . "'";
} else if ( isset( $frame['object'] ) && method_exists( $frame['object'], $frame['function'] ) ) { } elseif ( isset( $frame['object'] ) && method_exists( $frame['object'], $frame['function'] ) ) {
$callback = get_class( $frame['object'] ) . '->' . $frame['function'] . '()'; $callback = get_class( $frame['object'] ) . '->' . $frame['function'] . '()';
} else if ( isset( $frame['class'] ) && method_exists( $frame['class'], $frame['function'] ) ) { } elseif ( isset( $frame['class'] ) && method_exists( $frame['class'], $frame['function'] ) ) {
$callback = $frame['class'] . '::' . $frame['function'] . '()'; $callback = $frame['class'] . '::' . $frame['function'] . '()';
} else if ( ! empty( $frame['function'] ) && function_exists( $frame['function'] ) ) { } elseif ( ! empty( $frame['function'] ) && function_exists( $frame['function'] ) ) {
$callback = $frame['function'] . '()'; $callback = $frame['function'] . '()';
} elseif ( '__lambda_func' == $frame['function'] || '{closure}' == $frame['function'] ) { } elseif ( '__lambda_func' == $frame['function'] || '{closure}' == $frame['function'] ) {
$callback = 'function(){}'; $callback = 'function(){}';
@ -341,11 +354,11 @@ class Profiler {
} }
} }
$this->tick_callback = $callback; $this->tick_callback = $callback;
$this->tick_location = $location; $this->tick_location = $location;
$this->tick_start_time = microtime( true ); $this->tick_start_time = microtime( true );
$this->tick_query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0; $this->tick_query_offset = ! empty( $wpdb->queries ) ? count( $wpdb->queries ) : 0;
$this->tick_cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0; $this->tick_cache_hit_offset = ! empty( $wp_object_cache->cache_hits ) ? $wp_object_cache->cache_hits : 0;
$this->tick_cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0; $this->tick_cache_miss_offset = ! empty( $wp_object_cache->cache_misses ) ? $wp_object_cache->cache_misses : 0;
} }
@ -353,7 +366,7 @@ class Profiler {
* Profiling request time for any active Loggers * Profiling request time for any active Loggers
*/ */
public function wp_request_begin( $filter_value = null ) { public function wp_request_begin( $filter_value = null ) {
foreach( Logger::$active_loggers as $logger ) { foreach ( Logger::$active_loggers as $logger ) {
$logger->start_request_timer(); $logger->start_request_timer();
} }
return $filter_value; return $filter_value;
@ -363,7 +376,7 @@ class Profiler {
* Profiling request time for any active Loggers * Profiling request time for any active Loggers
*/ */
public function wp_request_end( $filter_value = null ) { public function wp_request_end( $filter_value = null ) {
foreach( Logger::$active_loggers as $logger ) { foreach ( Logger::$active_loggers as $logger ) {
$logger->stop_request_timer(); $logger->stop_request_timer();
} }
return $filter_value; return $filter_value;
@ -381,7 +394,7 @@ class Profiler {
if ( 'stage' === $this->type && true === $this->focus ) { if ( 'stage' === $this->type && true === $this->focus ) {
$hooks = array(); $hooks = array();
foreach( $this->stage_hooks as $stage_hook ) { foreach ( $this->stage_hooks as $stage_hook ) {
$hooks = array_merge( $hooks, $stage_hook ); $hooks = array_merge( $hooks, $stage_hook );
} }
$this->set_stage_hooks( $hooks ); $this->set_stage_hooks( $hooks );
@ -390,7 +403,7 @@ class Profiler {
if ( 'stage' === $this->type ) { if ( 'stage' === $this->type ) {
if ( 'bootstrap' === $this->focus ) { if ( 'bootstrap' === $this->focus ) {
$this->set_stage_hooks( $this->stage_hooks['bootstrap'] ); $this->set_stage_hooks( $this->stage_hooks['bootstrap'] );
} else if ( ! $this->focus ) { } elseif ( ! $this->focus ) {
$logger = new Logger( array( 'stage' => 'bootstrap' ) ); $logger = new Logger( array( 'stage' => 'bootstrap' ) );
$logger->start(); $logger->start();
} }
@ -412,7 +425,7 @@ class Profiler {
if ( 'stage' === $this->type ) { if ( 'stage' === $this->type ) {
if ( 'main_query' === $this->focus ) { if ( 'main_query' === $this->focus ) {
$this->set_stage_hooks( $this->stage_hooks['main_query'] ); $this->set_stage_hooks( $this->stage_hooks['main_query'] );
} else if ( ! $this->focus ) { } elseif ( ! $this->focus ) {
$logger = new Logger( array( 'stage' => 'main_query' ) ); $logger = new Logger( array( 'stage' => 'main_query' ) );
$logger->start(); $logger->start();
} }
@ -433,15 +446,15 @@ class Profiler {
define( 'WP_USE_THEMES', true ); define( 'WP_USE_THEMES', true );
// Template is normally loaded in global scope, so we need to replicate // Template is normally loaded in global scope, so we need to replicate
foreach( $GLOBALS as $key => $value ) { foreach ( $GLOBALS as $key => $value ) {
global $$key; global ${$key}; // phpcs:ignore PHPCompatibility.PHP.ForbiddenGlobalVariableVariable.NonBareVariableFound -- Syntax is updated to compatible with php 5 and 7.
} }
// Load the theme template. // Load the theme template.
if ( 'stage' === $this->type ) { if ( 'stage' === $this->type ) {
if ( 'template' === $this->focus ) { if ( 'template' === $this->focus ) {
$this->set_stage_hooks( $this->stage_hooks['template'] ); $this->set_stage_hooks( $this->stage_hooks['template'] );
} else if ( ! $this->focus ) { } elseif ( ! $this->focus ) {
$logger = new Logger( array( 'stage' => 'template' ) ); $logger = new Logger( array( 'stage' => 'template' ) );
$logger->start(); $logger->start();
} }
@ -467,20 +480,20 @@ class Profiler {
* Get a human-readable name from a callback * Get a human-readable name from a callback
*/ */
private static function get_name_location_from_callback( $callback ) { private static function get_name_location_from_callback( $callback ) {
$name = $location = ''; $name = $location = '';
$reflection = false; $reflection = false;
if ( is_array( $callback ) && is_object( $callback[0] ) ) { if ( is_array( $callback ) && is_object( $callback[0] ) ) {
$reflection = new \ReflectionMethod( $callback[0], $callback[1] ); $reflection = new \ReflectionMethod( $callback[0], $callback[1] );
$name = get_class( $callback[0] ) . '->' . $callback[1] . '()'; $name = get_class( $callback[0] ) . '->' . $callback[1] . '()';
} elseif ( is_array( $callback ) && method_exists( $callback[0], $callback[1] ) ) { } elseif ( is_array( $callback ) && method_exists( $callback[0], $callback[1] ) ) {
$reflection = new \ReflectionMethod( $callback[0], $callback[1] ); $reflection = new \ReflectionMethod( $callback[0], $callback[1] );
$name = $callback[0] . '::' . $callback[1] . '()'; $name = $callback[0] . '::' . $callback[1] . '()';
} elseif ( is_object( $callback ) && is_a( $callback, 'Closure' ) ) { } elseif ( is_object( $callback ) && is_a( $callback, 'Closure' ) ) {
$reflection = new \ReflectionFunction( $callback ); $reflection = new \ReflectionFunction( $callback );
$name = 'function(){}'; $name = 'function(){}';
} else if ( is_string( $callback ) && function_exists( $callback ) ) { } elseif ( is_string( $callback ) && function_exists( $callback ) ) {
$reflection = new \ReflectionFunction( $callback ); $reflection = new \ReflectionFunction( $callback );
$name = $callback . '()'; $name = $callback . '()';
} }
if ( $reflection ) { if ( $reflection ) {
$location = $reflection->getFileName() . ':' . $reflection->getStartLine(); $location = $reflection->getFileName() . ':' . $reflection->getStartLine();
@ -498,13 +511,13 @@ class Profiler {
$abspath = rtrim( realpath( ABSPATH ), '/' ) . '/'; $abspath = rtrim( realpath( ABSPATH ), '/' ) . '/';
if ( defined( 'WP_PLUGIN_DIR' ) && 0 === stripos( $location, WP_PLUGIN_DIR ) ) { if ( defined( 'WP_PLUGIN_DIR' ) && 0 === stripos( $location, WP_PLUGIN_DIR ) ) {
$location = str_replace( trailingslashit( WP_PLUGIN_DIR ), '', $location ); $location = str_replace( trailingslashit( WP_PLUGIN_DIR ), '', $location );
} else if ( defined( 'WPMU_PLUGIN_DIR' ) && 0 === stripos( $location, WPMU_PLUGIN_DIR ) ) { } elseif ( defined( 'WPMU_PLUGIN_DIR' ) && 0 === stripos( $location, WPMU_PLUGIN_DIR ) ) {
$location = str_replace( trailingslashit( dirname( WPMU_PLUGIN_DIR ) ), '', $location ); $location = str_replace( trailingslashit( dirname( WPMU_PLUGIN_DIR ) ), '', $location );
} else if ( function_exists( 'get_theme_root' ) && 0 === stripos( $location, get_theme_root() ) ) { } elseif ( function_exists( 'get_theme_root' ) && 0 === stripos( $location, get_theme_root() ) ) {
$location = str_replace( trailingslashit( get_theme_root() ), '', $location ); $location = str_replace( trailingslashit( get_theme_root() ), '', $location );
} else if ( 0 === stripos( $location, $abspath . 'wp-admin/' ) ) { } elseif ( 0 === stripos( $location, $abspath . 'wp-admin/' ) ) {
$location = str_replace( $abspath, '', $location ); $location = str_replace( $abspath, '', $location );
} else if ( 0 === stripos( $location, $abspath . 'wp-includes/' ) ) { } elseif ( 0 === stripos( $location, $abspath . 'wp-includes/' ) ) {
$location = str_replace( $abspath, '', $location ); $location = str_replace( $abspath, '', $location );
} }
return $location; return $location;
@ -514,8 +527,8 @@ class Profiler {
* Set the hooks for the current stage * Set the hooks for the current stage
*/ */
private function set_stage_hooks( $hooks ) { private function set_stage_hooks( $hooks ) {
$this->current_stage_hooks = $hooks; $this->current_stage_hooks = $hooks;
$pseudo_hook = "{$hooks[0]}:before"; $pseudo_hook = "{$hooks[0]}:before";
$this->loggers[ $pseudo_hook ] = new Logger( array( 'hook' => $pseudo_hook ) ); $this->loggers[ $pseudo_hook ] = new Logger( array( 'hook' => $pseudo_hook ) );
$this->loggers[ $pseudo_hook ]->start(); $this->loggers[ $pseudo_hook ]->start();
} }
@ -563,5 +576,5 @@ class Profiler {
$wp_filter[ $filter ] = $callbacks; $wp_filter[ $filter ] = $callbacks;
} }
} }
} }

61
phpcs.xml.dist Normal file
View file

@ -0,0 +1,61 @@
<?xml version="1.0"?>
<ruleset name="WP-CLI">
<description>Custom ruleset for WP-CLI</description>
<!-- For help in understanding this file: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml -->
<!-- For help in using PHPCS: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage -->
<!-- What to scan -->
<file>.</file>
<!-- Ignoring Files and Folders:
https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#ignoring-files-and-folders -->
<exclude-pattern>*/.git/*</exclude-pattern>
<exclude-pattern>*/ci/*</exclude-pattern>
<exclude-pattern>*/features/*</exclude-pattern>
<exclude-pattern>*/packages/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<exclude-pattern>*/utils/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<!-- How to scan -->
<arg value="sp"/> <!-- Show sniff and progress -->
<arg name="colors"/> <!-- Show results with colors -->
<arg name="extensions" value="php"/> <!-- Limit to PHP files -->
<!-- Rules: Check PHP version compatibility - see
https://github.com/PHPCompatibility/PHPCompatibilityWP -->
<rule ref="PHPCompatibilityWP">
<!-- Polyfill package is used so array_column() is available for PHP 5.4- -->
<exclude name="PHPCompatibility.PHP.NewFunctions.array_columnFound"/>
<!-- Both magic quotes directives set in wp-settings-cli.php to provide consistent starting point. -->
<exclude name="PHPCompatibility.PHP.DeprecatedIniDirectives.magic_quotes_runtimeDeprecatedRemoved"/>
<exclude name="PHPCompatibility.PHP.DeprecatedIniDirectives.magic_quotes_sybaseDeprecatedRemoved"/>
</rule>
<!-- For help in understanding this testVersion:
https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions -->
<config name="testVersion" value="5.4-"/>
<!-- Ignore php_uname mode issue, we're conditionally providing a callback -->
<rule ref="PHPCompatibility.PHP.NewFunctionParameters.php_uname_modeFound">
<exclude-pattern>*/php/commands/src/CLI_Command.php</exclude-pattern>
</rule>
<!-- Rules: WordPress Coding Standards - see
https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards -->
<rule ref="WordPress-Core">
<exclude name="Squiz.PHP.DisallowMultipleAssignments.Found" />
<exclude name="WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar" />
<exclude name="WordPress.NamingConventions.ValidVariableName.MemberNotSnakeCase" />
<exclude name="WordPress.NamingConventions.ValidVariableName.NotSnakeCase" />
</rule>
<!-- For help in understanding these custom sniff properties:
https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties -->
<rule ref="WordPress.Files.FileName">
<properties>
<property name="strict_class_file_names" value="false"/>
</properties>
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase" />
</rule>
</ruleset>

View file

@ -1,46 +0,0 @@
<?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 array();
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 );
}