502 lines
20 KiB
Python
502 lines
20 KiB
Python
"""S9.5 -- nothing that ships may reference the local scratch corpus.
|
|
|
|
This repository was written against a private corpus that is symlinked into the
|
|
checkout under a dot-directory (named by the ``DEV_DIR`` constant below, spelled
|
|
out nowhere in this file -- see *the wrinkle*, further down). That directory
|
|
exists on exactly one machine. A path pointing into it is not a path, it is a
|
|
claim about the author's desktop, and it is broken everywhere else: the reader
|
|
gets a dangling reference instead of a file.
|
|
|
|
So three things are banned from anything git would ship:
|
|
|
|
* the dot-directory itself, by name;
|
|
* the reference corpus' plan directory, by name;
|
|
* any absolute path pointing outside the repository -- a drive letter, a home
|
|
directory, an MSYS-style drive root.
|
|
|
|
**Why a test and not a review note.** Because the failure is invisible to every
|
|
tool the build already runs. Nothing fails when a comment grows a path; the
|
|
damage only shows up on someone else's checkout, in a doc link, months later.
|
|
|
|
**The file set is git's, not ours.** Tracked files *and* untracked-but-not-ignored
|
|
files are both scanned, so the check works while a tree is still being built --
|
|
which is when a local path is most likely to get pasted in. Files git already
|
|
ignores are out of scope by definition: they are not shipping.
|
|
|
|
**The wrinkle: this file, and its neighbours.** A scanner must spell out what it
|
|
scans for, so this file, ``tests/test_vendor_patch_manifest.py`` and
|
|
``tests/test_no_dead_modules.py`` all contain the forbidden strings as *data*.
|
|
Excluding them wholesale would be a hole big enough to walk a real leak through,
|
|
so instead each exclusion is an :class:`Allowance`: a named file, a written
|
|
reason, and a **hard ceiling on how many lines it may be exempt for**. The
|
|
exemption stops existing the moment the file grows past its ceiling -- which is
|
|
the moment it stops being a scanner quoting a pattern and starts being prose
|
|
that happens to mention one. ``TestTheAllowlistStaysNarrow`` is what enforces
|
|
that.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from dataclasses import dataclass
|
|
|
|
import pytest
|
|
|
|
from conftest import ROOT_DIR
|
|
|
|
# The banned name, assembled from pieces so that this module's own source does
|
|
# not contain it as a contiguous literal. That keeps the scanner honest against
|
|
# itself without an allowance for its own pattern table.
|
|
DEV_DIR = "." + "dev"
|
|
|
|
# `plans/` -- the reference corpus' plan directory. Same treatment.
|
|
PLANS_DIR = "plans" + "/"
|
|
|
|
# `<something>.<ext>` -- an extension anchor, so a `.dev` inside a longer word
|
|
# is not a hit. Preceded by nothing or by a non-alphanumeric, which is what
|
|
# keeps the upstream `pdm.fming.dev/` URL in .gitignore out of the results.
|
|
DOT_DIR = re.compile(r"(?<![A-Za-z0-9])" + re.escape(DEV_DIR) + r"(?![A-Za-z0-9])")
|
|
PLANS_PATH = re.compile(re.escape(PLANS_DIR))
|
|
|
|
# `C:\` or `C:/`. The lookbehind excludes URL schemes: in `https:/` the `s` is
|
|
# alphanumeric, so it is not mistaken for a drive letter.
|
|
WINDOWS_DRIVE = re.compile(r"(?<![A-Za-z0-9_.\-])[A-Za-z]:[\\/]")
|
|
|
|
# A home or system root: the shapes that only ever mean "somewhere else".
|
|
POSIX_ROOT = re.compile(r"/(?:home|Users|root|opt|mnt|media)/")
|
|
|
|
# An MSYS/Git-Bash drive root, e.g. `/c/_Dev/...`. The trailing underscore
|
|
# requirement is what keeps ordinary relative-looking paths out of the results.
|
|
MSYS_DRIVE = re.compile(r"(?<![A-Za-z0-9_])/[A-Za-z]/_")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Rule:
|
|
"""One banned shape, with the explanation a failure message should carry."""
|
|
|
|
name: str
|
|
pattern: re.Pattern[str]
|
|
why: str
|
|
|
|
|
|
RULES = (
|
|
Rule("scratch-directory", DOT_DIR, f"{DEV_DIR}/ exists only on the author's machine"),
|
|
Rule("corpus-plans-directory", PLANS_PATH, f"{PLANS_DIR} points into the private reference corpus"),
|
|
Rule("windows-absolute-path", WINDOWS_DRIVE, "a drive letter is a path off this machine"),
|
|
Rule("posix-absolute-path", POSIX_ROOT, "a home directory is a path off this machine"),
|
|
Rule("msys-drive-root", MSYS_DRIVE, "an MSYS drive root is a path off this machine"),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Allowance:
|
|
"""A file permitted to mention what the scan forbids, within a budget.
|
|
|
|
``max_lines`` is the point of the class. An exclusion without a ceiling is
|
|
a hole; with one, the exclusion expires on its own the first time the file
|
|
starts using the name for real instead of naming the rule.
|
|
"""
|
|
|
|
path: str
|
|
reason: str
|
|
max_lines: int
|
|
|
|
|
|
ALLOWANCES: tuple[Allowance, ...] = (
|
|
Allowance(
|
|
path="tests/test_no_dev_references.py",
|
|
reason=(
|
|
"This module is the scanner: the banned shapes have to be written "
|
|
"out literally here to be searched for at all."
|
|
),
|
|
max_lines=20,
|
|
),
|
|
Allowance(
|
|
path="tests/test_vendor_patch_manifest.py",
|
|
reason=(
|
|
"Its own leak scanner quotes the same needles -- a test is allowed "
|
|
"to mention the thing it forbids."
|
|
),
|
|
max_lines=4,
|
|
),
|
|
Allowance(
|
|
path="tests/test_vendor_integrity.py",
|
|
reason=(
|
|
"Same shape: it asserts the vendored tree carries no home-directory "
|
|
"path, so it has to write the shapes it forbids."
|
|
),
|
|
max_lines=2,
|
|
),
|
|
Allowance(
|
|
path="tests/test_no_dead_modules.py",
|
|
reason=(
|
|
"Its docstring names the dot-directories it skips, as examples; "
|
|
"prose describing the rule is not a reference to the corpus."
|
|
),
|
|
max_lines=4,
|
|
),
|
|
Allowance(
|
|
path=".gitignore",
|
|
reason=(
|
|
"The ignore pattern itself has to name the directory for the "
|
|
"exclusion to take effect, and the comment explaining why explains "
|
|
"it by name."
|
|
),
|
|
max_lines=8,
|
|
),
|
|
Allowance(
|
|
path="src/chatterbox_flash/eval/wer_seedtts.py",
|
|
reason=(
|
|
"Vendored upstream 0.1.0, byte-exact: its transcript-error logging "
|
|
"string interleaves a placeholder, a colon and a newline escape, "
|
|
"and the drive-letter regex reads that escape as a path separator. "
|
|
"No path is present."
|
|
),
|
|
max_lines=1,
|
|
),
|
|
)
|
|
|
|
ALLOWED = {allowance.path: allowance for allowance in ALLOWANCES}
|
|
|
|
# Anything larger than this is a binary or a data blob; neither is prose that
|
|
# can leak a path by accident, and reading them would cost seconds.
|
|
MAX_SCAN_BYTES = 2 * 1024 * 1024
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Violation:
|
|
"""One banned shape, found on one line of one file."""
|
|
|
|
path: str
|
|
line_number: int
|
|
line: str
|
|
rule: Rule
|
|
|
|
def render(self) -> str:
|
|
text = self.line.strip()
|
|
if len(text) > 90:
|
|
text = text[:87] + "..."
|
|
return (
|
|
f"{self.path}:{self.line_number}: [{self.rule.name}] {text}\n"
|
|
f" {self.rule.why}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The file set -- git decides what ships, so git decides what we read
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _git(*args: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(
|
|
("git", *args),
|
|
cwd=ROOT_DIR,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def _is_git_checkout() -> bool:
|
|
if shutil.which("git") is None:
|
|
return False
|
|
return _git("rev-parse", "--is-inside-work-tree").stdout.strip() == "true"
|
|
|
|
|
|
def candidate_files() -> list[str]:
|
|
"""Every file that could ship, as repository-relative POSIX paths.
|
|
|
|
Tracked files plus untracked-but-not-ignored files. Passing ``--exclude-standard``
|
|
to the second query is what keeps build output and editor droppings out:
|
|
those are already declared not to ship, so scanning them would only produce
|
|
noise from files nobody will ever commit.
|
|
"""
|
|
tracked = _git("ls-files", "-z").stdout
|
|
untracked = _git("ls-files", "-z", "-o", "--exclude-standard").stdout
|
|
paths = set()
|
|
for stream in (tracked, untracked):
|
|
for entry in stream.split("\0"):
|
|
if entry:
|
|
paths.add(entry.replace(os.sep, "/"))
|
|
return sorted(paths)
|
|
|
|
|
|
def _read_text(path: str, base: str = ROOT_DIR) -> str | None:
|
|
"""The file's text, or ``None`` when it is binary, too big or unreadable."""
|
|
absolute = os.path.join(base, path)
|
|
try:
|
|
if os.path.getsize(absolute) > MAX_SCAN_BYTES:
|
|
return None
|
|
with open(absolute, "rb") as handle:
|
|
raw = handle.read()
|
|
except OSError: # pragma: no cover - a vanished or locked file
|
|
return None
|
|
if b"\0" in raw:
|
|
return None
|
|
return raw.decode("utf-8", errors="replace")
|
|
|
|
|
|
def violations_in(path: str, text: str) -> list[Violation]:
|
|
"""Every banned shape in one file's text, in line order."""
|
|
found: list[Violation] = []
|
|
for number, line in enumerate(text.splitlines(), start=1):
|
|
for rule in RULES:
|
|
if rule.pattern.search(line):
|
|
found.append(Violation(path, number, line, rule))
|
|
return found
|
|
|
|
|
|
def exempt_lines(path: str, violations: list[Violation]) -> tuple[list[Violation], int]:
|
|
"""Split a file's violations into ``(shipped, exempt)``.
|
|
|
|
The allowance applies per *line*, and only while the file stays inside its
|
|
ceiling: past that, nothing is exempt and every hit is reported.
|
|
"""
|
|
allowance = ALLOWED.get(path)
|
|
if allowance is None:
|
|
return violations, 0
|
|
distinct = len({violation.line_number for violation in violations})
|
|
if distinct > allowance.max_lines:
|
|
return violations, 0
|
|
return [], distinct
|
|
|
|
|
|
def scan(
|
|
paths: list[str] | None = None, base: str = ROOT_DIR
|
|
) -> tuple[list[Violation], dict[str, int]]:
|
|
"""Run the whole scan.
|
|
|
|
``paths`` defaults to every file git would ship; ``base`` defaults to the
|
|
repository root and exists so the planted-violation tests can point the same
|
|
function at a temporary tree. Returns ``(shipped violations, exempt counts)``.
|
|
"""
|
|
shipped: list[Violation] = []
|
|
exempt: dict[str, int] = {}
|
|
for path in paths if paths is not None else candidate_files():
|
|
text = _read_text(path, base)
|
|
if text is None:
|
|
continue
|
|
found = violations_in(path, text)
|
|
if not found:
|
|
continue
|
|
remaining, count = exempt_lines(path, found)
|
|
if count:
|
|
exempt[path] = count
|
|
shipped.extend(remaining)
|
|
return shipped, exempt
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# The scanner itself -- proven against planted violations
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
CLEAN_SOURCE = '"""A module that talks about models and nothing else."""\n'
|
|
|
|
PLANTED = {
|
|
"scratch-directory": (
|
|
f'SCRATCH = "{DEV_DIR}/plans"\n',
|
|
f"The corpus lives at {DEV_DIR}/plans on the author's machine.",
|
|
),
|
|
"corpus-plans-directory": (
|
|
'PLAN = "reference/' + PLANS_DIR + 'step-one.md"\n',
|
|
f"see {PLANS_DIR}step-one.md for the design",
|
|
),
|
|
"windows-absolute-path": (
|
|
'ROOT = "C:\\\\Users\\\\someone\\\\ComfyUI"\n',
|
|
"the checkout lives at C:/Users/someone/ComfyUI",
|
|
),
|
|
"posix-absolute-path": (
|
|
'SRC = "/home/someone/reference/repos"\n',
|
|
"borrowed from /Users/someone/reference/repos",
|
|
),
|
|
"msys-drive-root": (
|
|
'PY = "/c/_Dev/Python/.venv"\n',
|
|
"run /c/_Dev/Python/.venv/Scripts/python.exe",
|
|
),
|
|
}
|
|
|
|
INNOCENT = {
|
|
"a-url-with-dev-in-the-host": "https://pdm.fming.dev/#use-with-ide\n",
|
|
"an-english-word": "# the device. the development stage. the devs.\n",
|
|
"a-url-scheme": "https://example.invalid/model\n",
|
|
"a-relative-path": "src/PATCHES.md and example_workflows/*.json\n",
|
|
"a-repo-path": "ComfyUI/models/tts/chatterbox/resembleai_default_voice\n",
|
|
"a-lone-slash": "ratio = 1 / 2 / 3\n",
|
|
}
|
|
|
|
|
|
class TestTheScannerDetectsPlantedViolations:
|
|
"""The scan is only worth its ratchet if it fires. Each rule gets a file
|
|
that must trip it -- written to a temporary directory, never to the repo."""
|
|
|
|
@pytest.mark.parametrize("rule_name", sorted(PLANTED))
|
|
def test_a_planted_violation_is_reported(self, tmp_path, rule_name: str) -> None:
|
|
rule = next(rule for rule in RULES if rule.name == rule_name)
|
|
for text in PLANTED[rule_name]:
|
|
found = [
|
|
violation
|
|
for violation in violations_in("planted.py", text)
|
|
if violation.rule is rule
|
|
]
|
|
assert found, (
|
|
f"{rule.name} did not fire on {text!r}. If the pattern was "
|
|
"weakened to make some existing file pass, this is where it "
|
|
"should have been caught."
|
|
)
|
|
assert found[0].line_number >= 1
|
|
assert rule.name in found[0].render()
|
|
|
|
@pytest.mark.parametrize("name", sorted(INNOCENT))
|
|
def test_an_innocent_line_is_not_reported(self, name: str) -> None:
|
|
text = INNOCENT[name]
|
|
assert not violations_in(name, text), (
|
|
f"{name} ({text.strip()!r}) tripped a rule; the patterns are too "
|
|
"broad and will drown real findings in false positives"
|
|
)
|
|
|
|
def test_a_clean_file_produces_nothing(self) -> None:
|
|
assert violations_in("clean.py", CLEAN_SOURCE) == []
|
|
|
|
def test_the_planted_scan_reports_every_rule_at_least_once(self, tmp_path) -> None:
|
|
"""End to end through :func:`scan`, not just the per-rule helper."""
|
|
planted = []
|
|
for index, texts in enumerate(PLANTED.values()):
|
|
for inner, text in enumerate(texts):
|
|
relative = f"tmp_planted/s{index}_{inner}.py"
|
|
(tmp_path / relative).parent.mkdir(parents=True, exist_ok=True)
|
|
(tmp_path / relative).write_text(text, encoding="utf-8")
|
|
planted.append(relative)
|
|
found, exempt = scan(planted, base=str(tmp_path))
|
|
reported = {violation.rule.name for violation in found}
|
|
assert not exempt, f"planted files must not be allowlisted, got {exempt}"
|
|
assert reported == {rule.name for rule in RULES}, (
|
|
f"the scan reported {sorted(reported)}, expected every rule"
|
|
)
|
|
# At least one hit per planted file. Not exactly one per file: a line
|
|
# can trip two rules at once, and that overlap is the scan working.
|
|
unnoticed = [path for path in planted if not any(v.path == path for v in found)]
|
|
assert not unnoticed, f"these planted files produced no hit at all: {unnoticed}"
|
|
assert len(found) >= len(planted)
|
|
|
|
def test_a_file_over_its_ceiling_stops_being_exempt(self, tmp_path) -> None:
|
|
"""The allowance is a budget, not a permanent pardon."""
|
|
allowance = ALLOWED["tests/test_vendor_patch_manifest.py"]
|
|
over = allowance.max_lines + 1
|
|
lines = [f'# reference {DEV_DIR}/{i}' for i in range(over)]
|
|
text = "\n".join(lines) + "\n"
|
|
remaining, count = exempt_lines(allowance.path, violations_in(allowance.path, text))
|
|
assert count == 0, "a file over its ceiling must lose its exemption"
|
|
assert len(remaining) == over
|
|
|
|
|
|
class TestNothingShipsALocalReference:
|
|
"""The actual assertion. Everything git would ship, scanned."""
|
|
|
|
@pytest.fixture(scope="class")
|
|
def scanned(self) -> tuple[list[Violation], dict[str, int]]:
|
|
return scan()
|
|
|
|
def test_the_scan_read_a_meaningful_number_of_files(self) -> None:
|
|
assert len(candidate_files()) > 50, (
|
|
"the candidate set is implausibly small -- git is probably failing "
|
|
"and this whole file would pass vacuously"
|
|
)
|
|
|
|
@pytest.mark.parametrize("rule", RULES, ids=lambda rule: rule.name)
|
|
def test_no_shipped_file_contains_this(self, scanned, rule: Rule) -> None:
|
|
shipped, exempt = scanned
|
|
offenders = [violation for violation in shipped if violation.rule is rule]
|
|
report = "\n".join(violation.render() for violation in offenders)
|
|
context = (
|
|
f"\n(exempt, inside its ceiling: {sorted(exempt)})" if exempt else ""
|
|
)
|
|
assert not offenders, (
|
|
f"{len(offenders)} shipped line(s) match {rule.name}: "
|
|
f"{rule.why}. Everything a user can install does not exist on this "
|
|
f"machine.\n{report}{context}"
|
|
)
|
|
|
|
|
|
class TestTheAllowlistStaysNarrow:
|
|
"""An allowlist is a hole with a comment on it. Keep it a small hole."""
|
|
|
|
def test_every_allowance_carries_a_real_reason(self) -> None:
|
|
for allowance in ALLOWANCES:
|
|
assert len(allowance.reason) >= 40, (
|
|
f"{allowance.path} is allowlisted with a reason too short to "
|
|
"justify it; say why the mention is unavoidable"
|
|
)
|
|
|
|
def test_every_allowance_names_a_file_that_exists(self) -> None:
|
|
for allowance in ALLOWANCES:
|
|
assert os.path.exists(os.path.join(ROOT_DIR, allowance.path)), (
|
|
f"{allowance.path} is allowlisted but no longer exists; drop the "
|
|
"allowance so a future file at that path is scanned again"
|
|
)
|
|
|
|
def test_the_allowlist_is_small(self) -> None:
|
|
assert len(ALLOWANCES) <= 6, (
|
|
"an allowlist of "
|
|
f"{len(ALLOWANCES)} entries is no longer a list of tests that have "
|
|
"to mention what they forbid"
|
|
)
|
|
|
|
def test_every_allowance_is_inside_its_own_ceiling(self) -> None:
|
|
"""Otherwise the allowance is dead weight -- and, worse, the next file "
|
|
"at that path inherits it silently."""
|
|
for allowance in ALLOWANCES:
|
|
text = _read_text(allowance.path)
|
|
assert text is not None, f"{allowance.path} is binary or unreadable"
|
|
found = violations_in(allowance.path, text)
|
|
remaining, count = exempt_lines(allowance.path, found)
|
|
assert count, (
|
|
f"{allowance.path} is allowlisted but no longer contains any "
|
|
"banned shape; remove the allowance"
|
|
)
|
|
assert not remaining, (
|
|
f"{allowance.path} has {len(remaining)} unexempted hit(s) -- it "
|
|
"has outgrown its ceiling"
|
|
)
|
|
|
|
def test_only_known_files_are_exempt(self) -> None:
|
|
"""A closed set: any exempt path not in :data:`ALLOWED` is a bug in the
|
|
splitter rather than a policy."""
|
|
assert set(ALLOWED) == {allowance.path for allowance in ALLOWANCES}
|
|
|
|
|
|
@pytest.mark.skipif(not _is_git_checkout(), reason="not a git checkout")
|
|
class TestGitIgnoresTheScratchDirectory:
|
|
"""The git-level half of S9.5. The ignore rules and the scan are one fix:
|
|
the scan stops a *reference* shipping, this stops the directory itself
|
|
ever being committed in the first place."""
|
|
|
|
def test_check_ignore_says_it_is_ignored(self) -> None:
|
|
result = _git("check-ignore", "-v", DEV_DIR)
|
|
assert result.returncode == 0, (
|
|
f"`git check-ignore {DEV_DIR}` exited {result.returncode}. The "
|
|
"pattern is slashless on purpose: git does not treat a symlink as a "
|
|
"directory, so trailing-slash variants never match it."
|
|
)
|
|
|
|
def test_the_ignore_pattern_is_named_in_gitignores_own_output(self) -> None:
|
|
source = _git("check-ignore", "-v", DEV_DIR).stdout
|
|
assert ".gitignore" in source, (
|
|
f"unexpected ignore source {source!r} -- if the scratch directory is "
|
|
"being hidden by a global gitignore, the fix has not landed here"
|
|
)
|
|
|
|
def test_the_scratch_directory_is_not_tracked(self) -> None:
|
|
tracked = _git("ls-files", "-z").stdout.split("\0")
|
|
assert DEV_DIR not in tracked, (
|
|
f"{DEV_DIR} is tracked by git; removing it from the index is part "
|
|
"of the same fix"
|
|
)
|
|
|
|
def test_the_scratch_directory_is_not_in_the_candidate_set(self) -> None:
|
|
assert not [
|
|
path for path in candidate_files() if path.split("/")[0] == DEV_DIR
|
|
], f"the scan would read files from inside {DEV_DIR}/; that defeats it" |