Skip to content
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ matrix:
env: WP_VERSION=3.7.11
- php: 5.3
env: WP_VERSION=3.7.11 DEPLOY_BRANCH=master
- php: 7.1
env: WP_VERSION=latest SAVE_COMMENTS_DISABLED=1

before_script:
- export PATH="$HOME/.composer/vendor/bin:$PATH"
Expand Down
7 changes: 6 additions & 1 deletion ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ vendor/bin/phpunit
BEHAT_TAGS=$(php ci/behat-tags.php)

# Run the functional tests
vendor/bin/behat --format progress $BEHAT_TAGS --strict
if [[ -n "$SAVE_COMMENTS_DISABLED" ]]; then
# Run the functional tests with opcache.save_comments disabled.
WP_CLI_PHP_ARGS='-dopcache.enable_cli=1 -dopcache.save_comments=0' vendor/bin/behat --format progress "$BEHAT_TAGS&&~@require-opcache-save-comments" --strict
else
vendor/bin/behat --format progress $BEHAT_TAGS --strict
fi
1 change: 1 addition & 0 deletions features/bootstrap.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Feature: Bootstrap WP-CLI

@require-opcache-save-comments
Scenario: Basic Composer stack
Given an empty directory
And a composer.json file:
Expand Down
30 changes: 24 additions & 6 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
use \WP_CLI\Process;
use \WP_CLI\Utils;

use Symfony\Component\Filesystem\Filesystem,
Symfony\Component\Filesystem\Exception\IOExceptionInterface;

// Inside a community package
if ( file_exists( __DIR__ . '/utils.php' ) ) {
require_once __DIR__ . '/utils.php';
Expand Down Expand Up @@ -44,7 +47,7 @@
*/
class FeatureContext extends BehatContext implements ClosuredContextInterface {

private static $cache_dir, $suite_cache_dir;
private static $cache_dir, $suite_cache_dir, $fs;

private static $db_settings = array(
'dbname' => 'wp_cli_test',
Expand Down Expand Up @@ -76,6 +79,9 @@ private static function get_process_env_variables() {
if ( $term = getenv( 'TERM' ) ) {
$env['TERM'] = $term;
}
if ( $php_args = getenv( 'WP_CLI_PHP_ARGS' ) ) {
$env['WP_CLI_PHP_ARGS'] = $php_args;
}
return $env;
}

Expand Down Expand Up @@ -113,7 +119,7 @@ public static function prepare( SuiteEvent $event ) {
*/
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();
self::$fs->remove( self::$suite_cache_dir );
}
}

Expand All @@ -131,13 +137,13 @@ public function afterScenario( $event ) {
if ( isset( $this->variables['RUN_DIR'] ) ) {
// remove altered WP install, unless there's an error
if ( $event->getResult() < 4 && 0 === strpos( $this->variables['RUN_DIR'], sys_get_temp_dir() ) ) {
$this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['RUN_DIR'] ) )->run();
self::$fs->remove( $this->variables['RUN_DIR'] );
}
}

// Remove WP-CLI package directory
if ( isset( $this->variables['PACKAGE_PATH'] ) ) {
$this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['PACKAGE_PATH'] ) )->run();
self::$fs->remove( $this->variables['PACKAGE_PATH'] );
}

foreach ( $this->running_procs as $proc ) {
Expand Down Expand Up @@ -192,6 +198,9 @@ 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 );
if ( ! self::$fs ) {
self::$fs = new FileSystem;
}
}

