Option C Tools
Loading...
Searching...
No Matches
test_lint_changed.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_linter/test_lint_changed.py
4
5"""
6Purpose
7-------
8Regression tests for ``oct lint --changed`` (Phase 4C — G-C4).
9
10Responsibilities
11----------------
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.
15
16Diagnostics
17-----------
18Domain: LINT-TESTS
19Levels:
20 L2 — test lifecycle
21 L3 — assertion details
22 L4 — deep tracing
23
24Contracts
25---------
26- All tests use tmp_path so they are automatically cleaned up.
27"""
28
29import json
30import shutil
31import subprocess
32import textwrap
33from pathlib import Path
34
35import pytest
36
37
38# =====================================================================
39# Helpers
40# =====================================================================
41
42
43def _git_available() -> bool:
44 return shutil.which("git") is not None
45
46
47def _git(repo: Path, *args: str, check: bool = True):
48 return subprocess.run(
49 ["git", *args],
50 cwd=repo,
51 check=check,
52 capture_output=True,
53 text=True,
54 )
55
56
57def _make_lint_project(tmp_path: Path) -> Path:
58 """Create a git repo with pyproject.toml and one committed Python file."""
59 root = tmp_path / "lintproj"
60 root.mkdir()
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",
67 )
68
69 compliant = textwrap.dedent("""\
70 #!/usr/bin/env python3
71 # -*- coding: utf-8 -*-
72 # src/ok.py
73
74 \"\"\"
75 Purpose
76 -------
77 A compliant module.
78
79 Responsibilities
80 ----------------
81 - None.
82
83 Diagnostics
84 -----------
85 Domain: TEST
86 Levels:
87 L1 — errors
88 L2 — lifecycle
89 L3 — details
90 L4 — deep trace
91
92 Contracts
93 ---------
94 - None.
95 \"\"\"
96
97 from oc_diagnostics import _dbg
98
99 x = 1
100 """)
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",
104 )
105
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")
112 return root
113
114
115# =====================================================================
116# Tests
117# =====================================================================
118
119
121
122 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
124 """--changed with a clean working tree → 'No modified Python files'."""
125 root = _make_lint_project(tmp_path)
126 from oct.linter.oct_lint import run_linter
127 result = run_linter(root, ["--changed"])
128 assert result == 0
129
130 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
131 def test_changed_file_linted(self, tmp_path, capsys):
132 """--changed with one modified .py → that file is linted."""
133 root = _make_lint_project(tmp_path)
134
135 # Modify the committed file.
136 path = root / "src" / "ok.py"
137 path.write_text(path.read_text(encoding="utf-8") + "\ny = 2\n", encoding="utf-8")
138
139 from oct.linter.oct_lint import run_linter
140 run_linter(root, ["--changed"])
141 captured = capsys.readouterr()
142 # Should mention the modified file.
143 assert "ok.py" in captured.out or "1" in captured.out
144
145 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
146 def test_non_py_changes_filtered(self, tmp_path):
147 """--changed with only non-.py modifications → no files to lint."""
148 root = _make_lint_project(tmp_path)
149 (root / "README.md").write_text("# readme\n", encoding="utf-8")
150
151 from oct.linter.oct_lint import run_linter
152 result = run_linter(root, ["--changed"])
153 assert result == 0
154
155 def test_non_git_dir_graceful(self, tmp_path):
156 """--changed on a non-git directory → graceful empty result."""
157 root = tmp_path / "nongit"
158 root.mkdir()
159 (root / "pyproject.toml").write_text(
160 '[project]\nname = "nongit"\n', encoding="utf-8",
161 )
162 (root / "src").mkdir()
163 (root / "src" / "x.py").write_text("x = 1\n", encoding="utf-8")
164
165 from oct.linter.oct_lint import run_linter
166 result = run_linter(root, ["--changed"])
167 assert result == 0
168
169 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
170 def test_changed_json_output(self, tmp_path, capsys):
171 """--changed --json produces valid JSON."""
172 root = _make_lint_project(tmp_path)
173 path = root / "src" / "ok.py"
174 path.write_text(path.read_text(encoding="utf-8") + "\nz = 3\n", encoding="utf-8")
175
176 from oct.linter.oct_lint import run_linter
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
_git(Path repo, *str args, bool check=True)
Path _make_lint_project(Path tmp_path)