From a528006f019e94d3780fdb1f917d52e939e21947 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Mon, 12 Oct 2026 22:19:45 +0000 Subject: [PATCH 1/2] tests.yml: the Windows job installs git-filter-repo 2.47.0, so the 23 test files that skip without it run there and an adoption is executed on Windows for the first time, and the comments and docstrings that said that job installs none now say why it does (#471) v1.0.0 says that every AGENTS.md procedure, adopt included, works end to end on Linux, macOS and Windows. `tests-windows` left `git-filter-repo` out on purpose, as a job for the entry point and the materializer alone, so every test that executes an adoption skipped there and the Windows half of that sentence was a claim no runner had checked. - `tests-windows` installs `git-filter-repo` at the ubuntu job's pin, 2.47.0, on ONE line: PowerShell is that runner's default shell, and the ubuntu job's `\` continuation is bash's. pip puts `git-filter-repo.exe` in Python's `Scripts` directory, which setup-python puts on PATH; probe run 38312924968 found it there and `git filter-repo --version` answered. - The suite still runs whole, as before. - The comment that explained leaving it out now says why it is installed; the job header no longer names it as a reason for a second job, and the macOS job's step no longer says "unlike the windows job". - Seven test docstrings said the Windows job installs none, as the reason their filter-repo-free tests run there. They now say a machine that has none runs them, which stays true. `tests/test_shape_doctor.py`'s `no_adoption_offered` said the missing-tool case was "the Windows CI job, by design"; no CI runner lacks the tool now, and its assertion holds either way. The 23 files are the 21 that spell `shutil.which("git-filter-repo")`, `test_adopt_filter_repo_failure.py` (`shutil.which(FILTER_REPO_TOOL)`) and `test_adopt_git_reads.py`, which imports `needs_filter_repo`. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/tests.yml | 23 +++++++++++++++-------- tests/test_adopt_clone_template.py | 2 +- tests/test_adopt_entry_paths.py | 4 ++-- tests/test_adopt_ignored_shape_files.py | 4 ++-- tests/test_adopt_mount_ignored.py | 4 ++-- tests/test_adopt_submodules.py | 2 +- tests/test_adopt_verify.py | 2 +- tests/test_shape_doctor.py | 11 ++++++----- 8 files changed, 30 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8242e3d1..e5e47255 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,8 +113,8 @@ jobs: # THE SAME SUITE ON WINDOWS, because `setup-project.py` exists for a machine # nobody here runs. A SECOND JOB rather than a matrix on the first one: that - # job also installs `git-filter-repo` and downloads a linux-amd64 actionlint - # binary, and neither belongs on a Windows runner. + # job downloads a linux-amd64 actionlint binary, which belongs on no Windows + # runner. tests-windows: runs-on: windows-latest # SKIPPED on a push to main: a squash-interaction regression is not @@ -168,11 +168,18 @@ jobs: - name: install the test runner run: python -m pip install --disable-pip-version-check pytest==8.3.4 - # NO git-filter-repo, on purpose. It is what `adopt-project.py` extracts - # a leg's history with, and the adoption suites SKIP without it — which - # is the honest outcome here: what this job is for is the entry point and - # the materializer on Windows, not a second platform's worth of history - # rewriting. The ubuntu job installs it and runs those tests. + # git-filter-repo IS installed here now, at the ubuntu job's pin (#471). + # It is what `adopt-project.py` extracts a leg's history with, and the + # adoption suites SKIP without it. This job used to leave it out on + # purpose, as a job for the entry point and the materializer alone. But + # v1.0.0 says that adopt works end to end on Windows too, and while no + # Windows runner ever executed an adoption, that sentence was a claim: + # the same claim the macOS job's step below exists to stop making. + # + # ONE LINE: PowerShell is this runner's default shell, and the ubuntu + # job's `\` continuation is bash's, not PowerShell's. + - name: install git-filter-repo + run: python -m pip install --disable-pip-version-check git-filter-repo==2.47.0 # Per-invocation identity, never `git config --global`. PowerShell is the # default shell on this runner, so the append is `>> $env:GITHUB_ENV` @@ -232,7 +239,7 @@ jobs: - name: install the test runner run: python3 -m pip install --disable-pip-version-check pytest==8.3.4 - # git-filter-repo IS installed here, unlike the windows job. The + # git-filter-repo IS installed here, as on the other two runners. The # adoption suites SKIP without it, and `adopt-project.py` is run on a # Mac by whoever adopts a repository from one — a claim that stays a # claim while the only runner that extracts history is Linux. Same pin. diff --git a/tests/test_adopt_clone_template.py b/tests/test_adopt_clone_template.py index aa1c2716..02f373fe 100644 --- a/tests/test_adopt_clone_template.py +++ b/tests/test_adopt_clone_template.py @@ -41,7 +41,7 @@ swapped after the mount. Only the end-to-end tests need `git filter-repo`. The verification tests build -their legs by hand, so the Windows job, which installs none, runs them. Every +their legs by hand, so a machine that has none still runs them. Every repository and remote is local; no network is used, and the operator's configuration reaches git through `GIT_CONFIG_GLOBAL`, a file each test writes, so nothing here reads or writes the global configuration of whoever diff --git a/tests/test_adopt_entry_paths.py b/tests/test_adopt_entry_paths.py index 45298bef..292ecd01 100644 --- a/tests/test_adopt_entry_paths.py +++ b/tests/test_adopt_entry_paths.py @@ -17,8 +17,8 @@ `check` to refuse that answer. NO NETWORK AND NO GITHUB. `git filter-repo` is needed only by the end-to-end -test at the bottom, which skips without it as `test_adopt_e2e.py` does; the -Windows job installs none, so there it is the tests above it that run. They +test at the bottom, which skips without it as `test_adopt_e2e.py` does; on a +machine that has none, it is the tests above it that run. They load `adopt-project.py` through importlib and stub its probe for the tool, as `test_adopt_execute_refusals.py` does: every refusal under test is raised before the tool would be used. diff --git a/tests/test_adopt_ignored_shape_files.py b/tests/test_adopt_ignored_shape_files.py index 10c4381a..5dfcd1a8 100644 --- a/tests/test_adopt_ignored_shape_files.py +++ b/tests/test_adopt_ignored_shape_files.py @@ -16,8 +16,8 @@ put it, and the check finds a pinned file through the split tree's own links, so it agrees with the first bootstrap. All repositories and remotes are local fixtures; no network is used. The end-to-end tests need `git filter-repo`; the -others do not, so they run on the Windows job, which installs none -- except -the ones that make a symlink on the DISK, which skip there. +others do not, so they run on a machine that has none too. The ones that make +a symlink on the DISK skip on Windows. """ from __future__ import annotations diff --git a/tests/test_adopt_mount_ignored.py b/tests/test_adopt_mount_ignored.py index a9751ef3..9d1d93e1 100644 --- a/tests/test_adopt_mount_ignored.py +++ b/tests/test_adopt_mount_ignored.py @@ -31,8 +31,8 @@ what its remediation says of the run, and that its next step adopts. The mount tests call `_mount_the_legs` on hand-made repositories, so they need -no `git filter-repo` and run on the Windows job, which installs none -- except -the two that make a symlink on the DISK, which skip there. The end-to-end +no `git filter-repo` and run on a machine that has none too; the two that +make a symlink on the DISK skip on Windows. The end-to-end tests need `git filter-repo`. All repositories and remotes are local fixtures; no network is used. """ diff --git a/tests/test_adopt_submodules.py b/tests/test_adopt_submodules.py index 15c665cf..b8736170 100644 --- a/tests/test_adopt_submodules.py +++ b/tests/test_adopt_submodules.py @@ -28,7 +28,7 @@ #: What the end-to-end test needs and the mount tests below do not: they call #: `_mount_the_legs` on hand-made repositories, so they run on a machine with -#: no `git filter-repo` (the Windows CI job installs none). +#: no `git filter-repo`. needs_filter_repo = pytest.mark.skipif( shutil.which("git-filter-repo") is None, reason="git filter-repo is not installed: `pip install git-filter-repo`") diff --git a/tests/test_adopt_verify.py b/tests/test_adopt_verify.py index 65725e6e..8821ad1a 100644 --- a/tests/test_adopt_verify.py +++ b/tests/test_adopt_verify.py @@ -12,7 +12,7 @@ EVERY TEST BUT THE LAST TWO CALLS `_verify` DIRECTLY on hand-made commits, their gitlinks written with `git update-index --cacheinfo 160000,...`. No `git -filter-repo` is involved, so the Windows job, which installs none, runs all of +filter-repo` is involved, so a machine that has none still runs all of those. The last two run `execute`, so they need `git filter-repo` and are skipped without it, as every `execute` test is: one proves `cmd_execute` hands `_verify` the PLAN'S mount paths and that the assembly clones and bootstraps diff --git a/tests/test_shape_doctor.py b/tests/test_shape_doctor.py index fc1c74fc..7eac15a5 100644 --- a/tests/test_shape_doctor.py +++ b/tests/test_shape_doctor.py @@ -1542,11 +1542,12 @@ def no_adoption_offered(text: str = "", rows: dict | None = None, THE `machine` ROW IS THE ONE PLACE THE NAME MAY APPEAR, and it is UNCHANGED by #205 (`machine_row_unchanged`): on a workstation without - `git-filter-repo` -- the Windows CI job, by design -- it says that only - `adopt-project.py` needs that tool, in a COMPLIANT report exactly as - here. That is a sentence about the machine and never an offer to adopt - anything, so the text report is read around that row's one line, and the - row itself may name the tool only while the tool is missing. Everything + `git-filter-repo` -- never a CI runner since #471 installed it on all + three -- it says that only `adopt-project.py` needs that tool, in a + COMPLIANT report exactly as here. That is a sentence about the machine + and never an offer to adopt anything, so the text report is read around + that row's one line, and the row itself may name the tool only while the + tool is missing. Everything else -- the kind line, every reason, every NEXT, every `detail`, the verdict -- names it not at all, `plan` or no `plan`. """ From a8b506421bf38915601a79da8693653cc598fdf8 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Mon, 12 Oct 2026 22:52:10 +0000 Subject: [PATCH 2/2] tests/test_adopt_ignored_shape_files.py: the bootstrap of the source that keeps a `.gitattributes` of its own is a strict xfail on Windows, because the shape's `eol=lf` rule lands in `shape/` and a Windows checkout writes CRLF into every pinned copy at the root, until the fix that appends that rule to the source's own file lands (#471) Probe run 38312924968 ran the adoption suites on Windows for the first time: 1696 passed, 34 skipped, and this one case failed. The source tracks `* text=auto` at its root, so `execute` writes the shape's `* text=auto eol=lf` beside it at `shape/.gitattributes`, where it governs only `shape/`. The nine copies `contracts/shape-pin.yaml` pins at their own paths are then governed by the source's rule alone, and git's checkout on Windows (`core.eol=native`, or Git for Windows' default `core.autocrlf=true`) writes them with CRLF. `validate-pins.py` digests the bytes on disk (#51), so the first bootstrap reports shape-copy-drift on all nine. Each digest Windows reported is the sha256 of the CRLF form of the pinned LF bytes, and the same nine findings, byte for byte, come back on Linux under `core.eol=crlf` or `core.autocrlf=true`. Brett Heap ruled at 2026-10-12T22:48Z that the split appends a marked block of `/ text eol=lf` lines to the source's own root `.gitattributes`. That fix is a separate pull request on #471. Until it lands this case is a STRICT xfail on Windows only, applied to this parameter alone through `request.applymarker`: the other test of the same fixture parameter passes there, and a mark on the fixture's param would make it a strict XPASS. The fix turns the xfail red, so it removes the mark. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 --- tests/test_adopt_ignored_shape_files.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_adopt_ignored_shape_files.py b/tests/test_adopt_ignored_shape_files.py index 5dfcd1a8..b02a89ae 100644 --- a/tests/test_adopt_ignored_shape_files.py +++ b/tests/test_adopt_ignored_shape_files.py @@ -534,9 +534,24 @@ def test_the_split_lands_the_files_the_pin_names(adopted): cwd=adopted.source).stdout == OWN_ATTRIBUTES +#: KNOWN ON WINDOWS, AND STRICT, for the source with attributes of its own +#: (#471): the shape's `* text=auto eol=lf` lands at `shape/.gitattributes`, +#: where it governs only `shape/`, so a Windows checkout writes CRLF into the +#: pinned copies at the root and the first bootstrap reports shape-copy-drift +#: on every one. Strict, so the fix that appends the shape's rule to the +#: source's own `.gitattributes` turns this red until the mark is removed. +WINDOWS_CRLF_COPIES = pytest.mark.xfail( + sys.platform == "win32", strict=True, + reason="#471: the source's own .gitattributes keeps the shape's eol=lf " + "rule off the root's pinned copies, which a Windows checkout " + "writes with CRLF, so bootstrap reports shape-copy-drift") + + @needs_filter_repo def test_the_assembly_bootstraps_where_it_failed_with_shape_copy_missing( - adopted, tmp_path): + adopted, tmp_path, request): + if adopted.own: + request.applymarker(WINDOWS_CRLF_COPIES) clone = tmp_path / PROJECT git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", BRANCH, str(adopted.source), str(clone), cwd=tmp_path)