From 4d069802771569c5f11a3021428c7e957bbb2567 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Fri, 22 May 2026 09:40:45 -0400 Subject: [PATCH 01/12] Use pre-installed PCOV within Docker environment. --- .env.example | 3 +++ .../workflows/reusable-phpunit-tests-v3.yml | 26 +------------------ docker-compose.yml | 2 ++ tools/local-env/php-config.ini | 6 +++++ tools/local-env/scripts/docker.js | 19 ++++++++++++++ 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index 6974d8554611a..ac96332b0ae92 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,9 @@ LOCAL_PHP_XDEBUG_MODE=develop,debug # Whether or not to enable Memcached. LOCAL_PHP_MEMCACHED=false +# Whether or not to enable PCOV. +LOCAL_PHP_PCOV=false + ## # The database software to use. # diff --git a/.github/workflows/reusable-phpunit-tests-v3.yml b/.github/workflows/reusable-phpunit-tests-v3.yml index bcad042dfe559..e06f9f24be17e 100644 --- a/.github/workflows/reusable-phpunit-tests-v3.yml +++ b/.github/workflows/reusable-phpunit-tests-v3.yml @@ -84,6 +84,7 @@ env: LOCAL_PHP: ${{ inputs.php }}-fpm LOCAL_PHP_XDEBUG: false LOCAL_PHP_XDEBUG_MODE: 'develop,debug' + LOCAL_PHP_PCOV: ${{ inputs.coverage-report }} LOCAL_DB_TYPE: ${{ inputs.db-type }} LOCAL_DB_VERSION: ${{ inputs.db-version }} LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} @@ -200,31 +201,6 @@ jobs: - name: Install WordPress run: npm run env:install - # Installs PCOV as the code coverage driver for the PHPUnit run below. - # - # The INI directives tune PCOV for WordPress's codebase: - # - `pcov.enabled` keeps the Zend hooks active (this is the default, but - # stated explicitly for clarity). - # - `pcov.directory` restricts instrumentation to `src/`, so PCOV does not - # record hits for `vendor/`, `tests/`, or WordPress test fixtures that - # PHPUnit would discard at report time anyway. - # - `pcov.initial.files` pre-sizes the internal file tracking array for - # the thousands of files under `src/`, avoiding reallocation churn - # during test warmup. The default of 64 is far too low here. - - name: Install PCOV coverage driver - if: ${{ inputs.coverage-report }} - run: | - docker compose exec -T -u 0 php sh -c ' - pecl install --force pcov-1.0.12 && - docker-php-ext-enable pcov && - { - echo "pcov.enabled=1" - echo "pcov.directory=/var/www/src" - echo "pcov.initial.files=10000" - } >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini && - php -m | grep -i pcov - ' - - name: Run PHPUnit tests${{ inputs.phpunit-test-groups && format( ' ({0} groups)', inputs.phpunit-test-groups ) || '' }}${{ inputs.coverage-report && ' with coverage report' || '' }} continue-on-error: ${{ inputs.allow-errors }} run: | diff --git a/docker-compose.yml b/docker-compose.yml index cc2ed8d94975e..71ecfb8f6b424 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,6 +40,7 @@ services: environment: LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false} XDEBUG_MODE: ${LOCAL_PHP_XDEBUG_MODE-develop,debug} + LOCAL_PHP_PCOV: ${LOCAL_PHP_PCOV-false} LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false} PHP_FPM_UID: ${PHP_FPM_UID-1000} PHP_FPM_GID: ${PHP_FPM_GID-1000} @@ -99,6 +100,7 @@ services: environment: LOCAL_PHP_XDEBUG: ${LOCAL_PHP_XDEBUG-false} + LOCAL_PHP_PCOV: ${LOCAL_PHP_PCOV-false} LOCAL_PHP_MEMCACHED: ${LOCAL_PHP_MEMCACHED-false} PHP_FPM_UID: ${PHP_FPM_UID-1000} PHP_FPM_GID: ${PHP_FPM_GID-1000} diff --git a/tools/local-env/php-config.ini b/tools/local-env/php-config.ini index 6f98802e5c68a..30ee177289f1d 100644 --- a/tools/local-env/php-config.ini +++ b/tools/local-env/php-config.ini @@ -5,3 +5,9 @@ post_max_size = 1G xdebug.start_with_request=trigger xdebug.discover_client_host=true xdebug.client_host=host.docker.internal + +# Pre-sizes the internal file tracking array for the thousands of files under +# `src/` or `build/`, avoiding reallocation churn during test warmup. +# +# The default of 64 is far too low here. +pcov.initial.files=10000 diff --git a/tools/local-env/scripts/docker.js b/tools/local-env/scripts/docker.js index c7b11f0058424..bfe2e36136739 100644 --- a/tools/local-env/scripts/docker.js +++ b/tools/local-env/scripts/docker.js @@ -20,6 +20,25 @@ if ( [ 'exec', 'run' ].includes( dockerCommand[0] ) && ! process.stdin.isTTY ) { dockerCommand.splice( 1, 0, '--no-TTY' ); } +/* + * `pcov.directory` restricts instrumentation to `src/`, so PCOV does not record hits for `vendor/`, `tests/`, or + * WordPress test fixtures that PHPUnit would discard at report time anyway. + */ +if ( process.env.LOCAL_PHP_PCOV === 'true' && dockerCommand.includes( '--coverage-clover' ) ) { + const phpunitIdx = dockerCommand.findIndex( ( arg ) => typeof arg === 'string' && arg.endsWith( 'phpunit' ) ); + if ( phpunitIdx !== -1 ) { + const localDir = process.env.LOCAL_DIR || 'src'; + dockerCommand.splice( + phpunitIdx, + 1, + 'php', + '-d', + `pcov.directory=/var/www/${ localDir }`, + dockerCommand[ phpunitIdx ] + ); + } +} + // Add a --defaults flag to any db command WP-CLI command. See https://core.trac.wordpress.org/ticket/63876. if ( dockerCommand.includes( 'cli' ) && dockerCommand.includes( 'db' ) && ! dockerCommand.includes( '--defaults' ) ) { dockerCommand.push( '--defaults' ); From 454ef979fca3297c0bea6d7845a51033f8ac138b Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Fri, 22 May 2026 09:41:44 -0400 Subject: [PATCH 02/12] Use test images from wpdev-docker-images. See https://github.com/WordPress/wpdev-docker-images/pull/210. --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 71ecfb8f6b424..0af433a972b13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: # The PHP container. ## php: - image: wordpressdevelop/php:${LOCAL_PHP-latest} + image: ghcr.io/wordpress/wpdev-docker-images/php:${LOCAL_PHP-latest}-210 networks: - wpdevnet @@ -93,7 +93,7 @@ services: # The WP CLI container. ## cli: - image: wordpressdevelop/cli:${LOCAL_PHP-latest} + image: ghcr.io/wordpress/wpdev-docker-images/cli:${LOCAL_PHP-latest}-210 networks: - wpdevnet From f667467fd706405c2715e4dde0185f2c732b5053 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Fri, 22 May 2026 10:01:53 -0400 Subject: [PATCH 03/12] Improve accuracy of inline comment. --- tools/local-env/scripts/docker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/local-env/scripts/docker.js b/tools/local-env/scripts/docker.js index bfe2e36136739..8391d2f5c6e98 100644 --- a/tools/local-env/scripts/docker.js +++ b/tools/local-env/scripts/docker.js @@ -21,8 +21,8 @@ if ( [ 'exec', 'run' ].includes( dockerCommand[0] ) && ! process.stdin.isTTY ) { } /* - * `pcov.directory` restricts instrumentation to `src/`, so PCOV does not record hits for `vendor/`, `tests/`, or - * WordPress test fixtures that PHPUnit would discard at report time anyway. + * `pcov.directory` restricts instrumentation to the configured `LOCAL_DIR`. This prevents PCOV from recording hits for + * `vendor/`, `tests/`, or WordPress test fixtures that PHPUnit would discard at report time anyway. */ if ( process.env.LOCAL_PHP_PCOV === 'true' && dockerCommand.includes( '--coverage-clover' ) ) { const phpunitIdx = dockerCommand.findIndex( ( arg ) => typeof arg === 'string' && arg.endsWith( 'phpunit' ) ); From e2c8774c7e8d88401bb648bed69fd1492b9350f7 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 06:49:34 -0500 Subject: [PATCH 04/12] Switch back to the published Docker images. WordPress/wpdev-docker-images#210 has been merged and the published `wordpressdevelop/php` and `wordpressdevelop/cli` images now include PCOV, so the temporary test images are no longer needed. Co-Authored-By: Claude Opus 5.5 --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0af433a972b13..71ecfb8f6b424 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: # The PHP container. ## php: - image: ghcr.io/wordpress/wpdev-docker-images/php:${LOCAL_PHP-latest}-210 + image: wordpressdevelop/php:${LOCAL_PHP-latest} networks: - wpdevnet @@ -93,7 +93,7 @@ services: # The WP CLI container. ## cli: - image: ghcr.io/wordpress/wpdev-docker-images/cli:${LOCAL_PHP-latest}-210 + image: wordpressdevelop/cli:${LOCAL_PHP-latest} networks: - wpdevnet From b6961c15126fa2453edcc932a7e9b0c08d5d341b Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:15:37 -0500 Subject: [PATCH 05/12] Improve accuracy of the `pcov.initial.files` inline comment. The setting sizes several internal PCOV tables, which also track files loaded from outside `pcov.directory` (such as test and vendor files). Code coverage is also only ever collected for `src/`. The comment now uses `;`, which is the comment syntax for INI files. Lines starting with `#` are parsed as directives, so the new wording would otherwise cause a syntax error and prevent `pcov.initial.files` from being applied. Co-Authored-By: Claude Opus 5.5 --- tools/local-env/php-config.ini | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/local-env/php-config.ini b/tools/local-env/php-config.ini index 30ee177289f1d..c01d94af40aa2 100644 --- a/tools/local-env/php-config.ini +++ b/tools/local-env/php-config.ini @@ -6,8 +6,7 @@ xdebug.start_with_request=trigger xdebug.discover_client_host=true xdebug.client_host=host.docker.internal -# Pre-sizes the internal file tracking array for the thousands of files under -# `src/` or `build/`, avoiding reallocation churn during test warmup. -# -# The default of 64 is far too low here. +; Pre-sizes PCOV's internal file tables (default: 64) to more than the number +; of PHP files loaded during a test run, including test and vendor files. +; This avoids repeatedly resizing the tables as files are loaded. pcov.initial.files=10000 From 6d8195a10d6448a4f6e9c363342dead02fa2ae91 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:16:38 -0500 Subject: [PATCH 06/12] Remove the `pcov.directory` override from `docker.js`. When `pcov.directory` is not set, PCOV restricts collection to the `src` directory within the working directory (`/var/www` in the containers). This matches the code coverage filter in `phpunit.xml.dist` and `tests/phpunit/multisite.xml`. Setting the directory to `LOCAL_DIR` would cause empty coverage reports when `LOCAL_DIR` is `build`, because the PHPUnit filter only includes files in `src`. Co-Authored-By: Claude Opus 5.5 --- tools/local-env/scripts/docker.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tools/local-env/scripts/docker.js b/tools/local-env/scripts/docker.js index a7e7edc50431e..a6b0f4e0af38e 100644 --- a/tools/local-env/scripts/docker.js +++ b/tools/local-env/scripts/docker.js @@ -19,25 +19,6 @@ if ( [ 'exec', 'run' ].includes( dockerCommand[0] ) && ! process.stdin.isTTY ) { dockerCommand.splice( 1, 0, '--no-TTY' ); } -/* - * `pcov.directory` restricts instrumentation to the configured `LOCAL_DIR`. This prevents PCOV from recording hits for - * `vendor/`, `tests/`, or WordPress test fixtures that PHPUnit would discard at report time anyway. - */ -if ( process.env.LOCAL_PHP_PCOV === 'true' && dockerCommand.includes( '--coverage-clover' ) ) { - const phpunitIdx = dockerCommand.findIndex( ( arg ) => typeof arg === 'string' && arg.endsWith( 'phpunit' ) ); - if ( phpunitIdx !== -1 ) { - const localDir = process.env.LOCAL_DIR || 'src'; - dockerCommand.splice( - phpunitIdx, - 1, - 'php', - '-d', - `pcov.directory=/var/www/${ localDir }`, - dockerCommand[ phpunitIdx ] - ); - } -} - // Add a --defaults flag to any db command WP-CLI command. See https://core.trac.wordpress.org/ticket/63876. if ( dockerCommand.includes( 'cli' ) && dockerCommand.includes( 'db' ) && ! dockerCommand.includes( '--defaults' ) ) { dockerCommand.push( '--defaults' ); From 48b9ae98c502cdd1b7474ef8aad67c137a50cb93 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:58:48 -0500 Subject: [PATCH 07/12] Honor the PCOV opt-in when generating HTML coverage reports. Passing `--coverage-html` (as `npm run test:coverage` does) previously always enabled Xdebug. Because PCOV is not interoperable with Xdebug, the container would then disable PCOV even when `LOCAL_PHP_PCOV` was set to `true`. Xdebug is now only enabled for HTML coverage reports when PCOV has not been enabled. Co-Authored-By: Claude Opus 5.5 --- tools/local-env/scripts/docker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/local-env/scripts/docker.js b/tools/local-env/scripts/docker.js index a6b0f4e0af38e..33aed5cbb504b 100644 --- a/tools/local-env/scripts/docker.js +++ b/tools/local-env/scripts/docker.js @@ -8,7 +8,8 @@ local_env_utils.ensure_env_file(); dotenvExpand.expand( dotenv.config() ); -if ( process.argv.includes( '--coverage-html' ) ) { +// Use Xdebug to generate HTML code coverage reports, unless PCOV has been enabled. +if ( process.argv.includes( '--coverage-html' ) && process.env.LOCAL_PHP_PCOV !== 'true' ) { process.env.LOCAL_PHP_XDEBUG = 'true'; process.env.LOCAL_PHP_XDEBUG_MODE = 'coverage'; } From d14a8613f8866ba9bc7f4344fc025bbbec384923 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 10:59:02 -0500 Subject: [PATCH 08/12] Note that PCOV requires Xdebug to be disabled in `.env.example`. Co-Authored-By: Claude Opus 5.5 --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index da71909650f2d..6e0e661b977a7 100644 --- a/.env.example +++ b/.env.example @@ -45,7 +45,12 @@ LOCAL_PHP_XDEBUG_MODE=develop,debug # Whether or not to enable Memcached. LOCAL_PHP_MEMCACHED=false +## # Whether or not to enable PCOV. +# +# PCOV is not interoperable with Xdebug. To use PCOV, `LOCAL_PHP_XDEBUG` must be +# set to `false`. When both are enabled, Xdebug is used and PCOV is disabled. +## LOCAL_PHP_PCOV=false ## From 1f67d1ad2710eea5f5c9ef65d7a229d55070e7e0 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:44:08 -0500 Subject: [PATCH 09/12] Modify Xdebug coverage condition for better flexibility Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tools/local-env/scripts/docker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/local-env/scripts/docker.js b/tools/local-env/scripts/docker.js index 33aed5cbb504b..6d0c9d787c688 100644 --- a/tools/local-env/scripts/docker.js +++ b/tools/local-env/scripts/docker.js @@ -9,7 +9,7 @@ local_env_utils.ensure_env_file(); dotenvExpand.expand( dotenv.config() ); // Use Xdebug to generate HTML code coverage reports, unless PCOV has been enabled. -if ( process.argv.includes( '--coverage-html' ) && process.env.LOCAL_PHP_PCOV !== 'true' ) { +if ( process.argv.includes( '--coverage-html' ) && ( process.env.LOCAL_PHP_PCOV !== 'true' || process.env.LOCAL_PHP_XDEBUG === 'true' ) ) { process.env.LOCAL_PHP_XDEBUG = 'true'; process.env.LOCAL_PHP_XDEBUG_MODE = 'coverage'; } From 202abdb27b2491fdf97c63cbbeab6032ad1a4ee8 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:06:20 -0500 Subject: [PATCH 10/12] Update the inline comment for the Xdebug coverage condition. The comment now reflects that Xdebug is also used when both PCOV and Xdebug are enabled. The condition is also split across multiple lines for readability. Co-Authored-By: Claude Opus 5.5 --- tools/local-env/scripts/docker.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/local-env/scripts/docker.js b/tools/local-env/scripts/docker.js index 6d0c9d787c688..0ef6d469d19d6 100644 --- a/tools/local-env/scripts/docker.js +++ b/tools/local-env/scripts/docker.js @@ -8,8 +8,11 @@ local_env_utils.ensure_env_file(); dotenvExpand.expand( dotenv.config() ); -// Use Xdebug to generate HTML code coverage reports, unless PCOV has been enabled. -if ( process.argv.includes( '--coverage-html' ) && ( process.env.LOCAL_PHP_PCOV !== 'true' || process.env.LOCAL_PHP_XDEBUG === 'true' ) ) { +// Use Xdebug to generate HTML code coverage reports, unless PCOV is enabled and Xdebug is not. +if ( + process.argv.includes( '--coverage-html' ) && + ( process.env.LOCAL_PHP_PCOV !== 'true' || process.env.LOCAL_PHP_XDEBUG === 'true' ) +) { process.env.LOCAL_PHP_XDEBUG = 'true'; process.env.LOCAL_PHP_XDEBUG_MODE = 'coverage'; } From ae01a60e8eb2005c1e6af8c29d76db10aa4f3a28 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:06:27 -0500 Subject: [PATCH 11/12] Note that PCOV is only available for PHP 7.4 and higher in `.env.example`. Co-Authored-By: Claude Opus 5.5 --- .env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.example b/.env.example index 6e0e661b977a7..a90402036839c 100644 --- a/.env.example +++ b/.env.example @@ -50,6 +50,8 @@ LOCAL_PHP_MEMCACHED=false # # PCOV is not interoperable with Xdebug. To use PCOV, `LOCAL_PHP_XDEBUG` must be # set to `false`. When both are enabled, Xdebug is used and PCOV is disabled. +# +# PCOV is only available for PHP 7.4 and higher. ## LOCAL_PHP_PCOV=false From d40605f2ba0ee05f67ac5f8d8fa143ffabe589ed Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:06:41 -0500 Subject: [PATCH 12/12] Document using PCOV to generate code coverage reports in the README. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 637e24359f034..0f41da84643bd 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,9 @@ npm run test:coverage The command will generate three coverage reports in HTML, PHP, and text formats, saving them in the `coverage` folder. -**Note:** xDebug is required to generate a code coverage report, which can slow down PHPUnit significantly. Passing selection-based options such as `--group` or `--filter` can decrease the overall time required but will result in an incomplete report. +**Note:** A code coverage driver is required to generate a code coverage report. By default, xDebug is used, which can slow down PHPUnit significantly. PCOV is a faster alternative that is available for PHP 7.4 and higher. To use it, set `LOCAL_PHP_PCOV` to `true` and `LOCAL_PHP_XDEBUG` to `false` in your `.env` file. + +Passing selection-based options such as `--group` or `--filter` can decrease the overall time required but will result in an incomplete report. #### To restart the development environment