Option C Tools
Loading...
Searching...
No Matches
test_format_changed.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_formatter/test_format_changed.py
4
5"""
6Purpose
7-------
8Tests for ``oct format --changed`` (Phase 4C — G-C2).
9
10Responsibilities
11----------------
12- Verify that ``--changed`` formats only git-modified Python files.
13- Verify graceful behaviour in non-git directories.
14- Verify JSON output mode with ``--changed``.
15
16Diagnostics
17-----------
18Domain: FMT-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_format_project(tmp_path: Path) -> Path:
58 """Create a git repo with pyproject.toml and one committed Python file."""
59 root = tmp_path / "fmtproj"
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 = "fmtproj"\n', encoding="utf-8",
67 )
68
69 # Write a compliant file and commit.
70 compliant = textwrap.dedent("""\
71 #!/usr/bin/env python3
72 # -*- coding: utf-8 -*-
73 # src/clean.py
74
75 \"\"\"
76 Purpose
77 -------
78 A clean module.
79
80 Responsibilities
81 ----------------
82 - None.
83
84 Diagnostics
85 -----------
86 Domain: TEST
87 Levels:
88 L1 — errors
89 L2 — lifecycle
90 L3 — details
91 L4 — deep trace
92
93 Contracts
94 ---------
95 - None.
96 \"\"\"
97
98 from oc_diagnostics import _dbg
99
100 x = 1
101 """)
102 (root / "src" / "clean.py").write_text(compliant, encoding="utf-8")
103
104 _git(root, "init", "--quiet", "-b", "main")
105 _git(root, "config", "user.email", "test@example.com")
106 _git(root, "config", "user.name", "Test")
107 _git(root, "config", "commit.gpgsign", "false")
108 _git(root, "add", "-A")
109 _git(root, "commit", "-m", "initial", "--quiet")
110 return root
111
112
113# =====================================================================
114# Tests
115# =====================================================================
116
117
119
120 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
122 """--changed with a clean working tree → no modified files."""
123 root = _make_format_project(tmp_path)
124 from oct.formatter.oct_format import run_formatter
125 # Should return None (no files to format, early exit).
126 result = run_formatter(root, ["--changed"])
127 assert result is None
128
129 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
130 def test_changed_file_processed(self, tmp_path, capsys):
131 """--changed with one modified file → that file is formatted."""
132 root = _make_format_project(tmp_path)
133
134 # Modify the committed file (make it dirty).
135 dirty = (root / "src" / "clean.py").read_text(encoding="utf-8")
136 (root / "src" / "clean.py").write_text(dirty + "\ny = 2\n", encoding="utf-8")
137
138 from oct.formatter.oct_format import run_formatter
139 run_formatter(root, ["--changed"])
140 captured = capsys.readouterr()
141 # Should mention 1 file processed.
142 assert "1" in captured.out or "clean.py" in captured.out
143
144 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
145 def test_unchanged_files_skipped(self, tmp_path, capsys):
146 """--changed should NOT process files without modifications."""
147 root = _make_format_project(tmp_path)
148
149 # Add a new file (modified) but leave clean.py untouched.
150 new_file = root / "src" / "added.py"
151 new_file.write_text("# added\nx = 1\n", encoding="utf-8")
152
153 from oct.formatter.oct_format import run_formatter
154 run_formatter(root, ["--changed"])
155 captured = capsys.readouterr()
156 # Output should only mention the added file, not clean.py.
157 assert "clean.py" not in captured.out or "1" in captured.out
158
159 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
160 def test_changed_json_output(self, tmp_path, capsys):
161 """--changed --json produces valid JSON."""
162 root = _make_format_project(tmp_path)
163 dirty = (root / "src" / "clean.py").read_text(encoding="utf-8")
164 (root / "src" / "clean.py").write_text(dirty + "\nz = 3\n", encoding="utf-8")
165
166 from oct.formatter.oct_format import run_formatter
167 run_formatter(root, ["--changed", "--json"])
168 captured = capsys.readouterr()
169 data = json.loads(captured.out)
170 assert "files" in data
171 assert isinstance(data["files"], list)
172
173 def test_non_git_dir_graceful(self, tmp_path, capsys):
174 """--changed on a non-git directory → graceful empty result."""
175 root = tmp_path / "nogit"
176 root.mkdir()
177 (root / "pyproject.toml").write_text(
178 '[project]\nname = "nogit"\n', encoding="utf-8",
179 )
180 (root / "src").mkdir()
181 (root / "src" / "x.py").write_text("x = 1\n", encoding="utf-8")
182
183 from oct.formatter.oct_format import run_formatter
184 result = run_formatter(root, ["--changed"])
185 assert result is None
186
187 @pytest.mark.skipif(not _git_available(), reason="git not on PATH")
188 def test_changed_fix_applies(self, tmp_path):
189 """--changed --fix should apply fixes only to changed files."""
190 root = _make_format_project(tmp_path)
191
192 # Create a file without a proper header (needs fix).
193 bad = root / "src" / "bad.py"
194 bad.write_text("# bad file\nx = 1\n", encoding="utf-8")
195
196 from oct.formatter.oct_format import run_formatter
197 run_formatter(root, ["--changed", "--fix"])
198 content = bad.read_text(encoding="utf-8")
199 # After fix, should have shebang line.
200 assert "#!/usr/bin/env python3" in content
_git(Path repo, *str args, bool check=True)