diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..6c06a141b --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,11 @@ + diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 000000000..12b980072 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,14 @@ + diff --git a/.gitignore b/.gitignore index f3b908269..54f24c800 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ node_modules/ vendor/ *.zip *.tar.gz -composer.lock diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..fa86aa52d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,8 @@ +Contributing +============ + +We appreciate you taking the initiative to contribute to this project. + +Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. + +For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. diff --git a/README.md b/README.md index e66e8fe7a..dfe9f2637 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Manage WP-CLI packages. [![Build Status](https://travis-ci.org/wp-cli/package-command.svg?branch=master)](https://travis-ci.org/wp-cli/package-command) -Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) +Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) | [Support](#support) ## Using @@ -243,30 +243,25 @@ We appreciate you taking the initiative to contribute to this project. Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. +For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. + ### Reporting a bug Think you’ve found a bug? We’d love for you to help us get it fixed. Before you create a new issue, you should [search existing issues](https://github.com/wp-cli/package-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. -Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/package-command/issues/new) with the following: - -1. What you were doing (e.g. "When I run `wp post list`"). -2. What you saw (e.g. "I see a fatal about a class being undefined."). -3. What you expected to see (e.g. "I expected to see the list of posts.") - -Include as much detail as you can, and clear steps to reproduce if possible. +Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/package-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). ### Creating a pull request Want to contribute a new feature? Please first [open a new issue](https://github.com/wp-cli/package-command/issues/new) to discuss whether the feature is a good fit for the project. -Once you've decided to commit the time to seeing your pull request through, please follow our guidelines for creating a pull request to make sure it's a pleasant experience: +Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. + +## Support -1. Create a feature branch for each contribution. -2. Submit your pull request early for feedback. -3. Include functional tests with your changes. [Read the WP-CLI documentation](https://wp-cli.org/docs/pull-requests/#functional-tests) for an introduction. -4. Follow the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/). +Github issues aren't for general support questions, but there are other venues you can try: http://wp-cli.org/#support *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* diff --git a/bin/test.sh b/bin/test.sh index d500f9307..bd3ae6e91 100755 --- a/bin/test.sh +++ b/bin/test.sh @@ -2,6 +2,12 @@ set -ex +# Run the unit tests, if they exist +if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ] +then + phpunit +fi + # Run the functional tests BEHAT_TAGS=$(php utils/behat-tags.php) behat --format progress $BEHAT_TAGS --strict diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 96e1647df..33620fa61 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -44,8 +44,31 @@ */ class FeatureContext extends BehatContext implements ClosuredContextInterface { - private static $cache_dir, $suite_cache_dir; + /** + * The current working directory for scenarios that have a "Given a WP install" or "Given an empty directory" step. Variable RUN_DIR. Lives until the end of the scenario. + */ + private static $run_dir; + + /** + * Where WordPress core is downloaded to for caching, and which is copied to RUN_DIR during a "Given a WP install" step. Lives until manually deleted. + */ + private static $cache_dir; + + /** + * The directory that the WP-CLI cache (WP_CLI_CACHE_DIR, normally "$HOME/.wp-cli/cache") is set to on a "Given an empty cache" step. + * Variable SUITE_CACHE_DIR. Lives until the end of the scenario (or until another "Given an empty cache" step within the scenario). + */ + private static $suite_cache_dir; + + /** + * Where the current WP-CLI source repository is copied to for Composer-based tests with a "Given a dependency on current wp-cli" step. + * Variable COMPOSER_LOCAL_REPOSITORY. Lives until the end of the suite. + */ + private static $composer_local_repository; + /** + * The test database settings. All but `dbname` can be set via environment variables. The database is dropped at the start of each scenario and created on a "Given a WP install" step. + */ private static $db_settings = array( 'dbname' => 'wp_cli_test', 'dbuser' => 'wp_cli_test', @@ -53,13 +76,24 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { 'dbhost' => '127.0.0.1', ); + /** + * Array of background process ids started by the current scenario. Used to terminate them at the end of the scenario. + */ private $running_procs = array(); + /** + * Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest. Some are step-dependent: + * RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario. + */ public $variables = array(); + /** + * The current feature file and scenario line number as '.'. Used in RUN_DIR and SUITE_CACHE_DIR directory names. Set at the start of each scenario. + */ + private static $temp_dir_infix; + /** * 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 @@ -76,13 +110,21 @@ 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; + } + if ( $travis_build_dir = getenv( 'TRAVIS_BUILD_DIR' ) ) { + $env['TRAVIS_BUILD_DIR'] = $travis_build_dir; + } return $env; } - // We cache the results of `wp core download` to improve test performance - // Ideally, we'd cache at the HTTP layer for more reliable tests + /** + * We cache the results of `wp core download` to improve test performance. + * Ideally, we'd cache at the HTTP layer for more reliable tests. + */ private static function cache_wp_files() { - self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test core-download-cache'; + self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache'; if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) return; @@ -112,8 +154,9 @@ public static function prepare( SuiteEvent $event ) { * @AfterSuite */ public static function afterSuite( SuiteEvent $event ) { - if ( self::$suite_cache_dir ) { - Process::create( Utils\esc_cmd( 'rm -r %s', self::$suite_cache_dir ), null, self::get_process_env_variables() )->run(); + if ( self::$composer_local_repository ) { + self::remove_dir( self::$composer_local_repository ); + self::$composer_local_repository = null; } } @@ -122,24 +165,39 @@ public static function afterSuite( SuiteEvent $event ) { */ public function beforeScenario( $event ) { $this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' ); + + // Used in the names of the RUN_DIR and SUITE_CACHE_DIR directories. + self::$temp_dir_infix = null; + if ( $file = self::get_event_file( $event, $line ) ) { + self::$temp_dir_infix = basename( $file ) . '.' . $line; + } } /** * @AfterScenario */ public function afterScenario( $event ) { - if ( isset( $this->variables['RUN_DIR'] ) ) { + + if ( self::$run_dir ) { // remove altered WP install, unless there's an error - if ( $event->getResult() < 4 && 0 === strpos( $this->variables['RUN_DIR'], sys_get_temp_dir() ) ) { - $this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['RUN_DIR'] ) )->run(); + if ( $event->getResult() < 4 ) { + self::remove_dir( self::$run_dir ); } + self::$run_dir = null; } - // Remove WP-CLI package directory + // Remove WP-CLI package directory if any. Set to `wp package path` by package-command and scaffold-package-command features, and by cli-info.feature. if ( isset( $this->variables['PACKAGE_PATH'] ) ) { - $this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['PACKAGE_PATH'] ) )->run(); + self::remove_dir( $this->variables['PACKAGE_PATH'] ); } + // Remove SUITE_CACHE_DIR if any. + if ( self::$suite_cache_dir ) { + self::remove_dir( self::$suite_cache_dir ); + self::$suite_cache_dir = null; + } + + // Remove any background processes. foreach ( $this->running_procs as $proc ) { $status = proc_get_status( $proc ); self::terminate_proc( $status['pid'] ); @@ -173,22 +231,37 @@ private static function terminate_proc( $master_pid ) { } } + /** + * Create a temporary WP_CLI_CACHE_DIR. Exposed as SUITE_CACHE_DIR in "Given an empty cache" step. + */ public static function create_cache_dir() { - self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-suite-cache-", TRUE ); + if ( self::$suite_cache_dir ) { + self::remove_dir( self::$suite_cache_dir ); + } + self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-suite-cache-' . self::$temp_dir_infix . '-', TRUE ); mkdir( self::$suite_cache_dir ); return self::$suite_cache_dir; } /** * Initializes context. - * Every scenario gets it's own context object. + * Every scenario gets its own context object. * * @param array $parameters context parameters (set them up through behat.yml) */ public function __construct( array $parameters ) { + if ( getenv( 'WP_CLI_TEST_DBUSER' ) ) { + self::$db_settings['dbuser'] = getenv( 'WP_CLI_TEST_DBUSER' ); + } + + if ( false !== getenv( 'WP_CLI_TEST_DBPASS' ) ) { + self::$db_settings['dbpass'] = getenv( 'WP_CLI_TEST_DBPASS' ); + } + if ( getenv( 'WP_CLI_TEST_DBHOST' ) ) { self::$db_settings['dbhost'] = getenv( 'WP_CLI_TEST_DBHOST' ); } + $this->drop_db(); $this->set_cache_dir(); $this->variables['CORE_CONFIG_SETTINGS'] = Utils\assoc_args_to_str( self::$db_settings ); @@ -202,6 +275,9 @@ public function getHookDefinitionResources() { return array(); } + /** + * Replace {VARIABLE_NAME}. Note that variable names can only contain uppercase letters and underscores (no numbers). + */ public function replace_variables( $str ) { $ret = preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str ); if ( false !== strpos( $str, '{WP_VERSION-' ) ) { @@ -210,6 +286,9 @@ public function replace_variables( $str ) { return $ret; } + /** + * Replace variables callback. + */ private function _replace_var( $matches ) { $cmd = $matches[0]; @@ -220,7 +299,9 @@ private function _replace_var( $matches ) { return $cmd; } - // Substitute "{WP_VERSION-version-latest}" variables. + /** + * Substitute "{WP_VERSION-version-latest}" variables. + */ private function _replace_wp_versions( $str ) { static $wp_versions = null; if ( null === $wp_versions ) { @@ -249,9 +330,27 @@ private function _replace_wp_versions( $str ) { return strtr( $str, $wp_versions ); } + /** + * Get the file and line number for the current behat event. + */ + private static function get_event_file( $event, &$line ) { + if ( method_exists( $event, 'getScenario' ) ) { + $scenario_feature = $event->getScenario(); + } elseif ( method_exists( $event, 'getFeature' ) ) { + $scenario_feature = $event->getFeature(); + } else { + return null; + } + $line = $scenario_feature->getLine(); + return $scenario_feature->getFile(); + } + + /** + * Create the RUN_DIR directory, unless already set for this scenario. + */ public function create_run_dir() { if ( !isset( $this->variables['RUN_DIR'] ) ) { - $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-run-", TRUE ); + self::$run_dir = $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', TRUE ); mkdir( $this->variables['RUN_DIR'] ); } } @@ -293,21 +392,21 @@ public function download_phar( $version = 'same' ) { . uniqid( 'wp-cli-download-', true ) . '.phar'; - Process::create( \WP_CLI\Utils\esc_cmd( - 'curl -sSL %s > %s', + Process::create( Utils\esc_cmd( + 'curl -sSfL %1$s > %2$s && chmod +x %2$s', $download_url, $this->variables['PHAR_PATH'] ) )->run_check(); - - Process::create( \WP_CLI\Utils\esc_cmd( - 'chmod +x %s', - $this->variables['PHAR_PATH'] - ) )->run_check(); } + /** + * CACHE_DIR is a cache for downloaded test data such as images. Lives until manually deleted. + */ private function set_cache_dir() { $path = sys_get_temp_dir() . '/wp-cli-test-cache'; - $this->proc( Utils\esc_cmd( 'mkdir -p %s', $path ) )->run_check(); + if ( ! file_exists( $path ) ) { + mkdir( $path ); + } $this->variables['CACHE_DIR'] = $path; } @@ -375,6 +474,20 @@ public function move_files( $src, $dest ) { rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" ); } + /** + * Remove a directory (recursive). + */ + public static function remove_dir( $dir ) { + Process::create( Utils\esc_cmd( 'rm -rf %s', $dir ) )->run_check(); + } + + /** + * Copy a directory (recursive). Destination directory must exist. + */ + public static function copy_dir( $src_dir, $dest_dir ) { + Process::create( Utils\esc_cmd( "cp -r %s/* %s", $src_dir, $dest_dir ) )->run_check(); + } + public function add_line_to_wp_config( &$wp_config_code, $line ) { $token = "/* That's all, stop editing!"; @@ -388,19 +501,25 @@ 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::copy_dir( self::$cache_dir, $dest_dir ); // disable emailing mkdir( $dest_dir . '/wp-content/mu-plugins' ); copy( __DIR__ . '/../extra/no-mail.php', $dest_dir . '/wp-content/mu-plugins/no-mail.php' ); } - public function create_config( $subdir = '' ) { + public function create_config( $subdir = '', $extra_php = false ) { $params = self::$db_settings; + // Replaces all characters that are not alphanumeric or an underscore into an underscore. $params['dbprefix'] = $subdir ? preg_replace( '#[^a-zA-Z\_0-9]#', '_', $subdir ) : 'wp_'; $params['skip-salts'] = true; + + if( false !== $extra_php ) { + $params['extra-php'] = $extra_php; + } + $this->proc( 'wp core config', $params, $subdir )->run_check(); } @@ -422,5 +541,77 @@ public function install_wp( $subdir = '' ) { $this->proc( 'wp core install', $install_args, $subdir )->run_check(); } -} + public function install_wp_with_composer() { + $this->create_run_dir(); + $this->create_db(); + + $yml_path = $this->variables['RUN_DIR'] . "/wp-cli.yml"; + file_put_contents( $yml_path, 'path: wordpress' ); + + $this->proc( 'composer init --name="wp-cli/composer-test" --type="project" --no-interaction' )->run_check(); + $this->proc( 'composer require johnpbloch/wordpress --optimize-autoloader --no-interaction' )->run_check(); + + $config_extra_php = "require_once dirname(__DIR__) . '/vendor/autoload.php';"; + $this->create_config( 'wordpress', $config_extra_php ); + + $install_args = array( + 'url' => 'http://localhost:8080', + 'title' => 'WP CLI Site with both WordPress and wp-cli as Composer dependencies', + 'admin_user' => 'admin', + 'admin_email' => 'admin@example.com', + 'admin_password' => 'password1' + ); + + $this->proc( 'wp core install', $install_args )->run_check(); + } + + public function composer_add_wp_cli_local_repository() { + if ( ! self::$composer_local_repository ) { + self::$composer_local_repository = sys_get_temp_dir() . '/' . uniqid( "wp-cli-composer-local-", TRUE ); + mkdir( self::$composer_local_repository ); + + $env = self::get_process_env_variables(); + $src = isset( $env['TRAVIS_BUILD_DIR'] ) ? $env['TRAVIS_BUILD_DIR'] : realpath( __DIR__ . '/../../' ); + + $dest = self::$composer_local_repository . '/'; + + self::copy_dir( $src, $dest ); + self::remove_dir( $dest . '.git' ); + self::remove_dir( $dest . 'vendor' ); + + $this->proc( "composer config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false}}'" )->run_check(); + } + $this->variables['COMPOSER_LOCAL_REPOSITORY'] = self::$composer_local_repository; + } + + public function composer_require_current_wp_cli() { + $this->composer_add_wp_cli_local_repository(); + $this->proc( 'composer require wp-cli/wp-cli:dev-master --optimize-autoloader --no-interaction' )->run_check(); + } + + public function get_php_binary() { + if ( getenv( 'WP_CLI_PHP_USED' ) ) + return getenv( 'WP_CLI_PHP_USED' ); + + if ( getenv( 'WP_CLI_PHP' ) ) + return getenv( 'WP_CLI_PHP' ); + + if ( defined( 'PHP_BINARY' ) ) + return PHP_BINARY; + + return 'php'; + } + + public function start_php_server() { + $cmd = Utils\esc_cmd( '%s -S %s -t %s -c %s %s', + $this->get_php_binary(), + 'localhost:8080', + $this->variables['RUN_DIR'] . '/wordpress/', + get_cfg_var( 'cfg_file_path' ), + $this->variables['RUN_DIR'] . '/vendor/wp-cli/server-command/router.php' + ); + $this->background_proc( $cmd ); + } + +} diff --git a/features/bootstrap/Process.php b/features/bootstrap/Process.php index 74939c5d0..858e1947e 100644 --- a/features/bootstrap/Process.php +++ b/features/bootstrap/Process.php @@ -6,11 +6,27 @@ * Run a system process, and learn what happened. */ class Process { + /** + * @var string The full command to execute by the system. + */ + private $command; + + /** + * @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory). + */ + private $cwd; + + /** + * @var array Environment variables to set when running the command. + */ + private $env; /** * @param string $command Command to execute. * @param string $cwd Directory to execute the command in. * @param array $env Environment variables to set when running the command. + * + * @return Process */ public static function create( $command, $cwd = null, $env = array() ) { $proc = new self; @@ -22,8 +38,6 @@ public static function create( $command, $cwd = null, $env = array() ) { return $proc; } - private $command, $cwd, $env; - private function __construct() {} /** @@ -54,7 +68,7 @@ public function run() { 'return_code' => proc_close( $proc ), 'command' => $this->command, 'cwd' => $cwd, - 'env' => $this->env + 'env' => $this->env, ) ); } @@ -66,6 +80,7 @@ public function run() { public function run_check() { $r = $this->run(); + // $r->STDERR is incorrect, but kept incorrect for backwards-compat if ( $r->return_code || !empty( $r->STDERR ) ) { throw new \RuntimeException( $r ); } diff --git a/features/bootstrap/ProcessRun.php b/features/bootstrap/ProcessRun.php index 4611cfbb6..aedc5f65d 100644 --- a/features/bootstrap/ProcessRun.php +++ b/features/bootstrap/ProcessRun.php @@ -6,6 +6,35 @@ * Results of an executed command. */ class ProcessRun { + /** + * @var string The full command executed by the system. + */ + public $command; + + /** + * @var string Captured output from the process' STDOUT. + */ + public $stdout; + + /** + * @var string Captured output from the process' STDERR. + */ + public $stderr; + + /** + * @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory). + */ + public $cwd; + + /** + * @var array Environment variables set for this process. + */ + public $env; + + /** + * @var int Exit code of the process. + */ + public $return_code; /** * @var array $props Properties of executed command. diff --git a/features/bootstrap/utils.php b/features/bootstrap/utils.php index dd187cd6d..46796053e 100644 --- a/features/bootstrap/utils.php +++ b/features/bootstrap/utils.php @@ -341,6 +341,8 @@ function pick_fields( $item, $fields ) { */ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { + check_proc_available( 'launch_editor_for_input' ); + $tmpdir = get_temp_dir(); do { @@ -415,6 +417,8 @@ function mysql_host_to_cli_args( $raw_host ) { } function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { + check_proc_available( 'run_mysql_command' ); + if ( !$descriptors ) $descriptors = array( STDIN, STDOUT, STDERR ); @@ -453,7 +457,7 @@ function mustache_render( $template_name, $data = array() ) { $template = file_get_contents( $template_name ); $m = new \Mustache_Engine( array( - 'escape' => function ( $val ) { return $val; } + 'escape' => function ( $val ) { return $val; }, ) ); return $m->render( $template, $data ); @@ -522,7 +526,7 @@ function is_windows() { function replace_path_consts( $source, $path ) { $replacements = array( '__FILE__' => "'$path'", - '__DIR__' => "'" . dirname( $path ) . "'" + '__DIR__' => "'" . dirname( $path ) . "'", ); $old = array_keys( $replacements ); @@ -568,7 +572,7 @@ function http_request( $method, $url, $data = null, $headers = array(), $options } } if ( empty( $options['verify'] ) ){ - WP_CLI::error_log( "Cannot find SSL certificate." ); + WP_CLI::error( "Cannot find SSL certificate." ); } } @@ -606,20 +610,20 @@ function increment_version( $current_version, $new_version ) { switch ( $new_version ) { case 'same': // do nothing - break; + break; case 'patch': $current_version[0][2]++; $current_version = array( $current_version[0] ); // drop possible pre-release info - break; + break; case 'minor': $current_version[0][1]++; $current_version[0][2] = 0; $current_version = array( $current_version[0] ); // drop possible pre-release info - break; + break; case 'major': $current_version[0][0]++; @@ -627,11 +631,11 @@ function increment_version( $current_version, $new_version ) { $current_version[0][2] = 0; $current_version = array( $current_version[0] ); // drop possible pre-release info - break; + break; default: // not a keyword $current_version = array( array( $new_version ) ); - break; + break; } // reconstruct version string @@ -694,6 +698,37 @@ function get_flag_value( $assoc_args, $flag, $default = null ) { return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; } +/** + * Get the home directory. + * + * @access public + * @category System + * + * @return string + */ +function get_home_dir() { + $home = getenv( 'HOME' ); + if ( ! $home ) { + // In Windows $HOME may not be defined + $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); + } + + return rtrim( $home, '/\\' ); +} + +/** + * Appends a trailing slash. + * + * @access public + * @category System + * + * @param string $string What to add the trailing slash to. + * @return string String with trailing slash added. + */ +function trailingslashit( $string ) { + return rtrim( $string, '/\\' ) . '/'; +} + /** * Get the system's temp directory. Warns user if it isn't writable. * @@ -705,17 +740,15 @@ function get_flag_value( $assoc_args, $flag, $default = null ) { function get_temp_dir() { static $temp = ''; - $trailingslashit = function( $path ) { - return rtrim( $path ) . '/'; - }; - - if ( $temp ) - return $trailingslashit( $temp ); + if ( $temp ) { + return $temp; + } - if ( function_exists( 'sys_get_temp_dir' ) ) { - $temp = sys_get_temp_dir(); - } else if ( ini_get( 'upload_tmp_dir' ) ) { - $temp = ini_get( 'upload_tmp_dir' ); + // `sys_get_temp_dir()` introduced PHP 5.2.1. + if ( $try = sys_get_temp_dir() ) { + $temp = trailingslashit( $try ); + } elseif ( $try = ini_get( 'upload_tmp_dir' ) ) { + $temp = trailingslashit( $try ); } else { $temp = '/tmp/'; } @@ -724,7 +757,7 @@ function get_temp_dir() { \WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); } - return $trailingslashit( $temp ); + return $temp; } /** @@ -741,18 +774,24 @@ function get_temp_dir() { * @return mixed */ function parse_ssh_url( $url, $component = -1 ) { - preg_match( '#^([^:/~]+)(:([\d]+))?((/|~)(.+))?$#', $url, $matches ); + preg_match( '#^((docker|docker\-compose|ssh):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); $bits = array(); foreach( array( - 1 => 'host', - 3 => 'port', - 4 => 'path', + 2 => 'scheme', + 4 => 'user', + 5 => 'host', + 7 => 'port', + 8 => 'path', ) as $i => $key ) { if ( ! empty( $matches[ $i ] ) ) { $bits[ $key ] = $matches[ $i ]; } } switch ( $component ) { + case PHP_URL_SCHEME: + return isset( $bits['scheme'] ) ? $bits['scheme'] : null; + case PHP_URL_USER: + return isset( $bits['user'] ) ? $bits['user'] : null; case PHP_URL_HOST: return isset( $bits['host'] ) ? $bits['host'] : null; case PHP_URL_PATH: @@ -1005,3 +1044,26 @@ function force_env_on_nix_systems( $command ) { } return $command; } + +/** + * Check that `proc_open()` and `proc_close()` haven't been disabled. + * + * @param string $context Optional. If set will appear in error message. Default null. + * @param bool $return Optional. If set will return false rather than error out. Default false. + * + * @return bool + */ +function check_proc_available( $context = null, $return = false ) { + if ( ! function_exists( 'proc_open' ) || ! function_exists( 'proc_close' ) ) { + if ( $return ) { + return false; + } + $msg = 'The PHP functions `proc_open()` and/or `proc_close()` are disabled. Please check your PHP ini directive `disable_functions` or suhosin settings.'; + if ( $context ) { + WP_CLI::error( sprintf( "Cannot do '%s': %s", $context, $msg ) ); + } else { + WP_CLI::error( $msg ); + } + } + return true; +} diff --git a/features/steps/given.php b/features/steps/given.php index c785df113..e7ae7bf6b 100644 --- a/features/steps/given.php +++ b/features/steps/given.php @@ -10,6 +10,22 @@ function ( $world ) { } ); +$steps->Given( '/^an? (empty|non-existent) ([^\s]+) directory$/', + function ( $world, $empty_or_nonexistent, $dir ) { + $dir = $world->replace_variables( $dir ); + if ( ! WP_CLI\Utils\is_path_absolute( $dir ) ) { + $dir = $world->variables['RUN_DIR'] . "/$dir"; + } + if ( 0 !== strpos( $dir, sys_get_temp_dir() ) ) { + throw new RuntimeException( sprintf( "Attempted to delete directory '%s' that is not in the temp directory '%s'. " . __FILE__ . ':' . __LINE__, $dir, sys_get_temp_dir() ) ); + } + $world->remove_dir( $dir ); + if ( 'empty' === $empty_or_nonexistent ) { + mkdir( $dir, 0777, true /*recursive*/ ); + } + } +); + $steps->Given( '/^an empty cache/', function ( $world ) { $world->variables['SUITE_CACHE_DIR'] = FeatureContext::create_cache_dir(); @@ -20,7 +36,10 @@ 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 /*recursive*/ ); + } file_put_contents( $full_path, $content ); } ); @@ -62,6 +81,12 @@ function ( $world, $subdir ) { } ); +$steps->Given( '/^a WP install with Composer$/', + function ( $world ) { + $world->install_wp_with_composer(); + } +); + $steps->Given( '/^a WP multisite (subdirectory|subdomain)?\s?install$/', function ( $world, $type = 'subdirectory' ) { $world->install_wp(); @@ -168,3 +193,15 @@ function($world) { file_put_contents( $wp_config_path, $wp_config_code ); } ); + +$steps->Given( '/^a dependency on current wp-cli$/', + function ( $world ) { + $world->composer_require_current_wp_cli(); + } +); + +$steps->Given( '/^a PHP built-in web server$/', + function ( $world ) { + $world->start_php_server(); + } +); diff --git a/features/steps/then.php b/features/steps/then.php index 22a7eb8e0..887555485 100644 --- a/features/steps/then.php +++ b/features/steps/then.php @@ -209,3 +209,10 @@ function ( $world, $path, $type, $action, $expected = null ) { throw new Exception( 'Invalid expectation' ); } }); + +$steps->Then( '/^the HTTP status code should be (\d+)$/', + function ( $world, $return_code ) { + $response = \Requests::request( 'http://localhost:8080' ); + assertEquals( $return_code, $response->status_code ); + } +);