Skip to content

Speed up PHPUnit suite and CI setup: trim duplicate tests, shared helpers, faster job setup - #7392

Merged
rebeccahum merged 6 commits into
developfrom
chore/speed-up-tests
Oct 7, 2026
Merged

rebeccahum merged 6 commits into
developfrom
chore/speed-up-tests

Conversation

@rebeccahum

@rebeccahum rebeccahum commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

Follow-up to #7302. Speeds up the PHPUnit suite and its CI jobs. The suite loses duplicate tests and tests that couldn't fail, copy-pasted tests become data providers, and repeated setup moves into shared helpers. There are two commits; reviewing them one at a time is easiest.

1. CI: cut per-job setup time

Each unit-test job spent ~25s in PHPUnit and ~100s in setup. This PR cuts that setup time:

  • Composer: a plain actions/cache keyed on composer.lock replaces ramsey/composer-install. Its cache key hashes every composer.json in the workspace (**/composer.json), which took ~10s per job once the mu-plugins-ext Jetpack and Parse.ly copies are in place.
  • Subversion is installed only when the WordPress test library isn't cached. sjinks/setup-wordpress-test-library needs svn only on a cache miss. Nightly installs it up front; any other version tries without it first and, if that fails, installs svn and retries. This saves ~10s per job.
  • Submodules are cloned in parallel (submodule.fetchJobs, set via GIT_CONFIG_* on the checkout step). That took a local clone from 38s to 25s.
  • mu-plugins-ext files are hard-linked with rsync --link-dest instead of copying ~500 MB (~10s per job). The resulting file set is identical, including the .dockerignore excludes and --delete.
  • Coverage now excludes vip-integrations/, which holds the 3,127 third-party plugin PHP files from mu-plugins-ext. They made up most of the ~47s "Generating code coverage report in Clover XML format" step in each of the 4 coverage jobs, and the longest jobs are the coverage ones. Codecov already ignored them because they aren't in this repo.

Measured on CI. Two green runs of this PR (1, 2) compared with the two most recent green develop runs that have the same 44-job matrix (1, 2):

develop this PR Change
Runner time per run (sum of the 44 unit-test jobs) 108 / 117 min 77 / 81 min −30%
Average job 147 / 160 s 105 / 111 s about −45 s
Coverage jobs (4) 232 / 221 s 124 / 135 s −43%
Slowest job (sets how long the run takes) 259 / 233 s 185 / 145 s −74 s or more

Per-job averages for each step, from the job logs:

Step develop this PR
PHPUnit run 27.4 s 13.7 s
Clover coverage report (coverage jobs only) 43 s 1 s
Subversion install ~12 s in every job nightly jobs only
Composer install 13 s 4.8 s
Copy mu-plugins-ext into the tree 12 s 3.6 s
Checkout with submodules 16.8 s 12.9 s
  • WordPress test library: every job found it in cache on the first attempt, so the Subversion install-and-retry fallback never ran.
  • Noise: cloning mu-plugins-ext varies by 10–20 s between runs, with or without this PR, so "Prepare source code" varies too.
  • One small regression: PHP setup went from about 3.3 s to 5.7 s. It may now pay for the package-list refresh that the removed Subversion install used to run first.
  • Not included: time waiting for a runner.

2. PHPUnit: trim and speed up the suite

WP 7.1.3, same machine, best of two runs, default order:

