Skip to content

DRAFT (do not merge): Phase 1 — plugin test harness in src/Testing/ - #2

Draft
detain wants to merge 6 commits into
masterfrom
feature/testing-harness
Draft

DRAFT (do not merge): Phase 1 — plugin test harness in src/Testing/#2
detain wants to merge 6 commits into
masterfrom
feature/testing-harness

Conversation

@detain

@detain detain commented Aug 3, 2026

Copy link
Copy Markdown
Member

⚠️ DRAFT — do not merge

Opened solely to run the 8.2 / 8.3 / 8.4 CI matrix. Per decision D6 of
plugin_plan.md, merge to master is gated on the Phase 4 pilot gate, not this
phase. 69 repos track detain/myadmin-plugin-installer: dev-master, so a merge here
breaks their CI instantly and can break a production composer update.

What this is

Phase 1 of the MyAdmin plugin test-harness buildout: src/Testing/, a shared harness
that lets a plugin's handlers actually execute under test instead of being described
by reflection.

Full design write-up: docs/testing-harness.md.

Additive only. No diff to src/Plugin.php, src/Installer.php,
src/PluginScanner.php, src/VendorGuard.php, src/Loader.php, src/modules.php,
src/function_requirements.php, src/Command/, or composer.json — verified per file.

Results

Fleet state re-measured independently before any change: 66 pass / 3 fail of 69. The
three red are exactly the repos blocked on this phase.

repo before after bootstrap
myadmin-virtuozzo-vps 52 tests, 52 errors, 119 assertions OK — 52 tests, 122 assertions 3 lines (had none)
myadmin-mail-module 60 tests, 39 errors + 1 failure, 43 assertions 0 errors, 1 failure, 119 assertions 3 lines
myadmin-vps-module 63 tests, 8 errors + 7 failures, 215 assertions identical failure set, 215 assertions 3 lines replacing 341
myadmin-kvm-vps (control) OK — 36 tests, 108 assertions OK — unchanged 3 lines

Both blocked repos went green with no change to their src/. vps-module's failing set
is byte-identical before and after. Fleet-wide: 66 of 66 loadable plugins execute
getSettings()
(337 settings registered, zero throws); 40 execute getMenu().

Installer suite: 329 tests / 727 assertions, OK, ~0.9 s (was 189 tests / 0.65 s).

How it works

The installer's autoload.files already defines get_module_db(),
get_module_settings(), get_service_define() and function_requirements() into every
production install, so a function_exists-guarded stub of those names is dead code.
Three of the four are pure delegations to \MyAdmin\App, so aliasing FakeApp into
that name makes the real, unmodified installer functions work against the fakes. The
fourth reads $GLOBALS['modules'], which Bootstrap populates via the installer's own
register_module(). Verified by spike, not assumed.

Review focus

  • D2 / risk R2 — the critical one. src/Testing/stubs.php defines myadmin_log(),
    has_acl() and dialog(). If it ever reached autoload.files it would shadow the real
    ones in every production install: logging stops, has_acl() returns a fixed answer.
    AutoloadTripwireTest enforces this and was observed failing when the path is added
    (mutation M1) and when a stub loses its guard (M9). composer dump-autoload -o confirms
    autoload_files.php holds only the two known entries and zero Testing paths.
  • Fake signature fidelity / risk R5. SignaturePinTest pins every FakeSettings /
    FakeMenu signature as data. Five entries in the plan's original list disagreed with
    core; the harness follows core.
  • ConstantStub over-capture / risk R3. 20 adversarial cases.

Mutation testing: 14 killed, 1 equivalent (documented).

✅ CI matrix — all four legs green

The two pre-existing failures that made master red since at least 2026-04-19 are now
fixed on master (4e8943c, db0d18d) and merged in here, so this branch's matrix
reflects the harness alone.

leg result
8.2 / ubuntu
8.3 / ubuntu
8.4 / ubuntu ✅ — resolves symfony/console v8.1.2, so the typed parent is genuinely exercised
8.2 / windows ✅ — 192 tests, no failures

Both fixes are recorded in detail in plugin_plan.md. In short:

  1. It was never a PHP 8.4 language bug — it is a symfony/console 8.0
    incompatibility. Symfony 8 added a native : void to Command::configure() and all
    three commands overrode it untyped, a load-time fatal. It surfaced only on 8.4 because
    symfony/console 8 requires PHP >= 8.4; the other legs resolve 7.4.x, whose parent is
    untyped. Fixed by declaring : void, which is correct against both majors, and pinned
    by a reflection test so the contract is checked on every leg rather than only where a
    resolver happens to pick Symfony 8.

  2. The Windows failures were not path-separator bugs — that was the symptom. Both
    VendorGuard::statusLines() and the test fixture shelled out with 2>/dev/null, which
    cmd.exe cannot resolve; the redirection failed and killed the command line before git
    ran
    . Every invocation returned non-zero, which statusLines() reads as "clean" — so
    the guard that exists to stop Composer silently stashing developer work was itself
    silently inert on Windows
    . (The earlier note in this PR attributing the 5 failures to
    PluginScannerTest was wrong; they were VendorGuardTest.)

Unix behaviour is unchanged by either fix.

Open decision for the owner

The namespace-scoped stub mechanism (amends D2/§629). It works, but Phase 1 measurement
shows it is not required for the four contested functions — FakeApp covers them all.
Recommendation is a hybrid: nothing by default, a generated committed forwarder file where
a plugin-specific helper genuinely needs one. Reasoning in docs/testing-harness.md §8.

🤖 Generated with Claude Code

detain and others added 6 commits August 3, 2026 18:08
Bootstrap::init() + fakes + ConstantStub so plugin handlers can actually
execute under test. Never shipped to master (D6): 69 repos track dev-master.

Key design point, verified by spike rather than assumed: the installer's
autoload.files already defines get_module_db/get_service_define/
function_requirements/get_module_settings into production, so a
function_exists-guarded stub of those names is dead code. Three of the four
are pure delegations to \MyAdmin\App, so aliasing FakeApp into that name
makes the real, unmodified installer functions work against the fakes. The
fourth reads $GLOBALS['modules'], which register_module() populates.

D2 tripwire test included and mutation-verified: adding a src/Testing/ path
to autoload.files turns the suite red.
Implicitly nullable parameter types are deprecated in PHP 8.4; 12 fake
constructors used 'CallLog $log = null'. Explicit '?CallLog' is valid from
7.1 so the >=7.4 floor is unaffected.
Brings the two pre-existing CI failures' fixes onto the harness branch so
PR #2's matrix reflects the harness alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant