tests/test_line_endings.py
Rama main · réplica de lectura del repositorio original.
"""No tracked text file carries a carriage return.
`.gitattributes` normalises line endings at commit, for every git client that
honours it. This catches whatever gets past that — a tool that ignores
attributes, a file uploaded through a web form — and says how to fix it.
It exists because #44 arrived with 325 files rewritten from LF to CRLF and
every check green. Python reads CRLF happily, so nothing failed; the only
symptom was a one-line fix that nobody could read in review.
"""
from __future__ import annotations
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# Walked only when there is no git metadata — inside the test image, which
# copies the source but not `.git`. Dot-directories are caches and virtualenvs,
# except these two, which hold files the repository ships.
_KEEP_DOTDIRS = {".github", ".claude"}
def _tracked_files() -> list[Path]:
"""Every file git tracks, or every file under ROOT if git is not there."""
try:
out = subprocess.run(["git", "ls-files", "-z"], cwd=ROOT, check=True,
capture_output=True).stdout
return [ROOT / name for name in out.decode().split("\0") if name]
except (OSError, subprocess.CalledProcessError):
found = []
for path in ROOT.rglob("*"):
parts = path.relative_to(ROOT).parts
if any(p.startswith(".") and p not in _KEEP_DOTDIRS for p in parts):
continue
if "__pycache__" in parts or not path.is_file():
continue
found.append(path)
return found
def _has_carriage_return(path: Path) -> bool:
"""True for a TEXT file containing a CR. A NUL byte in the first 8 KB is
git's own test for binary, and binary files are not this guard's business."""
data = path.read_bytes()
if b"\0" in data[:8000]:
return False
return b"\r" in data
def test_there_are_files_to_check():
"""An empty sweep proves nothing — the guard's own smoke test."""
names = {p.relative_to(ROOT).as_posix() for p in _tracked_files()}
assert "app/main.py" in names
assert len(names) > 100
def test_no_tracked_text_file_has_a_carriage_return():
offenders = sorted(p.relative_to(ROOT).as_posix()
for p in _tracked_files() if p.is_file()
and _has_carriage_return(p))
assert not offenders, (
f"{len(offenders)} file(s) have CRLF line endings: {offenders[:10]}. "
"Run `git add --renormalize .` and amend the commit — .gitattributes "
"will store them as LF."
)
def test_the_guard_notices_crlf(tmp_path):
"""Planted violation, so the check cannot pass by finding nothing to flag."""
crlf = tmp_path / "windows.py"
crlf.write_bytes(b"x = 1\r\ny = 2\r\n")
lf = tmp_path / "unix.py"
lf.write_bytes(b"x = 1\ny = 2\n")
binary = tmp_path / "image.png"
binary.write_bytes(b"\x89PNG\r\n\x1a\n\0\0\0\rIHDR")
assert _has_carriage_return(crlf) is True
assert _has_carriage_return(lf) is False
assert _has_carriage_return(binary) is False # a PNG header has CRLF in it