Skip to content

TIA: worker edge partials keyed by slot-stable TEST_TOKEN — --parallel --max-batch-size=N silently drops most of the recorded graph #1778

Description

@Stole

Summary

When recording a TIA graph with --tia --parallel --max-batch-size=N, each respawned worker process overwrites the previous worker's edge partial for the same slot. Only the last batch per worker slot survives into graph.json, so the dependency graph is silently incomplete. Subsequent --tia runs then re-execute most of the suite as "uncached" on every run, and (because replay runs don't record) the graph never heals.

There is no warning — recording appears to succeed.

Versions

  • Pest: v5.0.1
  • PHP: 8.4.23 (Xdebug 3.x, xdebug.mode=coverage, forwarded to workers via --passthru-php)
  • OS: macOS (arm64)

Steps to reproduce

On any suite large enough to need more than one batch per worker (i.e. more than processes × max-batch-size tests):

# 1. Record with batch recycling (fresh graph):
vendor/bin/pest --tia --fresh --parallel --max-batch-size=40 \
  --passthru-php="-d zend_extension=xdebug -d xdebug.mode=coverage"

# 2. Inspect the graph:
php -r '$g = json_decode(file_get_contents(getenv("HOME")."/.pest/tia/<project-key>/graph.json"), true);
        echo count($g["edges"]), " test files with edges\n";'

# 3. Re-run without --fresh: most of the suite executes as "uncached" every time.

Observed on our suite (~9,000 tests across ~900 test files, 12 processes): the recording run completes green, but the graph contains edges for only 202 of ~900 test files — roughly the final batches of each worker slot. Re-recording with --parallel but without --max-batch-size produces the complete graph (922 test files) and replay then works as designed (768s → 3.7s).

Cause

Pest\Plugins\Tia::flushWorkerPartial() writes each worker's edges to a state file keyed by workerToken():

$this->state->write(self::KEY_WORKER_EDGES_PREFIX.$this->workerToken().'.json', $json);

and workerToken() prefers ParaTest's TEST_TOKEN:

$raw = $_SERVER['TEST_TOKEN'] ?? $_ENV['TEST_TOKEN'] ?? null;

TEST_TOKEN is stable per worker slot, not per worker process. With --max-batch-size=N, ParaTest recycles the process in each slot every N tests, and every recycled process flushes its partial to the same filename, clobbering the edges recorded by its predecessors in that slot. The parent's merge at the end of the run only ever sees the last batch per slot.

The same keying is used for KEY_WORKER_RESULTS_PREFIX and KEY_WORKER_NO_DRIVER_PREFIX, which presumably lose data the same way.

Suggested fix

Key the partials by ParaTest's UNIQUE_TEST_TOKEN (unique per worker process, e.g. 2_6a690d236824c) instead of TEST_TOKEN, falling back to TEST_TOKEN/PID as now:

$raw = $_SERVER['UNIQUE_TEST_TOKEN'] ?? $_ENV['UNIQUE_TEST_TOKEN']
    ?? $_SERVER['TEST_TOKEN'] ?? $_ENV['TEST_TOKEN'] ?? null;

The parent already merges all files matching the prefix (keysWithPrefix), so accumulating one partial per process rather than per slot should merge cleanly without further changes.

Workaround for anyone else hitting this

Record without --max-batch-size (long-lived workers → one partial per slot is then correct). Note this removes the worker-recycling memory relief, so suites that rely on batching to stay under memory_limit may need a higher limit for recording runs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions