diff --git a/projects/plugins/jetpack/changelog/try-docker-jetpack-phpunit-php-version b/projects/plugins/jetpack/changelog/try-docker-jetpack-phpunit-php-version new file mode 100644 index 0000000000000..d4ad0c7da029a --- /dev/null +++ b/projects/plugins/jetpack/changelog/try-docker-jetpack-phpunit-php-version @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Update phpunit setup for `jetpack docker phpunit --php` + + diff --git a/projects/plugins/jetpack/changelog/try-docker-jetpack-phpunit-php-version#2 b/projects/plugins/jetpack/changelog/try-docker-jetpack-phpunit-php-version#2 new file mode 100644 index 0000000000000..53c681ee8c50d --- /dev/null +++ b/projects/plugins/jetpack/changelog/try-docker-jetpack-phpunit-php-version#2 @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Remove unnecessary Patchwork include from test. Bootstrap should already have loaded it. + + diff --git a/projects/plugins/jetpack/tests/php/_inc/lib/test-class-jetpack-blogging-prompts.php b/projects/plugins/jetpack/tests/php/_inc/lib/test-class-jetpack-blogging-prompts.php index fc880b625f82f..0559b977d7822 100644 --- a/projects/plugins/jetpack/tests/php/_inc/lib/test-class-jetpack-blogging-prompts.php +++ b/projects/plugins/jetpack/tests/php/_inc/lib/test-class-jetpack-blogging-prompts.php @@ -6,7 +6,6 @@ */ require_once JETPACK__PLUGIN_DIR . '/_inc/blogging-prompts.php'; -require_once JETPACK__PLUGIN_DIR . '/vendor/antecedent/patchwork/Patchwork.php'; /** * Class for testing Jetpack Blogging Prompt functions. diff --git a/projects/plugins/jetpack/tests/php/bootstrap.php b/projects/plugins/jetpack/tests/php/bootstrap.php index 04e10fc5c6b02..895b3d00bedd9 100644 --- a/projects/plugins/jetpack/tests/php/bootstrap.php +++ b/projects/plugins/jetpack/tests/php/bootstrap.php @@ -69,6 +69,11 @@ exit( 1 ); } +// If we're running under `jetpack docker phpunit --php`, load the autoloader for that. +if ( getenv( 'DOCKER_PHPUNIT_BASE_DIR' ) ) { + require getenv( 'DOCKER_PHPUNIT_BASE_DIR' ) . '/vendor/autoload.php'; +} + require $jp_autoloader; if ( '1' !== getenv( 'WP_MULTISITE' ) && ( ! defined( 'WP_TESTS_MULTISITE' ) || ! WP_TESTS_MULTISITE ) ) { diff --git a/projects/plugins/jetpack/tests/php/redefine-exit.php b/projects/plugins/jetpack/tests/php/redefine-exit.php index aca212fd0b467..684d9c3591cd1 100644 --- a/projects/plugins/jetpack/tests/php/redefine-exit.php +++ b/projects/plugins/jetpack/tests/php/redefine-exit.php @@ -14,7 +14,12 @@ class ExitException extends Exception { } -require_once __DIR__ . '/../../vendor/antecedent/patchwork/Patchwork.php'; +// If we're running under `jetpack docker phpunit --php`, Patchwork is located in DOCKER_PHPUNIT_BASE_DIR. +if ( getenv( 'DOCKER_PHPUNIT_BASE_DIR' ) ) { + require_once getenv( 'DOCKER_PHPUNIT_BASE_DIR' ) . '/vendor/antecedent/patchwork/Patchwork.php'; +} else { + require_once __DIR__ . '/../../vendor/antecedent/patchwork/Patchwork.php'; +} $exitfunc = function ( $arg = null ) { // While Patchwork does have a way to exclude files from replacement, diff --git a/tools/cli/commands/docker.js b/tools/cli/commands/docker.js index 0c6a4f727d7f4..58a6d41f438f1 100644 --- a/tools/cli/commands/docker.js +++ b/tools/cli/commands/docker.js @@ -340,7 +340,9 @@ const buildExecCmd = argv => { opts.splice( 1, 0, '-w', '/var/www/html/wp-content/plugins/jetpack' ); // Need to add this option to `exec` before the container name. opts.push( - 'vendor/bin/phpunit', + ...( argv.php + ? [ '/var/scripts/phpunit-version-wrapper.sh', argv.php ] + : [ 'vendor/bin/phpunit' ] ), '--configuration=/var/www/html/wp-content/plugins/jetpack/phpunit.xml.dist', ...unitArgs ); @@ -356,7 +358,9 @@ const buildExecCmd = argv => { opts.splice( 1, 0, '-w', '/var/www/html/wp-content/plugins/jetpack' ); // Need to add this option to `exec` before the container name. opts.push( - 'vendor/bin/phpunit', + ...( argv.php + ? [ '/var/scripts/phpunit-version-wrapper.sh', argv.php ] + : [ 'vendor/bin/phpunit' ] ), '--configuration=/var/www/html/wp-content/plugins/jetpack/tests/php.multisite.xml', ...unitArgs ); @@ -367,7 +371,9 @@ const buildExecCmd = argv => { opts.splice( 1, 0, '-w', '/var/www/html/wp-content/plugins/crm' ); // Need to add this option to `exec` before the container name. opts.push( - 'vendor/bin/phpunit', + ...( argv.php + ? [ '/var/scripts/phpunit-version-wrapper.sh', argv.php ] + : [ 'vendor/bin/phpunit' ] ), '--configuration=/var/www/html/wp-content/plugins/crm/phpunit.xml.dist', ...unitArgs ); @@ -624,10 +630,26 @@ export function dockerDefine( yargs ) { builder: yargExec => defaultOpts( yargExec ), handler: argv => execDockerCmdHandler( argv ), } ) + .command( { + command: 'select-php ', + description: + 'Select the version of PHP for use inside the container. See documentation for important notes!', + builder: yargCmd => { + yargCmd.positional( 'version', { + describe: 'The version to select, or "default".', + type: 'string', + } ); + }, + handler: argv => execDockerCmdHandler( argv ), + } ) .command( { command: 'phpunit', description: 'Run PHPUnit tests inside container', - builder: yargExec => defaultOpts( yargExec ), + builder: yargCmd => + defaultOpts( yargCmd ).option( 'php', { + describe: 'Use the specified version of PHP.', + type: 'string', + } ), handler: argv => execDockerCmdHandler( argv ), } ) .command( { @@ -652,14 +674,22 @@ export function dockerDefine( yargs ) { command: 'phpunit-multisite', alias: 'phpunit:multisite', description: 'Run multisite PHPUnit tests inside container ', - builder: yargExec => defaultOpts( yargExec ), + builder: yargCmd => + defaultOpts( yargCmd ).option( 'php', { + describe: 'Use the specified version of PHP.', + type: 'string', + } ), handler: argv => execDockerCmdHandler( argv ), } ) .command( { command: 'phpunit-crm', alias: 'phpunit:crm', description: 'Run Jetpack CRM PHPUnit inside container', - builder: yargExec => defaultOpts( yargExec ), + builder: yargCmd => + defaultOpts( yargCmd ).option( 'php', { + describe: 'Use the specified version of PHP.', + type: 'string', + } ), handler: argv => execDockerCmdHandler( argv ), } ) .command( { diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 86fa17e1f13d9..a3973b7a09306 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -12,6 +12,13 @@ ENV LC_ALL en_US.UTF-8 WORKDIR /tmp +# Record ARGs +RUN \ + echo "PHP_VERSION=$PHP_VERSION" > /etc/docker-args.sh \ + && echo "NODE_VERSION=$NODE_VERSION" >> /etc/docker-args.sh \ + && echo "COMPOSER_VERSION=$COMPOSER_VERSION" >> /etc/docker-args.sh \ + && echo "PNPM_VERSION=$PNPM_VERSION" >> /etc/docker-args.sh + # Install basic packages, including Apache. RUN --mount=type=cache,target=/var/lib/apt/lists/,sharing=private \ export DEBIAN_FRONTEND=noninteractive \ @@ -41,31 +48,11 @@ RUN --mount=type=cache,target=/var/lib/apt/lists/,sharing=private \ RUN a2enmod rewrite # Install requested version of PHP. +COPY ./config/php.ini /var/lib/jetpack-config/php.ini RUN --mount=type=cache,target=/var/lib/apt/lists/,sharing=private \ + --mount=type=bind,source=./bin/ensure-php-version.sh,target=/usr/local/bin/ensure-php-version.sh \ : "${PHP_VERSION:?Build argument PHP_VERSION needs to be set and non-empty.}" \ - && export DEBIAN_FRONTEND=noninteractive \ - && apt-get install -y \ - libapache2-mod-php${PHP_VERSION} \ - php${PHP_VERSION} \ - php${PHP_VERSION}-bcmath \ - php${PHP_VERSION}-cli \ - php${PHP_VERSION}-curl \ - php${PHP_VERSION}-intl \ - php${PHP_VERSION}-ldap \ - php${PHP_VERSION}-mbstring \ - php${PHP_VERSION}-mysql \ - php${PHP_VERSION}-opcache \ - php${PHP_VERSION}-pgsql \ - php${PHP_VERSION}-soap \ - php${PHP_VERSION}-sqlite3 \ - php${PHP_VERSION}-xdebug \ - php${PHP_VERSION}-xml \ - php${PHP_VERSION}-xsl \ - php${PHP_VERSION}-zip \ - && apt-get install -y --no-install-recommends \ - php${PHP_VERSION}-apcu \ - php${PHP_VERSION}-gd \ - php${PHP_VERSION}-imagick \ + && ensure-php-version.sh "$PHP_VERSION" \ && find /var/ -name '*-old' -delete && rm -rf /var/log/dpkg.log /var/log/alternatives.log /var/log/apt/ ~/.launchpadlib # Install requested version of Composer. @@ -109,10 +96,6 @@ RUN mkdir /usr/local/src/psysh \ # Copy a default config file for an apache host. COPY ./config/apache_default /etc/apache2/sites-available/000-default.conf -# Copy a default set of settings for PHP (php.ini). -COPY ./config/php.ini /etc/php/${PHP_VERSION}/mods-available/jetpack-wordpress.ini -RUN phpenmod jetpack-wordpress - # Copy single site htaccess to /var/lib/jetpack-config. run.sh will move it to the site's base dir if there's none present. COPY ./config/htaccess /var/lib/jetpack-config/htaccess COPY ./config/htaccess-multi /var/lib/jetpack-config/htaccess-multi diff --git a/tools/docker/bin/ensure-php-version.sh b/tools/docker/bin/ensure-php-version.sh new file mode 100755 index 0000000000000..fa7c384213a52 --- /dev/null +++ b/tools/docker/bin/ensure-php-version.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +set -eo pipefail + +source /etc/docker-args.sh + +VER=$1 +if [[ "$1" == default ]]; then + VER="$PHP_VERSION" +elif [[ ! "$1" =~ ^[0-9]+\.[0-9]+$ ]]; then + cat <<-EOF + USAGE: $0 + + may be "default" or a two-part version number like "$PHP_VERSION". + EOF + exit 1 +fi + +export DEBIAN_FRONTEND=noninteractive + +# Determine packages to install. +PKGS=( + "libapache2-mod-php${VER}" + "php${VER}" + "php${VER}-bcmath" + "php${VER}-cli" + "php${VER}-curl" + "php${VER}-intl" + "php${VER}-ldap" + "php${VER}-mbstring" + "php${VER}-mysql" + "php${VER}-opcache" + "php${VER}-pgsql" + "php${VER}-soap" + "php${VER}-sqlite3" + "php${VER}-xdebug" + "php${VER}-xml" + "php${VER}-xsl" + "php${VER}-zip" +) +NO_RECOMMENDS_PKGS=( + "php${VER}-apcu" + "php${VER}-gd" + "php${VER}-imagick" +) + +# php-json is built in in 8.0+. +if [[ "$VER" == [57].* ]]; then + PKGS+=( "php${VER}-json" ) +fi + +# Install selected packages. +printf '\e[1m== Installing PHP %s ==\e[0m\n' "$VER" +apt-get update -q +apt-get install -qy "${PKGS[@]}" +apt-get install -qy --no-install-recommends "${NO_RECOMMENDS_PKGS[@]}" + +# Enable our custom config for the new version. +[[ -e "/etc/php/${VER}/mods-available/jetpack-wordpress.ini" ]] || ln -s /var/lib/jetpack-config/php.ini "/etc/php/${VER}/mods-available/jetpack-wordpress.ini" +phpenmod -v "$VER" jetpack-wordpress diff --git a/tools/docker/bin/phpunit-version-wrapper.sh b/tools/docker/bin/phpunit-version-wrapper.sh new file mode 100755 index 0000000000000..b514886cdf439 --- /dev/null +++ b/tools/docker/bin/phpunit-version-wrapper.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -eo pipefail + +VER="$1" +DIR="/usr/local/src/phpunit-for-$VER/$( basename "$PWD" )" +shift + +/var/scripts/ensure-php-version.sh "$VER" + +printf '\n\e[1m== Installing Composer deps for PHP %s externally ==\e[0m\n' "$VER" +mkdir -p "$DIR" +jq --arg PWD "$PWD" --arg VER "$( "php$VER" -r 'printf( "%d.%d.%d", PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION );' )" ' + { + config: { + platform: { + php: $VER, + }, + }, + repositories: [ + { + type: "path", + url: ( $PWD + "/../../packages/*" ), + options: { + monorepo: true, + }, + } + ], + "require-dev": .["require-dev"], + } +' composer.json > "$DIR/composer.json" +composer --working-dir="$DIR" update + +printf '\n\e[1m== Uninstalling Composer dev deps from monorepo ==\e[0m\n' +composer install --no-dev +# The above may have created files owned by the in-docker user ID, which probably doesn't match the user ID on the host system. +# Avoid confusing users later by changing the ownership of such files. +if [[ $(stat -c %u .) -ne $EUID ]]; then + find -P vendor jetpack_vendor -xdev -user "$EUID" -exec chown --reference=. -h {} + &>/dev/null || true +fi + +echo +printf '\e[30;43m ** Note contents of vendor/ have been changed! ** \e[0m\n' +printf '\e[30;43m ** You may want to run `jetpack install` when done testing to fix it. ** \e[0m\n' + +printf '\n\e[1m== Running phpunit ==\e[0m\n' +export DOCKER_PHPUNIT_BASE_DIR="$DIR" +exec "php$VER" "$DIR/vendor/bin/phpunit" "$@"