From ce46abae5ebbefb5b2e6fd943444e519c7d08d3c Mon Sep 17 00:00:00 2001 From: gitlost Date: Thu, 16 Nov 2017 18:10:09 +0000 Subject: [PATCH] Update scaffolded tests to latest (enable error reporting). --- .travis.yml | 2 + bin/install-package-tests.sh | 1 + features/bootstrap/FeatureContext.php | 30 ++--- features/bootstrap/Process.php | 16 +++ features/bootstrap/support.php | 6 + features/bootstrap/utils.php | 182 ++++++++++++++++++++------ features/steps/then.php | 27 +++- features/steps/when.php | 2 +- utils/behat-tags.php | 6 +- 9 files changed, 207 insertions(+), 65 deletions(-) diff --git a/.travis.yml b/.travis.yml index 923370a29..b65648735 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,8 @@ env: matrix: include: + - php: 7.2 + env: WP_VERSION=nightly - php: 7.1 env: WP_VERSION=latest - php: 7.0 diff --git a/bin/install-package-tests.sh b/bin/install-package-tests.sh index 2ff49dd8d..d672d9a50 100755 --- a/bin/install-package-tests.sh +++ b/bin/install-package-tests.sh @@ -5,6 +5,7 @@ set -ex 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 + mysql -e 'GRANT ALL PRIVILEGES ON wp_cli_test_scaffold.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "password1"' -uroot } install_db diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index aaf39713b..453c604ee 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -131,6 +131,12 @@ private static function get_process_env_variables() { if ( $php_args = getenv( 'WP_CLI_PHP_ARGS' ) ) { $env['WP_CLI_PHP_ARGS'] = $php_args; } + if ( $php_used = getenv( 'WP_CLI_PHP_USED' ) ) { + $env['WP_CLI_PHP_USED'] = $php_used; + } + if ( $php = getenv( 'WP_CLI_PHP' ) ) { + $env['WP_CLI_PHP'] = $php; + } if ( $travis_build_dir = getenv( 'TRAVIS_BUILD_DIR' ) ) { $env['TRAVIS_BUILD_DIR'] = $travis_build_dir; } @@ -526,7 +532,8 @@ public function background_proc( $cmd ) { $status = proc_get_status( $proc ); if ( !$status['running'] ) { - throw new RuntimeException( stream_get_contents( $pipes[2] ) ); + $stderr = is_resource( $pipes[2] ) ? ( ': ' . stream_get_contents( $pipes[2] ) ) : ''; + throw new RuntimeException( sprintf( "Failed to start background process '%s'%s.", $cmd, $stderr ) ); } else { $this->running_procs[] = $proc; } @@ -617,7 +624,8 @@ public function install_wp( $subdir = '' ) { 'title' => 'WP CLI Site', 'admin_user' => 'admin', 'admin_email' => 'admin@example.com', - 'admin_password' => 'password1' + 'admin_password' => 'password1', + 'skip-email' => true, ); $install_cache_path = ''; @@ -658,7 +666,8 @@ public function install_wp_with_composer( $vendor_directory = 'vendor' ) { 'title' => 'WP CLI Site with both WordPress and wp-cli as Composer dependencies', 'admin_user' => 'admin', 'admin_email' => 'admin@example.com', - 'admin_password' => 'password1' + 'admin_password' => 'password1', + 'skip-email' => true, ); $this->proc( 'wp core install', $install_args )->run_check(); @@ -686,26 +695,13 @@ public function composer_require_current_wp_cli() { $this->composer_command( 'require wp-cli/wp-cli:dev-master --optimize-autoloader --no-interaction' ); } - public function get_php_binary() { - if ( getenv( 'WP_CLI_PHP_USED' ) ) - return getenv( 'WP_CLI_PHP_USED' ); - - if ( getenv( 'WP_CLI_PHP' ) ) - return getenv( 'WP_CLI_PHP' ); - - if ( defined( 'PHP_BINARY' ) ) - return PHP_BINARY; - - return 'php'; - } - public function start_php_server( $subdir = '' ) { $dir = $this->variables['RUN_DIR'] . '/'; if ( $subdir ) { $dir .= trim( $subdir, '/' ) . '/'; } $cmd = Utils\esc_cmd( '%s -S %s -t %s -c %s %s', - $this->get_php_binary(), + Utils\get_php_binary(), 'localhost:8080', $dir, get_cfg_var( 'cfg_file_path' ), diff --git a/features/bootstrap/Process.php b/features/bootstrap/Process.php index 5016580b7..70c3c9f82 100644 --- a/features/bootstrap/Process.php +++ b/features/bootstrap/Process.php @@ -115,4 +115,20 @@ public function run_check() { return $r; } + + /** + * Run the command, but throw an Exception on error. + * Same as `run_check()` above, but checks the correct stderr. + * + * @return ProcessRun + */ + public function run_check_stderr() { + $r = $this->run(); + + if ( $r->return_code || ! empty( $r->stderr ) ) { + throw new \RuntimeException( $r ); + } + + return $r; + } } diff --git a/features/bootstrap/support.php b/features/bootstrap/support.php index a37a064f6..6aa17c6c3 100644 --- a/features/bootstrap/support.php +++ b/features/bootstrap/support.php @@ -2,6 +2,12 @@ // Utility functions used by Behat steps +function assertRegExp( $regex, $actual ) { + if ( ! preg_match( $regex, $actual ) ) { + throw new Exception( "Actual value: " . var_export( $actual, true ) ); + } +} + function assertEquals( $expected, $actual ) { if ( $expected != $actual ) { throw new Exception( "Actual value: " . var_export( $actual, true ) ); diff --git a/features/bootstrap/utils.php b/features/bootstrap/utils.php index 32caac15f..bb6e9ad11 100644 --- a/features/bootstrap/utils.php +++ b/features/bootstrap/utils.php @@ -30,7 +30,9 @@ function extract_from_phar( $path ) { register_shutdown_function( function() use ( $tmp_path ) { - @unlink( $tmp_path ); + if ( file_exists( $tmp_path ) ) { + unlink( $tmp_path ); + } } ); @@ -58,7 +60,7 @@ function load_dependencies() { } if ( ! $has_autoload ) { - fputs( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); + fwrite( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); exit( 3 ); } } @@ -138,7 +140,7 @@ function find_file_upward( $files, $dir = null, $stop_check = null ) { if ( is_null( $dir ) ) { $dir = getcwd(); } - while ( @is_readable( $dir ) ) { + 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; @@ -166,7 +168,7 @@ function is_path_absolute( $path ) { return true; } - return $path[0] === '/'; + return '/' === $path[0]; } /** @@ -227,12 +229,12 @@ function locate_wp_config() { static $path; if ( null === $path ) { + $path = false; + 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 ) { @@ -244,7 +246,9 @@ function locate_wp_config() { } function wp_version_compare( $since, $operator ) { - return version_compare( str_replace( array( '-src' ), '', $GLOBALS['wp_version'] ), $since, $operator ); + $wp_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); + $since = str_replace( '-src', '', $since ); + return version_compare( $wp_version, $since, $operator ); } /** @@ -358,9 +362,9 @@ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { do { $tmpfile = basename( $filename ); $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); - $tmpfile .= '-' . substr( md5( rand() ), 0, 6 ); + $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); $tmpfile = $tmpdir . $tmpfile . '.tmp'; - $fp = @fopen( $tmpfile, 'x' ); + $fp = fopen( $tmpfile, 'xb' ); if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { $tmpfile = ''; continue; @@ -379,10 +383,10 @@ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { $editor = getenv( 'EDITOR' ); if ( ! $editor ) { + $editor = 'vi'; + if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) { $editor = 'notepad'; - } else { - $editor = 'vi'; } } @@ -416,9 +420,9 @@ function mysql_host_to_cli_args( $raw_host ) { list( $assoc_args['host'], $extra ) = $host_parts; $extra = trim( $extra ); if ( is_numeric( $extra ) ) { - $assoc_args['port'] = intval( $extra ); + $assoc_args['port'] = (int) $extra; $assoc_args['protocol'] = 'tcp'; - } elseif ( $extra !== '' ) { + } elseif ( '' !== $extra ) { $assoc_args['socket'] = $extra; } } else { @@ -436,7 +440,9 @@ function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { } if ( isset( $assoc_args['host'] ) ) { + //@codingStandardsIgnoreStart $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); + //@codingStandardsIgnoreEnd } $pass = $assoc_args['pass']; @@ -581,6 +587,7 @@ function replace_path_consts( $source, $path ) { function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { $cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; + $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; if ( inside_phar() ) { // cURL can't read Phar archives $options['verify'] = extract_from_phar( @@ -594,17 +601,24 @@ function http_request( $method, $url, $data = null, $headers = array(), $options } } if ( empty( $options['verify'] ) ) { - WP_CLI::error( 'Cannot find SSL certificate.' ); + $error_msg = 'Cannot find SSL certificate.'; + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); + } + throw new \RuntimeException( $error_msg ); } } try { - $request = \Requests::request( $url, $headers, $data, $method, $options ); - return $request; + return \Requests::request( $url, $headers, $data, $method, $options ); } catch ( \Requests_Exception $ex ) { // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ), true ) ) { - \WP_CLI::error( sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ) ); + $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); + } + throw new \RuntimeException( $error_msg, null, $ex ); } // Handle SSL certificate issues gracefully \WP_CLI::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) ); @@ -612,7 +626,11 @@ function http_request( $method, $url, $data = null, $headers = array(), $options try { return \Requests::request( $url, $headers, $data, $method, $options ); } catch ( \Requests_Exception $ex ) { - \WP_CLI::error( sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ) ); + $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); + } + throw new \RuntimeException( $error_msg, null, $ex ); } } } @@ -698,11 +716,13 @@ function get_named_sem_ver( $new_version, $original_version ) { if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { return 'patch'; - } elseif ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { + } + + if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { return 'minor'; - } else { - return 'major'; } + + return 'major'; } /** @@ -770,16 +790,16 @@ function get_temp_dir() { return $temp; } + $temp = '/tmp/'; + // `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/'; } - if ( ! @is_writable( $temp ) ) { + if ( ! is_writable( $temp ) ) { \WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); } @@ -813,6 +833,18 @@ function parse_ssh_url( $url, $component = -1 ) { $bits[ $key ] = $matches[ $i ]; } } + + // Find the hostname from `vagrant ssh-config` automatically. + if ( preg_match( '/^vagrant:?/', $url ) ) { + if ( 'vagrant' === $bits['host'] && empty( $bits['scheme'] ) ) { + $ssh_config = shell_exec( 'vagrant ssh-config 2>/dev/null' ); + if ( preg_match( '/Host\s(.+)/', $ssh_config, $matches ) ) { + $bits['scheme'] = 'vagrant'; + $bits['host'] = $matches[1]; + } + } + } + switch ( $component ) { case PHP_URL_SCHEME: return isset( $bits['scheme'] ) ? $bits['scheme'] : null; @@ -835,25 +867,28 @@ function parse_ssh_url( $url, $component = -1 ) { * @access public * @category Input * - * @param string $noun Resource being affected (e.g. plugin) - * @param string $verb Type of action happening to the noun (e.g. activate) - * @param integer $total Total number of resource being affected. - * @param integer $successes Number of successful operations. - * @param integer $failures Number of failures. + * @param string $noun Resource being affected (e.g. plugin) + * @param string $verb Type of action happening to the noun (e.g. activate) + * @param integer $total Total number of resource being affected. + * @param integer $successes Number of successful operations. + * @param integer $failures Number of failures. + * @param null|integer $skips Optional. Number of skipped operations. Default null (don't show skips). */ -function report_batch_operation_results( $noun, $verb, $total, $successes, $failures ) { +function report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips = null ) { $plural_noun = $noun . 's'; $past_tense_verb = past_tense_verb( $verb ); $past_tense_verb_upper = ucfirst( $past_tense_verb ); if ( $failures ) { + $failed_skipped_message = null === $skips ? '' : " ({$failures} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')'; if ( $successes ) { - WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}." ); + WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}{$failed_skipped_message}." ); } else { - WP_CLI::error( "No {$plural_noun} {$past_tense_verb}." ); + WP_CLI::error( "No {$plural_noun} {$past_tense_verb}{$failed_skipped_message}." ); } } else { - if ( $successes ) { - WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}." ); + $skipped_message = $skips ? " ({$skips} skipped)" : ''; + if ( $successes || $skips ) { + WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}{$skipped_message}." ); } else { $message = $total > 1 ? ucfirst( $plural_noun ) : ucfirst( $noun ); WP_CLI::success( "{$message} already {$past_tense_verb}." ); @@ -876,7 +911,7 @@ function parse_str_to_argv( $arguments ) { $argv = array_map( function( $arg ) { foreach ( array( '"', "'" ) as $char ) { - if ( $char === substr( $arg, 0, 1 ) && $char === substr( $arg, -1 ) ) { + if ( substr( $arg, 0, 1 ) === $char && substr( $arg, -1 ) === $char ) { $arg = substr( $arg, 1, -1 ); break; } @@ -916,14 +951,15 @@ function basename( $path, $suffix = '' ) { * * @return bool */ +// @codingStandardsIgnoreLine function isPiped() { $shellPipe = getenv( 'SHELL_PIPE' ); - if ( $shellPipe !== false ) { + if ( false !== $shellPipe ) { return filter_var( $shellPipe, FILTER_VALIDATE_BOOLEAN ); - } else { - return (function_exists( 'posix_isatty' ) && ! posix_isatty( STDOUT )); } + + return (function_exists( 'posix_isatty' ) && ! posix_isatty( STDOUT )); } /** @@ -992,9 +1028,11 @@ function glob_brace( $pattern, $dummy_flags = null ) { } $current++; } else { - if ( ( '}' === $pattern[ $current ] && $depth-- === 0 ) || ( ',' === $pattern[ $current ] && 0 === $depth ) ) { + if ( ( '}' === $pattern[ $current ] && 0 === $depth-- ) || ( ',' === $pattern[ $current ] && 0 === $depth ) ) { break; - } elseif ( '{' === $pattern[ $current++ ] ) { + } + + if ( '{' === $pattern[ $current++ ] ) { $depth++; } } @@ -1038,7 +1076,7 @@ function glob_brace( $pattern, $dummy_flags = null ) { . substr( $pattern, $p, $next - $p ) . substr( $pattern, $rest + 1 ); - if ( ( $result = glob_brace( $subpattern ) ) ) { + if ( $result = glob_brace( $subpattern ) ) { $paths = array_merge( $paths, $result ); } @@ -1070,6 +1108,31 @@ function glob_brace( $pattern, $dummy_flags = null ) { * @return string */ function get_suggestion( $target, array $options, $threshold = 2 ) { + + $suggestion_map = array( + 'check' => 'check-update', + 'clear' => 'flush', + 'decrement' => 'decr', + 'del' => 'delete', + 'directory' => 'dir', + 'exec' => 'eval', + 'exec-file' => 'eval-file', + 'increment' => 'incr', + 'language' => 'locale', + 'lang' => 'locale', + 'new' => 'create', + 'number' => 'count', + 'remove' => 'delete', + 'regen' => 'regenerate', + 'rep' => 'replace', + 'repl' => 'replace', + 'v' => 'version', + ); + + if ( array_key_exists( $target, $suggestion_map ) ) { + return $suggestion_map[ $target ]; + } + if ( empty( $options ) ) { return ''; } @@ -1218,3 +1281,40 @@ function past_tense_verb( $verb ) { } return $verb . 'ed'; } + +/** + * Get the path to the PHP binary used when executing WP-CLI. + * + * Environment values permit specific binaries to be indicated. + * + * @access public + * @category System + * + * @return string + */ +function get_php_binary() { + if ( $wp_cli_php_used = getenv( 'WP_CLI_PHP_USED' ) ) { + return $wp_cli_php_used; + } + + if ( $wp_cli_php = getenv( 'WP_CLI_PHP' ) ) { + return $wp_cli_php; + } + + // Available since PHP 5.4. + if ( defined( 'PHP_BINARY' ) ) { + return PHP_BINARY; + } + + // @codingStandardsIgnoreLine + if ( @is_executable( PHP_BINDIR . '/php' ) ) { + return PHP_BINDIR . '/php'; + } + + // @codingStandardsIgnoreLine + if ( is_windows() && @is_executable( PHP_BINDIR . '/php.exe' ) ) { + return PHP_BINDIR . '/php.exe'; + } + + return 'php'; +} diff --git a/features/steps/then.php b/features/steps/then.php index aa1f3f67f..21589e737 100644 --- a/features/steps/then.php +++ b/features/steps/then.php @@ -3,9 +3,9 @@ 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 ) { +$steps->Then( '/^the return code should( not)? be (\d+)$/', + function ( $world, $not, $return_code ) { + if ( ( ! $not && $return_code != $world->result->return_code ) || ( $not && $return_code == $world->result->return_code ) ) { throw new RuntimeException( $world->result ); } } @@ -153,7 +153,7 @@ function ( $world, $stream, $operator, $goal_ver ) { 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 ) { @@ -200,6 +200,25 @@ function ( $world, $path, $type, $action, $expected = null ) { } ); +$steps->Then( '/^the contents of the (.+) file should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', + function ( $world, $path, $expected ) { + $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"; + } + $contents = file_get_contents( $path ); + assertRegExp( $expected, $contents ); + } +); + +$steps->Then( '/^(STDOUT|STDERR) should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', + function ( $world, $stream, $expected ) { + $stream = strtolower( $stream ); + assertRegExp( $expected, $world->result->$stream ); + } +); + $steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) { if ( 'be sent' === $expected ) { assertNotEquals( 0, $world->email_sends ); diff --git a/features/steps/when.php b/features/steps/when.php index afe3f7a0d..d23aa0e66 100644 --- a/features/steps/when.php +++ b/features/steps/when.php @@ -6,7 +6,7 @@ function invoke_proc( $proc, $mode ) { $map = array( - 'run' => 'run_check', + 'run' => 'run_check_stderr', 'try' => 'run' ); $method = $map[ $mode ]; diff --git a/utils/behat-tags.php b/utils/behat-tags.php index ee51fc91e..9c87fb656 100644 --- a/utils/behat-tags.php +++ b/utils/behat-tags.php @@ -44,8 +44,10 @@ function version_tags( $prefix, $current, $operator = '<' ) { 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'; +# Skip Github API tests if `GITHUB_TOKEN` not available because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 +if ( ! getenv( 'GITHUB_TOKEN' ) ) { + $skip_tags[] = '@github-api'; +} # Skip tests known to be broken. $skip_tags[] = '@broken';