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]>
68 lines
2.6 KiB
PHP
Executable File
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);
|
|
}
|
|
}
|
|
}
|