8Regression tests for ``oct lint --changed`` (Phase 4C — G-C4).
12- Verify the Phase 4A ``_git_changed_files`` shim still works.
13- Verify that ``--changed`` lints only git-modified Python files.
14- Verify graceful fallback in non-git directories.
21 L3 — assertion details
26- All tests use tmp_path so they are automatically cleaned up.
33from pathlib
import Path
44 return shutil.which(
"git")
is not None
47def _git(repo: Path, *args: str, check: bool =
True):
48 return subprocess.run(
58 """Create a git repo with pyproject.toml and one committed Python file."""
59 root = tmp_path /
"lintproj"
61 (root /
"src").mkdir()
62 (root /
"tests").mkdir()
63 (root /
"docs").mkdir()
64 (root /
"oc_diagnostics").mkdir()
65 (root /
"pyproject.toml").write_text(
66 '[project]\nname = "lintproj"\n', encoding=
"utf-8",
69 compliant = textwrap.dedent(
"""\
70 #!/usr/bin/env python3
71 # -*- coding: utf-8 -*-
97 from oc_diagnostics import _dbg
101 (root /
"src" /
"ok.py").write_text(compliant, encoding=
"utf-8")
102 (root /
"tests" /
"test_ok.py").write_text(
103 '"""Stub test."""\ndef test_ok():\n pass\n', encoding=
"utf-8",
106 _git(root,
"init",
"--quiet",
"-b",
"main")
107 _git(root,
"config",
"user.email",
"test@example.com")
108 _git(root,
"config",
"user.name",
"Test")
109 _git(root,
"config",
"commit.gpgsign",
"false")
110 _git(root,
"add",
"-A")
111 _git(root,
"commit",
"-m",
"initial",
"--quiet")
122 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
124 """--changed with a clean working tree → 'No modified Python files'."""
127 result = run_linter(root, [
"--changed"])
130 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
132 """--changed with one modified .py → that file is linted."""
136 path = root /
"src" /
"ok.py"
137 path.write_text(path.read_text(encoding=
"utf-8") +
"\ny = 2\n", encoding=
"utf-8")
140 run_linter(root, [
"--changed"])
141 captured = capsys.readouterr()
143 assert "ok.py" in captured.out
or "1" in captured.out
145 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
147 """--changed with only non-.py modifications → no files to lint."""
149 (root /
"README.md").write_text(
"# readme\n", encoding=
"utf-8")
152 result = run_linter(root, [
"--changed"])
156 """--changed on a non-git directory → graceful empty result."""
157 root = tmp_path /
"nongit"
159 (root /
"pyproject.toml").write_text(
160 '[project]\nname = "nongit"\n', encoding=
"utf-8",
162 (root /
"src").mkdir()
163 (root /
"src" /
"x.py").write_text(
"x = 1\n", encoding=
"utf-8")
166 result = run_linter(root, [
"--changed"])
169 @pytest.mark.skipif(not _git_available(), reason=
"git not on PATH")
171 """--changed --json produces valid JSON."""
173 path = root /
"src" /
"ok.py"
174 path.write_text(path.read_text(encoding=
"utf-8") +
"\nz = 3\n", encoding=
"utf-8")
177 run_linter(root, [
"--changed",
"--json"])
178 captured = capsys.readouterr()
179 data = json.loads(captured.out)
180 assert isinstance(data, dict)
181 assert "files" in data
or "summary" in data
test_no_changes_reports_nothing(self, tmp_path)
test_non_git_dir_graceful(self, tmp_path)
test_changed_file_linted(self, tmp_path, capsys)
test_non_py_changes_filtered(self, tmp_path)
test_changed_json_output(self, tmp_path, capsys)
_git(Path repo, *str args, bool check=True)
Path _make_lint_project(Path tmp_path)