Repository navigation
fix: unregister app error handlers after testing boot - #652
Merged
Merged
Conversation
TestClient::boot() and TestDatabase::boot() boot the application, and the errors module's boot callback pushes a global error handler and exception handler that nothing removed. Every test that booted the app was reported risky, and the handlers piled up within a process. - core: module.php gains a `shutdown` callback, and Application::shutdown() runs every module's shutdown callback in reverse load order (then removes a leftover bootstrap error handler). Idempotent; usable by long-running workers too. - errors-simple / errors-advanced: bind ErrorHandlerInterface as a singleton and unregister the handler from the shutdown callback. - testing: BootedApplications tracks every app booted by TestClient::boot() and TestDatabase::boot(); TestCase::tearDown() and a new Pest plugin (ShutdownApplicationsPlugin, a PHPUnit AfterTestMethodFinished subscriber) shut them down after each test, plain it() tests included. Closes #647 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KaHtYgss6ELkNd1fkcNyX
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.
Summary
TestClient::boot()andTestDatabase::boot()boot the application. The errors module's boot callback then pushes a global error handler and exception handler, and nothing removed them. As a result PHPUnit reported every test that booted the app as risky ("did not remove its own error handlers"), which also triggers the Pest TIA false-green from pestphp/pest#2012. The handlers also piled up within a process.Design
The fix adds a public
Application::shutdown()in core. marko/testing now owns the teardown for the boots it performs.module.phpgets ashutdowncallback that mirrorsboot.Application::shutdown()runs every module's shutdown callback in reverse module load order, so handlers come off PHP's stacks in the reverse order they went on. It then removes the bootstrap error handler if a failed boot left it installed. Calling it again does nothing. Long-running workers can call it too.ErrorHandlerInterfaceis now also listed undersingletons, so the shutdown callback gets the same instance boot registered. The newshutdowncallback calls$handler->unregister(), which already pops exactly whatregister()pushed.BootedApplicationstracks every app booted byTestClient::boot()andTestDatabase::boot(). TheTestDatabaseapp is tracked right after boot, before its environment and driver checks, so a refused boot is cleaned up too.shutdownAll()shuts the apps down in reverse boot order. If one throws, it still shuts down the rest, then rethrows the first failure.Marko\Testing\TestCase::tearDown().ShutdownApplicationsPlugin, declared inextra.pest.pluginsnext toExpectationsPlugin. Pest has no globalafterEach(pest()->afterEach()only applies toin()paths), so the plugin registers a PHPUnitAfterTestMethodFinishedsubscriber. That event fires after tearDown and before PHPUnit's handler-stack check, so plainit()tests with nothing inPest.phpare covered.TestClient::forApplication()are not tracked: the caller booted them, so the caller shuts them down.Why not track apps only inside marko/testing and call
ErrorHandlerInterface::unregister()directly? marko/testing would need to know about the errors package and aboutBootstrapErrorHandler. A core lifecycle hook keeps that knowledge in the modules that install the handlers.Tests
shutdownclosure.errors-appfixture uses the real errors-simplemodule.php.TestClient::boot(), repeated boots, several clients in one test, a refusedTestDatabase::boot(), andTestCase::tearDown().pest-projectfixture now has a plainit()spec that callsTestClient::boot(), andfailOnRiskyis on. The subprocess run used to report "1 risky". It now passes cleanly.Notes
vendor/pest-plugins.json, which happens oncomposer install,updateordump-autoload.BootedApplicationsAPI), core (shutdownkey and the new "Shutting down" section), errors-simple and errors-advanced.Closes #647
🤖 Generated with Claude Code
https://claude.ai/code/session_017KaHtYgss6ELkNd1fkcNyX