Skip to content

Commit ccf80d5

Browse files
committed
feat: add _is_version_installed function and corresponding tests
1 parent 91a795b commit ccf80d5

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

cpp_linter_hooks/util.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ def parse_version(v: str):
5959
return None
6060

6161

62+
def _is_version_installed(tool: str, version: str) -> Optional[Path]:
63+
"""Return the tool path if the installed version matches, otherwise None."""
64+
existing = shutil.which(tool)
65+
if not existing:
66+
return None
67+
result = subprocess.run([existing, "--version"], capture_output=True, text=True)
68+
if version in result.stdout:
69+
return Path(existing)
70+
return None
71+
72+
6273
def _install_tool(tool: str, version: str) -> Optional[Path]:
6374
"""Install a tool using pip, logging output on failure."""
6475
result = subprocess.run(
@@ -87,4 +98,6 @@ def resolve_install(tool: str, version: Optional[str]) -> Optional[Path]:
8798
else DEFAULT_CLANG_TIDY_VERSION
8899
)
89100

90-
return _install_tool(tool, user_version)
101+
return _is_version_installed(tool, user_version) or _install_tool(
102+
tool, user_version
103+
)

tests/test_util.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from cpp_linter_hooks.util import (
88
get_version_from_dependency,
99
_resolve_version,
10+
_is_version_installed,
1011
_install_tool,
1112
resolve_install,
1213
DEFAULT_CLANG_FORMAT_VERSION,
@@ -117,6 +118,51 @@ def test_resolve_version_clang_tidy(user_input, expected):
117118
assert result == expected
118119

119120

121+
# Tests for _is_version_installed
122+
@pytest.mark.benchmark
123+
def test_is_version_installed_not_in_path():
124+
"""Test _is_version_installed when tool is not in PATH."""
125+
with patch("shutil.which", return_value=None):
126+
result = _is_version_installed("clang-format", "20.1.7")
127+
assert result is None
128+
129+
130+
@pytest.mark.benchmark
131+
def test_is_version_installed_version_matches():
132+
"""Test _is_version_installed when installed version matches."""
133+
mock_path = "/usr/bin/clang-format"
134+
135+
def patched_run(*args, **kwargs):
136+
return subprocess.CompletedProcess(
137+
args, returncode=0, stdout="clang-format version 20.1.7"
138+
)
139+
140+
with (
141+
patch("shutil.which", return_value=mock_path),
142+
patch("subprocess.run", side_effect=patched_run),
143+
):
144+
result = _is_version_installed("clang-format", "20.1.7")
145+
assert result == Path(mock_path)
146+
147+
148+
@pytest.mark.benchmark
149+
def test_is_version_installed_version_mismatch():
150+
"""Test _is_version_installed when installed version doesn't match."""
151+
mock_path = "/usr/bin/clang-format"
152+
153+
def patched_run(*args, **kwargs):
154+
return subprocess.CompletedProcess(
155+
args, returncode=0, stdout="clang-format version 22.1.0"
156+
)
157+
158+
with (
159+
patch("shutil.which", return_value=mock_path),
160+
patch("subprocess.run", side_effect=patched_run),
161+
):
162+
result = _is_version_installed("clang-format", "20.1.7")
163+
assert result is None
164+
165+
120166
# Tests for _install_tool
121167
@pytest.mark.benchmark
122168
def test_install_tool_success():
@@ -178,20 +224,32 @@ def test_resolve_install_tool_already_installed_correct_version():
178224
"""Test resolve_install when tool is already installed with correct version."""
179225
mock_path = "/usr/bin/clang-format"
180226

227+
def patched_run(*args, **kwargs):
228+
return subprocess.CompletedProcess(
229+
args, returncode=0, stdout="clang-format version 20.1.7"
230+
)
231+
181232
with (
182233
patch("shutil.which", return_value=mock_path),
234+
patch("subprocess.run", side_effect=patched_run),
183235
):
184236
result = resolve_install("clang-format", "20.1.7")
185237
assert Path(result) == Path(mock_path)
186238

187239

188240
@pytest.mark.benchmark
189241
def test_resolve_install_tool_version_mismatch():
190-
"""Test resolve_install when tool has wrong version."""
242+
"""Test resolve_install when tool has wrong version, triggering reinstall."""
191243
mock_path = "/usr/bin/clang-format"
192244

245+
def patched_run(*args, **kwargs):
246+
return subprocess.CompletedProcess(
247+
args, returncode=0, stdout="clang-format version 22.1.0"
248+
)
249+
193250
with (
194251
patch("shutil.which", return_value=mock_path),
252+
patch("subprocess.run", side_effect=patched_run),
195253
patch(
196254
"cpp_linter_hooks.util._install_tool", return_value=Path(mock_path)
197255
) as mock_install,

0 commit comments

Comments
 (0)