Skip to content

Closes #52: Make Unit/Integration bootstraps self-locating - #55

Merged
remyperona merged 1 commit into
developfrom
enhancement/52-self-locating-bootstrap
Aug 18, 2026
Merged

Closes #52: Make Unit/Integration bootstraps self-locating#55
remyperona merged 1 commit into
developfrom
enhancement/52-self-locating-bootstrap

Conversation

@remyperona

Copy link
Copy Markdown
Contributor

🤖 AI-generated — created by an automated pipeline. Review before acting on this.

Closes #52

Summary

PHPUnit re-runs the configured bootstrap file standalone in isolated child processes (@runInSeparateProcess), where the wpmedia-phpunit bin never runs and WPMEDIA_PHPUNIT_ROOT_DIR / WPMEDIA_PHPUNIT_ROOT_TEST_DIR are never defined, causing a fatal "Undefined constant" error. This fix makes both bootstraps self-locating: they now guard-invoke BootstrapManager::setupConstants() at startup to self-derive the constants when absent, eliminating the need for consumers to maintain per-plugin Tests/{Unit,Integration}/init-tests.php shims whose only job was to pre-define these two constants.

What was done

  • Added a self-locating guard block to the top of both src/Unit/bootstrap.php and src/Integration/bootstrap.php (before the existing requires).
  • The block mirrors the wpmedia-phpunit bin's pattern: guarded class_exists() check, require_once the BootstrapManager by relative path, then invoke setupConstants($suite).
  • The guard (if ( ! defined() )) ensures idempotence: when the bin pre-defines the constants in the parent process, or when the package's own self-test pre-defines them via init-tests.php, the block short-circuits and avoids a double-define warning (which becomes an exception under convertWarningsToExceptions).

How to test

  1. Package's own test regression: composer test-unit and composer test-integration both remain green (119 and 29 tests respectively), confirming the guard short-circuits correctly and convertWarningsToExceptions does not trip.
  2. PHPStan clean: composer phpstan passes against the existing baseline (no new issues introduced).
  3. Isolated child (@runInSeparateProcess): A consumer can now delete their Tests/{Unit,Integration}/init-tests.php shims, point phpunit's bootstrap= directly at vendor/wp-media/phpunit/src/Unit/bootstrap.php (or Integration), and run suites with isolated tests — the constants self-derive and no "Undefined constant" fatal occurs.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Affected Features & Quality Assurance Scope

  • Bootstrap initialization path for isolated test processes.
  • Unit and integration test suites (both library self-tests and downstream consumers).

Technical description

The root cause is architectural: src/Unit/bootstrap.php and src/Integration/bootstrap.php use the two constants but never define them — they assume a caller already did. This holds when invoked through the wpmedia-phpunit bin (which calls BootstrapManager::setupConstants() in the parent), but fails when PHPUnit re-executes the bootstrap file standalone in an isolated child process where the bin never ran.

The fix is a root-cause repair, not a workaround: add the same guarded setup block to the bootstraps themselves. Since the autoloader is not yet registered at the top of the bootstrap (it is only required later in init_test_suite()), we mirror the bin's approach: guard with class_exists(), then require_once the BootstrapManager by relative path (dirname(__DIR__)src/), then call setupConstants($suite). The if ( ! defined() ) guard is load-bearing for the package's own self-test, which uses convertWarningsToExceptions="true" and pre-defines the constants via Tests/{Unit,Integration}/init-tests.php — a re-define() would emit a warning and become a fatal.

