|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 |
|
3 | | -from ops.lintcommit import validate_message, validate_subject |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +from unittest.mock import patch |
| 6 | + |
| 7 | +import pytest |
| 8 | + |
| 9 | +from ops.lintcommit import run_range, validate_message, validate_subject |
4 | 10 |
|
5 | 11 |
|
6 | 12 | # region validate_subject: valid subjects |
@@ -151,3 +157,98 @@ def test_empty_message() -> None: |
151 | 157 | def test_invalid_subject_in_message() -> None: |
152 | 158 | error, _ = validate_message("invalid title") |
153 | 159 | assert error == "missing colon (:) char" |
| 160 | + |
| 161 | + |
| 162 | +# region run_range |
| 163 | + |
| 164 | + |
| 165 | +def _make_git_log_output(*messages: str) -> str: |
| 166 | + """Build fake ``git log --format=%H%n%B%n---END---`` output.""" |
| 167 | + blocks: list[str] = [] |
| 168 | + for i, msg in enumerate(messages): |
| 169 | + sha = f"abc{i:04d}" + "0" * 33 # 40-char fake SHA |
| 170 | + blocks.append(f"{sha}\n{msg}\n---END---") |
| 171 | + return "\n".join(blocks) |
| 172 | + |
| 173 | + |
| 174 | +def _completed(stdout: str = "", stderr: str = "", returncode: int = 0): |
| 175 | + """Shorthand for a ``subprocess.CompletedProcess``.""" |
| 176 | + from subprocess import CompletedProcess |
| 177 | + |
| 178 | + return CompletedProcess(args=[], returncode=returncode, stdout=stdout, stderr=stderr) |
| 179 | + |
| 180 | + |
| 181 | +@patch("subprocess.run") |
| 182 | +def test_run_range_all_valid(mock_run, capsys) -> None: |
| 183 | + log_output = _make_git_log_output( |
| 184 | + "feat: add new feature", |
| 185 | + "fix(sdk): resolve issue", |
| 186 | + ) |
| 187 | + mock_run.return_value = _completed(stdout=log_output) |
| 188 | + |
| 189 | + run_range("origin/main..HEAD", skip_dirty_check=True) |
| 190 | + |
| 191 | + out = capsys.readouterr().out |
| 192 | + assert "PASS" in out |
| 193 | + assert out.count("PASS") == 2 |
| 194 | + |
| 195 | + |
| 196 | +@patch("subprocess.run") |
| 197 | +def test_run_range_with_invalid_commit(mock_run, capsys) -> None: |
| 198 | + log_output = _make_git_log_output( |
| 199 | + "feat: add new feature", |
| 200 | + "bad commit no colon", |
| 201 | + ) |
| 202 | + mock_run.return_value = _completed(stdout=log_output) |
| 203 | + |
| 204 | + with pytest.raises(SystemExit, match="1"): |
| 205 | + run_range("origin/main..HEAD", skip_dirty_check=True) |
| 206 | + |
| 207 | + captured = capsys.readouterr() |
| 208 | + assert "PASS" in captured.out |
| 209 | + assert "FAIL" in captured.err |
| 210 | + |
| 211 | + |
| 212 | +@patch("subprocess.run") |
| 213 | +def test_run_range_empty(mock_run, capsys) -> None: |
| 214 | + mock_run.return_value = _completed(stdout="") |
| 215 | + |
| 216 | + run_range("origin/main..HEAD", skip_dirty_check=True) |
| 217 | + |
| 218 | + out = capsys.readouterr().out |
| 219 | + assert "No commits in range" in out |
| 220 | + |
| 221 | + |
| 222 | +@patch("subprocess.run") |
| 223 | +def test_run_range_git_failure(mock_run) -> None: |
| 224 | + mock_run.return_value = _completed(returncode=1, stderr="fatal: bad range") |
| 225 | + |
| 226 | + with pytest.raises(SystemExit, match="1"): |
| 227 | + run_range("bad..range", skip_dirty_check=True) |
| 228 | + |
| 229 | + |
| 230 | +@patch("subprocess.run") |
| 231 | +def test_run_range_dirty_worktree_skips(mock_run, capsys) -> None: |
| 232 | + """When skip_dirty_check=False and worktree is dirty, validation is skipped.""" |
| 233 | + mock_run.return_value = _completed(stdout=" M ops/lintcommit.py\n") |
| 234 | + |
| 235 | + run_range("origin/main..HEAD", skip_dirty_check=False) |
| 236 | + |
| 237 | + out = capsys.readouterr().out |
| 238 | + assert "uncommitted changes" in out |
| 239 | + # git log should never have been called (only git status) |
| 240 | + mock_run.assert_called_once() |
| 241 | + |
| 242 | + |
| 243 | +@patch("subprocess.run") |
| 244 | +def test_run_range_warnings_printed(mock_run, capsys) -> None: |
| 245 | + log_output = _make_git_log_output( |
| 246 | + "feat: add thing\n\n" + "x" * 80, |
| 247 | + ) |
| 248 | + mock_run.return_value = _completed(stdout=log_output) |
| 249 | + |
| 250 | + run_range("origin/main..HEAD", skip_dirty_check=True) |
| 251 | + |
| 252 | + out = capsys.readouterr().out |
| 253 | + assert "PASS" in out |
| 254 | + assert "exceeds 72 chars" in out |
0 commit comments