develop this PR
Tests 2048 1886 (1897 after rebasing onto #7386 and restoring cases found in review)
PHPUnit time, single-site 14.0s 5.7s
PHPUnit time, multisite 15.2s 6.8s
Test methods run in a separate process 19 13
Lines in tests/ 37.3k ~30k

Global

  • VIP_Test_Listener restores $_SERVER and clears Constant_Mocker around every test. That removes about 70 copied clear() calls and several $_SERVER backups, and fixes leaks between classes under --order-by=random.
  • tests/bootstrap.php lowers the bcrypt cost everywhere. Before, the test library only lowered it inside a test, so users created in wpSetUpBeforeClass() cost ~45ms each.
  • New shared helpers in tests/utils/:
    • run_php() runs PHP in a child process without booting WordPress.
    • http_response() builds an HTTP response array.
    • Captures_Errors asserts a return value and a trigger_error() message in one test.
    • Two Site Details helpers.
  • Private copies of the reflection helpers now use tests/utils/utils.php.

Speed

  • No real sleeps. The default-threshold remote-request test passes timeout: 0, and a separate assertion checks the default 1s timeout.
  • Process isolation is gone where it wasn't needed: the stats bootstrap test (it already runs its own child process), request block, Enterprise Search configure tests, 3 Search migration tests (ElasticPress auth header injected through a filter instead of a real ES_SHIELD define), and support-user link denials. Where isolation is still needed it covers fewer processes: the Jetpack compatibility cases share 1 process instead of 4, and the connector-controls cases need 6 instead of 13, grouped by the real constant value.
  • The file ACL tests no longer copy the 6.9 MB fixture image into uploads. They never removed those copies, so each run leaked about 69 MB.
  • Users, posts, attachments and subsites that tests only read are created once per class. Per-test rest_api_init calls and Search boots that the tests didn't use are gone.

Removed or merged

  • Exact duplicates; tests that only exercised WordPress core, PHP or a mock; and copy-paste groups merged into providers. For example, the per-integration is_loaded, version-folder, *_CONFIGS and "inactive when unavailable" tests now live in tests/integrations/test-integration-loading.php, and the old per-integration files are deleted.
  • tests/lib/utils/context.php is renamed to test-class-context.php. PHPUnit only runs test-*.php files, so its tests never ran before.

Tests that couldn't fail, now fixed.

  • Queue rate-limit tests looped over true values instead of post IDs.
  • SettingsHealthJob __in_progress couldn't fail.
  • The bulk_chunk_size override test used the default value.
  • test_replicate_deletes_to_other_index_versions never reached the replicated delete.
  • VIP_PHPMailer::isPermittedPath was never exercised, because tests use MockPHPMailer.
  • The HyperDB "already defined" constants row returned early for another reason.
  • A Queue test passed only when an earlier test had left a Search singleton behind.

Pre-review checklist

Please make sure the items below have been covered before requesting a review:

  • This change works and has been tested locally or in Codespaces (or has an appropriate fallback). The full suite passes locally on WP 7.1.3, single-site and multisite, in random and default order. It also passes in the Parse.ly filter_enabled mode, and on WP 6.5 single-site and multisite with Jetpack skipped. npm run test:smoke passes too.
  • This change works and has been tested on a sandbox. Not applicable; tests and CI only.
  • This change has relevant unit tests (if applicable).
  • This change uses a rollout method to ease with deployment (if applicable - especially for large scale actions that require writes). Not applicable.
  • This change has relevant documentation additions / updates (if applicable). docs/testing.md describes the listener behavior and the shared helpers.

Pre-deploy checklist

  • VIP staff: Ensure any alerts added/updated conform to internal standards (see internal documentation). Not applicable.

Steps to Test

  1. Check that the full CI matrix passes on this PR, and that job durations are in line with the measured numbers above.
  2. Locally: CI=1 ./bin/test.sh --wp 7.1.3 --order-by=random and CI=1 ./bin/test.sh --wp 7.1.3 --multisite 1 --order-by=random.
  3. npm run test:smoke.

🤖 Generated with Claude Code

rebeccahum and others added 2 commits October 7, 2026 13:13
- Install Composer dependencies with a plain cache keyed on composer.lock.
  ramsey/composer-install hashes every composer.json in the workspace,
  which takes ~10s per job once the mu-plugins-ext copies are in place.
- Only install Subversion when the WordPress test library isn't cached
  (always for nightly); otherwise the first attempt succeeds without it.
- Clone submodules in parallel (submodule.fetchJobs).
- Hard-link the mu-plugins-ext files with rsync --link-dest instead of
  copying ~500 MB.
- Exclude vip-integrations/ (third-party plugin copies from
  mu-plugins-ext) from coverage, which makes up most of the ~47s Clover
  report step in coverage jobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove duplicate tests and tests that couldn't fail, merge copy-pasted
tests into data providers, extract shared helpers, and cut avoidable
slow work. 2048 -> 1886 tests; PHPUnit time 14.0s -> 5.7s single-site
and 15.2s -> 6.8s multisite (WP 7.1.3).

- Listener restores $_SERVER and clears Constant_Mocker around every
  test; bootstrap lowers the bcrypt cost globally so users created in
  wpSetUpBeforeClass() are cheap. Drop the per-file copies.
- New shared helpers in tests/utils: run_php(), http_response(),
  Captures_Errors, site-details helpers.
- Fewer process-isolated tests (19 -> 13 methods); no real sleeps; no
  6.9 MB fixture uploads in the ACL tests; shared fixtures per class.
- Rename tests/lib/utils/context.php to test-class-context.php so its
  tests actually run.
- Fix tests that could pass regardless of production behavior, and an
  order-dependent Queue test.
- Point test:smoke at the kept update_core test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rebeccahum
rebeccahum requested a review from a team as a code owner October 7, 2026 19:15
Copilot AI balanced review requested due to automatic review settings October 7, 2026 19:15
@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 40.51%. Comparing base (3d3abac) to head (54d6558).

Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #7392      +/-   ##
=============================================
- Coverage      40.76%   40.51%   -0.25%     
+ Complexity      5591     5590       -1     
=============================================
  Files            305      303       -2     
  Lines          22245    22193      -52     
=============================================
- Hits            9068     8992      -76     
- Misses         13177    13201      +24     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sjinks

sjinks commented Oct 7, 2026

Copy link
Copy Markdown
Member

Awesome!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new child-process helper can deadlock when either output pipe fills, potentially hanging CI jobs.

2 open findings
What changed in this PR

Optimizes PHPUnit execution and CI setup while consolidating duplicated test infrastructure.

Changes:

  • Adds shared test helpers, fixtures, and global state cleanup.
  • Consolidates or removes redundant tests and process isolation.
  • Speeds CI through caching, parallel submodules, hard links, and narrower coverage.
File Description
tests/​vip-support/​test-role.php Creates users only when needed.
tests/​vip-parsely/​Telemetry/​Tracks/​test-class-tracks.php Uses shared reflection helper.
tests/​vip-jetpack/​connection-pilot/​test-class-jetpack-connection-pilot.php Reuses reflection helper.
tests/​vip-helpers/​vip-utils/​test-wpcom-vip-get-user-profile.php Reuses HTTP fixture helper.
tests/​vip-helpers/​vip-utils/​test-vip-utils-vip-is-jetpack-request.php Consolidates request cases.
tests/​vip-helpers/​vip-utils/​test-vip-utils-remote-requests.php Removes real timeout waits.
tests/​vip-feed-cache/​test-vip-feed-cache.php Removes redundant feed tests.
tests/​vip-dashboard/​test-vip-dashboard.php Shares an administrator fixture.
tests/​utils/​utils.php Loads shared test utilities.
tests/​utils/​trait-captures-errors.php Adds error-capture helper.
tests/​utils/​site-details-helpers.php Adds Site Details helpers.
tests/​utils/​run-php.php Adds child-process helper.
tests/​utils/​http-response.php Adds HTTP response builder.
tests/​test-wp-cli-ssl.php Reuses the primary site.
tests/​test-vip-plugins.php Restores plugin global state.
tests/​test-vip-jetpack.php Consolidates Jetpack cases.
tests/​test-vip-integrations.php Parametrizes public API delegation.
tests/​test-local-import-cleanup.php Uses global constant cleanup.
tests/​test-large-media-upload-warning.php Consolidates upload cases.
tests/​test-jetpack.php Reduces isolated processes.
tests/​test-cron-control-rest-access.php Parametrizes REST access cases.
tests/​test-core.php Removes redundant capability cases.
tests/​test-client-mu-plugins.php Consolidates empty results.
tests/​test-cleanup-connection-pilot.php Uses child-process helper.
tests/​test-cache-ttl-manager-rest.php Avoids unnecessary REST dispatch.
tests/​test-cache-purge-term.php Removes duplicate purge coverage.
tests/​telemetry/​trait-telemetry-test-helpers.php Shares telemetry users and setup.
tests/​telemetry/​trait-telemetry-client-test-cases.php Shares client contract tests.
tests/​telemetry/​tracks/​test-tracks-utils.php Consolidates hosting cases.
tests/​telemetry/​tracks/​test-class-tracks.php Removes duplicate Tracks tests.
tests/​telemetry/​tracks/​test-class-tracks-event.php Reuses telemetry fixtures.
tests/​telemetry/​tracks/​test-class-tracks-client.php Reuses client contract tests.
tests/​telemetry/​test-class-telemetry.php Shares users and environment setup.
tests/​telemetry/​test-class-telemetry-event-queue.php Reuses shared users.
tests/​telemetry/​pendo/​test-pendo-utils.php Uses global constant cleanup.
tests/​telemetry/​pendo/​test-class-pendo-track-event.php Trims duplicate property coverage.
tests/​telemetry/​pendo/​test-class-pendo-client.php Reuses client contract tests.
tests/​security/​test-password.php Shares user and parametrizes validation.
tests/​security/​test-login-error.php Clarifies priority behavior.
tests/​search/​test-search-dev-tools.php Removes redundant initialization.
tests/​search/​includes/​classes/​trait-search-test-bootstrap.php Adds shared Search bootstrap.
tests/​search/​includes/​classes/​trait-es-http-mock.php Expands shared ES mocking.
tests/​search/​includes/​classes/​test-concurrency-limiter.php Consolidates concurrency cases.
tests/​search/​includes/​classes/​test-class-versioningcleanupjob.php Reuses Search and HTTP helpers.
tests/​search/​includes/​classes/​test-class-healthjob.php Avoids unnecessary Search boot.
tests/​prometheus/​test-prometheus.php Uses shared warning capture.
tests/​prometheus-collectors/​test-class-filesystem-stats-collector.php Reuses stream fixture.
tests/​performance/​test-do-pings.php Parametrizes metadata filtering.
tests/​performance/​test-class-mime-types-caching.php Shares attachment fixtures.
tests/​parsely/​test-mu-parsely-integration-helpers.php Parametrizes activation requests.
tests/​logstash/​test-logger.php Consolidates validation cases.
tests/​lib/​utils/​test-class-context.php Renames previously undiscovered tests.
tests/​lib/​utils/​test-alerts.php Reuses HTTP fixtures.
tests/​lib/​proxy/​test-is-valid-proxy-verification-key.php Removes duplicate key tests.
tests/​lib/​proxy/​test-ip-forward.php Consolidates IP forwarding cases.
tests/​lib/​proxy/​test-get-proxy-verification-key.php Merges fallback-key coverage.
tests/​lib/​proxy/​test-get-ip-addresses-from-ip-trail.php Parametrizes IP trail parsing.
tests/​lib/​proxy/​test-fix-remote-address-with-verification-key.php Consolidates verified forwarding.
tests/​lib/​proxy/​test-fix-remote-address-from-ip-trail-with-verification-key.php Consolidates verified trails.
tests/​lib/​proxy/​class-ip-forward-test-base.php Centralizes verification-key setup.
tests/​lib/​helpers/​test-environment.php Trims wrapper duplication.
tests/​lib/​helpers/​test-app.php Consolidates Context wrappers.
tests/​lib/​feature/​test-class-feature-ms.php Restores feature state.
tests/​lib/​environment/​test-class-environment.php Removes redundant cleanup.
tests/​integrations/​trait-secondary-blog.php Shares multisite fixture.
tests/​integrations/​test-vip-workflows.php Removes superseded integration tests.
tests/​integrations/​test-vip-security-boost.php Removes superseded integration tests.
tests/​integrations/​test-vip-governance.php Removes superseded integration tests.
tests/​integrations/​test-vip-block-data-api.php Removes superseded integration tests.
tests/​integrations/​test-remote-data-blocks.php Removes shared loading cases.
tests/​integrations/​test-parsely.php Trims duplicate loading coverage.
tests/​integrations/​test-integrations.php Removes redundant reflection test.
tests/​integrations/​test-integrations-public-api.php Consolidates API behavior cases.
tests/​integrations/​test-integration-utils.php Shares filesystem fixtures.
tests/​integrations/​test-content-for-agents.php Removes superseded loading tests.
tests/​files/​trait-vip-stream-wrapper-fixture.php Adds reusable stream fixture.
tests/​files/​test-wp-filesystem-vip-uploads.php Uses reflection helper.
tests/​files/​test-a8c-files-image-resize.php Removes obsolete version guard.
tests/​files/​acl/​test-restrict-all-files.php Parametrizes visibility cases.
tests/​files/​acl/​test-pre-wp-utils.php Uses shared error capture.
tests/​config/​test-site-details-index.php Reuses Site Details helpers.
tests/​class-vip-test-listener.php Restores server and constant state.
tests/​bootstrap.php Lowers bcrypt cost globally.
tests/​admin-notice/​test-capability-condition-integration.php Removes redundant role cases.
tests/​001-core/​test-constants.php Consolidates HyperDB fixtures.
phpunit.xml Excludes copied integrations from coverage.
package.json Updates smoke-test target.
docs/​testing.md Documents optimized test infrastructure.
.github/​workflows/​parsely.yml Enables parallel submodule fetching.
.github/​workflows/​e2e.yml Enables parallel submodule fetching.
.github/​workflows/​ci.yml Enables parallel submodule fetching.
.github/​actions/​run-wp-tests/​action.yml Optimizes Composer, SVN, and coverage setup.
.github/​actions/​prepare-source/​action.yml Hard-links copied plugin files.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/utils/run-php.php Outdated
Comment thread tests/search/includes/classes/trait-search-test-bootstrap.php Outdated
rebeccahum and others added 2 commits October 7, 2026 13:27
…stant_Mocker cleanup contract'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- test_default_purge_permissions keyed its cases by role with a null key
  for the logged-out case, which PHP 8.5 deprecates (and PHPUnit turns
  into an error). Use [ role, allowed ] pairs.
- run_php() read the child's stdout to EOF before stderr, so a child
  that filled the stderr pipe would hang the test run. Send stdout and
  stderr to temporary files instead of pipes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The consolidated do-pings provider drops coverage that verifies an earlier false filter result remains false.

0 open findings

2 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Preserve false results from earlier add_post_metadata filters

tests/​performance/​test-do-pings.php:13

The merged provider drops the distinct case where an earlier add_post_metadata filter has already returned false. The test now claims that non-_encloseme keys preserve the earlier value, but only verifies true; a regression that turns false back into true would pass. Keep the former false-value case in this provider.

🧠 Review effort: Balanced

rebeccahum and others added 2 commits October 7, 2026 13:41
The merged provider only passed true from earlier filters for other meta keys, so a filter that forced true would pass. Restore the false row the old tests had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Jetpack sync queue filters: restore the "not set" rows. A filter that
  returned empty values early would pass every remaining row.
- Pendo track event: send a bool and an int through the event
  properties again. flatten_properties_to_strings() keeps them as-is
  (like the base is_multisite/is_vip_user properties), and no remaining
  test caught casting them to strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The broad test-suite reduction and CI setup changes require final human validation against complete matrix timings and coverage.

0 open findings

🧠 Review effort: Balanced

@rebeccahum
rebeccahum merged commit 537fa63 into develop Oct 7, 2026
62 checks passed
@rebeccahum
rebeccahum deleted the chore/speed-up-tests branch October 7, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants