Skip to content

Commit 9d28432

Browse files
bpamiriPeter Amiri
andauthored
fix(test): serialize web test-runner config swap under a named lock (#3025) (#3373)
The web test runner (vendor/wheels/tests/runner.cfm) swaps the LIVE application.wheels struct for test configuration (backup in application.$$$wheels) and restores it at the end of the run. Overlapping test requests clobbered each other's backup, which could restore TEST config as the live config until the next reload=true, or error the parent run's restore outright (key [$$$WHEELS] doesn't exist). Stage-1 slice of the issue-3025 analysis (the titled request-scoped overlay was ruled infeasible in the 2026-06-22 adversarial analysis): - Wrap the swap->run->restore window in an exclusive named cflock ('wheelsTestRunner_<applicationName>', timeout 1800s, throwOnTimeout), following the swap-under-lock precedent in migrator/TenantMigrator.cfc. - Re-entrancy guard: ParallelRunner partition sub-requests re-enter runner.cfm via fresh top-level HTTP GETs while the parent holds the swap. They detect the already-applied swap (application.$$$wheels exists) and skip BOTH the swap and the shared lock (a unique per-request lock-name suffix), so parallel mode cannot deadlock. - The restore now runs in a finally block, and only the request that created the backup restores it — an erroring suite no longer leaves test config live. No loops in the finally block (Lucee 7 miscompiles local-scoped loops in finally). - Delete the orphaned legacy RocketUnit runner twin vendor/wheels/rocketunit_tests/Test.cfc (nothing loads it; the Global.cfc scan-exclusion entry stays). The active CLI/legacy chain via wheels.Test and $restoreTestRunnerApplicationScope is untouched. New spec vendor/wheels/tests/specs/internal/TestRunnerSwapLockSpec.cfc: structural guard for the lock acquisition + re-entrancy detection, and a behavioral nested-run test that reproduced the backup clobber before the fix. Residual gap (intentional, refs #3025): this serializes test-vs-test only. A normal request concurrent with a test run still reads swapped config; true isolation is deferred to a separate-application-context design. Refs #3025 Signed-off-by: Peter Amiri <petera@pai.com> Co-authored-by: Peter Amiri <petera@pai.com>
1 parent c8d24f0 commit 9d28432

4 files changed

Lines changed: 285 additions & 156 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
- Web test runner (`/wheels/core/tests` and `/wheels/app/tests`): the swap→run→restore window that
2+
temporarily replaces the live `application.wheels` config with test configuration is now serialized
3+
under an exclusive named lock, and the restore runs in a `finally` block. Overlapping test requests
4+
can no longer clobber each other's `application.$$$wheels` backup and leave test config live until
5+
the next `reload=true`, and an erroring suite now restores the original config too. ParallelRunner
6+
partition sub-requests detect the already-applied swap and skip both the swap and the shared lock,
7+
so parallel test mode does not deadlock. Note: this serializes test-vs-test only — a normal request
8+
concurrent with a test run still sees swapped config; true isolation is deferred to a
9+
separate-application-context design (refs [#3025](https://github.com/wheels-dev/wheels/issues/3025)).
10+
Also removes the orphaned legacy RocketUnit runner twin `vendor/wheels/rocketunit_tests/Test.cfc`
11+
(nothing loads it; the active legacy chain via `wheels.Test` is unchanged).

vendor/wheels/rocketunit_tests/Test.cfc

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)