Files
projectsend/tests/TestCase.php
ignacionelson 41b4e477b5 Give each parallel test worker its own directory for upload parts
A full parallel run failed once and passed on retry while I was doing the
#1703 follow-up. A flake is worse than a steady failure: it trains you to
re-run rather than look, and it quietly weakens every green run reported
beside it.

Upload parts are real files under storage_path('app/uploads-tmp/{session_id}'),
not a faked disk. Every parallel worker gets its own database, so session
ids restart at 1 in each of them, and two workers writing parts land in
the same directory. On top of that ChunkedUploadsTest's afterEach deleted
the whole tree rather than its own share, for everybody. Six test files
write parts, so this was reachable without anything I added.

The same collision exists inside one worker: RefreshDatabase rolls back,
so ids restart at 1 for every test, and a run that died before its
cleanup leaves parts sitting under the id the next test is about to
claim.

LocalPartStore now reads its root from config, defaulting to exactly
where it always was -- an installation with UPLOAD_PARTS_PATH unset
behaves identically. Tests\TestCase points it at a per-worker directory
and empties that directory per test, which closes the cross-worker, the
cross-run and the intra-worker versions together. ChunkedUploadsTest's
cleanup and its two directory assertions read the configured root rather
than the hardcoded path, so they can no longer reach into a neighbour.

Verified with eight consecutive parallel runs, green, and by watching the
per-worker directories appear separately (w1, w2, w4 … w14) rather than
one shared tree. The isolation itself cannot be asserted from inside a
single test; what a test can pin is the mechanism it rests on, so one
does: parts go where the configured root says.
2026-08-26 18:00:22 -03:00

40 lines
1.5 KiB
PHP

<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\ParallelTesting;
abstract class TestCase extends BaseTestCase
{
/**
* Give each parallel worker its own directory for upload parts.
*
* Parts are real files under a real path, not a faked disk, and every
* worker's database restarts session ids at 1 — so two workers writing
* parts land in the same directory, and ChunkedUploadsTest's afterEach
* deletes the whole tree for all of them. That showed up as a
* one-run-in-three failure, which is worse than a steady one: it
* trains you to re-run rather than look.
*
* Outside a parallel run the token is null and everything lands under
* w0, which is still held apart from whatever a previous run left.
*/
protected function setUp(): void
{
parent::setUp();
$root = storage_path('app/uploads-tmp/w'.(ParallelTesting::token() ?: '0'));
config(['projectsend.uploads.parts_path' => $root]);
// Emptied per test, not just per file. RefreshDatabase rolls back,
// so session ids restart at 1 in every test — two tests in the
// same worker reuse the same directory name, and a run that
// crashed before its cleanup leaves the previous one's parts
// sitting there under the id the next test is about to claim.
File::deleteDirectory($root);
}
}