diff --git a/.travis.yml b/.travis.yml index f6fcbf8..428188c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,22 +10,19 @@ php: - 5.6 - 7.0 -node_js: - - 0.10 - env: - WP_VERSION=latest WP_MULTISITE=0 - WP_VERSION=latest WP_MULTISITE=1 - WP_VERSION=trunk WP_MULTISITE=0 - WP_VERSION=trunk WP_MULTISITE=1 -before_script: +install: - export DEV_LIB_PATH=dev-lib - if [ ! -e "$DEV_LIB_PATH" ] && [ -L .travis.yml ]; then export DEV_LIB_PATH=$( dirname $( readlink .travis.yml ) ); fi - - source $DEV_LIB_PATH/travis.before_script.sh + - source $DEV_LIB_PATH/travis.install.sh script: - - $DEV_LIB_PATH/travis.script.sh + - source $DEV_LIB_PATH/travis.script.sh after_script: - - $DEV_LIB_PATH/travis.after_script.sh + - source $DEV_LIB_PATH/travis.after_script.sh diff --git a/check-diff.sh b/check-diff.sh new file mode 100755 index 0000000..7b3f428 --- /dev/null +++ b/check-diff.sh @@ -0,0 +1,645 @@ +#!/bin/bash + +set -e + +function realpath { + php -r 'echo realpath( $argv[1] );' "$1" +} + +function upsearch { + # via http://unix.stackexchange.com/a/13474 + slashes=${PWD//[^\/]/} + directory="./" + for (( n=${#slashes}; n>0; --n )); do + test -e "$directory/$1" && echo "$directory/$1" && return + if [ "$2" != 'git_boundless' ] && test -e '.git'; then + return + fi + directory="$directory/.." + done +} + +function remove_diff_range { + sed 's/:[0-9][0-9]*-[0-9][0-9]*$//' | sort | uniq +} + +function set_environment_variables { + + TEMP_DIRECTORY=$(mktemp -d 2>/dev/null || mktemp -d -t 'dev-lib') + PROJECT_DIR=$( git rev-parse --show-toplevel ) + DEV_LIB_PATH=${DEV_LIB_PATH:-$( dirname "$0" )/} + DEV_LIB_PATH=$(realpath "$DEV_LIB_PATH") + PROJECT_SLUG=${PROJECT_SLUG:-$( basename "$PROJECT_DIR" | sed 's/^wp-//' )} + PATH_INCLUDES=${PATH_INCLUDES:-./} + + if [ -z "$PROJECT_TYPE" ]; then + if [ -e style.css ]; then + PROJECT_TYPE=theme + elif grep -isqE "^[ ]*\*[ ]*Plugin Name[ ]*:" "$PROJECT_DIR"/*.php; then + PROJECT_TYPE=plugin + else + PROJECT_TYPE=unknown + fi + fi + + if [ ! -z "$LIMIT_TRAVIS_PR_CHECK_SCOPE" ]; then + echo "LIMIT_TRAVIS_PR_CHECK_SCOPE is obsolete; use CHECK_SCOPE env var instead" 1>&2 + return 1 + fi + CHECK_SCOPE=${CHECK_SCOPE:-patches} # 'all', 'changed-files', 'patches' + + if [ "$TRAVIS" == true ]; then + if [[ "$TRAVIS_PULL_REQUEST" != 'false' ]]; then + DIFF_BASE=${DIFF_BASE:-$TRAVIS_BRANCH} + else + DIFF_BASE=${DIFF_BASE:-$TRAVIS_COMMIT^} + fi + DIFF_HEAD=${DIFF_HEAD:-$TRAVIS_COMMIT} + else + DIFF_BASE=${DIFF_BASE:-HEAD} + DIFF_HEAD=${DIFF_HEAD:-WORKING} + fi + while [[ $# > 0 ]]; do + key="$1" + case "$key" in + -b|--diff-base) + DIFF_BASE="$2" + shift # past argument + ;; + -h|--diff-head) + DIFF_HEAD="$2" + shift # past argument + ;; + -s|--scope) + CHECK_SCOPE="$2" + shift # past argument + ;; + -i|--ignore-paths) + IGNORE_PATHS="$2" + shift # past argument + ;; + -v|--verbose) + VERBOSE=1 + ;; + *) + # unknown option + ;; + esac + shift # past argument or value + done + + PHPCS_PHAR_URL=https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar + PHPCS_RULESET_FILE=$( upsearch phpcs.ruleset.xml ) + PHPCS_IGNORE=${PHPCS_IGNORE:-'vendor/*'} + PHPCS_GIT_TREE=${PHPCS_GIT_TREE:-master} + PHPCS_GITHUB_SRC=${PHPCS_GITHUB_SRC:-squizlabs/PHP_CodeSniffer} + + if [ -z "$PHPUNIT_CONFIG" ]; then + if [ -e phpunit.xml ]; then + PHPUNIT_CONFIG=phpunit.xml + elif [ -e phpunit.xml.dist ]; then + PHPUNIT_CONFIG=phpunit.xml.dist + fi + fi + + WPCS_DIR=${WPCS_DIR:-/tmp/wpcs} + WPCS_GITHUB_SRC=${WPCS_GITHUB_SRC:-WordPress-Coding-Standards/WordPress-Coding-Standards} + WPCS_GIT_TREE=${WPCS_GIT_TREE:-master} + WPCS_STANDARD=${WPCS_STANDARD:-WordPress-Core} + + if [ -z "$CODECEPTION_CONFIG" ] && [ -e codeception.yml ]; then + CODECEPTION_CONFIG=codeception.yml + fi + + DB_HOST=${DB_HOST:-localhost} + DB_NAME=${DB_NAME:-wordpress_test} + DB_USER=${DB_USER:-root} + DB_PASS=${DB_PASS:-root} + + if [ -z "$WP_INSTALL_TESTS" ]; then + if [ "$TRAVIS" == true ]; then + WP_INSTALL_TESTS=true + else + WP_INSTALL_TESTS=false + fi + fi + WP_CORE_DIR=${WP_CORE_DIR:-/tmp/wordpress} + WP_VERSION=${WP_VERSION:-latest} + + YUI_COMPRESSOR_CHECK=${YUI_COMPRESSOR_CHECK:-1} + YUI_COMPRESSOR_PATH=/tmp/yuicompressor-2.4.8.jar + DISALLOW_EXECUTE_BIT=${DISALLOW_EXECUTE_BIT:-0} + SYNC_README_MD=${SYNC_README_MD:-1} + + VERBOSE=${VERBOSE:-0} + CODECEPTION_CHECK=${CODECEPTION_CHECK:-1} + VAGRANTFILE=$( upsearch 'Vagrantfile' git_boundless ) + + if [ -z "$JSCS_CONFIG" ]; then + JSCS_CONFIG="$( upsearch .jscsrc )" + fi + if [ -z "$JSCS_CONFIG" ]; then + JSCS_CONFIG="$( upsearch .jscs.json )" + fi + + if [ -z "$JSHINT_CONFIG" ]; then + JSHINT_CONFIG="$( upsearch .jshintrc )" + fi + if [ -z "$JSHINT_CONFIG" ]; then + JSHINT_CONFIG="$DEV_LIB_PATH/.jshintrc" + fi + + if [ -z "$JSHINT_IGNORE" ]; then + JSHINT_IGNORE="$( upsearch .jshintignore )" + else + JSHINT_IGNORE="$DEV_LIB_PATH/.jshintignore" + fi + + # Load any environment variable overrides from config files + ENV_FILE=$( upsearch .ci-env.sh ) + if [ ! -z "$ENV_FILE" ]; then + source "$ENV_FILE" + fi + ENV_FILE=$( upsearch .dev-lib ) + if [ ! -z "$ENV_FILE" ]; then + source "$ENV_FILE" + fi + + if [ ! -e "$PROJECT_DIR/.git" ]; then + echo "Error: Must run from Git root" 1>&2 + return 1 + fi + + CHECK_SCOPE=$( tr '[A-Z]' '[a-z]' <<< "$CHECK_SCOPE" ) + if [ "$CHECK_SCOPE" == 'files' ]; then + CHECK_SCOPE='changed-files' + fi + + if [ "$CHECK_SCOPE" != 'all' ] && [ "$CHECK_SCOPE" != 'changed-files' ] && [ "$CHECK_SCOPE" != 'patches' ]; then + echo "Error: CHECK_SCOPE must be 'all', 'changed-files', or 'patches'" 1>&2 + return 1 + fi + + if [ "$DIFF_HEAD" == 'INDEX' ]; then + DIFF_HEAD='STAGE' + fi + + if [ "$DIFF_BASE" == 'HEAD' ] && [ "$DIFF_HEAD" != 'STAGE' ] && [ "$DIFF_HEAD" != 'WORKING' ]; then + echo "Error: when DIFF_BASE is 'HEAD' then DIFF_HEAD must be 'STAGE' or 'WORKING' (you supplied '$DIFF_HEAD')" 1>&2 + return 1 + fi + if [ "$DIFF_HEAD" == 'WORKING' ] && [ "$DIFF_BASE" != 'STAGE' ] && [ "$DIFF_BASE" != 'HEAD' ]; then + echo "Error: when DIFF_HEAD is 'WORKING' then DIFF_BASE must be 'STAGE' or 'HEAD' (you supplied '$DIFF_BASE')" 1>&2 + return 1 + fi + if [ "$TRAVIS" == 'true' ]; then + if [ "$DIFF_HEAD" == 'WORKING' ]; then + echo "Error: DIFF_HEAD cannot be WORKING in TRAVIS" 1>&2 + return 1 + fi + if [ "$DIFF_HEAD" == 'STAGE' ]; then + echo "Error: DIFF_HEAD cannot be STAGE in TRAVIS" 1>&2 + return 1 + fi + fi + + # treeishA to treeishB (git diff treeishA...treeishB) + # treeish to STAGE (git diff --staged treeish) + # HEAD to WORKING [default] (git diff HEAD) + if [ "$DIFF_HEAD" == 'STAGE' ]; then + if [ "$DIFF_BASE" == 'HEAD' ]; then + DIFF_ARGS="--staged" + else + DIFF_ARGS="--staged $DIFF_BASE" + fi + elif [ "$DIFF_HEAD" == 'WORKING' ]; then + DIFF_ARGS="$DIFF_BASE" + else + DIFF_ARGS="$DIFF_BASE...$DIFF_HEAD" + fi + + if [ "$CHECK_SCOPE" == 'patches' ]; then + git diff --diff-filter=AM --no-prefix --unified=0 "$DIFF_ARGS" -- $PATH_INCLUDES | php "$DEV_LIB_PATH/diff-tools/parse-diff-ranges.php" > "$TEMP_DIRECTORY/paths-scope" + elif [ "$CHECK_SCOPE" == 'changed-files' ]; then + git diff "$DIFF_ARGS" --name-only $PATH_INCLUDES > "$TEMP_DIRECTORY/paths-scope" + else + git ls-files -- $PATH_INCLUDES > "$TEMP_DIRECTORY/paths-scope" + fi + + cat "$TEMP_DIRECTORY/paths-scope" | grep -E '\.php(:|$)' | cat - > "$TEMP_DIRECTORY/paths-scope-php" + cat "$TEMP_DIRECTORY/paths-scope" | grep -E '\.js(:|$)' | cat - > "$TEMP_DIRECTORY/paths-scope-js" + cat "$TEMP_DIRECTORY/paths-scope" | grep -E '\.(css|scss)(:|$)' | cat - > "$TEMP_DIRECTORY/paths-scope-scss" + cat "$TEMP_DIRECTORY/paths-scope" | grep -E '\.(xml|svg|xml.dist)(:|$)' | cat - > "$TEMP_DIRECTORY/paths-scope-xml" + + # Gather the proper states of files to run through linting (this won't apply to phpunit) + if [ "$DIFF_HEAD" != 'working' ]; then + LINTING_DIRECTORY="$(realpath $TEMP_DIRECTORY)/index" + mkdir -p "$LINTING_DIRECTORY" + + for path in $( cat "$TEMP_DIRECTORY/paths-scope" | remove_diff_range ); do + # Skip submodules or files are deleted + if [ -d "$path" ] || [ ! -e "$path" ]; then + continue + fi + + mkdir -p "$LINTING_DIRECTORY/$(dirname "$path")" + if [ "$DIFF_HEAD" == 'STAGE' ]; then + git show :"$path" > "$LINTING_DIRECTORY/$path" + else + git show "$DIFF_HEAD":"$path" > "$LINTING_DIRECTORY/$path" + fi + done + else + LINTING_DIRECTORY="$PROJECT_DIR" + fi + + if [ ! -z "$JSHINT_CONFIG" ]; then JSHINT_CONFIG=$(realpath "$JSHINT_CONFIG"); fi + if [ ! -z "$JSHINT_IGNORE" ]; then JSHINT_IGNORE=$(realpath "$JSHINT_IGNORE"); fi + if [ ! -z "$JSCS_CONFIG" ]; then JSCS_CONFIG=$(realpath "$JSCS_CONFIG"); fi + if [ ! -z "$ENV_FILE" ]; then ENV_FILE=$(realpath "$ENV_FILE"); fi + if [ ! -z "$PHPCS_RULESET_FILE" ]; then PHPCS_RULESET_FILE=$(realpath "$PHPCS_RULESET_FILE"); fi + if [ ! -z "$CODECEPTION_CONFIG" ]; then CODECEPTION_CONFIG=$(realpath "$CODECEPTION_CONFIG"); fi + if [ ! -z "$VAGRANTFILE" ]; then VAGRANTFILE=$(realpath "$VAGRANTFILE"); fi + # Note: PHPUNIT_CONFIG must be a relative path for the sake of running in Vagrant + + return 0 +} + +function dump_environment_variables { + echo 1>&2 + echo "## CONFIG VARIABLES" 1>&2 + + # List obtained via ack -o '[A-Z][A-Z0-9_]*(?==)' | tr '\n' ' ' + for var in JSHINT_CONFIG LINTING_DIRECTORY TEMP_DIRECTORY DEV_LIB_PATH PROJECT_DIR PROJECT_SLUG PATH_INCLUDES PROJECT_TYPE CHECK_SCOPE DIFF_BASE DIFF_HEAD PHPCS_GITHUB_SRC PHPCS_GIT_TREE PHPCS_RULESET_FILE PHPCS_IGNORE WPCS_DIR WPCS_GITHUB_SRC WPCS_GIT_TREE WPCS_STANDARD WP_CORE_DIR WP_TESTS_DIR YUI_COMPRESSOR_CHECK DISALLOW_EXECUTE_BIT CODECEPTION_CHECK JSCS_CONFIG JSCS_CONFIG ENV_FILE ENV_FILE DIFF_BASE DIFF_HEAD CHECK_SCOPE IGNORE_PATHS HELP VERBOSE DB_HOST DB_NAME DB_USER DB_PASS WP_INSTALL_TESTS; do + echo "$var=${!var}" 1>&2 + done + echo 1>&2 +} + +function verbose_arg { + if [ "$VERBOSE" == 1 ]; then + echo '-v' + fi +} + +function download { + if command -v curl >/dev/null 2>&1; then + curl $(verbose_arg) -L -s "$1" > "$2" + elif command -v wget >/dev/null 2>&1; then + wget $(verbose_arg) -n -O "$2" "$1" + else + echo '' + return 1 + fi +} + +function install_tools { + + TEMP_TOOL_PATH="/tmp/dev-lib-bin" + mkdir -p "$TEMP_TOOL_PATH" + PATH="$TEMP_TOOL_PATH:$PATH" + + if ! php -r "if ( version_compare( phpversion(), '5.3', '<' ) ) { exit( 1 ); }"; then + pecl install phar + SKIP_COMPOSER=1 + fi + + # Install PHP tools. + if [ -s "$TEMP_DIRECTORY/paths-scope-php" ]; then + if ! command -v phpunit >/dev/null 2>&1; then + echo "Downloading PHPUnit phar" + download https://phar.phpunit.de/phpunit.phar "$TEMP_TOOL_PATH/phpunit" + chmod +x "$TEMP_TOOL_PATH/phpunit" + fi + + if [ -z "$WPCS_STANDARD" ]; then + echo "Skipping PHPCS since WPCS_STANDARD (and PHPCS_RULESET_FILE) is empty." 1>&2 + else + if ! command -v phpcs >/dev/null 2>&1; then + echo "Downloading PHPCS phar" + download "$PHPCS_PHAR_URL" "$TEMP_TOOL_PATH/phpcs" + chmod +x "$TEMP_TOOL_PATH/phpcs" + fi + + if ! phpcs -i | grep -q 'WordPress'; then + git clone -b "$WPCS_GIT_TREE" "https://github.com/$WPCS_GITHUB_SRC.git" $WPCS_DIR + # @todo Pull periodically + phpcs --config-set installed_paths $WPCS_DIR + fi + fi + fi + + # Install JS tools. + if [ -s "$TEMP_DIRECTORY/paths-scope-js" ]; then + + # Install JSHint + if ! command -v jshint >/dev/null 2>&1; then + echo "Installing JSHint" + npm install -g jshint + fi + + # Install jscs + if [ -n "$JSCS_CONFIG" ] && [ -e "$JSCS_CONFIG" ] && ! command -v jscs >/dev/null 2>&1; then + echo "JSCS" + npm install -g jscs + fi + + # @todo ESLint + + # YUI Compressor + if [ "$YUI_COMPRESSOR_CHECK" == 1 ] && command -v java >/dev/null 2>&1; then + if [ ! -e "$YUI_COMPRESSOR_PATH" ]; then + download https://github.com/yui/yuicompressor/releases/download/v2.4.8/yuicompressor-2.4.8.jar "$YUI_COMPRESSOR_PATH" + fi + fi + fi + + # Install Composer + if [ -e composer.json ] && [ -z "$SKIP_COMPOSER" ]; then + if command -v composer >/dev/null 2>&1; then + ( + cd "$TEMP_TOOL_PATH" + download "http://getcomposer.org/installer" composer-installer.php + php composer-installer.php + mv composer.phar composer + chmod +x composer + ) + fi + + composer install + fi +} + +## Begin functions for phpunit ########################### + +function install_wp { + + if [ -d "$WP_CORE_DIR" ]; then + return 0 + fi + if ! command -v svn >/dev/null 2>&1; then + echo "install_wp failure: svn is not installed" + return 1 + fi + + if [ "$WP_VERSION" == 'latest' ]; then + local TAG=$( svn ls https://develop.svn.wordpress.org/tags | tail -n 1 | sed 's:/$::' ) + local SVN_URL="https://develop.svn.wordpress.org/tags/$TAG/" + elif [ "$WP_VERSION" == 'trunk' ]; then + local SVN_URL=https://develop.svn.wordpress.org/trunk/ + else + local SVN_URL="https://develop.svn.wordpress.org/tags/$WP_VERSION/" + fi + + echo "Installing WP from $SVN_URL to $WP_CORE_DIR" + + svn export -q "$SVN_URL" "$WP_CORE_DIR" + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php "$WP_CORE_DIR/src/wp-content/db.php" +} + +function install_test_suite { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + cd "$WP_CORE_DIR" + + if [ ! -f wp-tests-config.php ]; then + cp wp-tests-config-sample.php wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" wp-tests-config.php + fi + +} + +function install_db { + if ! command -v mysqladmin >/dev/null 2>&1; then + echo "install_db failure: mysqladmin is not present" + return 1 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z "$DB_HOSTNAME" ] ; then + if [ $(echo "$DB_SOCK_OR_PORT" | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z "$DB_SOCK_OR_PORT" ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z "$DB_HOSTNAME" ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # drop the database if it exists + mysqladmin drop -f "$DB_NAME" --silent --no-beep --user="$DB_USER" --password="$DB_PASS"$EXTRA || echo "$DB_NAME does not exist yet" + + # create database + if ! mysqladmin create "$DB_NAME" --user="$DB_USER" --password="$DB_PASS"$EXTRA; then + return 1 + fi + + echo "DB $DB_NAME created" +} + +function run_phpunit_local { + if [ ! -s "$TEMP_DIRECTORY/paths-scope-php" ] || [ -z "$PHPUNIT_CONFIG" ]; then + return + fi + + # TODO: This should eventually run unit tests only in the state of the DIFF_HEAD + + ( + echo "## phpunit" + if [ "$USER" != 'vagrant' ]; then + + # Check if we're in Vagrant + if [ ! -z "$VAGRANTFILE" ]; then + cd $( dirname "$VAGRANTFILE" ) + VAGRANT_ROOT=$(pwd) + if [ -e www/wp-content/themes/vip/plugins/vip-init.php ]; then + ABSOLUTE_VAGRANT_PATH=/srv${PROJECT_DIR:${#VAGRANT_ROOT}} + elif grep -q vvv Vagrantfile; then + ABSOLUTE_VAGRANT_PATH=/srv${PROJECT_DIR:${#VAGRANT_ROOT}} + fi + cd - > /dev/null + fi + + if [ ! -z "$ABSOLUTE_VAGRANT_PATH" ]; then + echo "Running phpunit in Vagrant" + vagrant ssh -c "cd $ABSOLUTE_VAGRANT_PATH && phpunit -c $PHPUNIT_CONFIG" + elif command -v vassh >/dev/null 2>&1; then + echo "Running phpunit in vagrant via vassh..." + vassh phpunit -c "$PHPUNIT_CONFIG" + fi + elif ! command -v phpunit >/dev/null 2>&1;then + echo "Skipping phpunit since not installed" + elif [ -z "$WP_TESTS_DIR" ]; then + echo "Skipping phpunit since WP_TESTS_DIR env missing" + else + phpunit -c "$PHPUNIT_CONFIG" + fi + ) +} + +function run_phpunit_travisci { + if [ ! -s "$TEMP_DIRECTORY/paths-scope-php" ] || [ -z "$PHPUNIT_CONFIG" ]; then + return + fi + + if ! command -v phpunit >/dev/null 2>&1; then + echo "Skipping PHPUnit because phpunit tool not installed" + return + fi + + echo + echo "## PHPUnit tests" + + if [ "$PROJECT_TYPE" != plugin ]; then + echo "Skipping since currently only applies to plugins" + return + fi + + if [ "$PROJECT_TYPE" == plugin ]; then + INSTALL_PATH="$WP_CORE_DIR/src/wp-content/plugins/$PROJECT_SLUG" + fi + + # Credentials on Travis + DB_USER=root + DB_PASS='' + + # Install the WordPress Unit Tests + # Note: This is installed here instead of during the install phase because it is run last and can take longer + if [ "$WP_INSTALL_TESTS" == 'true' ]; then + if install_wp && install_test_suite && install_db; then + echo "WP and unit tests installed" + else + echo "Failed to install unit tests" + fi + fi + + WP_TESTS_DIR=${WP_CORE_DIR}/tests/phpunit # This is a bit of a misnomer: it is the *PHP* tests dir + export WP_CORE_DIR + export WP_TESTS_DIR + + # Rsync the files into the right location + mkdir -p "$INSTALL_PATH" + rsync -a $(verbose_arg) --exclude .git/hooks --delete "$PROJECT_DIR/" "$INSTALL_PATH/" + cd "$INSTALL_PATH" + + echo "Location: $INSTALL_PATH" + + # Run the tests + phpunit $(verbose_arg) --configuration "$PHPUNIT_CONFIG" --stop-on-failure + cd - > /dev/null +} + + +## End functions for PHPUnit + +function lint_js_files { + if [ ! -s "$TEMP_DIRECTORY/paths-scope-js" ]; then + return + fi + + set -e + + # Run YUI Compressor. + if [ "$YUI_COMPRESSOR_CHECK" == 1 ] && command -v java >/dev/null 2>&1; then + ( + echo "## YUI Compressor" + cd "$LINTING_DIRECTORY" + cat "$TEMP_DIRECTORY/paths-scope-js" | remove_diff_range | xargs java -jar "$YUI_COMPRESSOR_PATH" --nomunge --disable-optimizations -o /dev/null 2>&1 + ) + fi + + # Run JSHint. + if [ -n "$JSHINT_CONFIG" ] && command -v jshint >/dev/null 2>&1; then + ( + echo "## JSHint" + cd "$LINTING_DIRECTORY" + if ! cat "$TEMP_DIRECTORY/paths-scope-js" | remove_diff_range | xargs jshint --reporter=unix --config="$JSHINT_CONFIG" $( if [ -n "$JSHINT_IGNORE" ]; then echo --exclude-path "$JSHINT_IGNORE"; fi ) > "$TEMP_DIRECTORY/jshint-report"; then + cat "$TEMP_DIRECTORY/jshint-report" | php "$DEV_LIB_PATH/diff-tools/filter-report-for-patch-ranges.php" "$TEMP_DIRECTORY/paths-scope-js" + fi + ) + fi + + # Run JSCS. + if [ -n "$JSCS_CONFIG" ] && command -v jscs >/dev/null 2>&1; then + ( + echo "## JSCS" + cd "$LINTING_DIRECTORY" + if ! cat "$TEMP_DIRECTORY/paths-scope-js" | remove_diff_range | xargs jscs --reporter=inlinesingle --verbose --config="$JSCS_CONFIG" > "$TEMP_DIRECTORY/jscs-report"; then + cat "$TEMP_DIRECTORY/jscs-report" | php "$DEV_LIB_PATH/diff-tools/filter-report-for-patch-ranges.php" "$TEMP_DIRECTORY/paths-scope-js" + fi + ) + fi +} + +function lint_php_files { + if [ ! -s "$TEMP_DIRECTORY/paths-scope-php" ]; then + return + fi + + set -e + + ( + echo "## PHP syntax check" + cd "$LINTING_DIRECTORY" + for php_file in $( cat "$TEMP_DIRECTORY/paths-scope-php" | remove_diff_range ); do + php -lf "$php_file" + done + ) + + # Check PHP_CodeSniffer WordPress-Coding-Standards. + if command -v phpcs >/dev/null 2>&1 && ( [ -n "$WPCS_STANDARD" ] || [ -n "$PHPCS_RULESET_FILE" ] ); then + ( + echo "## PHP_CodeSniffer" + cd "$LINTING_DIRECTORY" + if ! cat "$TEMP_DIRECTORY/paths-scope-php" | remove_diff_range | xargs phpcs -s --report-emacs="$TEMP_DIRECTORY/phpcs-report" --standard="$( if [ ! -z "$PHPCS_RULESET_FILE" ]; then echo "$PHPCS_RULESET_FILE"; else echo "$WPCS_STANDARD"; fi )"; then + cat "$TEMP_DIRECTORY/phpcs-report" | php "$DEV_LIB_PATH/diff-tools/filter-report-for-patch-ranges.php" "$TEMP_DIRECTORY/paths-scope-php" | cut -c$( expr ${#LINTING_DIRECTORY} + 2 )- + phpcs_status="${PIPESTATUS[1]}" + if [[ $phpcs_status != 0 ]]; then + return $phpcs_status + fi + fi + ) + fi +} + +function run_codeception { + if [ "$CODECEPTION_CHECK" != 1 ]; then + return + fi + if [ -z "$CODECEPTION_CONFIG" ]; then + echo "Skipping codeception since not configured" + return + fi + + # Download if it does not exist + if [ ! -f "/tmp/codeception.phar" ]; then + download http://codeception.com/codecept.phar /tmp/codecept.phar + fi + php /tmp/codecept.phar run +} + +function check_execute_bit { + if [ "$DISALLOW_EXECUTE_BIT" != 1 ]; then + return + fi + for FILE in $( cat "$TEMP_DIRECTORY/paths-scope" | remove_diff_range ); do + if [ -x "$PROJECT_DIR/$FILE" ] && [ ! -d "$PROJECT_DIR/$FILE" ]; then + echo "Error: Executable file being committed: $FILE. Do chmod -x on this." + return 1 + fi + done +} diff --git a/diff-tools/class-git-patch-checker.php b/diff-tools/class-git-patch-checker.php new file mode 100644 index 0000000..2fb83da --- /dev/null +++ b/diff-tools/class-git-patch-checker.php @@ -0,0 +1,85 @@ +.+)#', $line, $matches ) ) { + $current_file_path = $matches['file_path']; + continue; + } + if ( empty( $current_file_path ) ) { + continue; + } + if ( preg_match( '#^@@ -(\d+)(?:,(\d+))? \+(?P\d+)(?:,(?P\d+))? @@#', $line, $matches ) ) { + if ( empty( $matches['line_count'] ) ) { + $matches['line_count'] = 0; + } + $start_line = intval( $matches['line_number'] ); + $end_line = intval( $matches['line_number'] ) + intval( $matches['line_count'] ); + $file_path = $current_file_path; + $ranges[] = compact( 'file_path', 'start_line', 'end_line' ); + } + } + + return $ranges; + } + + /** + * Parse and select the lines of a lint report that lie within diff ranges. + * + * @param string $report Diff report string. + * @param array $diff_ranges Ranges for the diff. + * @return array + */ + static public function filter_report_for_patch_ranges( $report, $diff_ranges ) { + $filtered_report_lines = array(); + + foreach ( explode( "\n", $report ) as $line ) { + $matched = ( + preg_match( '#^(?P.+):(?P\d+):\d+:.+$$#', $line, $matches ) + || + preg_match( '/^(?P.+): line (?P\d+),/', $line, $matches ) + ); + if ( ! $matched ) { + continue; + } + $file_path = realpath( $matches['file_path'] ); + if ( ! array_key_exists( $file_path, $diff_ranges ) ) { + continue; + } + $line_number = intval( $matches['line_number'] ); + $matched = false; + foreach ( $diff_ranges[ $file_path ] as $range ) { + if ( $line_number >= $range['start_line'] && $line_number <= $range['end_line'] ) { + $matched = true; + break; + } + } + if ( $matched ) { + $filtered_report_lines[] = $matches; + } + } + + return $filtered_report_lines; + } +} diff --git a/filter-report-for-patch-ranges.php b/diff-tools/filter-report-for-patch-ranges.php old mode 100644 new mode 100755 similarity index 100% rename from filter-report-for-patch-ranges.php rename to diff-tools/filter-report-for-patch-ranges.php diff --git a/diff-tools/parse-diff-ranges.php b/diff-tools/parse-diff-ranges.php new file mode 100755 index 0000000..cc20d98 --- /dev/null +++ b/diff-tools/parse-diff-ranges.php @@ -0,0 +1,16 @@ +#!/usr/bin/env php +parse_diff_ranges( file_get_contents( 'php://stdin' ) ); +foreach ( $ranges as $range ) { + printf( "%s:%d-%d\n", $range['file_path'], $range['start_line'], $range['end_line'] ); +} diff --git a/install-wp-tests.sh b/install-wp-tests.sh deleted file mode 100644 index 1a2d7d4..0000000 --- a/install-wp-tests.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash - -if [ $# -lt 3 ]; then - echo "usage: $0 [db-host] [wp-version]" - exit 1 -fi - -DB_NAME=$1 -DB_USER=$2 -DB_PASS=$3 -DB_HOST=${4-localhost} -WP_VERSION=${5-latest} - -WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress} -WP_TESTS_DIR=${WP_TESTS_DIR-${WP_CORE_DIR}/tests/phpunit} - -set -ex - -download() { - if [ `which curl` ]; then - curl -s "$1" > "$2"; - elif [ `which wget` ]; then - wget -nv -O "$2" "$1" - fi -} - -install_wp() { - - if [ -d $WP_CORE_DIR ]; then - return; - fi - - if [ $WP_VERSION == 'latest' ]; then - local TAG=$( svn ls https://develop.svn.wordpress.org/tags | tail -n 1 | sed 's:/$::' ) - local SVN_URL=https://develop.svn.wordpress.org/tags/$TAG/ - elif [ $WP_VERSION == 'trunk' ]; then - local SVN_URL=https://develop.svn.wordpress.org/trunk/ - else - local SVN_URL=https://develop.svn.wordpress.org/tags/$WP_VERSION/ - fi - - svn export -q $SVN_URL $WP_CORE_DIR - - download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/src/wp-content/db.php -} - -install_test_suite() { - # portable in-place argument for both GNU sed and Mac OSX sed - if [[ $(uname -s) == 'Darwin' ]]; then - local ioption='-i .bak' - else - local ioption='-i' - fi - - cd $WP_CORE_DIR - - if [ ! -f wp-tests-config.php ]; then - cp wp-tests-config-sample.php wp-tests-config.php - sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" wp-tests-config.php - sed $ioption "s/yourusernamehere/$DB_USER/" wp-tests-config.php - sed $ioption "s/yourpasswordhere/$DB_PASS/" wp-tests-config.php - sed $ioption "s|localhost|${DB_HOST}|" wp-tests-config.php - fi - -} - -install_db() { - # parse DB_HOST for port or socket references - local PARTS=(${DB_HOST//\:/ }) - local DB_HOSTNAME=${PARTS[0]}; - local DB_SOCK_OR_PORT=${PARTS[1]}; - local EXTRA="" - - if ! [ -z $DB_HOSTNAME ] ; then - if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then - EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" - elif ! [ -z $DB_SOCK_OR_PORT ] ; then - EXTRA=" --socket=$DB_SOCK_OR_PORT" - elif ! [ -z $DB_HOSTNAME ] ; then - EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" - fi - fi - - # create database - mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA -} - -install_wp -install_test_suite -install_db diff --git a/pre-commit b/pre-commit index 4395d19..307aa33 100755 --- a/pre-commit +++ b/pre-commit @@ -1,181 +1,37 @@ #!/bin/bash -# WordPress Plugin pre-commit hook +# WordPress pre-commit hook set -e +shopt -s expand_aliases -# via http://unix.stackexchange.com/a/13474 -upsearch () { - slashes=${PWD//[^\/]/} - directory="./" - for (( n=${#slashes}; n>0; --n )); do - test -e "$directory/$1" && echo "$directory/$1" && return - if [ "$2" != 'git_boundless' ] && test -e '.git'; then - return - fi - directory="$directory/.." - done -} - -if [ -z "$WP_TESTS_DIR" ]; then - WP_TESTS_DIR=/tmp/wordpress-tests/ -fi - -YUI_COMPRESSOR_CHECK=1 -CODECEPTION_CHECK=1 -DISALLOW_EXECUTE_BIT=0 -PARENT_DIR_SLUG="$(basename "$(dirname "$(pwd)")")" -SELF_DIR=$( pwd ) -PHPCS_DIR=/tmp/phpcs -PHPCS_GITHUB_SRC=squizlabs/PHP_CodeSniffer -PHPCS_GIT_TREE=master -PHPCS_IGNORE='tests/*,vendor/*' -WPCS_DIR=/tmp/wpcs -WPCS_GITHUB_SRC=WordPress-Coding-Standards/WordPress-Coding-Standards -WPCS_GIT_TREE=master -PHPCS_FILE=$( upsearch phpcs.ruleset.xml ) -WPCS_STANDARD=$(if [ ! -z "$PHPCS_FILE" ]; then echo "$PHPCS_FILE"; else echo WordPress-Core; fi) -JSCS_CONFIG="$( upsearch .jscsrc )" -if [ -z "$JSCS_CONFIG" ]; then - JSCS_CONFIG="$( upsearch .jscs.json )" +if [ -L "$0" ]; then + DEV_LIB_PATH=$( dirname "$0" )/$( dirname $( readlink "$0" ) ) +else + DEV_LIB_PATH=dev-lib fi -PATH_INCLUDES=./ -ENV_FILE=$( upsearch .ci-env.sh ) -if [ ! -z "$ENV_FILE" ]; then - source "$ENV_FILE" -fi - -MESSAGE="Checking staged changes..." -GIT_STATUS_EGREP='^[MARC].+' - -for i; do - case "$i" - in - -m) - MESSAGE="Checking any uncommitted changes..." - GIT_STATUS_EGREP='^.?[MARC].+' - shift;; - esac -done - -echo "$MESSAGE" - -# Check for staged JS files -STAGED_JS_FILES=( $(git status --porcelain -- $PATH_INCLUDES | sed 's/[^ ]* -> *//g' | egrep $GIT_STATUS_EGREP'\.js$' | cut -c4-) ) -if [ ${#STAGED_JS_FILES[@]} != 0 ]; then - # YUI Compressor - if [ "$YUI_COMPRESSOR_CHECK" == 1 ] && command -v java >/dev/null 2>&1; then - YUI_COMPRESSOR_PATH=/tmp/yuicompressor-2.4.8.jar - if [ ! -e "$YUI_COMPRESSOR_PATH" ]; then - wget -O "$YUI_COMPRESSOR_PATH" https://github.com/yui/yuicompressor/releases/download/v2.4.8/yuicompressor-2.4.8.jar - fi - java -jar "$YUI_COMPRESSOR_PATH" -o /dev/null "${STAGED_JS_FILES[@]}" 2>&1 - fi - - # JSHint - echo "## JSHint" - if command -v jshint >/dev/null 2>&1; then - jshintignorepath=$( upsearch .jshintignore ) - jshint $( if [ -n "$jshintignorepath" ]; then echo "--exclude-path $jshintignorepath"; fi ) "${STAGED_JS_FILES[@]}" - else - echo "Skipping jshint since not installed" - fi - - # JSCS - if [ -n "$JSCS_CONFIG" ]; then - echo "## JSCS" - if command -v jscs >/dev/null 2>&1; then - jscs --verbose --config="$JSCS_CONFIG" "${STAGED_JS_FILES[@]}" - else - echo "Skipping JSCS since not installed" - fi - fi +if [ ! -e "$DEV_LIB_PATH/check-diff.sh" ]; then + echo "Unable to determine DEV_LIB_PATH" + exit 1 fi -# Check for staged PHP files -STAGED_PHP_FILES=( $(git status --porcelain -- $PATH_INCLUDES | sed 's/[^ ]* -> *//g' | egrep $GIT_STATUS_EGREP'\.php$' | cut -c4-) ) -if [ ${#STAGED_PHP_FILES[@]} != 0 ]; then - # PHP Syntax Check - for PHP_FILE in "${STAGED_PHP_FILES[@]}"; do - php -lf "$PHP_FILE" - done - - # PHP_CodeSniffer WordPress Coding Standards - echo "## phpcs" - if command -v phpcs >/dev/null 2>&1; then - phpcs -p -s -v --standard="$WPCS_STANDARD" $(if [ -n "$PHPCS_IGNORE" ]; then echo --ignore="$PHPCS_IGNORE"; fi) "${STAGED_PHP_FILES[@]}" - else - echo "Skipping phpcs since not installed" - fi - - # PHPUnit - PHPUNIT_XML_FILE=$( upsearch phpunit.xml ) - if [ -z "$PHPUNIT_XML_FILE" ]; then - PHPUNIT_XML_FILE=$( upsearch phpunit.xml.dist ) - fi - if [ ! -z "$PHPUNIT_XML_FILE" ]; then - echo "## phpunit" - if [ "$USER" != 'vagrant' ]; then - - # Check if we're in VVV - VAGRANTFILE=$( upsearch 'Vagrantfile' git_boundless ) - if [ ! -z "$VAGRANTFILE" ]; then - cd $( dirname "$VAGRANTFILE" ) - VAGRANT_ROOT=$(pwd) - if [ -e www/wp-content/themes/vip/plugins/vip-init.php ]; then - ABSOLUTE_VAGRANT_PATH=/srv${SELF_DIR:${#VAGRANT_ROOT}} - elif grep -q vvv Vagrantfile; then - ABSOLUTE_VAGRANT_PATH=/srv${SELF_DIR:${#VAGRANT_ROOT}} - fi - cd "$SELF_DIR" - fi +source "$DEV_LIB_PATH/check-diff.sh" +set_environment_variables --diff-base HEAD --diff-head STAGE +install_tools - if [ ! -z "$ABSOLUTE_VAGRANT_PATH" ]; then - echo "Running phpunit in Vagrant" - vagrant ssh -c "cd $ABSOLUTE_VAGRANT_PATH && phpunit -c $PHPUNIT_XML_FILE" - elif command -v vassh >/dev/null 2>&1; then - echo "Running phpunit in vagrant via vassh..." - vassh phpunit -c "$PHPUNIT_XML_FILE" - fi - elif ! command -v phpunit >/dev/null 2>&1;then - echo "Skipping phpunit since not installed" - elif [ -z "$WP_TESTS_DIR" ]; then - echo "Skipping phpunit since WP_TESTS_DIR env missing" - else - phpunit -c "$PHPUNIT_XML_FILE" - fi - fi -fi - -# Run Codeception tests -if [ "$CODECEPTION_CHECK" == 1 ]; then - if [ -f "$SELF_DIR/codeception.yml" ]; then - # Download if it does not exist - if [ ! -f "/tmp/codeception.phar" ]; then - wget -O "/tmp/codecept.phar" http://codeception.com/codecept.phar - fi - php /tmp/codecept.phar run - else - echo "Skipping codeception since not configured" - fi -fi +echo "## Checking files, scope $CHECK_SCOPE:" +cat "$TEMP_DIRECTORY/paths-scope" -# Check for staged files with execute bit on -if [ "$DISALLOW_EXECUTE_BIT" == 1 ]; then - for EXECUTABLE_FILE in $( git status --porcelain -- $PATH_INCLUDES ); do - if [ -x "$EXECUTABLE_FILE" ] && [[ "" != $( git status --porcelain "$EXECUTABLE_FILE" | sed 's/[^ ]* -> *//g' | egrep "$GIT_STATUS_EGREP" | cut -c4- ) ]]; then - echo "Error: Executable file being committed: $EXECUTABLE_FILE. Do chmod -x on this." - exit 1 - fi - done -fi +check_execute_bit +lint_js_files +lint_php_files +run_phpunit_local +run_codeception -# Make sure the readme.md never gets out of sync with the readme.txt -if [ -e readme.txt ]; then - # TODO: This is not going to work if wp-plugin-dev-lib is outside of the repo - GENERATE_MARKDOWN_README=$(find . -name generate-markdown-readme -print -quit) - if [ -n "$GENERATE_MARKDOWN_README" ]; then - MARKDOWN_README_PATH=$($GENERATE_MARKDOWN_README) +# Make sure the readme.md never gets out of sync with the readme.txt, if it is staged for commit. +if [[ $SYNC_README_MD == 1 ]] && [ -e readme.txt ] && cat "$TEMP_DIRECTORY/paths-scope" | remove_diff_range | grep -sqE '^readme\.txt$'; then + MARKDOWN_README_PATH=$($DEV_LIB_PATH/generate-markdown-readme) + if [ -n "$MARKDOWN_README_PATH" ]; then git add $MARKDOWN_README_PATH fi fi diff --git a/readme.md b/readme.md index a5200ae..36b7016 100644 --- a/readme.md +++ b/readme.md @@ -5,9 +5,7 @@ wp-dev-lib ## Installation -It is intended that this repo be included in plugin repo via git-submodule in a `dev-lib/` directory. (Previously it was recommended to be a git-subtree, but this has changed now that the `.travis.yml` is now lightweight enough to not require a symlink.) - -To **add** it to your repo, do: +It is intended that this repo be included in plugin repo via git-submodule in a `dev-lib/` directory. To **add** it to your repo, do: ```bash git submodule add https://github.com/xwp/wp-dev-lib.git dev-lib @@ -21,7 +19,26 @@ git add dev-lib git commit -m "Update dev-lib" ``` -## Travis +If Travis CI is not available (below) and you don't want to install the submodule, you can instead just clone the repo somewhere on your system and then just add the `pre-commit` hook (also below) to symlink to this location, for example: + +```bash +git clone https://github.com/xwp/wp-dev-lib.git ~/shared/dev-lib +cd my-plugin/.git/hooks +ln -s ~/shared/dev-lib/pre-commit +``` + +Or to install dev-lib for all plugins that don't already have a `pre-commit` hook installed via symlinks (and using symlinks here is important, so it can find the path to the dev-lib repo): + +```bash +git clone https://github.com/xwp/wp-dev-lib.git ~/shared/dev-lib +for plugin_git_dir in $( find . -type d -path '*/wp-content/plugins/*/.git' ); do + if [ ! -e "$plugin_git_dir/hooks/pre-commit" ]; then + ln -s ~/shared/dev-lib/pre-commit $plugin_git_dir/hooks/pre-commit + fi +done +``` + +## Travis CI Copy the [`.travis.yml`](.travis.yml) file into the root of your repo: @@ -29,14 +46,14 @@ Copy the [`.travis.yml`](.travis.yml) file into the root of your repo: cp dev-lib/.travis.yml . ``` -Note that the builk of the logic in this config file is now moved to [`travis.before_script.sh`](travis.before_script.sh), [`travis.script.sh`](travis.script.sh), and [`travis.after_script.sh`](travis.after_script.sh), so there is minimal chance for the `.travis.yml` to diverge from upstream. +Note that the builk of the logic in this config file is located in [`travis.install.sh`](travis.install.sh), [`travis.script.sh`](travis.script.sh), and [`travis.after_script.sh`](travis.after_script.sh), so there is minimal chance for the `.travis.yml` to diverge from upstream. Additionally, since each project likely may need to have unique environment targets (such as which PHP versions, whether multisite is relevant, etc), it makes sense that `.travis.yml` gets forked. Edit the `.travis.yml` to change the target PHP version(s) and WordPress version(s) you need to test for and also whether you need to test on multisite or not: ```yml php: - 5.3 - - 5.5 + - 7.0 env: - WP_VERSION=latest WP_MULTISITE=0 @@ -45,7 +62,7 @@ env: - WP_VERSION=trunk WP_MULTISITE=1 ``` -Having more variations here is good for open source plugins, which are free for Travis. However, if you are using Travis CI with a private repo you probably want to limit the jobs necessary to complete a build. So if your production environment is running PHP 5.5, is on the latest stable version of WordPress, and is not multisite, then your `.travis.yml` could just be: +Having more variations here is good for open source plugins, which are free for Travis CI. However, if you are using Travis CI with a private repo you probably want to limit the jobs necessary to complete a build. So if your production environment is running PHP 5.5, is on the latest stable version of WordPress, and is not multisite, then your `.travis.yml` could just be: ```yml php: @@ -55,17 +72,17 @@ env: - WP_VERSION=4.0 WP_MULTISITE=0 ``` -This will greatly speed up the time build time, giving you quicker feedback on your Pull Request status, and prevent your Travis build queue from getting too backlogged. +This will greatly speed up the time build time, giving you quicker feedback on your pull request status, and prevent your Travis build queue from getting too backlogged. -### Limiting scope of Travis CI checks +### Limiting Scope of Checks -A barrier of entry for adding Travis CI to an existing project is that may complaining—a lot—about issues in your codebase. To get passing builds you then have a major effort to clean up your codebase to make it conforming to PHP_CodeSniffer, JSHint, and other tools. This is not ideal and can be problematic in projects with a lot of activity since these changes will add lots of conflicts with others' pull requests. +A barrier of entry for adding automated code quality checks to an existing project is that there may be _a lot_ of issues in your codebase that get reported initially. So to get passing builds you would then have a major effort to clean up your codebase to make it conforming to PHP_CodeSniffer, JSHint, and other tools. This is not ideal and can be problematic in projects with a lot of activity since these changes will add lots of conflicts with others' pull requests. -To get around this the barrier of entry for Travis CI, there is now an environment variable defined in [`travis.before_script.sh`](travis.before_script.sh): `LIMIT_TRAVIS_PR_CHECK_SCOPE`. By default its value is `files` which means that when a pull request is opened and Travis runs its checks on the PR, it will limit the checks only to the files that have been touched in the pull request. Additionally, you can override this in the `.ci-env.sh` to be `LIMIT_TRAVIS_PR_CHECK_SCOPE=patches` so that Travis will restrict its scope even more and _only fail a build if any issues are reported on the changed lines (patches) in a PR_ (only PHP_CodeSniffer and JSHint currently are supported for this). +To get around this issue, there is now an environment variable available for configuration: `CHECK_SCOPE`. By default its value is `patches` which means that when a `pre-commit` runs or a pull request is opened, the checks will be restricted in their scope to _only report on issues occurring in the changed lines (patches)_. What's more is that `CHECK_SCOPE=changed-files` can be added in the project config so that the checks will be limited _only to the files that have been modified_. -With `LIMIT_TRAVIS_PR_CHECK_SCOPE=files` and `LIMIT_TRAVIS_PR_CHECK_SCOPE=patches` available, it is much easier to integrate Travis CI checks on existing projects that may have a lot of unconforming legacy code. You can fix up a codebase incrementally file-by-file or line-by-line in the normal course of fixing bugs and adding new features. +With `CHECK_SCOPE=patches` and `CHECK_SCOPE=changed-files` available, it is much easier to integrate automated checks on existing projects that may have a lot of nonconforming legacy code. You can fix up a codebase incrementally line-by-line or file-by-file in the normal course of fixing bugs and adding new features. -If you want to disable Travis from limiting its scope, you can just add `LIMIT_TRAVIS_PR_CHECK_SCOPE=off`. +If you want to disable the scope-limiting behavior, you can define `CHECK_SCOPE=all`. ## Symlinks @@ -123,23 +140,21 @@ cd .git/hooks && ln -s ../../dev-lib/pre-commit . && cd - ## Environment Variables You may customize the behavior of the `.travis.yml` and `pre-commit` hook by -specifying a `.ci-env.sh` in the root of the repo, for example: +specifying a `.dev-lib` (formerly `.ci-env.sh`) Bash script in the root of the repo, for example: ```bash -export PHPCS_GITHUB_SRC=xwpco/PHP_CodeSniffer -export PHPCS_GIT_TREE=phpcs-patch -export PHPCS_IGNORE='tests/*,includes/vendor/*' # See also PATH_INCLUDES below -export WPCS_GIT_TREE=develop -export WPCS_STANDARD=WordPress-Extra -export DISALLOW_EXECUTE_BIT=1 -export YUI_COMPRESSOR_CHECK=1 -export PATH_INCLUDES="docroot/wp-content/plugins/acme-* docroot/wp-content/themes/acme-*" -export LIMIT_TRAVIS_PR_CHECK_SCOPE=patches +PHPCS_GITHUB_SRC=xwp/PHP_CodeSniffer +PHPCS_GIT_TREE=phpcs-patch +PHPCS_IGNORE='tests/*,includes/vendor/*' # See also PATH_INCLUDES below +WPCS_GIT_TREE=develop +WPCS_STANDARD=WordPress-Extra +DISALLOW_EXECUTE_BIT=1 +YUI_COMPRESSOR_CHECK=1 +PATH_INCLUDES="docroot/wp-content/plugins/acme-* docroot/wp-content/themes/acme-*" +CHECK_SCOPE=patches ``` -The last one here `PATH_INCLUDES` is especially useful when the dev-lib is used in the context of an entire site, so you can target just the themes and plugins that you're responsible for. For *excludes*, you can specify a `PHPCS_IGNORE` var and override the `.jshintignore`, though it would be better to have a `PATH_EXCLUDES` as well. - -It is better to add these statements to this file instead of to the `before_script` section of your `.travis.yml` because the `.ci-env.sh` is also `source`ed by the `pre-commit` hook. +The `PATH_INCLUDES` is especially useful when the dev-lib is used in the context of an entire site, so you can target just the themes and plugins that you're responsible for. For *excludes*, you can specify a `PHPCS_IGNORE` var and override the `.jshintignore` (it would be better to have a `PATH_EXCLUDES` as well). ## Plugin Helpers diff --git a/travis.before_script.sh b/travis.before_script.sh deleted file mode 100755 index a04cbae..0000000 --- a/travis.before_script.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -set -e -shopt -s expand_aliases - -# TODO: These should not override any existing environment variables -export WP_CORE_DIR=/tmp/wordpress -export WP_TESTS_DIR=${WP_CORE_DIR}/tests/phpunit -export PLUGIN_DIR=$(pwd) -export PLUGIN_SLUG=$(basename $(pwd) | sed 's/^wp-//') -export PHPCS_DIR=/tmp/phpcs -export PHPCS_GITHUB_SRC=squizlabs/PHP_CodeSniffer -export PHPCS_GIT_TREE=master -export PHPCS_IGNORE='dev-lib/*,node_modules/*,tests/*,vendor/*' -export WPCS_DIR=/tmp/wpcs -export WPCS_GITHUB_SRC=WordPress-Coding-Standards/WordPress-Coding-Standards -export WPCS_GIT_TREE=master -export YUI_COMPRESSOR_CHECK=1 -export DISALLOW_EXECUTE_BIT=0 -export LIMIT_TRAVIS_PR_CHECK_SCOPE=files # when set to 'patches', limits reports to only lines changed; TRAVIS_PULL_REQUEST must not be 'false' -export PATH_INCLUDES=./ -export WPCS_STANDARD=$(if [ -e phpcs.ruleset.xml ]; then echo phpcs.ruleset.xml; else echo WordPress-Core; fi) -if [ -e .jscsrc ]; then - export JSCS_CONFIG=.jscsrc -elif [ -e .jscs.json ]; then - export JSCS_CONFIG=.jscs.json -fi - -# Load a .ci-env.sh to override the above environment variables -if [ -e .ci-env.sh ]; then - source .ci-env.sh -fi - -# Install the WordPress Unit Tests -if [ -e phpunit.xml ] || [ -e phpunit.xml.dist ]; then - bash $DEV_LIB_PATH/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - cd ${WP_CORE_DIR}/src/wp-content/plugins - mv $PLUGIN_DIR $PLUGIN_SLUG - cd $PLUGIN_SLUG - ln -s $(pwd) $PLUGIN_DIR - echo "Plugin location: $(pwd)" - - if ! command -v phpunit >/dev/null 2>&1; then - wget -O /tmp/phpunit.phar https://phar.phpunit.de/phpunit.phar - chmod +x /tmp/phpunit.phar - alias phpunit='/tmp/phpunit.phar' - fi -fi - -# Install PHP_CodeSniffer and the WordPress Coding Standards -mkdir -p $PHPCS_DIR && curl -L https://github.com/$PHPCS_GITHUB_SRC/archive/$PHPCS_GIT_TREE.tar.gz | tar xz --strip-components=1 -C $PHPCS_DIR -mkdir -p $WPCS_DIR && curl -L https://github.com/$WPCS_GITHUB_SRC/archive/$WPCS_GIT_TREE.tar.gz | tar xz --strip-components=1 -C $WPCS_DIR -$PHPCS_DIR/scripts/phpcs --config-set installed_paths $WPCS_DIR - -# Install JSHint -if ! command -v jshint >/dev/null 2>&1; then - npm install -g jshint -fi - -# Install jscs -if [ -n "$JSCS_CONFIG" ] && [ -e "$JSCS_CONFIG" ] && ! command -v jscs >/dev/null 2>&1; then - npm install -g jscs -fi - -# Install Composer -if [ -e composer.json ]; then - curl -s http://getcomposer.org/installer | php && php composer.phar install -fi - -set +e diff --git a/travis.install.sh b/travis.install.sh new file mode 100755 index 0000000..ccb34bf --- /dev/null +++ b/travis.install.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e +shopt -s expand_aliases +source "$DEV_LIB_PATH/check-diff.sh" +set_environment_variables +install_tools diff --git a/travis.script.sh b/travis.script.sh index 21c13c3..b0a3ac1 100755 --- a/travis.script.sh +++ b/travis.script.sh @@ -2,82 +2,12 @@ set -e -if [ "$TRAVIS_PULL_REQUEST" != 'false' ] && ( [ "$LIMIT_TRAVIS_PR_CHECK_SCOPE" == 'files' ] || [ "$LIMIT_TRAVIS_PR_CHECK_SCOPE" == 'patches' ] ); then - git diff --diff-filter=AM --no-prefix --unified=0 $TRAVIS_BRANCH...$TRAVIS_COMMIT -- $PATH_INCLUDES | php $DEV_LIB_PATH/parse-diff-ranges.php > /tmp/checked-files -else - find $PATH_INCLUDES -type f | grep -v -E "^./(dev-lib|\.git)/" | sed 's:^\.//*::' > /tmp/checked-files -fi - -echo "LIMIT_TRAVIS_PR_CHECK_SCOPE: $LIMIT_TRAVIS_PR_CHECK_SCOPE" -echo "TRAVIS_BRANCH: $TRAVIS_BRANCH" -echo "Files to check:" -cat /tmp/checked-files +echo "## Checking files, scope $CHECK_SCOPE:" +cat "$TEMP_DIRECTORY/paths-scope" echo -function remove_diff_range { - sed 's/:[0-9][0-9]*-[0-9][0-9]*$//' | sort | uniq -} -function filter_php_files { - grep -E '\.php(:|$)' -} -function filter_js_files { - grep -E '\.js(:|$)' -} - -# Run PHP syntax check -for php_file in $( cat /tmp/checked-files | remove_diff_range | filter_php_files ); do - php -lf "$php_file" -done - -# Run JSHint -if ! cat /tmp/checked-files | remove_diff_range | filter_js_files | xargs --no-run-if-empty jshint --reporter=unix $( if [ -e .jshintignore ]; then echo "--exclude-path .jshintignore"; fi ) > /tmp/jshint-report; then - echo "## JSHint" - if [ "$LIMIT_TRAVIS_PR_CHECK_SCOPE" == 'patches' ]; then - # Note that filter-report-for-patch-ranges will exit 1 if any files and lines in the report match any files of /tmp/checked-files - echo "Filtering issues to patch subsets..." - cat /tmp/jshint-report | php $DEV_LIB_PATH/filter-report-for-patch-ranges.php /tmp/checked-files - else - cat /tmp/jshint-report - exit 1 - fi -fi - -# Run JSCS -if [ -n "$JSCS_CONFIG" ] && [ -e "$JSCS_CONFIG" ]; then - echo "## JSCS" - if ! cat /tmp/checked-files | remove_diff_range | filter_js_files | xargs --no-run-if-empty jscs --reporter=inlinesingle --verbose --config="$JSCS_CONFIG" > /tmp/jscs-report; then - if [ "$LIMIT_TRAVIS_PR_CHECK_SCOPE" == 'patches' ]; then - # Note that filter-report-for-patch-ranges will exit 1 if any files and lines in the report match any files of /tmp/checked-files - echo "Filtering issues to patch subsets..." - cat /tmp/jscs-report | php $DEV_LIB_PATH/filter-report-for-patch-ranges.php /tmp/checked-files - else - cat /tmp/jscs-report - exit 1 - fi - fi -fi - -# Run PHP_CodeSniffer -echo "## PHP_CodeSniffer" -if ! cat /tmp/checked-files | remove_diff_range | filter_php_files | xargs --no-run-if-empty $PHPCS_DIR/scripts/phpcs -s --report-emacs=/tmp/phpcs-report --standard=$WPCS_STANDARD $(if [ -n "$PHPCS_IGNORE" ]; then echo --ignore=$PHPCS_IGNORE; fi); then - if [ "$LIMIT_TRAVIS_PR_CHECK_SCOPE" == 'patches' ]; then - # Note that filter-report-for-patch-ranges will exit 1 if any files and lines in the report match any files of /tmp/checked-files - echo "Filtering issues to patch subsets..." - cat /tmp/phpcs-report | php $DEV_LIB_PATH/filter-report-for-patch-ranges.php /tmp/checked-files - else - cat /tmp/phpcs-report - exit 1 - fi -fi - -# Run PHPUnit tests -if [ -e phpunit.xml ] || [ -e phpunit.xml.dist ]; then - phpunit $( if [ -e .coveralls.yml ]; then echo --coverage-clover build/logs/clover.xml; fi ) -fi - -# Run YUI Compressor Check -if [ "$YUI_COMPRESSOR_CHECK" == 1 ] && [ 0 != $( cat /tmp/checked-files | filter_js_files | wc -l ) ]; then - YUI_COMPRESSOR_PATH=/tmp/yuicompressor-2.4.8.jar - wget -O "$YUI_COMPRESSOR_PATH" https://github.com/yui/yuicompressor/releases/download/v2.4.8/yuicompressor-2.4.8.jar - cat /tmp/checked-files | remove_diff_range | filter_js_files | xargs --no-run-if-empty java -jar "$YUI_COMPRESSOR_PATH" -o /dev/null 2>&1 -fi +check_execute_bit +lint_js_files +lint_php_files +run_phpunit_travisci +run_codeception