fix: skip user hooks when replaying cached skipped, incomplete, and failed tests - #1806
Open
oddvalue wants to merge 1 commit into
Open
fix: skip user hooks when replaying cached skipped, incomplete, and failed tests#1806oddvalue wants to merge 1 commit into
oddvalue wants to merge 1 commit into
Conversation
…ailed tests When Tia replays a cached skipped, incomplete, or failed result, setUp() throws before parent::setUp() has booted anything, but $__replay was left at ReplayType::None. PHPUnit still invokes the after-test hooks after a setUp() throw, so tearDown() ran the full afterEach chain (and parent::tearDown()) against a test case that was never set up. In Laravel suites, any afterEach touching the framework then failed with "A facade root has not been set", turning cached skips into errors and making replay results oscillate between runs. Mark the replay state before throwing, so tearDown() short-circuits for every replayed result exactly as it does for the Pass/Risky replay path. Fixes pestphp#1785
|
Just want to +1 this — we ran into the exact same thing yesterday and spent half a day chasing it before finding this PR. 😅 Our setup: ~11k test Laravel suite where tearDown() resolves tenancy from the container, and 14 tests that skip at runtime when ffmpeg isn't installed. Replays kept flip-flopping — run it once, 14 errors ("A facade root has not been set"), run it again, all green, run it again, 14 errors. No code changes in between. Drove us a bit crazy until we instrumented the graph and saw the cached skips getting overwritten by the teardown errors, exactly like this PR describes. So: confirmed independently, fix works great on a big real-world suite. Would love to see this merged 🙏 |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What:
Description:
When TIA replays a cached Pass/Risky result,
Testable::setUp()sets$__replayand returns early, andtearDown()checks$__replayand short-circuits too — neitherparent::setUp()(which, in Laravel suites, boots the application) nor the user'sbeforeEach/afterEachchains run. That symmetry is the established semantic of replay: a replayed test executes no user hooks.The cached Skipped, Incomplete, and Failure branches broke that symmetry: they throw (
markTestSkipped()/markTestIncomplete()/AssertionFailedError) from insidesetUp()beforeparent::setUp()has run, while leaving$__replayatReplayType::None. PHPUnit'srunBare()still invokes the after-test hooks whensetUp()throws, and because$__replaywasNone, Pest'stearDown()did not short-circuit — the fullafterEachchain andparent::tearDown()ran against a test case that was never set up (no booted app, noFacade::$app, empty container).In a Laravel suite, any
afterEachthat touches the framework (e.g.DB::flushQueryLog()) then throwsA facade root has not been set, and PHPUnit converts the cached skip into an error (TestCase::runBare()explicitly promotes aSkippedWithMessageExceptionto an error when tearDown throws). This also made TIA results oscillate between runs: the errored result evicted the cached skip, the next run executed the test green and re-cached the skip, and the run after that errored again.The fix marks the replay state before the throwing branches dispatch, so
tearDown()short-circuits for every replayed result exactly as it does on the Pass/Risky path.__runTest()is unreachable after asetUp()throw, so the hoisted__beginReplay()has no effect on the Pass/Risky behaviour, and the replayed skip/incomplete statuses are re-recorded unchanged, keeping the cache stable.The regression test seeds a TIA graph with cached pass/skipped/incomplete results for a fixture whose
afterEachthrows (and logs to a spy file), then replays it in a subprocess: all three results replay with their cached statuses, exit code 0, and no hook executes. Without the fix, the cached skip becomes aRuntimeExceptionerror from theafterEachhook.Related: