Context
Phase 2 of the subfolder-deployment work (discussion #2887, issue #2968, PR #2985). The runtime subpath setting shipped in #2985; this issue tracks the test-runner half — the reporter's headline pain point ("the application scope hacks break the main application, which a reload=true fixes. They seem to fight each other").
Problem
The web test runner swaps the live application configuration globally, in place, for the duration of a test request:
vendor/wheels/tests/runner.cfm:36-38 — backs up application.wheels to application.$$$wheels, then mutates the live application.wheels that every concurrent request reads.
vendor/wheels/tests/runner.cfm:286-287 — restores and deletes the backup at end-of-response.
vendor/wheels/Global.cfc:4068 $restoreTestRunnerApplicationScope() + the hooks in events/EventMethods.cfc:4,305 and the flag at events/init/views.cfm:41 exist precisely because this swap can be left half-applied (aborts, errors, timeouts) — the recovery hook is an admission of the fragility, not a fix for it.
- Legacy twin:
vendor/wheels/rocketunit_tests/Test.cfc:8,33-34.
Consequences:
- Any real traffic hitting the app while a test request is in flight executes against test configuration (test datasource, test routes).
- A failed restore leaves the app broken until
?reload=true — the exact "they fight each other" symptom from Running Wheels 4 in a subfolder - And figuring out tests (broken) #2887.
- Subfolder deployments don't cause this; they just make it easier to trip over (per-app paths make a stale restore more visible).
Proposed direction
Replace the global swap with a request-scoped overlay: resolve the effective config per request (e.g. a request.wheels.configOverlay consulted by the config readers, or a composed view over the application struct) so test requests see test config while concurrent requests keep seeing live config, and there is nothing to restore — the overlay dies with the request.
Cross-engine cautions (this touches the hottest config-read paths):
- Config reads happen everywhere; the overlay check must be near-free on the non-test path.
- No function members on
application scope (Adobe), no closures capturing this across scopes — see CLAUDE.md Cross-Engine Invariants.
- The
$restoreTestRunnerApplicationScope() machinery and application.$$$wheels become removable once the overlay lands — delete them in the same PR so the old path can't half-engage.
Acceptance
- A test request running concurrently with a normal request never changes what the normal request reads.
- Killing a test request mid-run (abort/timeout) requires no recovery hook and no
reload=true.
/wheels/app/tests works under a subpath-mounted app (ties back to Running Wheels 4 in a subfolder - And figuring out tests (broken) #2887's setup).
- The
$$$wheels backup/restore and recovery hooks are removed.
Refs #2887. Follow-up to #2985 (phase 1).
Context
Phase 2 of the subfolder-deployment work (discussion #2887, issue #2968, PR #2985). The runtime
subpathsetting shipped in #2985; this issue tracks the test-runner half — the reporter's headline pain point ("the application scope hacks break the main application, which a reload=true fixes. They seem to fight each other").Problem
The web test runner swaps the live application configuration globally, in place, for the duration of a test request:
vendor/wheels/tests/runner.cfm:36-38— backs upapplication.wheelstoapplication.$$$wheels, then mutates the liveapplication.wheelsthat every concurrent request reads.vendor/wheels/tests/runner.cfm:286-287— restores and deletes the backup at end-of-response.vendor/wheels/Global.cfc:4068$restoreTestRunnerApplicationScope()+ the hooks inevents/EventMethods.cfc:4,305and the flag atevents/init/views.cfm:41exist precisely because this swap can be left half-applied (aborts, errors, timeouts) — the recovery hook is an admission of the fragility, not a fix for it.vendor/wheels/rocketunit_tests/Test.cfc:8,33-34.Consequences:
?reload=true— the exact "they fight each other" symptom from Running Wheels 4 in a subfolder - And figuring out tests (broken) #2887.Proposed direction
Replace the global swap with a request-scoped overlay: resolve the effective config per request (e.g. a
request.wheels.configOverlayconsulted by the config readers, or a composed view over the application struct) so test requests see test config while concurrent requests keep seeing live config, and there is nothing to restore — the overlay dies with the request.Cross-engine cautions (this touches the hottest config-read paths):
applicationscope (Adobe), no closures capturingthisacross scopes — see CLAUDE.md Cross-Engine Invariants.$restoreTestRunnerApplicationScope()machinery andapplication.$$$wheelsbecome removable once the overlay lands — delete them in the same PR so the old path can't half-engage.Acceptance
reload=true./wheels/app/testsworks under asubpath-mounted app (ties back to Running Wheels 4 in a subfolder - And figuring out tests (broken) #2887's setup).$$$wheelsbackup/restore and recovery hooks are removed.Refs #2887. Follow-up to #2985 (phase 1).