From 01a98d03ccf1e09050c37e6129294e353a1c0dce Mon Sep 17 00:00:00 2001 From: Jonathan Schultz Date: Sat, 26 Sep 2026 19:04:58 +0000 Subject: [PATCH] fix(tests): build test wheels from a private source copy tests/test_packaging.py ran `python -m build` in the repo root from every test. setuptools stages builds in build/ and *.egg-info inside the source tree, so under pytest-xdist parallel builds deleted each other's staging directory mid-build: error: [Errno 2] No such file or directory: 'build/bdist.linux-x86_64/wheel' _build_wheel now copies the checkout (minus .git, build/dist droppings, caches, *.sif and the local .plans symlink) into the test's tmp_path and builds there. tests/ is kept in the copy so the "tests are not shipped" check still has something to exclude. Reproduced first: 4/4 runs of `pytest -n 5 tests/test_packaging.py` had a failure. After the fix: 0/4, and the file passes serially. Contributions: - Claude (Opus 5.5): code edits, review, and conceptual discussion - Jonathan Schultz: overall vision, planning, review, and orchestration Co-authored-by: Jonathan Schultz Co-authored-by: Claude Opus 5.5 --- tests/test_packaging.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 706e926..7e12c30 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -18,6 +18,7 @@ from __future__ import annotations +import shutil import subprocess import sys import zipfile @@ -28,11 +29,38 @@ REPO_ROOT = Path(__file__).parent.parent +# Build droppings and VCS data that a private source copy must not carry +# over: a stale build/ or *.egg-info could leak old files into the wheel. +_COPY_IGNORE = shutil.ignore_patterns( + ".git", + "build", + "dist", + "*.egg-info", + "__pycache__", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + "*.sif", + ".plans", # local symlink to the planning repo (CLAUDE.md) +) + + def _build_wheel(tmp_path: Path) -> Path: - """Build the project wheel into ``tmp_path`` and return its path.""" + """Build the project wheel into ``tmp_path`` and return its path. + + setuptools stages every build in ``build/`` and ``*.egg-info`` inside the + source tree, so parallel builds from the same checkout (pytest-xdist runs + these tests on several workers) delete each other's staging directory + mid-build ("No such file or directory: 'build/bdist./wheel'"). + Building from a private copy of the tree gives each test its own. The + copy keeps ``tests/``, so the "tests are not shipped" check still has + something to exclude. + """ + src = tmp_path / "src" + shutil.copytree(REPO_ROOT, src, ignore=_COPY_IGNORE, symlinks=True) subprocess.run( [sys.executable, "-m", "build", "--wheel", "--outdir", str(tmp_path)], - cwd=REPO_ROOT, + cwd=src, check=True, capture_output=True, text=True,