utils/dep_freshness/tests/test_install.py
Krzysztof kuhy Rudnicki 23f4322197
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
Refuse to overwrite utils' own checker with the delegate shim
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 16:02:34 +02:00

250 lines
8.7 KiB
Python

"""The gate installer: three pieces in, idempotent, and no comments lost.
The point of this module is that sixteen repos get byte-identical copies, so
the assertions compare against the templates rather than against a paraphrase
of them -- a test that spelled the expected content out by hand would let the
template and the installed file drift, which is the failure the installer
exists to prevent.
"""
from __future__ import annotations
import runpy
from pathlib import Path
import pytest
from dep_freshness import install as installer
from dep_freshness.install_cli import main
from dep_freshness.tests.conftest import write
TEMPLATES = Path(installer.__file__).resolve().parent / "templates"
def _template(name: str) -> str:
return (TEMPLATES / name).read_text(encoding="utf-8")
def test_a_bare_repo_gets_all_three_pieces(repo):
assert installer.install(repo) == [
"scripts/check_dependency_freshness.sh",
".github/workflows/dependency-freshness.yml",
".pre-commit-config.yaml (dependency-freshness hook)",
]
delegate = repo / "scripts/check_dependency_freshness.sh"
assert delegate.read_text(encoding="utf-8") == _template("delegate.sh")
assert delegate.stat().st_mode & 0o111, "the hook execs it; it must be +x"
assert (repo / ".github/workflows/dependency-freshness.yml").read_text(
encoding="utf-8"
) == _template("workflow.yml")
assert "id: dependency-freshness" in (repo / ".pre-commit-config.yaml").read_text(
encoding="utf-8"
)
def test_a_second_run_writes_nothing(repo):
installer.install(repo)
assert installer.install(repo) == []
assert installer.plan(repo) == []
def test_plan_names_what_is_missing_without_writing_it(repo):
assert installer.plan(repo) == [
"scripts/check_dependency_freshness.sh",
".github/workflows/dependency-freshness.yml",
".pre-commit-config.yaml (dependency-freshness hook)",
]
assert not (repo / "scripts").exists()
def test_a_drifted_copy_is_rewritten(repo):
installer.install(repo)
delegate = repo / "scripts/check_dependency_freshness.sh"
delegate.write_text("#!/bin/bash\necho lol\n", encoding="utf-8")
workflow = repo / ".github/workflows/dependency-freshness.yml"
workflow.write_text("name: something else\n", encoding="utf-8")
assert installer.install(repo) == [
"scripts/check_dependency_freshness.sh",
".github/workflows/dependency-freshness.yml",
]
assert delegate.read_text(encoding="utf-8") == _template("delegate.sh")
def test_an_existing_pre_commit_config_keeps_its_hooks_and_comments(repo):
"""Appending, not re-emitting: a YAML round-trip drops every comment.
Each of these configs explains why its hooks exist, and that prose is the
only record of it.
"""
write(
repo,
".pre-commit-config.yaml",
"""\
# The 250-line cap. A file that cannot be read in one piece forces re-reads.
repos:
- repo: local
hooks:
- id: file-length-cap
name: file length <= 250 lines
entry: bash scripts/check_file_length.sh
language: system
""",
)
installer.install(repo)
body = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8")
assert "# The 250-line cap." in body
assert "id: file-length-cap" in body
assert "id: dependency-freshness" in body
def test_a_config_that_already_has_the_hook_is_left_alone(repo):
write(
repo,
".pre-commit-config.yaml",
"""\
repos:
- repo: local
hooks:
- id: dependency-freshness
entry: scripts/check_dependency_freshness.sh
language: system
""",
)
before = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8")
installer.install(repo)
assert (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8") == before
def test_a_config_without_a_trailing_newline_still_appends_cleanly(repo):
write(repo, ".pre-commit-config.yaml", "repos:\n - repo: local\n hooks:")
installer.install(repo)
body = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8")
assert body.startswith("repos:\n - repo: local\n hooks:\n\n #")
assert "id: dependency-freshness" in body
def test_fvmrc_is_only_for_repos_that_hold_a_pubspec(repo):
assert installer.write_fvmrc(repo, "3.47.2") is False
assert not (repo / ".fvmrc").exists()
write(repo, "app/pubspec.yaml", "name: demo\n")
assert installer.write_fvmrc(repo, "3.47.2") is True
assert (repo / ".fvmrc").read_text(
encoding="utf-8"
) == '{\n "flutter": "3.47.2"\n}\n'
assert installer.write_fvmrc(repo, "3.47.2") is False
def test_the_cli_installs_and_reports_each_piece(repo, capsys):
assert main([str(repo)]) == 0
out = capsys.readouterr().out
assert "wrote: scripts/check_dependency_freshness.sh" in out
assert "wrote: .github/workflows/dependency-freshness.yml" in out
def test_the_cli_check_mode_writes_nothing(repo, capsys):
assert main([str(repo), "--check"]) == 0
assert (
"would write: scripts/check_dependency_freshness.sh" in capsys.readouterr().out
)
assert not (repo / "scripts").exists()
main([str(repo)])
assert main([str(repo), "--check"]) == 0
assert "gate already current" in capsys.readouterr().out
def test_the_cli_pins_the_sdk_when_asked(repo, monkeypatch, capsys):
monkeypatch.setattr(
"dep_freshness.install_cli.flutter_latest", lambda: ("3.47.2", "3.13.2")
)
write(repo, "pubspec.yaml", "name: demo\n")
assert main([str(repo), "--fvm"]) == 0
assert "wrote: .fvmrc (flutter 3.47.2)" in capsys.readouterr().out
# Same version again: the pin is already right, so nothing is reported.
assert main([str(repo), "--fvm"]) == 0
assert ".fvmrc" not in capsys.readouterr().out
def test_an_unresolvable_sdk_leaves_the_pin_alone(repo, monkeypatch, capsys):
"""Offline, the alternative is pinning every repo to a guessed number."""
monkeypatch.setattr(
"dep_freshness.install_cli.flutter_latest", lambda: (None, None)
)
write(repo, "pubspec.yaml", "name: demo\n")
assert main([str(repo), "--fvm"]) == 1
assert "could not resolve latest stable Flutter" in capsys.readouterr().err
assert not (repo / ".fvmrc").exists()
def test_the_module_entrypoint_runs_the_cli(repo, monkeypatch):
monkeypatch.setattr("sys.argv", ["install_main", str(repo), "--check"])
with pytest.raises(SystemExit) as exit_info:
runpy.run_module("dep_freshness.install_main", run_name="__main__")
assert exit_info.value.code == 0
def test_the_hook_lands_in_the_last_local_block_not_at_end_of_file(repo):
"""Most of these configs interleave local and remote hook repos.
diet-guard, screen-locker and wake-alarm carry nine `- repo:` blocks each,
and a hook appended under a remote one is handed to that repo's hook
definitions -- which fails with an error naming an unknown id rather than
the real problem.
"""
write(
repo,
".pre-commit-config.yaml",
"""\
repos:
- repo: local
hooks:
- id: file-length-cap
entry: bash scripts/check_file_length.sh
language: system
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.0
hooks:
- id: ruff
""",
)
installer.install(repo)
lines = (repo / ".pre-commit-config.yaml").read_text(encoding="utf-8").split("\n")
hook = lines.index(" - id: dependency-freshness")
remote = lines.index(" - repo: https://github.com/astral-sh/ruff-pre-commit")
assert hook < remote, "the hook must sit inside the local block"
assert lines[-2] == " - id: ruff", "the remote block keeps its own hooks"
def test_a_config_with_no_local_block_refuses_rather_than_guessing(repo):
write(
repo,
".pre-commit-config.yaml",
"""\
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.0
hooks:
- id: ruff
""",
)
with pytest.raises(ValueError, match="no ` - repo: local` block"):
installer.install(repo)
def test_utils_own_checker_is_never_replaced_by_the_delegate(repo):
"""The one repo whose copy is meant to differ.
utils holds the real checker at the delegate's path. Overwriting it with
the shim makes the gate exec itself, because the shim resolves the shared
gate to exactly the file it just replaced.
"""
real = "#!/bin/bash\n# the real checker, not a shim\n"
write(repo, "dep_freshness/check.py", "# the shared implementation\n")
write(repo, "scripts/check_dependency_freshness.sh", real)
assert "scripts/check_dependency_freshness.sh" not in installer.plan(repo)
installer.install(repo)
assert (repo / "scripts/check_dependency_freshness.sh").read_text(
encoding="utf-8"
) == real