{
    "slug": "test_parallelization_gotchas",
    "term": "Test Parallelization Gotchas",
    "category": "testing",
    "difficulty": "intermediate",
    "short": "Hidden runtime hazards when tests execute concurrently — shared state, race conditions, and resource contention turn green suites red intermittently.",
    "long": "Running tests in parallel cuts suite time dramatically, but it exposes assumptions that sequential execution hid. Tests that quietly shared a database row, a temp file path, a global singleton, an environment variable, or a fixed port now collide with sibling tests running in other workers. The symptoms look like flakiness: passes locally, fails in CI, fails differently on each run, fails only when the suite is fast enough to interleave operations.\n\nThe common culprits fall into a few categories. Shared mutable state: static properties, container singletons, module-level caches, environment variables mutated with putenv. Shared external resources: one database, one Redis instance, one filesystem directory, one fixed port number — all racing workers stomp on each other. Time-sensitive logic: tests asserting on 'now' that drift when CPU scheduling delays one worker. Order dependence: a test that only passes because an earlier test seeded data, now run in isolation by a different worker. Resource exhaustion: parallel workers spawning real HTTP connections, browser instances, or DB connections beyond pool limits, producing timeout errors that masquerade as logic failures.\n\nThe fix is not to disable parallelism but to make each test self-contained. Give each worker its own database (PARATEST_TOKEN, TEST_TOKEN_DB suffixes), its own temp directory, its own port from a dynamic allocator. Reset singletons between tests. Avoid mutating process-global state, or wrap it in a teardown that restores the original. Inject the clock instead of reading wall time. Treat parallel safety as a property of the test, not the runner — if a test would break when run twice simultaneously against the same process, it is broken regardless of how the CI happens to schedule it today.",
    "aliases": [
        "parallel test flakiness",
        "concurrent test hazards",
        "paratest gotchas"
    ],
    "tags": [
        "testing",
        "parallel-execution",
        "flaky-tests",
        "race-conditions",
        "ci"
    ],
    "misconception": "If a test passes in isolation and the framework supports parallel execution, it must be parallel-safe — actually, hidden shared state only surfaces when timing causes two workers to touch the same resource at the same moment.",
    "why_it_matters": "Parallel test failures are non-deterministic and time-dependent, so they erode trust in the suite, get retried until green, and mask real regressions behind 'it's just flaky' culture.",
    "common_mistakes": [
        "Sharing one database across workers without per-worker schemas or token-suffixed names, causing inserts and truncations to collide.",
        "Writing to a fixed temp path like /tmp/test-output.json instead of a per-process directory, so concurrent tests overwrite each other.",
        "Mutating static properties, container singletons, or putenv values without resetting them in teardown.",
        "Binding to a hardcoded port (8080, 6379) instead of allocating a free port per worker.",
        "Relying on test execution order — for example, a test that assumes a fixture row exists from a previous test that may now run in a different worker."
    ],
    "when_to_use": [
        "Suite runtime is a bottleneck for CI feedback or pre-commit hooks.",
        "Tests can be isolated per worker via database tokens, temp directories, or container-per-worker setups.",
        "You already battle flaky tests caused by shared state — making the suite parallel-safe forces the underlying isolation fixes."
    ],
    "avoid_when": [
        "Test suite is small enough (under ~30 seconds) that parallel execution provides no meaningful speedup.",
        "Tests integrate with an external system that cannot be sharded or mocked, where parallelism would only multiply contention.",
        "Codebase has heavy static state and refactoring for parallel safety would exceed the time savings."
    ],
    "related": [
        "test_fixture_setup",
        "integration_testing"
    ],
    "prerequisites": [
        "unit_testing",
        "flaky_tests",
        "test_isolation_strategies"
    ],
    "refs": [
        "https://github.com/paratestphp/paratest",
        "https://docs.pytest.org/en/stable/explanation/flaky.html",
        "https://playwright.dev/docs/test-parallel",
        "https://martinfowler.com/articles/nonDeterminism.html"
    ],
    "bad_code": "// Two parallel workers, one shared resource:\nclass ReportTest extends TestCase {\n    public function testGeneratesCsv(): void {\n        $path = '/tmp/report.csv'; // Fixed path - workers collide\n        Report::writeTo($path);\n        $this->assertFileExists($path);\n        $this->assertStringContainsString('total', file_get_contents($path));\n    }\n\n    public function testCachesSingleton(): void {\n        Cache::$instance = null;       // Static - bleeds between workers\n        putenv('FEATURE_X=1');         // Global env mutation\n        $this->assertTrue(FeatureFlags::enabled('x'));\n        // No teardown - next test inherits FEATURE_X=1\n    }\n}",
    "good_code": "// Per-worker isolation, no shared globals:\nclass ReportTest extends TestCase {\n    private string $tmpDir;\n    private ?string $originalFeatureX;\n\n    protected function setUp(): void {\n        // Unique dir per worker + per test\n        $token = getenv('TEST_TOKEN') ?: (string) getmypid();\n        $this->tmpDir = sys_get_temp_dir() . \"/report-{$token}-\" . uniqid();\n        mkdir($this->tmpDir, 0700, true);\n        $this->originalFeatureX = getenv('FEATURE_X') ?: null;\n    }\n\n    protected function tearDown(): void {\n        // Restore globals so siblings see clean state\n        $this->originalFeatureX === null\n            ? putenv('FEATURE_X')\n            : putenv(\"FEATURE_X={$this->originalFeatureX}\");\n        Cache::reset();\n    }\n\n    public function testGeneratesCsv(): void {\n        $path = $this->tmpDir . '/report.csv';\n        Report::writeTo($path);\n        $this->assertFileExists($path);\n    }\n}",
    "quick_fix": "Make each test pick a unique resource (per-worker DB, temp dir, port) and restore any global state it mutates in tearDown so workers cannot collide.",
    "severity": "high",
    "effort": "medium",
    "created": "2026-05-23",
    "updated": "2026-05-23",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/test_parallelization_gotchas",
        "html_url": "https://codeclaritylab.com/glossary/test_parallelization_gotchas",
        "json_url": "https://codeclaritylab.com/glossary/test_parallelization_gotchas.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Test Parallelization Gotchas](https://codeclaritylab.com/glossary/test_parallelization_gotchas) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/test_parallelization_gotchas"
            }
        }
    }
}