diff --git a/tests/conftest.py b/tests/conftest.py index f6672b8c..9f517dc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,7 @@ from __future__ import annotations +import importlib.util import os import re import select @@ -36,6 +37,7 @@ import sys import time from argparse import Namespace +from collections.abc import Mapping, Sequence from pathlib import Path import pytest @@ -65,7 +67,9 @@ def clone_just_pushed(remote: Path, dest: Path, - branch: str | None = None + branch: str | None = None, *, recurse: bool = True, + bare: bool = False, config: Sequence[str] = (), + env: Mapping[str, str] | None = None ) -> subprocess.CompletedProcess: """`git clone --recurse-submodules` of a bare remote a scaffold has JUST pushed into, READ THROUGH GIT AND NOT THROUGH ITS FILES (#440). @@ -90,18 +94,73 @@ def clone_just_pushed(remote: Path, dest: Path, branch into the SOURCE, a plain repository, which is then `remote`. The two legs it pushed into `--local-remote-dir` are read by the rewrite only while that directory sits under `remote`'s parent. + + The keywords are for the tests whose subject is the clone itself or the + environment it is made in (#544). `recurse=False` is a plain `git clone`, + which leaves a submodule's mount empty; `bare` is a bare copy of the + remote, with no submodules to read; `config` is more `-c` arguments for + the one command (`["-c", "core.autocrlf=true"]`); and `env` is variables + laid over this process's own, as `git_under` and `raw` lay them. The + output is decoded as UTF-8 whatever the platform's code page: a refusal + names a path, and a project's name can be any text. """ + remote = Path(remote) remotes = remote.parent.resolve() uri = remotes.as_uri().rstrip("/") + "/" prefixes = dict.fromkeys([os.path.join(str(remotes), ""), remotes.as_posix().rstrip("/") + "/"]) rewrites = [arg for prefix in prefixes for arg in ("-c", f"url.{uri}.insteadOf={prefix}")] + if bare: + mode = ["--bare"] + else: + mode = ["--recurse-submodules"] if recurse else [] return subprocess.run( - ["git", *FILE_PROTOCOL, *rewrites, "clone", "-q", "--no-local", - *(["-b", branch] if branch else []), - "--recurse-submodules", str(remote), str(dest)], - capture_output=True, text=True, check=False) + ["git", *FILE_PROTOCOL, *config, *rewrites, "clone", "-q", + "--no-local", *(["-b", branch] if branch else []), *mode, + str(remote), str(dest)], + capture_output=True, encoding="utf-8", errors="replace", check=False, + env=None if env is None else {**os.environ, **env}) + + +def cloned_just_pushed(remote: Path, dest: Path, branch: str | None = None, + **options) -> Path: + """`clone_just_pushed`, failing the test where the clone fails; `dest`. + `options` are the ones `clone_just_pushed` takes.""" + cloned = clone_just_pushed(remote, dest, branch, **options) + assert cloned.returncode == 0, (f"git clone of {remote} failed:\n" + f"{cloned.stderr}{cloned.stdout}") + return Path(dest) + + +def load_without_bytecode(path: Path, name: str): + """`path` run as a module called `name`, and no bytecode written for it + (#544; it is `load()` of `test_bootstrap_spelling.py`, #456, moved here). + + `SourceFileLoader` caches what it compiles in a `__pycache__` beside the + source, and these sources are the TEMPLATES: a tree other tests read file + by file as text (`test_scaffold_leg_path_collisions.py` does, and a `.pyc` + is not UTF-8), and a tree a materializer walks. `.gitignore` hides the + stray, so `git status` never says it is there. The flag is process-wide, so + it is restored; it also covers the `repo_shape` a root imports as it runs. + """ + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + wrote_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + try: + spec.loader.exec_module(module) + finally: + sys.dont_write_bytecode = wrote_bytecode + return module + + +#: `load_without_bytecode`'s counterpart for a CHILD process (#544). `family.py +#: siblings` runs the standard's `templates/family-root/scripts/siblings.py` +#: through `runpy`, and that file imports `bootstrap` from beside it, so +#: without this in the child's environment the import writes +#: `__pycache__/bootstrap.*.pyc` into the template tree. +NO_BYTECODE = {"PYTHONDONTWRITEBYTECODE": "1"} #: THE DEFAULT IDENTITY `git()` GIVES GIT (#279). A commit needs an author and @@ -396,8 +455,7 @@ def scaffolded(tmp_path_factory) -> dict: ) assert result.returncode == 0, result.stderr + result.stdout clones.mkdir() - proc = clone_just_pushed(remotes / f"{PROJECT}.git", clones / PROJECT) - assert proc.returncode == 0, proc.stderr + cloned_just_pushed(remotes / f"{PROJECT}.git", clones / PROJECT) return { "base": base, "remotes": remotes, "work": work, "clone": clones / PROJECT, "stdout": result.stdout, diff --git a/tests/test_adopt_clone_template.py b/tests/test_adopt_clone_template.py index 02f373fe..0ffc344f 100644 --- a/tests/test_adopt_clone_template.py +++ b/tests/test_adopt_clone_template.py @@ -61,8 +61,8 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, git, make_source_repo, - resolve, run_script, write_plan) +from conftest import (ADOPT, FILE_PROTOCOL, REPO, cloned_just_pushed, git, + make_source_repo, resolve, run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) from shape_materialize import SHAPE_REPOSITORY # noqa: E402 @@ -339,10 +339,8 @@ def test_a_clean_recursive_clone_succeeds_and_the_projects_validators_pass( not contain` the planted commit (exit 128). `bootstrap.py` is the last of the validators: it places each leg AT its pin, so it fails where a gitlink and a pin disagree.""" - clone = tmp_path / PROJECT - git_under(adopted.clean, *FILE_PROTOCOL, "clone", "-q", - "--recurse-submodules", "-b", BRANCH, str(adopted.source), - str(clone), cwd=tmp_path) + clone = cloned_just_pushed(adopted.source, tmp_path / PROJECT, BRANCH, + env=adopted.clean) for role in ROLES: assert git_under(adopted.clean, "rev-parse", "HEAD", cwd=clone / role) \ == pushed_tip(adopted.remotes, role) diff --git a/tests/test_adopt_e2e.py b/tests/test_adopt_e2e.py index 77f2147d..1403b7de 100644 --- a/tests/test_adopt_e2e.py +++ b/tests/test_adopt_e2e.py @@ -18,7 +18,7 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, git, make_source_repo, +from conftest import (ADOPT, REPO, cloned_just_pushed, git, make_source_repo, resolve, run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) @@ -61,13 +61,8 @@ def adopted(tmp_path_factory) -> dict: @pytest.fixture def adopted_clone(adopted, tmp_path): """A recursive clone of the source at the split branch.""" - target = tmp_path / PROJECT - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - "-b", "adopt/three-repo-shape", str(adopted["source"]), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr - return target + return cloned_just_pushed(adopted["source"], tmp_path / PROJECT, + "adopt/three-repo-shape") # --- what execute refuses -------------------------------------------------- @@ -122,10 +117,8 @@ def test_both_legs_exist_with_more_than_one_commit(adopted): def test_a_files_history_is_followable_inside_its_leg(adopted, tmp_path): """The point of `git filter-repo`: the commits that made a file come with it. The specification was edited in a second commit; the leg has both.""" - clone = tmp_path / "spec-leg" - subprocess.run(["git", "clone", "-q", - str(adopted["remotes"] / f"{PROJECT}-spec.git"), - str(clone)], check=True) + clone = cloned_just_pushed(adopted["remotes"] / f"{PROJECT}-spec.git", + tmp_path / "spec-leg") log = git("log", "--oneline", "--", "specs/001-feature/spec.md", cwd=clone).stdout.splitlines() assert len(log) == 2, f"expected two commits for the spec file, got {log}" @@ -139,10 +132,8 @@ def test_a_files_history_is_followable_inside_its_leg(adopted, tmp_path): def test_the_legs_keep_the_paths_they_had_in_the_source(adopted, tmp_path): """Paths are NOT flattened: `scripts/validate.py` stays there, which is what makes `../spec/contracts` from `code/` the correct read-across.""" - clone = tmp_path / "code-leg" - subprocess.run(["git", "clone", "-q", - str(adopted["remotes"] / f"{PROJECT}-code.git"), - str(clone)], check=True) + clone = cloned_just_pushed(adopted["remotes"] / f"{PROJECT}-code.git", + tmp_path / "code-leg") assert (clone / "src" / "app" / "main.py").is_file() assert (clone / "tests" / "test_main.py").is_file() assert not (clone / "specs").exists() diff --git a/tests/test_adopt_execute_refusals.py b/tests/test_adopt_execute_refusals.py index 3fd809e7..7c767af6 100644 --- a/tests/test_adopt_execute_refusals.py +++ b/tests/test_adopt_execute_refusals.py @@ -1017,6 +1017,13 @@ def a_whole_pin(product: str) -> str: #: then passes. A `paths:` with no entries is valid as a TYPE and leaves every #: source path uncovered, which is `plan-uncovered`'s to say, as it was. A #: `pins:` is a list of mappings, as `plan` writes it since #273. +#: +#: A TEXT in the third place is what `check` says instead, because the key +#: is read as it always was (#544, #435's `source is None` and `legs is None` +#: arms): `source:` bare or absent is `plan-no-source`, a refusal, exit 2, and +#: `legs:` bare is the names' own `naming-role-mismatch`, a finding, exit 1. +#: Neither is `plan-bad-source` or `plan-bad-legs`, which say a value is the +#: wrong TYPE, and a bare key has no value at all. GOOD_KEYS = { "pins-empty": ("pins", "pins: []\n", True), "pins-bare": ("pins", "pins:\n", True), @@ -1029,6 +1036,9 @@ def a_whole_pin(product: str) -> str: "allow-absent": ("allow_empty_legs", "", True), "allow-both": ("allow_empty_legs", "allow_empty_legs: [spec, code]\n", True), + "source-bare": ("source", "source:\n", "REFUSED plan-no-source"), + "source-absent": ("source", "", "REFUSED plan-no-source"), + "legs-bare": ("legs", "legs:\n", "FINDING naming-role-mismatch"), "paths-empty": ("paths", "paths: []\n", False), "paths-bare": ("paths", "paths:\n", False), "paths-absent": ("paths", "", False), @@ -1036,17 +1046,23 @@ def a_whole_pin(product: str) -> str: @pytest.mark.parametrize("edit", sorted(GOOD_KEYS)) -def test_a_list_an_empty_list_a_bare_key_and_no_key_are_not_findings( +def test_a_list_an_empty_list_a_bare_key_and_no_key_are_not_plan_bad_findings( adopter, world, edit): """#386 (d): what `plan` writes, and the ways of writing none, are not findings of this kind; a plan with no entries is `plan-uncovered`, as it - was, and never `plan-bad-paths`.""" + was, and never `plan-bad-paths`. A `source:` or `legs:` left bare, or no + `source:` at all, is read as it always was too (#544), never as the wrong + type.""" key, block, passes = GOOD_KEYS[edit] text = with_key(world.answered, key, block) checked = run_check(write_edited(world, text)) assert_no_traceback(checked) assert "FINDING plan-bad-" not in checked.stderr, checked.stderr - if passes: + if isinstance(passes, str): + assert f"{passes}: " in checked.stderr, checked.stderr + assert checked.returncode == (2 if passes.startswith("REFUSED") + else 1), checked.stderr + elif passes: assert checked.returncode == 0, checked.stderr + checked.stdout plan, source = open_plan(adopter, world, text) assert adopter._refuse_what_check_finds(plan, source) is None diff --git a/tests/test_adopt_gitmodules_values.py b/tests/test_adopt_gitmodules_values.py index 41aa3734..ca9348bc 100644 --- a/tests/test_adopt_gitmodules_values.py +++ b/tests/test_adopt_gitmodules_values.py @@ -27,9 +27,9 @@ import pytest -from conftest import ADOPT, FILE_PROTOCOL, GITLINK, GITMODULES, as_human, \ - exported_in_a_clone, findings, git, in_the_history, link, \ - make_source_repo, planned, put_gitmodules, refused_before_any_leg, \ +from conftest import ADOPT, GITLINK, GITMODULES, as_human, \ + cloned_just_pushed, exported_in_a_clone, findings, git, in_the_history, \ + link, make_source_repo, planned, put_gitmodules, refused_before_any_leg, \ run_script needs_filter_repo = pytest.mark.skipif( @@ -454,10 +454,7 @@ def test_a_refused_value_git_never_reads_adopts_and_clones(tmp_path, where): adopted = execute_in(plan, tmp_path) assert adopted.returncode == 0, adopted.stderr + adopted.stdout assert "adoption verified" in adopted.stdout - clone = tmp_path / "clone" - cloned = git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - "adopt/three-repo-shape", str(source), str(clone), - cwd=tmp_path, check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(source, tmp_path / "clone", + "adopt/three-repo-shape") assert (clone / "code" / GITMODULES).read_bytes() \ == with_line("update = bogus").encode() diff --git a/tests/test_adopt_ignored_shape_files.py b/tests/test_adopt_ignored_shape_files.py index 034bc21d..5feb03dd 100644 --- a/tests/test_adopt_ignored_shape_files.py +++ b/tests/test_adopt_ignored_shape_files.py @@ -41,8 +41,9 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, clear_ambient_pin_sources, - git, make_source_repo, resolve, run_script, write_plan) +from conftest import (ADOPT, REPO, clear_ambient_pin_sources, + cloned_just_pushed, git, make_source_repo, resolve, + run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) from repo_shape import parse_yaml # noqa: E402 @@ -559,9 +560,7 @@ def test_the_split_lands_the_files_the_pin_names(adopted): @needs_filter_repo def test_the_assembly_bootstraps_where_it_failed_with_shape_copy_missing( adopted, tmp_path): - clone = tmp_path / PROJECT - git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", BRANCH, - str(adopted.source), str(clone), cwd=tmp_path) + clone = cloned_just_pushed(adopted.source, tmp_path / PROJECT, BRANCH) bootstrapped = run_script(clone / "scripts" / "bootstrap.py", cwd=clone) output = bootstrapped.stderr + bootstrapped.stdout assert "shape-copy-missing" not in output, output @@ -601,13 +600,8 @@ def test_a_checkout_that_writes_crlf_still_bootstraps(adopted, tmp_path, key, shape copy, its `* text=auto` alone governs it, and it IS checked out with CRLF, so the setting reached the checkout and the pass means what it says.""" - clone = tmp_path / PROJECT - cloned = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - BRANCH, str(adopted.source), str(clone)], cwd=str(tmp_path), - env=command_scope(key, value), capture_output=True, text=True, - check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(adopted.source, tmp_path / PROJECT, BRANCH, + env=command_scope(key, value)) if adopted.own: assert b"\r\n" in (clone / README).read_bytes(), \ "this clone did not write CRLF, so it tests nothing" @@ -752,9 +746,8 @@ def test_a_root_kept_symlink_the_shape_writes_through_adopts( def test_the_assembly_with_a_root_kept_symlink_bootstraps( adopted_through_a_link, tmp_path): assert adopted_through_a_link.result.returncode == 0 - clone = tmp_path / PROJECT - git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", BRANCH, - str(adopted_through_a_link.source), str(clone), cwd=tmp_path) + clone = cloned_just_pushed(adopted_through_a_link.source, + tmp_path / PROJECT, BRANCH) bootstrapped = run_script(clone / "scripts" / "bootstrap.py", cwd=clone) output = bootstrapped.stderr + bootstrapped.stdout assert bootstrapped.returncode == 0, output diff --git a/tests/test_adopt_mount_ignored.py b/tests/test_adopt_mount_ignored.py index 9d1d93e1..d1dd3e1e 100644 --- a/tests/test_adopt_mount_ignored.py +++ b/tests/test_adopt_mount_ignored.py @@ -50,7 +50,8 @@ import pytest from conftest import (ADOPT, DEFAULT_EDITS, FILE_PROTOCOL, SYNTHETIC_TREE, - git, make_source_repo, resolve, run_script, write_plan) + cloned_just_pushed, git, make_source_repo, resolve, + run_script, write_plan) #: What the end-to-end tests need and the mount tests do not. Spelled here #: rather than imported from another test module, so that the files do not @@ -193,9 +194,9 @@ def head(repo: Path) -> str: return git("rev-parse", "HEAD", cwd=repo).stdout.strip() -def clone(source: Path, destination: Path, *options: str) -> None: - git(*FILE_PROTOCOL, "clone", "-q", *options, str(source), - str(destination), cwd=destination.parent) +def clone(source: Path, destination: Path) -> None: + git(*FILE_PROTOCOL, "clone", "-q", str(source), str(destination), + cwd=destination.parent) def assembly_of(source: Path, tmp_path: Path) -> Path: @@ -621,8 +622,7 @@ def test_the_assembly_whose_gitignore_hides_its_mounts_bootstraps(adopted, """With #167's forced staging of the shape's own files, nothing else stands between this source and a working assembly.""" assert adopted.result.returncode == 0, adopted.result.stderr - checkout = tmp_path / PROJECT - clone(adopted.source, checkout, "--recurse-submodules", "-b", BRANCH) + checkout = cloned_just_pushed(adopted.source, tmp_path / PROJECT, BRANCH) bootstrapped = run_script(checkout / "scripts" / "bootstrap.py", cwd=checkout) output = bootstrapped.stderr + bootstrapped.stdout diff --git a/tests/test_adopt_pins.py b/tests/test_adopt_pins.py index 77897adc..918ed38b 100644 --- a/tests/test_adopt_pins.py +++ b/tests/test_adopt_pins.py @@ -43,10 +43,10 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, SCAFFOLD, SYNTHETIC_TREE, - WINDOWS_SKIP, as_human, findings, git, make_source_repo, - planned, refused_before_any_leg, resolve, rmtree, - run_script) +from conftest import (ADOPT, REPO, SCAFFOLD, SYNTHETIC_TREE, WINDOWS_SKIP, + as_human, cloned_just_pushed, findings, git, + make_source_repo, planned, refused_before_any_leg, + resolve, rmtree, run_script) sys.path.insert(0, str(REPO / "scripts")) import shape_materialize # noqa: E402 @@ -231,11 +231,7 @@ def adopted(request, tmp_path_factory, glass): "--work-dir", str(base / "work"), env=env) clone = base / "clone" / PROJECT if executed.returncode == 0: - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - "-b", BRANCH, str(source), str(clone)], - capture_output=True, check=False) - assert proc.returncode == 0, proc.stderr.decode(UTF8, "replace") + cloned_just_pushed(source, clone, BRANCH) return {"base": base, "source": source, "plan": plan, "env": env, "repository": repository, "commit": commit, "readable": readable, "planned": planned_run, @@ -372,11 +368,7 @@ def adopted_as_text(request, tmp_path_factory): "--work-dir", str(base / "work"), env=env) clone = base / "clone" / TEXT_PROJECT if executed.returncode == 0: - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - "-b", BRANCH, str(source), str(clone)], - capture_output=True, check=False) - assert proc.returncode == 0, proc.stderr.decode(UTF8, "replace") + cloned_just_pushed(source, clone, BRANCH) return {"product": product, "spelled": spelled, "commit": commit, "readable": readable, "source": source, "plan": plan, "env": env, "checked": checked, "executed": executed, "clone": clone} diff --git a/tests/test_adopt_plan_yaml_quoting.py b/tests/test_adopt_plan_yaml_quoting.py index 0511c0c0..aaf43d0d 100644 --- a/tests/test_adopt_plan_yaml_quoting.py +++ b/tests/test_adopt_plan_yaml_quoting.py @@ -74,7 +74,7 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, git, make_source_repo, +from conftest import (ADOPT, REPO, cloned_just_pushed, git, make_source_repo, resolve, run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) @@ -2032,11 +2032,7 @@ def test_what_is_still_accepted_adopts_clones_and_bootstraps(tmp_path): assert result.returncode == 0, result.stderr + result.stdout assert "adoption verified" in result.stdout - clone = tmp_path / "clone" - cloned = git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - SPLIT_BRANCH, str(source), str(clone), cwd=tmp_path, - check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(source, tmp_path / "clone", SPLIT_BRANCH) bootstrapped = run_script(clone / "scripts/bootstrap.py", cwd=clone) assert bootstrapped.returncode == 0, (bootstrapped.stderr + bootstrapped.stdout) diff --git a/tests/test_adopt_relative_submodule_url.py b/tests/test_adopt_relative_submodule_url.py index e33e5681..df2ee558 100644 --- a/tests/test_adopt_relative_submodule_url.py +++ b/tests/test_adopt_relative_submodule_url.py @@ -26,8 +26,8 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, git, make_source_repo, resolve, - run_script, write_plan) +from conftest import (ADOPT, FILE_PROTOCOL, clone_just_pushed, git, + make_source_repo, resolve, run_script, write_plan) needs_filter_repo = pytest.mark.skipif( shutil.which("git-filter-repo") is None, @@ -626,9 +626,7 @@ def execute(plan: Path, work: Path, remotes: Path): def clone_the_split(source: Path, clone: Path): """`git clone --recurse-submodules` of the branch `execute` pushed the split to: what a person runs next.""" - return git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - "adopt/three-repo-shape", str(source), str(clone), - cwd=clone.parent, check=False) + return clone_just_pushed(source, clone, "adopt/three-repo-shape") @needs_filter_repo diff --git a/tests/test_adopt_root_symlinks.py b/tests/test_adopt_root_symlinks.py index f3f56ab4..5a098072 100644 --- a/tests/test_adopt_root_symlinks.py +++ b/tests/test_adopt_root_symlinks.py @@ -60,8 +60,9 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, clear_ambient_pin_sources, - git, make_source_repo, run_script, write_plan) +from conftest import (ADOPT, REPO, clear_ambient_pin_sources, + cloned_just_pushed, git, make_source_repo, run_script, + write_plan) sys.path.insert(0, str(REPO / "scripts")) from shape_materialize import ( # noqa: E402 @@ -1828,9 +1829,7 @@ def split_modes(source: Path) -> dict: def bootstraps(source: Path, base: Path) -> None: """A recursive clone of the split, and its bootstrap, exit 0.""" - clone = base / PROJECT - git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", BRANCH, - str(source), str(clone), cwd=base) + clone = cloned_just_pushed(source, base / PROJECT, BRANCH) booted = run_script(clone / SCRIPTS / "bootstrap.py", cwd=clone) assert booted.returncode == 0, booted.stderr + booted.stdout diff --git a/tests/test_adopt_spec_only.py b/tests/test_adopt_spec_only.py index 19ec9ae0..a300abfc 100644 --- a/tests/test_adopt_spec_only.py +++ b/tests/test_adopt_spec_only.py @@ -16,12 +16,11 @@ from __future__ import annotations import shutil -import subprocess import sys import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, git, make_source_repo, +from conftest import (ADOPT, REPO, cloned_just_pushed, git, make_source_repo, resolve, run_script, write_plan) from test_scaffold_ignored_shape_files import excluding, ignored, tracked_under @@ -96,13 +95,8 @@ def adopted(tmp_path_factory) -> dict: @pytest.fixture def adopted_clone(adopted, tmp_path): - target = tmp_path / PROJECT - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - "-b", "adopt/three-repo-shape", str(adopted["source"]), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr - return target + return cloned_just_pushed(adopted["source"], tmp_path / PROJECT, + "adopt/three-repo-shape") # --- the plan records it ---------------------------------------------------- @@ -191,10 +185,8 @@ def test_the_consent_may_be_given_on_execute_itself(tmp_path): # --- the seeded leg --------------------------------------------------------- def test_the_seeded_leg_is_one_commit_of_the_template(adopted, tmp_path): - clone = tmp_path / "code-leg" - subprocess.run(["git", "clone", "-q", - str(adopted["remotes"] / f"{PROJECT}-code.git"), - str(clone)], check=True) + clone = cloned_just_pushed(adopted["remotes"] / f"{PROJECT}-code.git", + tmp_path / "code-leg") assert int(git("rev-list", "--count", "HEAD", cwd=clone).stdout) == 1 assert (clone / "README.md").is_file() assert (clone / "src" / ".gitkeep").is_file() @@ -243,10 +235,8 @@ def test_the_seeded_leg_keeps_its_template_files_under_the_operators_excludes( def test_the_spec_leg_still_carries_the_history(adopted, tmp_path): - clone = tmp_path / "spec-leg" - subprocess.run(["git", "clone", "-q", - str(adopted["remotes"] / f"{PROJECT}-spec.git"), - str(clone)], check=True) + clone = cloned_just_pushed(adopted["remotes"] / f"{PROJECT}-spec.git", + tmp_path / "spec-leg") assert int(git("rev-list", "--count", "HEAD", cwd=clone).stdout) > 1 assert (clone / "specs" / "001-render" / "spec.md").is_file() assert "Source Human" in git("log", "--format=%an", cwd=clone).stdout diff --git a/tests/test_adopt_submodules.py b/tests/test_adopt_submodules.py index 5a519fa6..e9d9b2a5 100644 --- a/tests/test_adopt_submodules.py +++ b/tests/test_adopt_submodules.py @@ -19,8 +19,9 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, git, make_source_repo, - resolve, run_script, write_plan) +from conftest import (ADOPT, FILE_PROTOCOL, REPO, clone_just_pushed, + cloned_just_pushed, git, make_source_repo, resolve, + run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) from repo_shape import load_yaml, parse_yaml # noqa: E402 @@ -85,9 +86,8 @@ def test_adopt_moves_existing_gitmodules_with_its_dependency(tmp_path, "--work-dir", str(tmp_path / "work")) assert result.returncode == 0, result.stderr + result.stdout - clone = tmp_path / "assembly" - git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - "adopt/three-repo-shape", str(source), str(clone), cwd=tmp_path) + clone = cloned_just_pushed(source, tmp_path / "assembly", + "adopt/three-repo-shape") leg = clone / dependency_leg assert git("rev-parse", "HEAD:.gitmodules", cwd=leg).stdout.strip() \ == original_modules @@ -1614,9 +1614,8 @@ def execute_in(plan: Path, base: Path): def clone_the_split(source: Path, tmp_path: Path): """`git clone --recurse-submodules` of the branch `execute` pushed the split to, into `tmp_path/clone`: what a person runs next.""" - return git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - "adopt/three-repo-shape", str(source), str(tmp_path / "clone"), - cwd=tmp_path, check=False) + return clone_just_pushed(source, tmp_path / "clone", + "adopt/three-repo-shape") def source_state(source: Path) -> list[str]: diff --git a/tests/test_adopt_verify.py b/tests/test_adopt_verify.py index 8821ad1a..4969a9ae 100644 --- a/tests/test_adopt_verify.py +++ b/tests/test_adopt_verify.py @@ -30,8 +30,8 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, git, make_source_repo, - resolve, run_script, write_plan) +from conftest import (ADOPT, FILE_PROTOCOL, REPO, clone_just_pushed, git, + make_source_repo, resolve, run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) from shape_materialize import SHAPE_REPOSITORY # noqa: E402 @@ -341,9 +341,7 @@ def test_a_spec_gitlink_not_where_the_plan_spells_the_mount_is_two_findings( def clone_the_split(source: Path, tmp_path: Path): """`git clone --recurse-submodules` of the split branch into `tmp_path/clone`: what the NEXT lines tell the human to run.""" - return git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", "-b", - SPLIT_BRANCH, str(source), str(tmp_path / "clone"), - cwd=tmp_path, check=False) + return clone_just_pushed(source, tmp_path / "clone", SPLIT_BRANCH) @needs_filter_repo diff --git a/tests/test_bootstrap_after_bump.py b/tests/test_bootstrap_after_bump.py index 78063c4c..ee36854c 100644 --- a/tests/test_bootstrap_after_bump.py +++ b/tests/test_bootstrap_after_bump.py @@ -56,7 +56,7 @@ import pytest -from conftest import (FILE_PROTOCOL, ORG, PROJECT, REPO, SCAFFOLD, git, +from conftest import (ORG, PROJECT, REPO, SCAFFOLD, cloned_just_pushed, git, run_script) BUMP = REPO / "scripts" / "bump-leg.py" @@ -144,9 +144,8 @@ def bootstrapped(tmp_path_factory) -> dict: "2026-09-02", "--local-remote-dir", str(remotes), "--work-dir", str(base / "work")) assert made.returncode == 0, made.stderr + made.stdout - clone = base / "clone" / PROJECT - git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(remotes / f"{PROJECT}.git"), str(clone), cwd=base) + clone = cloned_just_pushed(remotes / f"{PROJECT}.git", + base / "clone" / PROJECT) first = bootstrap(clone) assert first.returncode == 0, first.stderr + first.stdout assert head_ref(clone / "spec") == MAIN, first.stdout @@ -172,9 +171,8 @@ def bumped(root: Path, remotes: Path, tmp_path: Path, branch: str, only that some branch of the remote contains the commit. With `touch`, the commit also adds a line at the end of that tracked file. """ - work = tmp_path / "spec-work" - git("clone", "-q", str(remotes / f"{PROJECT}-spec.git"), str(work), - cwd=tmp_path) + work = cloned_just_pushed(remotes / f"{PROJECT}-spec.git", + tmp_path / "spec-work") (work / ADDED).write_bytes(b"# a contract the leg gained\n") git("add", "--", ADDED, cwd=work) if touch is not None: @@ -513,9 +511,8 @@ def leg_main_ahead(tmp_path_factory) -> dict: "2026-09-02", "--local-remote-dir", str(remotes), "--work-dir", str(base / "work")) assert made.returncode == 0, made.stderr + made.stdout - work = base / "spec-work" - git("clone", "-q", str(remotes / f"{PROJECT}-spec.git"), str(work), - cwd=base) + work = cloned_just_pushed(remotes / f"{PROJECT}-spec.git", + base / "spec-work") (work / MOVED_ON).write_bytes(b"# the leg's main moved on\n") git("add", "--", MOVED_ON, cwd=work) git("commit", "-q", "-m", "The leg moves on past the pin", cwd=work) @@ -528,10 +525,8 @@ def fresh(leg_main_ahead, tmp_path) -> Path: """`git clone --recurse-submodules` of the root, as AGENTS.md has a person run it, never bootstrapped: git made the spec leg's `main` at the remote's tip and left the leg detached at the pin.""" - clone = tmp_path / PROJECT - git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(leg_main_ahead["remotes"] / f"{PROJECT}.git"), str(clone), - cwd=tmp_path) + clone = cloned_just_pushed(leg_main_ahead["remotes"] / f"{PROJECT}.git", + tmp_path / PROJECT) leg = clone / "spec" assert head_ref(leg) is None and rev(leg) == rev(clone, "HEAD:spec") assert rev(leg, MAIN) == leg_main_ahead["ahead"] diff --git a/tests/test_bootstrap_spelling.py b/tests/test_bootstrap_spelling.py index 6cf55d0f..715c05ad 100644 --- a/tests/test_bootstrap_spelling.py +++ b/tests/test_bootstrap_spelling.py @@ -49,7 +49,6 @@ from __future__ import annotations import ast -import importlib.util import io import shutil import subprocess @@ -59,7 +58,7 @@ import pytest -from conftest import REPO, git, run_script +from conftest import REPO, git, load_without_bytecode, run_script from test_stdout_encoding import child_environ, run_tool_bytes, shown sys.path.insert(0, str(REPO / "scripts")) @@ -660,27 +659,6 @@ def test_a_members_row_and_the_make_target_are_spelled(holder, tmp_path): # --- the two functions, in repo_shape.py, and the roots that import them ----- -def load(path: Path, name: str): - """`path` run as a module called `name`, and no bytecode written for it. - - `SourceFileLoader` caches what it compiles in a `__pycache__` beside the - source, and these sources are the TEMPLATES: a tree other tests read file - by file as text (`test_scaffold_leg_path_collisions.py` does, and a `.pyc` - is not UTF-8), and a tree a materializer walks. `.gitignore` hides the - stray, so `git status` never says it is there. The flag is process-wide, so - it is restored; it also covers the `repo_shape` a root imports as it runs. - """ - spec = importlib.util.spec_from_file_location(name, path) - module = importlib.util.module_from_spec(spec) - wrote_bytecode = sys.dont_write_bytecode - sys.dont_write_bytecode = True - try: - spec.loader.exec_module(module) - finally: - sys.dont_write_bytecode = wrote_bytecode - return module - - def test_loading_a_root_leaves_no_file_where_it_was_loaded_from(tmp_path, monkeypatch): """The tree under `templates/` is the same after both roots are loaded. @@ -697,9 +675,9 @@ def test_loading_a_root_leaves_no_file_where_it_was_loaded_from(tmp_path, copy.parent.mkdir(parents=True) shutil.copyfile(path, copy) before_copy = set(copy.parent.rglob("*")) - load(path, f"bootstrap_spelling_probe_{number}") + load_without_bytecode(path, f"bootstrap_spelling_probe_{number}") assert set(templates.rglob("*")) == before, path - load(copy, f"bootstrap_spelling_probe_copy_{number}") + load_without_bytecode(copy, f"bootstrap_spelling_probe_copy_{number}") assert set(copy.parent.rglob("*")) == before_copy, copy.parent @@ -708,8 +686,9 @@ def test_loading_a_root_leaves_no_file_where_it_was_loaded_from(tmp_path, def template(request): """Each root's `bootstrap.py`, loaded by path under a name of its own, so nothing here leaves a module called `bootstrap` importable.""" - return load(request.param, "bootstrap_spelling_" - f"{request.param.parts[-3].replace('-', '_')}") + return load_without_bytecode( + request.param, + f"bootstrap_spelling_{request.param.parts[-3].replace('-', '_')}") @pytest.mark.parametrize("text", [ diff --git a/tests/test_bump_leg.py b/tests/test_bump_leg.py index 966be224..9f489ad0 100644 --- a/tests/test_bump_leg.py +++ b/tests/test_bump_leg.py @@ -24,7 +24,7 @@ import pytest -from conftest import ORG, PROJECT, REPO, git, run_script +from conftest import ORG, PROJECT, REPO, cloned_just_pushed, git, run_script #: The trailer helpers and the two lines a lane's run lands, imported from #: the file that owns those rules rather than retyped — see #: `tests/test_commit_trailers.py`. @@ -79,12 +79,9 @@ def bump_leg_module(): @pytest.fixture(scope="module") def advanced(scaffolded, tmp_path_factory) -> str: """A real new commit on the spec leg's bare remote; returns its sha.""" - work = tmp_path_factory.mktemp("spec-advance") / f"{PROJECT}-spec" - proc = subprocess.run( - ["git", "clone", "-q", - str(scaffolded["remotes"] / f"{PROJECT}-spec.git"), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded["remotes"] / f"{PROJECT}-spec.git", + tmp_path_factory.mktemp("spec-advance") / f"{PROJECT}-spec") (work / "NEW-CONTRACT.md").write_text("# a contract the leg gained\n") git("add", "--", "NEW-CONTRACT.md", cwd=work) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", diff --git a/tests/test_bump_leg_dry_run_tracking.py b/tests/test_bump_leg_dry_run_tracking.py index 220cf761..196d567f 100644 --- a/tests/test_bump_leg_dry_run_tracking.py +++ b/tests/test_bump_leg_dry_run_tracking.py @@ -31,7 +31,7 @@ import pytest -from conftest import PROJECT, git +from conftest import PROJECT, cloned_just_pushed, git from test_bump_leg import branched, bump, reference_both_legs from test_bump_leg_rollback import assert_nothing_changed, snapshot from test_commit_trailers import TRAILERS @@ -54,13 +54,10 @@ @pytest.fixture(scope="module") def advanced(scaffolded, tmp_path_factory) -> str: """A real new commit on the spec leg's bare remote; returns its sha.""" - work = (tmp_path_factory.mktemp("spec-advance-dry-run-tracking") - / f"{PROJECT}-spec") - proc = subprocess.run( - ["git", "clone", "-q", - str(scaffolded["remotes"] / f"{PROJECT}-spec.git"), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded["remotes"] / f"{PROJECT}-spec.git", + tmp_path_factory.mktemp("spec-advance-dry-run-tracking") + / f"{PROJECT}-spec") (work / "DRY-RUN-TRACKING.md").write_text("# a contract gained\n") git("add", "--", "DRY-RUN-TRACKING.md", cwd=work) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", diff --git a/tests/test_bump_leg_rollback.py b/tests/test_bump_leg_rollback.py index f46c42f4..94e42b11 100644 --- a/tests/test_bump_leg_rollback.py +++ b/tests/test_bump_leg_rollback.py @@ -46,13 +46,12 @@ import io import os import stat -import subprocess import sys from pathlib import Path import pytest -from conftest import PROJECT, git +from conftest import PROJECT, cloned_just_pushed, git # A fixture is used by being named in this module's namespace, which is what # the import of `bump_leg_module` is for. from test_bump_leg import ( # noqa: F401 @@ -73,12 +72,9 @@ @pytest.fixture(scope="module") def advanced(scaffolded, tmp_path_factory) -> str: """A real new commit on the spec leg's bare remote; returns its sha.""" - work = tmp_path_factory.mktemp("spec-advance-rollback") / f"{PROJECT}-spec" - proc = subprocess.run( - ["git", "clone", "-q", - str(scaffolded["remotes"] / f"{PROJECT}-spec.git"), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded["remotes"] / f"{PROJECT}-spec.git", + tmp_path_factory.mktemp("spec-advance-rollback") / f"{PROJECT}-spec") (work / "ROLLBACK-CONTRACT.md").write_text("# a contract the leg gained\n") git("add", "--", "ROLLBACK-CONTRACT.md", cwd=work) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", diff --git a/tests/test_bump_leg_unopenable.py b/tests/test_bump_leg_unopenable.py index eb49862f..9a3fe37a 100644 --- a/tests/test_bump_leg_unopenable.py +++ b/tests/test_bump_leg_unopenable.py @@ -57,13 +57,12 @@ import errno import os import stat -import subprocess import sys from pathlib import Path import pytest -from conftest import ORG, PROJECT, git +from conftest import ORG, PROJECT, cloned_just_pushed, git # A fixture is used by being named in this module's namespace, which is what # the import of `bump_leg_module` is for. from test_bump_leg import ( # noqa: F401 @@ -92,13 +91,9 @@ @pytest.fixture(scope="module") def advanced(scaffolded, tmp_path_factory) -> str: """A real new commit on the spec leg's bare remote; returns its sha.""" - work = (tmp_path_factory.mktemp("spec-advance-unopenable") - / f"{PROJECT}-spec") - proc = subprocess.run( - ["git", "clone", "-q", - str(scaffolded["remotes"] / f"{PROJECT}-spec.git"), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded["remotes"] / f"{PROJECT}-spec.git", + tmp_path_factory.mktemp("spec-advance-unopenable") / f"{PROJECT}-spec") (work / "UNOPENABLE-CONTRACT.md").write_text("# a contract gained\n") git("add", "--", "UNOPENABLE-CONTRACT.md", cwd=work) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", diff --git a/tests/test_bump_leg_unreadable.py b/tests/test_bump_leg_unreadable.py index 22773bb2..0a07f891 100644 --- a/tests/test_bump_leg_unreadable.py +++ b/tests/test_bump_leg_unreadable.py @@ -39,12 +39,11 @@ from __future__ import annotations import codecs -import subprocess from pathlib import Path import pytest -from conftest import ORG, PROJECT, git +from conftest import ORG, PROJECT, cloned_just_pushed, git # A fixture is used by being named in this module's namespace, which is what # the import of `bump_leg_module` is for. from test_bump_leg import ( # noqa: F401 @@ -74,13 +73,9 @@ @pytest.fixture(scope="module") def advanced(scaffolded, tmp_path_factory) -> str: """A real new commit on the spec leg's bare remote; returns its sha.""" - work = (tmp_path_factory.mktemp("spec-advance-unreadable") - / f"{PROJECT}-spec") - proc = subprocess.run( - ["git", "clone", "-q", - str(scaffolded["remotes"] / f"{PROJECT}-spec.git"), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded["remotes"] / f"{PROJECT}-spec.git", + tmp_path_factory.mktemp("spec-advance-unreadable") / f"{PROJECT}-spec") (work / "UNREADABLE-CONTRACT.md").write_text("# a contract gained\n") git("add", "--", "UNREADABLE-CONTRACT.md", cwd=work) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", diff --git a/tests/test_clone_just_pushed.py b/tests/test_clone_just_pushed.py new file mode 100644 index 00000000..8cd617eb --- /dev/null +++ b/tests/test_clone_just_pushed.py @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: Apache-2.0 +"""The options of `clone_just_pushed` and `cloned_just_pushed` (#544). + +#467 added `clone_just_pushed` for two fixtures. #544 moved every other test +that cloned a just-pushed local remote by its plain path onto it, and those +tests clone in the ways this file asks of the helper: without the submodules, +as a bare copy, with `-c` for the one command, with variables of their own, +and at a branch. Each case is one of those, and each case that makes a clone +asks the one thing the helper is for of it: the clone's store holds a pack and +no loose object, which a clone that walked the remote's own files would not +(`assert_store_read_through_git`). The option itself is asked of git beside +it, and the last case is the clone that fails. +""" + +from __future__ import annotations + +import os + +import pytest + +from conftest import (PROJECT, assert_read_through_git, + assert_store_read_through_git, clone_just_pushed, + cloned_just_pushed, git) + + +@pytest.fixture +def remote(scaffolded): + """The bare remote the session's scaffold pushed the root to.""" + return scaffolded["remotes"] / f"{PROJECT}.git" + + +def test_a_recursive_clone_holds_the_root_and_both_legs_as_packs(remote, + tmp_path): + clone = cloned_just_pushed(remote, tmp_path / "clone") + assert_read_through_git(clone) + assert any((clone / "spec").iterdir()), "the mount was not checked out" + + +def test_without_the_submodules_the_mounts_stay_empty(remote, tmp_path): + clone = cloned_just_pushed(remote, tmp_path / "clone", recurse=False) + assert_store_read_through_git(clone, "the root") + assert not any((clone / "spec").iterdir()), "the mount was checked out" + assert not any((clone / "code").iterdir()), "the mount was checked out" + + +def test_a_bare_copy_is_bare_and_was_read_through_git(remote, tmp_path): + copy = cloned_just_pushed(remote, tmp_path / "copy.git", bare=True) + assert git("rev-parse", "--is-bare-repository", + cwd=copy).stdout.strip() == "true" + assert_store_read_through_git(copy, "the bare copy") + + +def test_a_branch_is_the_one_checked_out(remote, tmp_path): + clone = cloned_just_pushed(remote, tmp_path / "clone", "main") + assert_read_through_git(clone) + assert git("rev-parse", "--abbrev-ref", "HEAD", + cwd=clone).stdout.strip() == "main" + + +def test_a_branch_the_remote_lacks_fails_the_clone(remote, tmp_path): + """`-b` reaches git: `main` is the default branch, so the case above + passes without it, and a branch no remote has is refused only by git.""" + asked = clone_just_pushed(remote, tmp_path / "x", "no-such-branch") + assert asked.returncode != 0, asked.stderr + assert "no-such-branch" in asked.stderr, asked.stderr + with pytest.raises(AssertionError, match="no-such-branch"): + cloned_just_pushed(remote, tmp_path / "x", "no-such-branch") + + +def test_config_and_env_reach_the_one_command_and_nothing_is_stored( + remote, tmp_path): + """`clone.defaultRemoteName` is read by `git clone` and is the name its + remote is given, so each of the two ways of passing it shows in the clone. + The `url..insteadOf` the helper adds is a `-c` too, and must not be + stored: the clone's `origin` keeps the plain path it was given.""" + by_config = cloned_just_pushed( + remote, tmp_path / "by-config", recurse=False, + config=["-c", "clone.defaultRemoteName=by-config"]) + assert_store_read_through_git(by_config, "the clone made with `config`") + assert git("remote", cwd=by_config).stdout.split() == ["by-config"] + # APPENDED to what the environment holds, as `command_scope` does in + # `test_adopt_ignored_shape_files.py`: the Windows job sets two entries + # of its own there, and `GIT_CONFIG_COUNT=1` would drop them. + held = int(os.environ.get("GIT_CONFIG_COUNT") or 0) + by_env = cloned_just_pushed( + remote, tmp_path / "by-env", recurse=False, + env={"GIT_CONFIG_COUNT": str(held + 1), + f"GIT_CONFIG_KEY_{held}": "clone.defaultRemoteName", + f"GIT_CONFIG_VALUE_{held}": "by-env"}) + assert_store_read_through_git(by_env, "the clone made with `env`") + assert git("remote", cwd=by_env).stdout.split() == ["by-env"] + stored = git("config", "--local", "--list", cwd=by_env).stdout + assert "insteadof" not in stored.lower(), stored + assert git("remote", "get-url", "by-env", cwd=by_env).stdout.strip() \ + == str(remote) + + +def test_a_clone_that_fails_fails_the_test_and_says_why(tmp_path): + missing = tmp_path / "remotes" / "Nothing.git" + assert clone_just_pushed(missing, tmp_path / "clone").returncode != 0 + with pytest.raises(AssertionError, match="git clone of .* failed"): + cloned_just_pushed(missing, tmp_path / "clone") diff --git a/tests/test_doctor_gitlink_repairs.py b/tests/test_doctor_gitlink_repairs.py index 59c95c02..98ae92b1 100644 --- a/tests/test_doctor_gitlink_repairs.py +++ b/tests/test_doctor_gitlink_repairs.py @@ -48,7 +48,7 @@ import pytest -from conftest import FILE_PROTOCOL, REPO, git, run_script +from conftest import FILE_PROTOCOL, REPO, cloned_just_pushed, git, run_script from test_commit_trailers import LANE from test_shape_doctor import ( # noqa: F401 DOCTOR, DRIFT_TARGET, GIT_IDENTITY, PROJECT, doctor, doctor_module, @@ -74,11 +74,9 @@ def advance(scaffolded_here, tmp_path_factory, role: str, """A new commit on a branch of a leg's remote, as a merged pull request leaves one, adding `relative` (a path the `placement` row puts in that leg); its sha.""" - work = tmp_path_factory.mktemp(f"{role}-advance") / f"{PROJECT}-{role}" - remote = scaffolded_here["base"] / "remotes" / f"{PROJECT}-{role}.git" - proc = subprocess.run(["git", "clone", "-q", str(remote), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded_here["base"] / "remotes" / f"{PROJECT}-{role}.git", + tmp_path_factory.mktemp(f"{role}-advance") / f"{PROJECT}-{role}") (work / relative).parent.mkdir(parents=True, exist_ok=True) (work / relative).write_text("# R478\n", encoding="utf-8") git("add", "--", relative, cwd=work) diff --git a/tests/test_doctor_legs_members_next.py b/tests/test_doctor_legs_members_next.py index 54670422..cc2dc543 100644 --- a/tests/test_doctor_legs_members_next.py +++ b/tests/test_doctor_legs_members_next.py @@ -47,7 +47,7 @@ import pytest -from conftest import WINDOWS_SKIP, git, rmtree +from conftest import WINDOWS_SKIP, cloned_just_pushed, git, rmtree from test_shape_doctor import ( # noqa: F401 FAMILY_MEMBER, FAMILY_NAME, GIT_IDENTITY, PROJECT, commit_all, doctor, doctor_module, holder, project, record_gitlink, rows_of, @@ -508,12 +508,9 @@ def test_a_plain_clones_empty_mounts_are_cured_by_the_pasted_line( mounts empty. Without `--init` the update prints `not initialized` and the row stays red; the fixture's own recursive clone sets `submodule.active`, so every other paste here would pass without it.""" - plain = tmp_path / "Plain" - cloned = subprocess.run( - ["git", "clone", "-q", str(scaffolded_here["base"] / "remotes" - / f"{PROJECT}.git"), str(plain)], - capture_output=True, text=True, check=False, env=PASTE_ENV) - assert cloned.returncode == 0, cloned.stderr + plain = cloned_just_pushed( + scaffolded_here["base"] / "remotes" / f"{PROJECT}.git", + tmp_path / "Plain", recurse=False, env=PASTE_ENV) row = row_of(standard, plain, "legs") assert row["status"] == "FINDING", row assert command_of(row).endswith("-- spec code"), row["next"] diff --git a/tests/test_doctor_unfinished_merge.py b/tests/test_doctor_unfinished_merge.py index 8150d582..0094ba05 100644 --- a/tests/test_doctor_unfinished_merge.py +++ b/tests/test_doctor_unfinished_merge.py @@ -38,7 +38,7 @@ import pytest -from conftest import FILE_PROTOCOL, git, run_script +from conftest import FILE_PROTOCOL, cloned_just_pushed, git, run_script from test_shape_doctor import ( # noqa: F401 FAMILY_NAME, PROJECT, doctor, doctor_module, holder, powershell_tokens, project, rows_of, scaffolded_here, standard, verdict_line, @@ -122,9 +122,8 @@ def assert_compliant_with_no_merge_row(standard: Path, root: Path) -> None: def spec_commits_on_branches(remotes: Path, tmp_path: Path) -> tuple: """Two commits on the spec leg's remote, siblings of its pin, each on a branch of its own, so a merge of two bumps conflicts on the gitlink.""" - work = tmp_path / "spec-work" - git(*FILE_PROTOCOL, "clone", "-q", str(remotes / f"{PROJECT}-spec.git"), - str(work), cwd=tmp_path) + work = cloned_just_pushed(remotes / f"{PROJECT}-spec.git", + tmp_path / "spec-work") base = git("rev-parse", "HEAD", cwd=work).stdout.strip() made = [] for side in ("a", "b"): diff --git a/tests/test_doctor_validator_rows.py b/tests/test_doctor_validator_rows.py index 07dee634..564ed618 100644 --- a/tests/test_doctor_validator_rows.py +++ b/tests/test_doctor_validator_rows.py @@ -40,7 +40,7 @@ import pytest -from conftest import FILE_PROTOCOL, WINDOWS_SKIP, git, run_script +from conftest import WINDOWS_SKIP, cloned_just_pushed, git, run_script from conftest import PTY_SKIP, REPO, run_in_a_terminal #: The neutral product's stand-in, from the module that scaffolds a declared #: descendant: a second way of building one would test a different one. @@ -411,12 +411,8 @@ def descendant_run(standard, tmp_path_factory) -> dict: "--work-dir", str(base / "work"), env={"PATH": f"{quiet}{os.pathsep}{os.environ['PATH']}"}) assert made.returncode == 0, made.stderr + made.stdout - clone = base / "clones" / "MedxGlass" - cloned = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(base / "remotes" / "MedxGlass.git"), str(clone)], - capture_output=True, text=True, check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(base / "remotes" / "MedxGlass.git", + base / "clones" / "MedxGlass") assert not (clone.parent / PRODUCT).exists() bin_dir, calls = logging_gh(base / "doctor-gh") operators_config = str(base / "operators-gh-config") diff --git a/tests/test_family.py b/tests/test_family.py index 610df4c4..ef5ad3b1 100644 --- a/tests/test_family.py +++ b/tests/test_family.py @@ -32,7 +32,8 @@ import pytest -from conftest import FILE_PROTOCOL, REPO, git, rmtree, run_script +from conftest import (NO_BYTECODE, REPO, cloned_just_pushed, git, rmtree, + run_script) #: The trailer helpers and the two lines a lane's run lands, imported from #: the file that owns those rules rather than retyped — see #: `tests/test_commit_trailers.py`. @@ -1368,12 +1369,8 @@ def test_remove_refuses_a_member_that_is_not_there(holder): @pytest.fixture(scope="module") def bootstrapped(family, tmp_path_factory): """A fresh recursive clone of the family, bootstrapped.""" - target = tmp_path_factory.mktemp("family-clone") / NAME - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(family["remotes"] / f"{NAME}.git"), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + target = cloned_just_pushed(family["remotes"] / f"{NAME}.git", + tmp_path_factory.mktemp("family-clone") / NAME) result = run_script(target / "scripts" / "bootstrap.py", cwd=target, env=ALLOW_FILE_PROTOCOL) return {"root": target, "result": result} @@ -1400,12 +1397,8 @@ def test_bootstrap_degrades_when_a_member_cannot_be_fetched(family, tmp_path): """A missing credential is a DEGRADE, not a failure: without `protocol.file.allow` git refuses these local submodules exactly as it would refuse a private one with no token.""" - target = tmp_path / NAME - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", - str(family["remotes"] / f"{NAME}.git"), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + target = cloned_just_pushed(family["remotes"] / f"{NAME}.git", + tmp_path / NAME, recurse=False) result = run_script(target / "scripts" / "bootstrap.py", cwd=target) assert result.returncode == 0, result.stderr + result.stdout assert "FETCH INCOMPLETE" in result.stderr @@ -1943,7 +1936,7 @@ def one(name: str, runner) -> dict: theirs = one("target", lambda holder: siblings(holder)) ours = one("shim", lambda holder: run_script( FAMILY, "siblings", "--family-root", str(holder), - env=ALLOW_FILE_PROTOCOL)) + env={**ALLOW_FILE_PROTOCOL, **NO_BYTECODE})) assert layout(ours["result"].stdout) == layout(theirs["result"].stdout) for project in MEMBERS: diff --git a/tests/test_family_add_excludes.py b/tests/test_family_add_excludes.py index 80044c29..1dd0f2cc 100644 --- a/tests/test_family_add_excludes.py +++ b/tests/test_family_add_excludes.py @@ -43,7 +43,7 @@ import pytest -from conftest import FILE_PROTOCOL, git, rmtree, run_script +from conftest import FILE_PROTOCOL, cloned_just_pushed, git, rmtree, run_script #: The family fixture, its names and the offline environment, imported from #: the modules that own them rather than rebuilt: a holder made any other way #: would test a different holder. @@ -149,10 +149,7 @@ def recursive_clone(root: Path, tmp_path: Path) -> Path: git_under(env, "init", "-q", "--bare", "-b", "main", str(bare), cwd=tmp_path) git_under(env, "push", "-q", str(bare), "HEAD:refs/heads/main", cwd=root) - clone = tmp_path / "clone" / NAME - git_under(env, *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(bare), str(clone), cwd=tmp_path) - return clone + return cloned_just_pushed(bare, tmp_path / "clone" / NAME, env=env) def refused_before_the_mount(result: subprocess.CompletedProcess, root: Path, diff --git a/tests/test_family_add_member_id.py b/tests/test_family_add_member_id.py index 5393473d..4409f505 100644 --- a/tests/test_family_add_member_id.py +++ b/tests/test_family_add_member_id.py @@ -38,7 +38,7 @@ import pytest -from conftest import git, run_script +from conftest import cloned_just_pushed, git, run_script #: The family fixture, its names and the offline environment, imported from #: the modules that own them rather than rebuilt: a holder made any other way #: would test a different holder. @@ -82,8 +82,8 @@ def renamed_copy(family, tmp_path: Path) -> Path: name. The fixture's own remotes are not written to.""" remotes = tmp_path / "remotes" remotes.mkdir() - git("clone", "-q", "--bare", str(family["remotes"] / f"{PINNED}.git"), - str(remotes / f"{RENAMED}.git"), cwd=tmp_path) + cloned_just_pushed(family["remotes"] / f"{PINNED}.git", + remotes / f"{RENAMED}.git", bare=True) return remotes diff --git a/tests/test_family_bump_remove_rollback.py b/tests/test_family_bump_remove_rollback.py index 066591cd..3dcfe47c 100644 --- a/tests/test_family_bump_remove_rollback.py +++ b/tests/test_family_bump_remove_rollback.py @@ -61,7 +61,8 @@ import pytest -from conftest import FILE_PROTOCOL, git, rmtree, run_script +from conftest import (FILE_PROTOCOL, cloned_just_pushed, git, rmtree, + run_script) #: The family fixture, its names, the offline environment and the holder's #: byte-for-byte state, imported from the modules that own them rather than #: rebuilt: a holder made, or compared, any other way would test a @@ -866,7 +867,7 @@ def test_a_checkout_whose_git_dir_deinit_absorbed_is_put_back( cwd=holder).stdout.strip() rmtree(mount) rmtree(holder / ".git" / "modules" / "members" / MEMBER) - git(*FILE_PROTOCOL, "clone", "-q", url, str(mount), cwd=tmp_path) + cloned_just_pushed(Path(url), mount, recurse=False) assert (mount / ".git").is_dir(), "premise: an embedded .git directory" embedded = Path(git("rev-parse", "--absolute-git-dir", cwd=mount ).stdout.strip()) diff --git a/tests/test_family_bump_to_a_tag.py b/tests/test_family_bump_to_a_tag.py index 0788db97..c02bdbe8 100644 --- a/tests/test_family_bump_to_a_tag.py +++ b/tests/test_family_bump_to_a_tag.py @@ -38,6 +38,7 @@ import pytest +from conftest import cloned_just_pushed #: The family fixture and its names, from the module that owns them. from test_family import ORG, family, manifest, validate # noqa: F401 from test_family_add_refusals import holder, offline, refusal # noqa: F401 @@ -144,8 +145,8 @@ def test_a_release_tag_only_the_fetch_brings_is_refused_after_the_fetch( # A PRIVATE COPY of the member's remote, which the mount is pointed at: # the fixture's own remote is read here and never written to. remote = tmp_path / f"{MEMBER}-private.git" - raw(tmp_path, env, "clone", "-q", "--bare", "--", - str(family["remotes"] / f"{MEMBER}.git"), str(remote)) + cloned_just_pushed(family["remotes"] / f"{MEMBER}.git", remote, + bare=True, env=env) work = tmp_path / f"{MEMBER}-release" raw(tmp_path, env, "clone", "-q", "--", str(remote), str(work)) (work / "RELEASE.md").write_bytes(b"release 2\n") diff --git a/tests/test_family_dry_run.py b/tests/test_family_dry_run.py index b8daf51a..b501facd 100644 --- a/tests/test_family_dry_run.py +++ b/tests/test_family_dry_run.py @@ -49,7 +49,7 @@ import pytest -from conftest import git, run_script +from conftest import cloned_just_pushed, git, run_script #: The trailer lines a lane's run lands, from the file that owns them. from test_commit_trailers import TRAILERS, trailers_of #: The family fixture, its names and the offline environment, imported from @@ -1046,8 +1046,7 @@ def a_renamed_copy_of_a_member(root: Path) -> Path: f"submodule.members/{PINNED}.url", cwd=root).stdout.strip() remotes = root.parent / "renamed-remotes" remotes.mkdir() - git("clone", "-q", "--bare", source, str(remotes / f"{RENAMED}.git"), - cwd=root.parent) + cloned_just_pushed(Path(source), remotes / f"{RENAMED}.git", bare=True) return remotes diff --git a/tests/test_family_member_name.py b/tests/test_family_member_name.py index 6263925b..50425ca5 100644 --- a/tests/test_family_member_name.py +++ b/tests/test_family_member_name.py @@ -47,7 +47,7 @@ import pytest -from conftest import REPO, git, run_script +from conftest import REPO, cloned_just_pushed, git, run_script from test_family import ( # noqa: F401 FAMILY, ORG, SPARE, family, family_module, manifest, validate, ) @@ -92,8 +92,8 @@ def remotes_for(family, tmp_path: Path) -> Path: remotes = tmp_path / "remotes" remotes.mkdir() for project in PROJECTS: - git("clone", "-q", "--bare", str(family["remotes"] / f"{SPARE}.git"), - str(remotes / f"{project}.git"), cwd=tmp_path) + cloned_just_pushed(family["remotes"] / f"{SPARE}.git", + remotes / f"{project}.git", bare=True) return remotes diff --git a/tests/test_family_member_path.py b/tests/test_family_member_path.py index f5c2bd2d..8bd2d266 100644 --- a/tests/test_family_member_path.py +++ b/tests/test_family_member_path.py @@ -49,7 +49,7 @@ import pytest -from conftest import git, run_script +from conftest import cloned_just_pushed, git, run_script #: The family fixture, its names and the offline environment, imported from #: the modules that own them rather than rebuilt: a holder made any other way #: would test a different holder. @@ -254,8 +254,8 @@ def test_bump_refuses_a_row_naming_a_directory_that_is_really_there( as a gitlink of its own. The refusal comes before all of that: the clone stays where it was.""" env = isolated(offline, tmp_path) - git("clone", "-q", str(family["remotes"] / f"{MEMBER}.git"), GLOB, - cwd=holder) + cloned_just_pushed(family["remotes"] / f"{MEMBER}.git", holder / GLOB, + recurse=False) star = holder / "members" / "*" assert (star / ".git").exists(), "premise: the literal checkout is there" tip = raw(star, env, "rev-parse", "HEAD").stdout diff --git a/tests/test_family_printed_quoting.py b/tests/test_family_printed_quoting.py index c1af5b64..53a105bc 100644 --- a/tests/test_family_printed_quoting.py +++ b/tests/test_family_printed_quoting.py @@ -43,7 +43,7 @@ import pytest -from conftest import REPO, git, run_script +from conftest import NO_BYTECODE, REPO, git, run_script #: The family fixture and its names, imported from the module that owns them. from test_family import ( # noqa: F401 FAMILY, NAME, ORG, SPARE, family, manifest, validate, @@ -372,7 +372,7 @@ def test_the_siblings_mv_lines_paste_whole_and_move_the_holder(family, move the holder to `/InkRouter/InkRouter`.""" holder = copied_holder(family, tmp_path / folder) result = run_script(FAMILY, "siblings", "--family-root", str(holder), - "--dry-run") + "--dry-run", env=NO_BYTECODE) assert result.returncode == 0, result.stdout + result.stderr assert "NOTHING WAS MOVED" in result.stderr assert "in a POSIX shell (Git Bash" in result.stderr diff --git a/tests/test_leg_path_yaml_scalars.py b/tests/test_leg_path_yaml_scalars.py index 110e3993..523cd352 100644 --- a/tests/test_leg_path_yaml_scalars.py +++ b/tests/test_leg_path_yaml_scalars.py @@ -47,7 +47,7 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, ORG, REPO, SCAFFOLD, git, +from conftest import (ADOPT, ORG, REPO, SCAFFOLD, cloned_just_pushed, git, make_source_repo, resolve, run_script, write_plan) sys.path.insert(0, str(REPO / "scripts")) @@ -519,11 +519,8 @@ def test_a_lookalike_scaffolds_clones_and_bootstraps(tmp_path): "--local-remote-dir", str(tmp_path / REMOTES), "--work-dir", str(tmp_path / WORK)) assert result.returncode == 0, result.stderr + result.stdout - clone = tmp_path / "clone" - cloned = git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(tmp_path / REMOTES / "Northwind.git"), str(clone), - cwd=tmp_path, check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(tmp_path / REMOTES / "Northwind.git", + tmp_path / "clone") manifest = load_yaml(clone / "project.yaml") assert [leg["path"] for leg in manifest["legs"]] == [".", spec_path, code_path] diff --git a/tests/test_scaffold_e2e.py b/tests/test_scaffold_e2e.py index 45d7ce17..bbcee142 100644 --- a/tests/test_scaffold_e2e.py +++ b/tests/test_scaffold_e2e.py @@ -11,8 +11,9 @@ import re import subprocess -from conftest import (FILE_PROTOCOL, ORG, PROJECT, REPO, SCAFFOLD, - WINDOWS_SKIP, assert_read_through_git, git, run_script) +from conftest import (ORG, PROJECT, REPO, SCAFFOLD, WINDOWS_SKIP, + assert_read_through_git, cloned_just_pushed, git, + run_script) DEGRADE_LINE = "authority is not wallet-carried in this org" @@ -304,12 +305,8 @@ def test_the_legs_are_submodules_at_the_declared_paths(project): def test_a_recursive_clone_leaves_the_legs_detached(scaffolded, tmp_path): """The submodule tax bootstrap exists to pay, demonstrated.""" - target = tmp_path / "fresh" - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(scaffolded["remotes"] / f"{PROJECT}.git"), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + target = cloned_just_pushed(scaffolded["remotes"] / f"{PROJECT}.git", + tmp_path / "fresh") head = git("rev-parse", "--abbrev-ref", "HEAD", cwd=target / "spec").stdout assert head.strip() == "HEAD", "expected a detached HEAD before bootstrap" @@ -903,12 +900,7 @@ def test_a_bad_visibility_is_refused_by_argparse(tmp_path): def clone_with_autocrlf(source, target): """A clone made the way Git for Windows' default makes one.""" - proc = subprocess.run( - ["git", *FILE_PROTOCOL, *AUTOCRLF, "clone", "-q", - "--recurse-submodules", str(source), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr - return target + return cloned_just_pushed(source, target, config=AUTOCRLF) def test_the_scaffolded_root_says_what_its_bytes_are(project, monkeypatch): @@ -959,12 +951,8 @@ def test_without_the_attributes_file_the_same_clone_goes_red(scaffolded, tmp_pat the file above stops. Without this assertion the green test could be green because `core.autocrlf` did nothing at all. """ - plain = tmp_path / "plain" - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(scaffolded["remotes"] / f"{PROJECT}.git"), str(plain)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + plain = cloned_just_pushed(scaffolded["remotes"] / f"{PROJECT}.git", + tmp_path / "plain") git("rm", "-q", "--", ".gitattributes", cwd=plain) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-q", "-m", "Drop the attributes file", "--", ".gitattributes", cwd=plain) diff --git a/tests/test_scaffold_ignored_shape_files.py b/tests/test_scaffold_ignored_shape_files.py index 7e055c28..2da364d2 100644 --- a/tests/test_scaffold_ignored_shape_files.py +++ b/tests/test_scaffold_ignored_shape_files.py @@ -34,8 +34,9 @@ import pytest -from conftest import (FILE_PROTOCOL, ORG, PROJECT, REPO, SCAFFOLD, - clear_ambient_pin_sources, git, run_script) +from conftest import (ORG, PROJECT, REPO, SCAFFOLD, + clear_ambient_pin_sources, cloned_just_pushed, git, + run_script) sys.path.insert(0, str(REPO / "scripts")) import shape_materialize # noqa: E402 @@ -193,10 +194,8 @@ def test_the_first_commits_hold_every_file_the_scaffold_wrote( def test_the_scaffold_bootstraps_under_the_same_excludes(excluded_scaffold, tmp_path): env = excluded_scaffold.env - clone = tmp_path / PROJECT - git_under(env, *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(excluded_scaffold.remotes / f"{PROJECT}.git"), str(clone), - cwd=tmp_path) + clone = cloned_just_pushed(excluded_scaffold.remotes / f"{PROJECT}.git", + tmp_path / PROJECT, env=env) result = run_script(clone / "scripts" / "bootstrap.py", cwd=clone, env=env) assert result.returncode == 0, result.stderr + result.stdout assert "bootstrap ok" in result.stdout @@ -236,9 +235,8 @@ def test_every_file_the_holder_wrote_is_in_its_first_commit(excluded_family): def test_the_holder_bootstraps_under_the_same_excludes(excluded_family, tmp_path): env = excluded_family.env - clone = tmp_path / HOLDER - git_under(env, "clone", "-q", str(excluded_family.bare), str(clone), - cwd=tmp_path) + clone = cloned_just_pushed(excluded_family.bare, tmp_path / HOLDER, + recurse=False, env=env) result = run_script(clone / "scripts" / "bootstrap.py", cwd=clone, env=env) assert result.returncode == 0, result.stderr + result.stdout assert "family bootstrap ok" in result.stdout diff --git a/tests/test_scaffold_leg_paths.py b/tests/test_scaffold_leg_paths.py index 3db6f97b..6148584f 100644 --- a/tests/test_scaffold_leg_paths.py +++ b/tests/test_scaffold_leg_paths.py @@ -28,8 +28,8 @@ import pytest -from conftest import (ADOPT, FILE_PROTOCOL, ORG, REPO, SCAFFOLD, WINDOWS_SKIP, - git, run_script) +from conftest import (ADOPT, ORG, REPO, SCAFFOLD, WINDOWS_SKIP, + cloned_just_pushed, git, run_script) sys.path.insert(0, str(REPO / "scripts")) import shape_materialize # noqa: E402 @@ -270,10 +270,7 @@ def test_canonical_paths_still_scaffold_and_a_clone_survives_the_work_dir( "a registration still points at the scaffold's work directory") (tmp_path / WORK).rename(tmp_path / "work.moved") - clone = tmp_path / "clone" - cloned = git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(assembly), str(clone), cwd=tmp_path, check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(assembly, tmp_path / "clone") for path in paths: assert any((clone / path).iterdir()), f"{path} was not checked out" diff --git a/tests/test_scaffold_manifest_values.py b/tests/test_scaffold_manifest_values.py index f36a9bf5..36bbe81f 100644 --- a/tests/test_scaffold_manifest_values.py +++ b/tests/test_scaffold_manifest_values.py @@ -53,8 +53,8 @@ import pytest -from conftest import (FILE_PROTOCOL, ORG, REPO, SCAFFOLD, WINDOWS_SKIP, git, - run_script) +from conftest import (ORG, REPO, SCAFFOLD, WINDOWS_SKIP, cloned_just_pushed, + git, run_script) sys.path.insert(0, str(REPO / "scripts")) import shape_materialize # noqa: E402 @@ -456,11 +456,8 @@ def test_what_is_typed_is_what_the_project_holds_and_bootstrap_agrees( result = scaffold(tmp_path, f"--tracking-branch={branch}", f"--id={project_id}", work=True) assert result.returncode == 0, result.stderr + result.stdout - clone = tmp_path / "Atlas" - cloned = git(*FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(tmp_path / REMOTES / "Atlas.git"), str(clone), - cwd=tmp_path, check=False) - assert cloned.returncode == 0, cloned.stderr + clone = cloned_just_pushed(tmp_path / REMOTES / "Atlas.git", + tmp_path / "Atlas") manifest = load_yaml(clone / "project.yaml") assert manifest["id"] == project_id assert manifest["tracking_branch"] == branch diff --git a/tests/test_scaffold_mount_ignored.py b/tests/test_scaffold_mount_ignored.py index b3d5a6d1..9b00c496 100644 --- a/tests/test_scaffold_mount_ignored.py +++ b/tests/test_scaffold_mount_ignored.py @@ -64,7 +64,7 @@ import pytest -from conftest import (FILE_PROTOCOL, ORG, SCAFFOLD, git, run_script) +from conftest import (ORG, SCAFFOLD, cloned_just_pushed, git, run_script) from test_scaffold_ignored_shape_files import excluding, git_under PROJECT = "Northwind" @@ -241,9 +241,7 @@ def clone_and_check(root: Path, env: dict, tmp_path: Path, *mounts: str): its tracking branch AT the pin, so it fails where a gitlink and a pin disagree and `validate-pins.py` had not looked at the checkout. """ - clone = tmp_path / PROJECT - git_under(env, *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(root), str(clone), cwd=tmp_path) + clone = cloned_just_pushed(root, tmp_path / PROJECT, env=env) for path in mounts: assert any((clone / path).iterdir()), f"{path} was not checked out" for validator, ok in (("validate-manifest.py", "manifest ok"), diff --git a/tests/test_shape_doctor.py b/tests/test_shape_doctor.py index 7a420fdf..d2ca4ed2 100644 --- a/tests/test_shape_doctor.py +++ b/tests/test_shape_doctor.py @@ -43,7 +43,8 @@ import pytest from conftest import (FILE_PROTOCOL, REPO, WINDOWS_SKIP, - assert_read_through_git, clone_just_pushed, git, rmtree, + assert_read_through_git, clone_just_pushed, + cloned_just_pushed, git, load_without_bytecode, rmtree, run_script) from gh_guard import PROBES as GH_PROBES @@ -476,16 +477,10 @@ def siblings(): scripts = REPO / "templates" / "family-root" / "scripts" saved_path, saved_module = list(sys.path), sys.modules.get("bootstrap") try: - spec = importlib.util.spec_from_file_location( - "doctor_family_bootstrap", scripts / "bootstrap.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - sys.modules["bootstrap"] = module - spec = importlib.util.spec_from_file_location( - "doctor_family_siblings", scripts / "siblings.py") - loaded = importlib.util.module_from_spec(spec) - spec.loader.exec_module(loaded) - yield loaded + sys.modules["bootstrap"] = load_without_bytecode( + scripts / "bootstrap.py", "doctor_family_bootstrap") + yield load_without_bytecode(scripts / "siblings.py", + "doctor_family_siblings") finally: sys.path[:] = saved_path if saved_module is None: @@ -3080,15 +3075,17 @@ def member_beside(holder: dict, tmp_path: Path) -> tuple[Path, Path]: itself refuses to answer any question about (`fatal: not a git repository`). It passed only because the row asked nothing of origin (#146). `git clone` from the url the holder mounts the member from is - both a real clone and the exact thing `siblings.py` does. + both a real clone and the exact thing `siblings.py` does, but for the + one flag of #544: the member's remote was just pushed, so the clone is + read through git (`cloned_just_pushed`) and not through its files. """ base = tmp_path / "fam" base.mkdir() root = base / FAMILY_NAME shutil.copytree(holder["root"], root, symlinks=True) remote = holder["base"] / "remotes" / f"{FAMILY_MEMBER}.git" - git("clone", "-q", str(remote), str(base / FAMILY_MEMBER), cwd=base) - return root, base / FAMILY_MEMBER + return root, cloned_just_pushed(remote, base / FAMILY_MEMBER, + recurse=False) def test_a_real_working_clone_beside_the_holder_is_counted(standard, holder, diff --git a/tests/test_shape_pin_files_not_a_list.py b/tests/test_shape_pin_files_not_a_list.py index 18459c54..0c931507 100644 --- a/tests/test_shape_pin_files_not_a_list.py +++ b/tests/test_shape_pin_files_not_a_list.py @@ -19,10 +19,11 @@ except `files: abc`, which is a finding per character or a refusal that names the wrong thing. -The doctor's VERDICT for a root pin `read_pin` refuses is #237's business -(PR #267 moves it from CANNOT ANSWER, exit 3, to INVALID, exit 1), so the -doctor's cases assert the ROW and that a verdict was printed at all, never -which one. +The doctor's VERDICT for a root pin `read_pin` refuses is INVALID, exit 1 +(#237, moved there by #267 from CANNOT ANSWER, exit 3, which is the failure +this checkout has when it cannot ask, and not a statement about the pin). The +doctor's cases assert that exit, that verdict and the ROW, so a doctor that +answered CANNOT ANSWER for `files: abc` would fail here. NOTHING HERE REACHES A NETWORK: the scaffold and the holder use `--local-remote-dir`, and `read_pin` refuses before `--upstream` is opened. @@ -66,11 +67,6 @@ pytest.param("abc", "'abc'", id="files-abc"), ] -#: Every verdict the doctor's text report can end on. -VERDICT = re.compile(r"^(COMPLIANT|DRIFTED|MISPLACED|INVALID|NOT A SHAPE ROOT" - r"|CANNOT ANSWER)\b") - - def commit_files(root, files_text: str, message: str) -> None: """Replace the pin's `files:` rows by `files_text` and commit it. @@ -146,10 +142,12 @@ def assert_one_shape_pin_row_finding(result, shown: str) -> None: def assert_shape_currency_row(result, shown: str) -> dict: - """The doctor printed a report, and its shape-currency row names the - refusal `read_pin` raised for the pin.""" + """The doctor printed an INVALID report, exit 1, and its shape-currency + row names the refusal `read_pin` raised for the pin.""" no_traceback(result) - assert result.returncode in (1, 3), result.stdout + result.stderr + assert result.returncode == 1, result.stdout + result.stderr + verdict = json.loads(result.stdout)["verdict"] + assert verdict.startswith("INVALID"), verdict rows = rows_of(result) row = rows["shape-currency"] assert row["status"] == "FINDING", row @@ -259,9 +257,9 @@ def test_the_doctor_reports_it_in_the_shape_currency_row(project, value, plain = run_script(DOCTOR, "--root", str(project)) no_traceback(plain) assert f"`files:` is {shown}, not a list" in plain.stdout, plain.stdout + assert plain.returncode == 1, plain.stdout + plain.stderr last = [line for line in plain.stdout.splitlines() if line.strip()][-1] - assert VERDICT.match(last), plain.stdout - assert not last.startswith("COMPLIANT"), last + assert last.startswith("INVALID"), plain.stdout @pytest.mark.parametrize("value, shown", NOT_A_LIST) diff --git a/tests/test_template_text_placeholders.py b/tests/test_template_text_placeholders.py index 9a2f7ba2..6327e259 100644 --- a/tests/test_template_text_placeholders.py +++ b/tests/test_template_text_placeholders.py @@ -38,13 +38,12 @@ import re import shutil -import subprocess import sys from pathlib import Path import pytest -from conftest import (ADOPT, FILE_PROTOCOL, REPO, SCAFFOLD, git, +from conftest import (ADOPT, REPO, SCAFFOLD, cloned_just_pushed, git, make_source_repo, run_script) from test_family import family_module @@ -155,15 +154,6 @@ def heads(bare: Path) -> list[str]: cwd=bare).stdout.split() -def clone(remote: Path, target: Path, *extra: str) -> Path: - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - *extra, str(remote), str(target)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr - return target - - def assert_the_root_reads_what_it_was_given(manifest: dict, pins: dict, project_id: str, branch: str, spec: str, code: str) -> None: @@ -225,7 +215,8 @@ def test_a_scaffold_reads_back_every_value_it_was_given(tmp_path, project_id, for name in (PROJECT, f"{PROJECT}-spec", f"{PROJECT}-code"): assert heads(remotes / f"{name}.git") == [branch], name - root = clone(remotes / f"{PROJECT}.git", tmp_path / "clone" / PROJECT) + root = cloned_just_pushed(remotes / f"{PROJECT}.git", + tmp_path / "clone" / PROJECT) assert_the_clone_works(root, branch, spec, code) # `bump-leg.py` reads the branch and the leg's path out of the same @@ -328,7 +319,8 @@ def show(rel: str) -> str: for leg in ("spec", "code"): assert heads(remotes / f"{ADOPTED}-{leg}.git") == [branch], leg - root = clone(source, tmp_path / "clone" / ADOPTED, "-b", ADOPT_BRANCH) + root = cloned_just_pushed(source, tmp_path / "clone" / ADOPTED, + ADOPT_BRANCH) assert_the_clone_works(root, branch, spec, code) diff --git a/tests/test_update_shape.py b/tests/test_update_shape.py index 7cb6acab..36490f49 100644 --- a/tests/test_update_shape.py +++ b/tests/test_update_shape.py @@ -25,7 +25,7 @@ import pytest -from conftest import (FILE_PROTOCOL, ORG, REPO, WINDOWS_SKIP, git, +from conftest import (ORG, REPO, WINDOWS_SKIP, cloned_just_pushed, git, run_script) #: The trailer helpers and the two lines a lane's run lands, imported from #: the file that owns those rules rather than retyped — see @@ -109,12 +109,8 @@ def scaffold_upstream_and_project(tmp_path_factory, subdir: str, "--work-dir", str(base / "work"), *scaffold_args) assert result.returncode == 0, result.stderr + result.stdout - clone = base / "clone" / PROJECT - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(base / "remotes" / f"{PROJECT}.git"), str(clone)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + clone = cloned_just_pushed(base / "remotes" / f"{PROJECT}.git", + base / "clone" / PROJECT) return base, upstream, commit_a, clone diff --git a/tests/test_update_shape_add.py b/tests/test_update_shape_add.py index 1aaa97b6..9f3a5053 100644 --- a/tests/test_update_shape_add.py +++ b/tests/test_update_shape_add.py @@ -42,7 +42,7 @@ import pytest -from conftest import FILE_PROTOCOL, ORG, REPO, git, run_script +from conftest import ORG, REPO, cloned_just_pushed, git, run_script sys.path.insert(0, str(REPO / "scripts")) from repo_shape import file_sha256, load_yaml # noqa: E402 @@ -186,12 +186,8 @@ def standard(tmp_path_factory) -> dict: *SCAFFOLD_ARGS, "--local-remote-dir", str(base / "remotes"), "--work-dir", str(base / "work")) assert result.returncode == 0, result.stderr + result.stdout - clone = base / "clone" / PROJECT - proc = subprocess.run( - ["git", *FILE_PROTOCOL, "clone", "-q", "--recurse-submodules", - str(base / "remotes" / f"{PROJECT}.git"), str(clone)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + clone = cloned_just_pushed(base / "remotes" / f"{PROJECT}.git", + base / "clone" / PROJECT) created = run_script( upstream / "scripts" / "family.py", "init", "--org", FAMILY, diff --git a/tests/test_validator_child_encoding.py b/tests/test_validator_child_encoding.py index 2cc826c6..04a754fd 100644 --- a/tests/test_validator_child_encoding.py +++ b/tests/test_validator_child_encoding.py @@ -48,7 +48,7 @@ import pytest -from conftest import PROJECT, REPO, git +from conftest import PROJECT, REPO, cloned_just_pushed, git from test_stdout_encoding import NAME, child_environ, run_tool_bytes from test_update_shape import (CHANGED, CHANGED_SOURCE, UPSTREAM_FIX_LINE, commit_upstream_changes, @@ -131,12 +131,9 @@ def rename_to_a_name_no_western_code_page_holds(project: Path) -> str: @pytest.fixture(scope="module") def advanced(scaffolded, tmp_path_factory) -> str: """A real new commit on the spec leg's bare remote; returns its sha.""" - work = tmp_path_factory.mktemp("validator-child-spec") / f"{PROJECT}-spec" - proc = subprocess.run( - ["git", "clone", "-q", - str(scaffolded["remotes"] / f"{PROJECT}-spec.git"), str(work)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr + work = cloned_just_pushed( + scaffolded["remotes"] / f"{PROJECT}-spec.git", + tmp_path_factory.mktemp("validator-child-spec") / f"{PROJECT}-spec") (work / "NEW-CONTRACT.md").write_text("# a contract the leg gained\n") git("add", "--", "NEW-CONTRACT.md", cwd=work) git("-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", diff --git a/tests/test_windows_paths.py b/tests/test_windows_paths.py index 088ed3b0..0f5bf35d 100644 --- a/tests/test_windows_paths.py +++ b/tests/test_windows_paths.py @@ -34,7 +34,7 @@ import pytest -from conftest import REPO +from conftest import REPO, load_without_bytecode sys.path.insert(0, str(REPO / "scripts")) from repo_shape import ( # noqa: E402 @@ -80,16 +80,10 @@ def siblings(): scripts = REPO / "templates" / "family-root" / "scripts" saved_path, saved_module = list(sys.path), sys.modules.get("bootstrap") try: - spec = importlib.util.spec_from_file_location( - "family_root_bootstrap", scripts / "bootstrap.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - sys.modules["bootstrap"] = module - spec = importlib.util.spec_from_file_location( - "family_root_siblings", scripts / "siblings.py") - loaded = importlib.util.module_from_spec(spec) - spec.loader.exec_module(loaded) - yield loaded + sys.modules["bootstrap"] = load_without_bytecode( + scripts / "bootstrap.py", "family_root_bootstrap") + yield load_without_bytecode(scripts / "siblings.py", + "family_root_siblings") finally: sys.path[:] = saved_path if saved_module is None: