Skip to content

test: write fake executables from a child so parallel tests cannot make them busy - #189

Merged
scbrown merged 1 commit into
mainfrom
malcolm/zke-etxtbsy
Oct 24, 2026
Merged

scbrown merged 1 commit into
mainfrom
malcolm/zke-etxtbsy

Conversation

@scbrown

@scbrown scbrown commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

What

A test-only helper, test_exec::write_executable, now writes every lib fixture that is written as a script and then exec'd. Five fixtures use it.

Why

main went red on adapter::quipu_alias_layout_tests with Text file busy (os error 26), on a docs-only commit. This is the parallel-test fork race:

  1. A test holds a just-written script open for writing.
  2. A sibling test thread forks, and the forked child inherits that descriptor until it calls exec. O_CLOEXEC does not close the window between fork and exec.
  3. The test then execs the script and gets ETXTBSY.

The helper writes through a short-lived sh -c 'cat > "$1"' child. This process never holds the write descriptor, so no fork can inherit it, and the file has no writer once the child exits. That removes the race instead of retrying around it. A retry would have to go at each call site, because every exec here happens inside the code under test (read_version, identify, owner_with, dp apply/verify).

The population was wider than the two failing tests

Reproduced on unmodified main over 200 full-lib runs at --test-threads=8: 12 runs failed, across four tests:

  • seeds_identity_accepts_both_editions_and_refuses_foreign_sd: 8;
  • owned_layout_with_qp_beside_quipu_still_passes: 2;
  • package_managed_layout_passes_with_qp_beside_the_resolved_quipu: 1;
  • dp_alias::tests::keeps_a_different_local_target: 1.

The seeds test hides its error behind .is_ok(). With the error printed temporarily, all 6 sampled failures were Text file busy. After the first fix pass, a 300-run control surfaced one more, apt_owned (same pattern; its error is swallowed by .ok()?), so that fixture and the rollback ones are converted too.

Evidence

  • Before: 12/200 full-lib runs failed at --test-threads=8.
  • After: 0/500 at --test-threads=8.
  • cargo test --all-features: 0 failed. cargo clippy --all-targets --all-features -- -D warnings and cargo fmt --check are clean.

Not in this PR

Integration tests under tests/ have their own write-then-exec fixtures, in separate binaries, and cannot use a cfg(test) lib helper. None failed in CI or locally. They are noted on the item rather than changed here.

Rollback

Revert. Test code only; no runtime change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BzgSY2LpdGhBhb7D1nHbiF

…ke them busy

Five lib fixtures wrote a script in-process and then exec'd it. A sibling
test thread that forks during the write inherits the write descriptor until
it execs, so the exec fails with ETXTBSY ("Text file busy"). Main 6409674
went red on two of them.

test_exec::write_executable writes through a short-lived sh child, so this
process never holds the descriptor and no fork can inherit it.

Before: 12/200 full-lib runs failed at --test-threads=8 (four tests; the
seeds one confirmed as Text file busy). After: 0/500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzgSY2LpdGhBhb7D1nHbiF
@scbrown
scbrown merged commit 7ae5e2c into main Oct 24, 2026
20 of 22 checks passed
@scbrown
scbrown deleted the malcolm/zke-etxtbsy branch October 24, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant