Files
ghassanandClaude Opus 4.7 b7f3f6989b test: rewind output-buffer stack in tearDown to clear risky warnings
PHPUnit was flagging 18-19 tests per run as "did not close its own
output buffers". The leaks come from Blade — a @once block, a @push,
or a view that throws mid-render leaves ob_start()'ed buffers open
when the test method returns.

Capture ob_get_level() in setUp, then unwind back to that depth in
tearDown. Suite is now green AND clean: 46/46, 0 failures, 0 risky.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-10-21 11:58:12 +03:00

68 lines
2.6 KiB
PHP
Executable File

<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
/** Output-buffer depth captured before each test, so tearDown can
* rewind back to it instead of leaving dangling buffers that PHPUnit
* reports as "Test code or tested code did not close its own output
* buffers". The depth leaks when a view throws mid-render — common
* with Blade's compiled files when a feature test exercises an
* error path. */
private int $preTestObLevel = 0;
/**
* Belt-and-braces guard against a repeat of the 2026-10-21 incident, where
* a test with the RefreshDatabase trait ran migrate:fresh against the
* shared dev SQLite file and wiped every row.
*
* By the time setUp() runs we know the final DB config. If it points at
* anything other than an in-memory SQLite (or a file inside the test
* directory), we abort the whole run before any migration can execute.
*/
protected function setUp(): void
{
parent::setUp();
$this->assertSafeTestDatabase();
$this->preTestObLevel = ob_get_level();
}
protected function tearDown(): void
{
// Collapse any output buffers the test (or Blade view it rendered)
// left open. Without this, each view()->render() call that touches
// a Blade partial with its own @once / @push block can leave the
// buffer stack one level deep — PHPUnit reports every one of those
// tests as "risky".
while (ob_get_level() > $this->preTestObLevel) {
ob_end_clean();
}
parent::tearDown();
}
private function assertSafeTestDatabase(): void
{
$driver = config('database.default');
$database = config("database.connections.{$driver}.database");
$allowed = $database === ':memory:'
|| $database === null
|| str_contains((string) $database, '/testing')
|| str_contains((string) $database, '/tmp/')
|| str_ends_with((string) $database, '.testing.sqlite');
if (! $allowed) {
// Fatal, not an assertion failure — a wrong DB target has to stop
// execution before Laravel can run even one query against it.
fwrite(STDERR, "\n\n*** ABORTING TESTS: unsafe DB target '{$database}' on connection '{$driver}'. ***\n");
fwrite(STDERR, "Set DB_CONNECTION=sqlite and DB_DATABASE=:memory: in .env.testing (or in your test's setUp before parent::setUp).\n\n");
exit(2);
}
}
}