Summary
discover: prune: true prunes the discover test directory (discover/<phase>/tests), but it does not touch the plan worktree (<plan_workdir>/tree). The worktree is always a full rsync of the metadata tree root. When a plan imports and fans out into many child plans, each child materializes its own full-tree worktree, so the whole source repository gets copied once per child plan on the runner.
Impact
Seen in a Testing Farm run where an imported openssl plan expanded into ~40 child plans. Each child had a 17M tree/ worktree holding the entire openssl test repo (every test dir plus shared helpers), even though each plan runs a single test. That is ~680M of near-identical copies for one run, and prune: true on those plans does not reduce it.
Reproducer
D=$(mktemp -d); cd "$D"
# "remote" repo: 3 plans, each runs ONE small test, all with prune: true.
# heavy/blob.bin (5MB) belongs to no test.
mkdir remote && cd remote && git init -q && mkdir -p .fmf && echo 1 > .fmf/version
mkdir -p tests/t1 tests/t2 tests/t3 heavy Plans
printf '/t1:\n test: ./r.sh\n path: /tests/t1\n/t2:\n test: ./r.sh\n path: /tests/t2\n/t3:\n test: ./r.sh\n path: /tests/t3\n' > tests/main.fmf
for t in t1 t2 t3; do echo "echo $t" > tests/$t/r.sh; done
head -c 5000000 /dev/zero > heavy/blob.bin
for n in 1 2 3; do
printf 'discover:\n how: fmf\n test:\n - /tests/t%s\n prune: true\nprovision:\n how: local\nexecute:\n how: tmt\n' "$n" > Plans/tier$n.fmf
done
git add -A && git commit -qm init
R=$(pwd); cd "$D"
# local repo: import all the plans (fan-out)
mkdir local && cd local && git init -q && mkdir -p .fmf && echo 1 > .fmf/version
printf 'plan:\n import:\n url: file://%s\n name: /Plans\n scope: all-plans\n importing: become-parent\n' "$R" > importer.fmf
git add -A && git commit -qm init
run=$(mktemp -d)
tmt run --scratch -i "$run" discover
echo "=== worktrees (tree/) ==="
find "$run" -name tree -type d | while read t; do printf '%s\t%s\n' "$(du -sh "$t"|cut -f1)" "${t#$run/}"; done
echo "=== one worktree contents ==="
t=$(find "$run" -name tree -type d | head -1); ls -A "$t"; ls "$t/tests"
echo "=== matching pruned discover tests ==="
d=$(find "$run" -path '*discover*tests' -type d | head -1); ls "$d/tests"
Actual output
=== worktrees (tree/) ===
4.9M importer/Plans/tier3/tree
4.9M importer/Plans/tier2/tree
4.9M importer/Plans/tier1/tree
=== one worktree contents ===
.fmf heavy Plans tests
main.fmf t1 t2 t3 # all three tests + the 5MB blob, in every worktree
=== matching pruned discover tests ===
main.fmf t3 # discover IS pruned to the single selected test
Each imported child plan gets a full 4.9M copy of the whole repo (heavy/blob.bin, all of tests/t1 t2 t3), while its discover/.../tests is correctly pruned to the one selected test.
Expected
With prune: true, the on-runner footprint of an imported/fan-out plan should not scale with the full repository size times the number of child plans. Either the worktree should honor pruning, or sibling child plans sharing the same source should not each copy the whole tree.
Notes on the mechanism
- The worktree is created in
Plan._initialize_worktree() (tmt/base/plan.py), which rsyncs the whole tree root into <plan_workdir>/tree. It returns early only for is_remote_plan_reference, which is the importing plan, not the materialized child plans, so every child gets a full worktree.
prune/prune_tree() (tmt/steps/discover/__init__.py) operates on the discover step's test_dir only. It never touches the worktree, so prune: true cannot shrink the copies above.
prune_tree() itself works correctly for imported plans; the discover tree is pruned as expected. The bloat is the worktree.
Version
Reproduced with tmt 1.76.0.
Generated-by: Claude Code
Summary
discover: prune: trueprunes the discover test directory (discover/<phase>/tests), but it does not touch the plan worktree (<plan_workdir>/tree). The worktree is always a full rsync of the metadata tree root. When a plan imports and fans out into many child plans, each child materializes its own full-tree worktree, so the whole source repository gets copied once per child plan on the runner.Impact
Seen in a Testing Farm run where an imported
opensslplan expanded into ~40 child plans. Each child had a 17Mtree/worktree holding the entire openssl test repo (every test dir plus shared helpers), even though each plan runs a single test. That is ~680M of near-identical copies for one run, andprune: trueon those plans does not reduce it.Reproducer
Actual output
Each imported child plan gets a full 4.9M copy of the whole repo (
heavy/blob.bin, all oftests/t1 t2 t3), while itsdiscover/.../testsis correctly pruned to the one selected test.Expected
With
prune: true, the on-runner footprint of an imported/fan-out plan should not scale with the full repository size times the number of child plans. Either the worktree should honor pruning, or sibling child plans sharing the same source should not each copy the whole tree.Notes on the mechanism
Plan._initialize_worktree()(tmt/base/plan.py), which rsyncs the whole tree root into<plan_workdir>/tree. It returns early only foris_remote_plan_reference, which is the importing plan, not the materialized child plans, so every child gets a full worktree.prune/prune_tree()(tmt/steps/discover/__init__.py) operates on the discover step'stest_dironly. It never touches the worktree, soprune: truecannot shrink the copies above.prune_tree()itself works correctly for imported plans; the discover tree is pruned as expected. The bloat is the worktree.Version
Reproduced with tmt 1.76.0.
Generated-by: Claude Code