Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions autoform_cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,7 @@ def diagnose_project(
reason = _sanitize_issues(error.issues, paths)
checks.append(DoctorCheck("runtime", False, "canonical graph is invalid"))
checks.append(DoctorCheck("graph", False, reason))
checks.append(DoctorCheck("references", False, "not checked because graph validation failed"))
checks.append(DoctorCheck("audit", False, "not checked because graph validation failed"))
checks.append(_blocked_lean_check("graph validation failed", lean_root))
return _result(checks)
return _blocked_result(checks, "graph validation failed", lean_root=lean_root)

resolved_lean_root, lean_root_valid = _resolve_lean_root(lean_root)
try:
Expand All @@ -128,10 +125,7 @@ def diagnose_project(

if runtime is None:
checks.append(DoctorCheck("graph", False, "not summarized because runtime projection failed"))
checks.append(DoctorCheck("references", False, "not checked because runtime projection failed"))
checks.append(DoctorCheck("audit", False, "not checked because runtime projection failed"))
checks.append(_blocked_lean_check("runtime projection failed", lean_root))
return _result(checks)
return _blocked_result(checks, "runtime projection failed", lean_root=lean_root)

checks.append(
DoctorCheck(
Expand Down
43 changes: 21 additions & 22 deletions autoform_cli/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,20 @@ def _is_link(metadata: os.stat_result) -> bool:
return stat.S_ISLNK(metadata.st_mode) or bool(attributes & reparse)


def _read_at_most(descriptor: int, size: int) -> bytes:
"""Read from *descriptor* until end of file or *size* bytes, whichever is first."""

chunks: list[bytes] = []
remaining = size
while remaining:
chunk = os.read(descriptor, min(64 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
return b"".join(chunks)


def _read_bounded_regular_file(
path: Path,
*,
Expand Down Expand Up @@ -609,15 +623,7 @@ def _read_bounded_regular_file(
):
raise ScaffoldError([f"the {label} changed while it was being read"])

chunks: list[bytes] = []
remaining = limit + 1
while remaining:
chunk = os.read(descriptor, min(64 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
content = b"".join(chunks)
content = _read_at_most(descriptor, limit + 1)
after_identity = _node_identity(os.fstat(descriptor))
named_identity = _node_identity(os.stat(path, follow_symlinks=False))
if (
Expand Down Expand Up @@ -818,18 +824,11 @@ def _read_gitignore_descriptor(
[f"refusing to merge .gitignore larger than {_MAX_GITIGNORE_BYTES} bytes"]
)
os.lseek(descriptor, 0, os.SEEK_SET)
chunks: list[bytes] = []
total = 0
while True:
chunk = os.read(descriptor, min(64 * 1024, _MAX_GITIGNORE_BYTES - total + 1))
if not chunk:
break
chunks.append(chunk)
total += len(chunk)
if total > _MAX_GITIGNORE_BYTES:
raise ScaffoldError(
[f"refusing to merge .gitignore larger than {_MAX_GITIGNORE_BYTES} bytes"]
)
content = _read_at_most(descriptor, _MAX_GITIGNORE_BYTES + 1)
if len(content) > _MAX_GITIGNORE_BYTES:
raise ScaffoldError(
[f"refusing to merge .gitignore larger than {_MAX_GITIGNORE_BYTES} bytes"]
)
after = os.fstat(descriptor)
named = os.stat(path, follow_symlinks=False)
opened_identity = _node_identity(opened)
Expand All @@ -844,7 +843,7 @@ def _read_gitignore_descriptor(
!= _cross_interface_identity(opened_identity)
):
raise ScaffoldError([f".gitignore changed while it was being inspected: {path}"])
return b"".join(chunks), named_identity
return content, named_identity


def _gitignore_suffix(existing: bytes, required: bytes) -> bytes | None:
Expand Down
36 changes: 0 additions & 36 deletions tests/test_scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,6 @@ def test_no_ci_rather_than_a_guessed_pin(tmp_path: Path, monkeypatch: pytest.Mon
`facebookresearch/autoform-bot@main`, a commit predating the CLI, and every
project scaffolded that way got CI that died at the first step.
"""
from autoform_cli import scaffold as scaffold_module

monkeypatch.setattr(scaffold_module, "plugin_pin", lambda _templates=None: ("", ""))
result = scaffold_module.scaffold_project(tmp_path, title="Finite Flat")

Expand Down Expand Up @@ -773,8 +771,6 @@ def test_a_ref_alone_restores_ci(tmp_path: Path, monkeypatch: pytest.MonkeyPatch
supplied too, following that instruction would still yield no CI, and the
fail-closed behaviour would be indistinguishable from a broken flag.
"""
from autoform_cli import scaffold as scaffold_module

monkeypatch.setattr(scaffold_module, "plugin_pin", lambda _templates=None: ("", ""))
result = scaffold_module.scaffold_project(tmp_path, title="Finite Flat", autoform_ref="2" * 40)

Expand Down Expand Up @@ -826,8 +822,6 @@ def test_a_mutable_ref_is_refused(ref: str, tmp_path: Path, monkeypatch: pytest.
which is how projects got a build with no `autoform` command in the first
place. The scaffold refuses instead of writing CI that rots.
"""
from autoform_cli import scaffold as scaffold_module

monkeypatch.setattr(scaffold_module, "plugin_pin", lambda _templates=None: ("", ""))
with pytest.raises(scaffold_module.ScaffoldError) as caught:
scaffold_module.scaffold_project(tmp_path, title="Finite Flat", autoform_ref=ref)
Expand All @@ -840,8 +834,6 @@ def test_a_mutable_ref_is_refused(ref: str, tmp_path: Path, monkeypatch: pytest.
def test_an_explicit_source_overrides_the_default(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from autoform_cli import scaffold as scaffold_module

monkeypatch.setattr(scaffold_module, "plugin_pin", lambda _templates=None: ("", ""))
scaffold_module.scaffold_project(
tmp_path,
Expand Down Expand Up @@ -900,8 +892,6 @@ def test_an_unsafe_explicit_source_is_refused_without_persisting_it(
def test_an_unsafe_plugin_pin_fails_closed_without_persisting_credentials(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from autoform_cli import scaffold as scaffold_module

secret_source = "https://token:secret@example.test/autoform.git"
monkeypatch.setattr(
scaffold_module,
Expand All @@ -922,8 +912,6 @@ def test_an_unsafe_plugin_pin_fails_closed_without_persisting_credentials(


def test_plugin_pin_is_empty_outside_a_checkout(monkeypatch: pytest.MonkeyPatch) -> None:
from autoform_cli import scaffold as scaffold_module

monkeypatch.setattr(scaffold_module, "_git", lambda *args, **kwargs: None)
monkeypatch.setattr(scaffold_module, "_marketplace_checkout", lambda: None)
assert scaffold_module.plugin_pin() == ("", "")
Expand Down Expand Up @@ -993,8 +981,6 @@ def test_an_installed_plugin_pins_from_the_marketplace_checkout(
is asked for a commit that nothing on their machine reports. That is a real
provenance record, not the guess `plugin_pin` refuses to make.
"""
from autoform_cli import scaffold as scaffold_module

_, _, head = _fake_plugin_install(tmp_path, monkeypatch)

assert scaffold_module.plugin_pin() == (
Expand Down Expand Up @@ -1077,8 +1063,6 @@ def test_plugin_pin_prefers_canonical_upstream_over_a_containing_fork_origin(
def test_plugin_pin_fails_closed_when_checkout_or_copy_bytes_do_not_match_head(
changed: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from autoform_cli import scaffold as scaffold_module

checkout, copied, _ = _fake_plugin_install(tmp_path, monkeypatch)
selected = checkout if changed.startswith("checkout-") else copied
if changed == "copy-executable-mode":
Expand Down Expand Up @@ -1144,8 +1128,6 @@ def test_direct_checkout_pin_ignores_unrelated_untracked_files(
def test_plugin_pin_accepts_umask_permission_noise(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from autoform_cli import scaffold as scaffold_module

checkout, copied, head = _fake_plugin_install(tmp_path, monkeypatch)
for root in (checkout, copied):
(root / "autoform_cli" / "scaffold.py").chmod(0o664)
Expand All @@ -1164,8 +1146,6 @@ def test_an_unrelated_marketplace_checkout_is_not_trusted(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A location that is not Autoform would pin CI to somebody else's repo."""
from autoform_cli import scaffold as scaffold_module

checkout, _, _ = _fake_plugin_install(tmp_path, monkeypatch)
(checkout / "autoform_cli" / "scaffold.py").unlink()

Expand All @@ -1183,8 +1163,6 @@ def test_a_copy_inside_an_unrelated_repository_is_not_its_provenance(
project, so its CI would be pinned to install the project instead of
Autoform, at a sha that moves with every commit the author makes.
"""
from autoform_cli import scaffold as scaffold_module

project = tmp_path / "their-project"
_repository(project, "https://github.com/someone/their-project.git")
installed = project / ".venv/lib/python3.12/site-packages"
Expand All @@ -1200,8 +1178,6 @@ def test_a_branch_in_the_marketplace_checkout_is_refused(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Whatever the provenance says, only a full sha may reach the workflows."""
from autoform_cli import scaffold as scaffold_module

checkout, _, _ = _fake_plugin_install(tmp_path, monkeypatch)

def fake_git(*args: str, root: Path | None = None) -> str | None:
Expand All @@ -1220,8 +1196,6 @@ def test_a_symlinked_subdirectory_cannot_redirect_the_scaffold(tmp_path: Path) -
`project/blueprint` pointing elsewhere sent the whole vault outside the
project, and --force would have overwritten whatever it found there.
"""
from autoform_cli import scaffold as scaffold_module

project = tmp_path / "project"
outside = tmp_path / "outside"
project.mkdir()
Expand All @@ -1237,8 +1211,6 @@ def test_a_symlinked_subdirectory_cannot_redirect_the_scaffold(tmp_path: Path) -

def test_a_dangling_destination_symlink_cannot_redirect_the_scaffold(tmp_path: Path) -> None:
"""`Path.exists()` is false for a link whose outside target is absent."""
from autoform_cli import scaffold as scaffold_module

project = tmp_path / "project"
outside = tmp_path / "outside" / "mkdocs.yml"
project.mkdir()
Expand All @@ -1252,17 +1224,13 @@ def test_a_dangling_destination_symlink_cannot_redirect_the_scaffold(tmp_path: P

def test_a_title_with_a_colon_stays_one_yaml_key(tmp_path: Path) -> None:
"""`site_name: Algebra: Foundations` is a nested mapping, not a title."""
from autoform_cli import scaffold as scaffold_module

scaffold_module.scaffold_project(tmp_path, title="Algebra: Foundations")

config = (tmp_path / "mkdocs.yml").read_text(encoding="utf-8")
assert 'site_name: "Algebra: Foundations"' in config


def test_a_quoted_title_is_escaped_not_just_wrapped(tmp_path: Path) -> None:
from autoform_cli import scaffold as scaffold_module

scaffold_module.scaffold_project(tmp_path, title='The "Hard" Case')

config = (tmp_path / "mkdocs.yml").read_text(encoding="utf-8")
Expand All @@ -1277,8 +1245,6 @@ def test_a_source_without_a_ref_does_not_borrow_this_checkouts_commit(
Keeping the inferred ref while replacing the source emitted
`git+other.git@our-sha`, which does not resolve in `other`.
"""
from autoform_cli import scaffold as scaffold_module

monkeypatch.setattr(
scaffold_module,
"plugin_pin",
Expand All @@ -1293,8 +1259,6 @@ def test_a_source_without_a_ref_does_not_borrow_this_checkouts_commit(


def test_a_source_with_its_own_ref_is_honoured(tmp_path: Path) -> None:
from autoform_cli import scaffold as scaffold_module

scaffold_module.scaffold_project(
tmp_path,
title="Probe",
Expand Down
Loading