public function getStepDefinitionResources() {
Expand Down Expand Up @@ -307,7 +316,9 @@ public function download_phar( $version = 'same' ) {

private function set_cache_dir() {
$path = sys_get_temp_dir() . '/wp-cli-test-cache';
$this->proc( Utils\esc_cmd( 'mkdir -p %s', $path ) )->run_check();
if ( ! file_exists( $path ) ) {
mkdir( $path, 0777, true );
}
$this->variables['CACHE_DIR'] = $path;
}

Expand Down Expand Up @@ -375,6 +386,13 @@ public function move_files( $src, $dest ) {
rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" );
}

/**
* Remove a directory (recursive).
*/
public function remove_dir( $dir ) {
self::$fs->remove( $dir );
}

public function add_line_to_wp_config( &$wp_config_code, $line ) {
$token = "/* That's all, stop editing!";

Expand All @@ -388,7 +406,7 @@ public function download_wp( $subdir = '' ) {
mkdir( $dest_dir );
}

$this->proc( Utils\esc_cmd( "cp -r %s/* %s", self::$cache_dir, $dest_dir ) )->run_check();
self::$fs->mirror( self::$cache_dir, $dest_dir );

// disable emailing
mkdir( $dest_dir . '/wp-content/mu-plugins' );
Expand Down
5 changes: 5 additions & 0 deletions features/cli-info.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
Feature: Review CLI information

Background:
When I run `wp package path`
Then save STDOUT as {PACKAGE_PATH}

Scenario: Get the path to the packages directory
Given an empty directory
And an empty {PACKAGE_PATH} directory

When I run `wp cli info --format=json`
Then STDOUT should be JSON containing:
Expand Down
2 changes: 1 addition & 1 deletion features/cli.feature
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Feature: `wp cli` tasks
y
"""

When I run `{PHAR_PATH} cli check-update --minor --field=version`
When I run `{PHAR_PATH} cli check-update --field=version | head -1`
Then STDOUT should not be empty
And save STDOUT as {UPDATE_VERSION}

Expand Down
11 changes: 10 additions & 1 deletion features/steps/given.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ function ( $world ) {
}
);

$steps->Given( '/^an empty ([^\s]+) directory$/',
function ( $world, $dir ) {
$world->remove_dir( $world->replace_variables( $dir ) );
}
);

$steps->Given( '/^an empty cache/',
function ( $world ) {
$world->variables['SUITE_CACHE_DIR'] = FeatureContext::create_cache_dir();
Expand All @@ -20,7 +26,10 @@ function ( $world ) {
function ( $world, $path, PyStringNode $content ) {
$content = (string) $content . "\n";
$full_path = $world->variables['RUN_DIR'] . "/$path";
Process::create( \WP_CLI\utils\esc_cmd( 'mkdir -p %s', dirname( $full_path ) ) )->run_check();
$dir = dirname( $full_path );
if ( ! file_exists( $dir ) ) {
mkdir( $dir, 0777, true );
}
file_put_contents( $full_path, $content );
}
);
Expand Down
86 changes: 83 additions & 3 deletions php/WP_CLI/Dispatcher/CommandFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
*/
class CommandFactory {

// Cache of file contents, indexed by filename. Only used if opcache.save_comments is disabled.
private static $file_contents = array();

/**
* Create a new CompositeCommand (or Subcommand if class has __invoke())
*
Expand Down Expand Up @@ -40,6 +43,13 @@ public static function create( $name, $callable, $parent ) {
return $command;
}

/**
* Clear the file contents cache.
*/
public static function clear_file_contents_cache() {
self::$file_contents = array();
}

/**
* Create a new Subcommand instance.
*
Expand All @@ -51,7 +61,8 @@ public static function create( $name, $callable, $parent ) {
* @param string $method Class method to be called upon invocation.
*/
private static function create_subcommand( $parent, $name, $callable, $reflection ) {
$docparser = new \WP_CLI\DocParser( $reflection->getDocComment() );
$doc_comment = self::get_doc_comment( $reflection );
$docparser = new \WP_CLI\DocParser( $doc_comment );

if ( is_array( $callable ) ) {
if ( !$name )
Expand All @@ -60,6 +71,9 @@ private static function create_subcommand( $parent, $name, $callable, $reflectio
if ( !$name )
$name = $reflection->name;
}
if ( ! $doc_comment ) {
\WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' );
}

$when_invoked = function ( $args, $assoc_args ) use ( $callable ) {
if ( is_array( $callable ) ) {
Expand All @@ -82,7 +96,11 @@ private static function create_subcommand( $parent, $name, $callable, $reflectio
*/
private static function create_composite_command( $parent, $name, $callable ) {
$reflection = new \ReflectionClass( $callable );
$docparser = new \WP_CLI\DocParser( $reflection->getDocComment() );
$doc_comment = self::get_doc_comment( $reflection );
if ( ! $doc_comment ) {
\WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' );
}
$docparser = new \WP_CLI\DocParser( $doc_comment );

$container = new CompositeCommand( $parent, $name, $docparser );

Expand Down Expand Up @@ -110,5 +128,67 @@ private static function create_composite_command( $parent, $name, $callable ) {
private static function is_good_method( $method ) {
return $method->isPublic() && !$method->isStatic() && 0 !== strpos( $method->getName(), '__' );
}
}

/**
* Gets the document comment. Caters for PHP directive `opcache.save comments` being disabled.
*
* @param ReflectionMethod|ReflectionClass|ReflectionFunction $reflection Reflection instance.
* @return string|false|null Doc comment string if any, false if none (same as `Reflection*::getDocComment()`), null if error.
*/
private static function get_doc_comment( $reflection ) {
$doc_comment = $reflection->getDocComment();

if ( false !== $doc_comment || ! ( ini_get( 'opcache.enable_cli' ) && ! ini_get( 'opcache.save_comments' ) ) ) {
// Either have doc comment, or no doc comment and save comments enabled - standard situation.
if ( ! getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ) ) {
return $doc_comment;
}
}

$filename = $reflection->getFileName();

if ( isset( self::$file_contents[ $filename ] ) ) {
$contents = self::$file_contents[ $filename ];
} elseif ( is_readable( $filename ) && ( $contents = file_get_contents( $filename ) ) ) {
self::$file_contents[ $filename ] = $contents = explode( "\n", $contents );
} else {
\WP_CLI::debug( "Could not read contents for filename '{$filename}'.", 'commandfactory' );
return null;
}

return self::extract_last_doc_comment( implode( "\n", array_slice( $contents, 0, $reflection->getStartLine() ) ) );
}

/**
* Returns the last doc comment if any in `$content`.
*
* @param string $content The content, which should end at the class or function declaration.
* @return string|bool The last doc comment if any, or false if none.
*/
private static function extract_last_doc_comment( $content ) {
$content = trim( $content );
$comment_end_pos = strrpos( $content, '*/' );
if ( false === $comment_end_pos ) {
return false;
}
// Make sure comment end belongs to this class/function.
if ( preg_match_all( '/(?:^|[\s;}])(?:class|function)\s+/', substr( $content, $comment_end_pos + 2 ), $dummy /*needed for PHP 5.3*/ ) > 1 ) {
return false;
}
$content = substr( $content, 0, $comment_end_pos + 2 );
if ( false === ( $comment_start_pos = strrpos( $content, '/**' ) ) || $comment_start_pos + 2 === $comment_end_pos ) {
return false;
}
// Make sure comment start belongs to this comment end.
if ( false !== ( $comment_end2_pos = strpos( substr( $content, $comment_start_pos ), '*/' ) ) && $comment_start_pos + $comment_end2_pos < $comment_end_pos ) {
return false;
}
// Allow for '/**' within doc comment.
$subcontent = substr( $content, 0, $comment_start_pos );
while ( false !== ( $comment_start2_pos = strrpos( $subcontent, '/**' ) ) && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) {
$comment_start_pos = $comment_start2_pos;
$subcontent = substr( $subcontent, 0, $comment_start_pos );
}
return substr( $content, $comment_start_pos, $comment_end_pos + 2 );
}
}
2 changes: 1 addition & 1 deletion php/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ function http_request( $method, $url, $data = null, $headers = array(), $options
}
}
if ( empty( $options['verify'] ) ){
WP_CLI::error_log( "Cannot find SSL certificate." );
WP_CLI::error( "Cannot find SSL certificate." );
}
}

Expand Down
70 changes: 70 additions & 0 deletions tests/data/commandfactory-doc_comment-class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/**
* Basic class
*
* ## EXAMPLES
*
* # Foo.
* $ wp foo
*/
class CommandFactoryTests_Get_Doc_Comment_1_Command extends WP_CLI_Command {
/**
* Command1 method
*
* ## OPTIONS
*
* ## EXAMPLES
*
* $ wp foo command1 public
*/
function command1() {
}

/**
* Command2 function
*
* ## OPTIONS
*
* [--path=<path>]
*
* ## EXAMPLES
*
* $ wp foo command2 --path=/**a/**b/**c/**
*/

final
protected
static
function
command2() {
}

/**
* Command3 function
*
* ## OPTIONS
*
* [--path=<path>]
*
* ## EXAMPLES
*
* $ wp foo command3 --path=/**a/**b/**c/**
function*/public function command3( $function ) {}

function command4() {}
}

/**
* Basic class
*
* ## EXAMPLES
*
* # Foo.
* $ wp foo --final abstract
class*/abstract class
CommandFactoryTests_Get_Doc_Comment_2_Command
extends WP_CLI_Command
{
function command1() {}
}
19 changes: 19 additions & 0 deletions tests/data/commandfactory-doc_comment-function.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/**
* foo
*/
function commandfactorytests_get_doc_comment_func_1( $function = blah ) {
}

/**
* bar
function*/function commandfactorytests_get_doc_comment_func_2( $function = blah ) {
}

/**
* /** baz
*/$commandfactorytests_get_doc_comment_func_3
=
function ( $args ) {
};
Loading