The derivation itself is unchanged: BootstrapManager::getRootDir(false) walks four levels up from src/ to the consumer root (verified against the existing getRootDir.php test case), and setupConstants() defaults the test directory to WPMEDIA_PHPUNIT_ROOT_DIR . '/Tests/' . ucfirst($suite) when no path= override is present (the isolated-child case, where argv carries only PHPUnit's own args).

New dependencies

None.

Risks

None identified. The fix is guarded and mirrors the well-tested bin pattern; it only adds logic to an already-defined block in both bootstraps.

Notes

Intentional scope exclusions:

  • The package's own Tests/{Unit,Integration}/init-tests.php are not deleted. They must stay: the package is not installed under vendor/, so getRootDir(false)'s four-levels-up walk would resolve outside the repo and break the self-test (the documented "self-test caveat" in CLAUDE.md).
  • The wpmedia-phpunit bin is unchanged.
  • The secondary suggestion (adding a WPMEDIA_PHPUNIT_SRC env var or composer extra config to scope code-coverage) is a separate concern and deferred as a potential follow-up (Improve the library to include classes that could be common to all plugins. #30 scope). It is not trivially coupled to this deliverable.

Contributes to #30 (reduce per-plugin boilerplate).

@remyperona remyperona self-assigned this Aug 18, 2026
@remyperona

Copy link
Copy Markdown
Contributor Author

Note

Generated by the AI delivery pipeline (lead-reviewer · Claude Opus 4.8).

Review: ✅ PASS

Verified: composer phpstan clean, composer test-unit green (119/119). Manually reproduced the isolated-child scenario (fresh process, no pre-defined constants, no autoloader) against a synthetic vendor/wp-media/phpunit consumer layout — WPMEDIA_PHPUNIT_ROOT_DIR/WPMEDIA_PHPUNIT_ROOT_TEST_DIR self-derive correctly and no "Undefined constant" or "class not found" fatal occurs. Also verified the ! defined() guard short-circuits silently (no redefine warning) when constants are pre-defined, matching the convertWarningsToExceptions requirement. Matches the spec's Option A exactly; the missing automated test is a sanctioned exception per the spec (agreed — define() is process-global/irreversible and the package's own suite always pre-defines the constants via init-tests.php, so no in-process test can exercise the new branch).

Nice-to-haves:

  • src/Integration/bootstrap.php — the new guard block uses the fully-qualified \WPMedia\PHPUnit\BootstrapManager even though use WPMedia\PHPUnit\BootstrapManager; is already imported at the top of this file (and used unqualified everywhere else in the same file, e.g. BootstrapManager::isGroup(...)). Purely cosmetic — drop the leading \WPMedia\PHPUnit\ for consistency with the rest of the file. (src/Unit/bootstrap.php has no such import, so the FQCN is correctly required there.)

@remyperona

Copy link
Copy Markdown
Contributor Author

Note

Generated by the AI delivery pipeline (qa-engineer · Claude Opus 4.8).

QA: ⚠️ PARTIAL

Headless library (no UI) — Strategy B not applicable. Used API/Analysis (reading src/BootstrapManager.php, src/{Unit,Integration}/bootstrap.php) and test-suite execution via wp-env (Docker was available, so integration/admin suites ran too, not just unit).

Acceptance Criterion Method Result Notes
1. Both bootstraps self-define the two constants via setupConstants('unit'|'integration') when absent Analysis + direct probe Diff matches spec's Option A exactly; extracted the guard block into a standalone PHP script run inside the wp-env container without any pre-defined constants — both WPMEDIA_PHPUNIT_ROOT_DIR and WPMEDIA_PHPUNIT_ROOT_TEST_DIR ended up defined, no fatal.
2. wpmedia-phpunit bin path still works — ! defined() guard skips, no double-define under convertWarningsToExceptions=true Test suite + probe composer test-unit (119 tests), composer test-integration (29 tests), composer test-integration-admin (3 tests) all green — this exercises exactly the pre-defined-constant path (init-tests.php defines first, then requires the bootstrap) under convertWarningsToExceptions=true. Re-running the guard a 2nd time in the probe script also confirmed short-circuit (no re-define() attempted). Note: literally invoking php wpmedia-phpunit unit inside this repo's own checkout fails, but that is the documented pre-existing "self-test caveat" (getRootDir(false) walking 4 levels up lands outside the repo when not installed under vendor/) — unrelated to this PR, BootstrapManager.php is untouched.
3. Isolated child re-running bootstrap standalone no longer fatals with "Undefined constant" Direct probe Same probe as #1: constants absent → guard fires → class_exists false → require_once BootstrapManager.phpsetupConstants('unit') → both constants defined → no fatal.
4. Consumer can point bootstrap= at the library file and delete both init-tests.php shims ⚠️ CANNOT_VERIFY Requires an actual downstream consumer repo (e.g. wp-media/mcp-oauth) with a real @runInSeparateProcess test; out of scope for this local checkout. The mechanism validated in #1/#3 is consistent with this working, but it was not exercised end-to-end.
5. Package's own suite still passes Test suite composer test-unit 119/119, composer test-integration 29/29, composer test-integration-admin 3/3, composer phpstan clean against baseline — all via wp-env on the PR branch.

Scope check: diff touches only src/Unit/bootstrap.php and src/Integration/bootstrap.php (7 lines each) — BootstrapManager.php, the bin, and both init-tests.php shims are untouched, matching the spec's Option A (no test additions expected/required, since the spec explicitly notes setupConstants() can't be unit-tested in-process here).

Smoke tests:

  • Unit suite (119 tests): PASS
  • Integration suite (29 tests) + AdminOnly group (3 tests): PASS
  • PHPStan: clean, no baseline drift

Blockers:

PHPUnit re-runs the configured bootstrap file standalone in isolated
child processes (@runInSeparateProcess), where the wpmedia-phpunit bin
never runs and WPMEDIA_PHPUNIT_ROOT_DIR / WPMEDIA_PHPUNIT_ROOT_TEST_DIR
are never defined, causing a fatal. Guard both bootstraps to self-derive
the constants via BootstrapManager::setupConstants() when absent,
mirroring the bin's class_exists/require_once pattern, so consumers no
longer need a per-plugin Tests/{Unit,Integration}/init-tests.php shim
whose only job was to define these two constants.

Closes #52

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@remyperona
remyperona force-pushed the enhancement/52-self-locating-bootstrap branch from 13109c6 to f87b243 Compare August 18, 2026 18:56
@remyperona
remyperona marked this pull request as ready for review August 18, 2026 18:59
@remyperona
remyperona merged commit 3fae45f into develop Aug 18, 2026
9 checks passed
@remyperona
remyperona deleted the enhancement/52-self-locating-bootstrap branch August 18, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make src/{Unit,Integration}/bootstrap.php self-locating to drop per-project init-tests.php

1 participant