Skip to content

Commit 0d96eaa

Browse files
committed
increase test coverage
1 parent 02670a3 commit 0d96eaa

File tree

1 file changed

+311
-2
lines changed

1 file changed

+311
-2
lines changed

tests/test_util.py

Lines changed: 311 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
import logging
22
import pytest
33
from itertools import product
4-
from unittest.mock import patch
4+
from unittest.mock import patch, mock_open, MagicMock
5+
from pathlib import Path
6+
import subprocess
7+
import sys
58

6-
from cpp_linter_hooks.util import ensure_installed, is_installed
9+
from cpp_linter_hooks.util import (
10+
ensure_installed,
11+
is_installed,
12+
get_version_from_dependency,
13+
_resolve_version,
14+
_get_runtime_version,
15+
_install_tool,
16+
_resolve_install,
17+
CLANG_FORMAT_VERSIONS,
18+
CLANG_TIDY_VERSIONS,
19+
DEFAULT_CLANG_FORMAT_VERSION,
20+
DEFAULT_CLANG_TIDY_VERSION,
21+
)
722

823

924
VERSIONS = [None, "20"]
@@ -79,3 +94,297 @@ def test_ensure_installed_tool_not_found(caplog):
7994
"not found and could not be installed" in record.message
8095
for record in caplog.records
8196
)
97+
98+
99+
# Tests for get_version_from_dependency
100+
@pytest.mark.benchmark
101+
def test_get_version_from_dependency_success():
102+
"""Test get_version_from_dependency with valid pyproject.toml."""
103+
mock_toml_content = {
104+
"project": {
105+
"dependencies": [
106+
"clang-format==20.1.7",
107+
"clang-tidy==20.1.0",
108+
"other-package==1.0.0"
109+
]
110+
}
111+
}
112+
113+
with patch("pathlib.Path.exists", return_value=True), \
114+
patch("toml.load", return_value=mock_toml_content):
115+
116+
result = get_version_from_dependency("clang-format")
117+
assert result == "20.1.7"
118+
119+
result = get_version_from_dependency("clang-tidy")
120+
assert result == "20.1.0"
121+
122+
123+
@pytest.mark.benchmark
124+
def test_get_version_from_dependency_missing_file():
125+
"""Test get_version_from_dependency when pyproject.toml doesn't exist."""
126+
with patch("pathlib.Path.exists", return_value=False):
127+
result = get_version_from_dependency("clang-format")
128+
assert result is None
129+
130+
131+
@pytest.mark.benchmark
132+
def test_get_version_from_dependency_missing_dependency():
133+
"""Test get_version_from_dependency with missing dependency."""
134+
mock_toml_content = {
135+
"project": {
136+
"dependencies": [
137+
"other-package==1.0.0"
138+
]
139+
}
140+
}
141+
142+
with patch("pathlib.Path.exists", return_value=True), \
143+
patch("toml.load", return_value=mock_toml_content):
144+
145+
result = get_version_from_dependency("clang-format")
146+
assert result is None
147+
148+
149+
@pytest.mark.benchmark
150+
def test_get_version_from_dependency_malformed_toml():
151+
"""Test get_version_from_dependency with malformed toml."""
152+
mock_toml_content = {}
153+
154+
with patch("pathlib.Path.exists", return_value=True), \
155+
patch("toml.load", return_value=mock_toml_content):
156+
157+
result = get_version_from_dependency("clang-format")
158+
assert result is None
159+
160+
161+
# Tests for _resolve_version
162+
@pytest.mark.benchmark
163+
@pytest.mark.parametrize("user_input,expected", [
164+
(None, None),
165+
("20", "20.1.7"), # Should find latest 20.x
166+
("20.1", "20.1.7"), # Should find latest 20.1.x
167+
("20.1.7", "20.1.7"), # Exact match
168+
("18", "18.1.8"), # Should find latest 18.x
169+
("18.1", "18.1.8"), # Should find latest 18.1.x
170+
("99", None), # Non-existent major version
171+
("20.99", None), # Non-existent minor version
172+
("invalid", None), # Invalid version string
173+
])
174+
def test_resolve_version_clang_format(user_input, expected):
175+
"""Test _resolve_version with various inputs for clang-format."""
176+
result = _resolve_version(CLANG_FORMAT_VERSIONS, user_input)
177+
assert result == expected
178+
179+
180+
@pytest.mark.benchmark
181+
@pytest.mark.parametrize("user_input,expected", [
182+
(None, None),
183+
("20", "20.1.0"), # Should find latest 20.x
184+
("18", "18.1.8"), # Should find latest 18.x
185+
("19", "19.1.0.1"), # Should find latest 19.x
186+
("99", None), # Non-existent major version
187+
])
188+
def test_resolve_version_clang_tidy(user_input, expected):
189+
"""Test _resolve_version with various inputs for clang-tidy."""
190+
result = _resolve_version(CLANG_TIDY_VERSIONS, user_input)
191+
assert result == expected
192+
193+
194+
# Tests for _get_runtime_version
195+
@pytest.mark.benchmark
196+
def test_get_runtime_version_clang_format():
197+
"""Test _get_runtime_version for clang-format."""
198+
mock_output = "Ubuntu clang-format version 20.1.7-1ubuntu1\n"
199+
200+
with patch("subprocess.check_output", return_value=mock_output):
201+
result = _get_runtime_version("clang-format")
202+
assert result == "20.1.7-1ubuntu1"
203+
204+
205+
@pytest.mark.benchmark
206+
def test_get_runtime_version_clang_tidy():
207+
"""Test _get_runtime_version for clang-tidy."""
208+
mock_output = "LLVM (http://llvm.org/):\n LLVM version 20.1.0\n"
209+
210+
with patch("subprocess.check_output", return_value=mock_output):
211+
result = _get_runtime_version("clang-tidy")
212+
assert result == "20.1.0"
213+
214+
215+
@pytest.mark.benchmark
216+
def test_get_runtime_version_exception():
217+
"""Test _get_runtime_version when subprocess fails."""
218+
with patch("subprocess.check_output", side_effect=subprocess.CalledProcessError(1, ["clang-format"])):
219+
result = _get_runtime_version("clang-format")
220+
assert result is None
221+
222+
223+
@pytest.mark.benchmark
224+
def test_get_runtime_version_clang_tidy_single_line():
225+
"""Test _get_runtime_version for clang-tidy with single line output."""
226+
mock_output = "LLVM version 20.1.0\n"
227+
228+
with patch("subprocess.check_output", return_value=mock_output):
229+
result = _get_runtime_version("clang-tidy")
230+
assert result is None # Should return None for single line
231+
232+
233+
# Tests for _install_tool
234+
@pytest.mark.benchmark
235+
def test_install_tool_success():
236+
"""Test _install_tool successful installation."""
237+
mock_path = "/usr/bin/clang-format"
238+
239+
with patch("subprocess.check_call") as mock_check_call, \
240+
patch("shutil.which", return_value=mock_path):
241+
242+
result = _install_tool("clang-format", "20.1.7")
243+
assert result == mock_path
244+
245+
mock_check_call.assert_called_once_with([
246+
sys.executable, "-m", "pip", "install", "clang-format==20.1.7"
247+
])
248+
249+
250+
@pytest.mark.benchmark
251+
def test_install_tool_failure():
252+
"""Test _install_tool when pip install fails."""
253+
with patch("subprocess.check_call", side_effect=subprocess.CalledProcessError(1, ["pip"])), \
254+
patch("cpp_linter_hooks.util.LOG") as mock_log:
255+
256+
result = _install_tool("clang-format", "20.1.7")
257+
assert result is None
258+
259+
mock_log.error.assert_called_once_with("Failed to install %s==%s", "clang-format", "20.1.7")
260+
261+
262+
@pytest.mark.benchmark
263+
def test_install_tool_success_but_not_found():
264+
"""Test _install_tool when install succeeds but tool not found in PATH."""
265+
with patch("subprocess.check_call"), \
266+
patch("shutil.which", return_value=None):
267+
268+
result = _install_tool("clang-format", "20.1.7")
269+
assert result is None
270+
271+
272+
# Tests for _resolve_install
273+
@pytest.mark.benchmark
274+
def test_resolve_install_tool_already_installed_correct_version():
275+
"""Test _resolve_install when tool is already installed with correct version."""
276+
mock_path = "/usr/bin/clang-format"
277+
278+
with patch("shutil.which", return_value=mock_path), \
279+
patch("cpp_linter_hooks.util._get_runtime_version", return_value="20.1.7"):
280+
281+
result = _resolve_install("clang-format", "20.1.7")
282+
assert result == Path(mock_path)
283+
284+
285+
@pytest.mark.benchmark
286+
def test_resolve_install_tool_version_mismatch():
287+
"""Test _resolve_install when tool has wrong version."""
288+
mock_path = "/usr/bin/clang-format"
289+
290+
with patch("shutil.which", return_value=mock_path), \
291+
patch("cpp_linter_hooks.util._get_runtime_version", return_value="18.1.8"), \
292+
patch("cpp_linter_hooks.util._install_tool", return_value=Path(mock_path)) as mock_install, \
293+
patch("cpp_linter_hooks.util.LOG") as mock_log:
294+
295+
result = _resolve_install("clang-format", "20.1.7")
296+
assert result == Path(mock_path)
297+
298+
mock_install.assert_called_once_with("clang-format", "20.1.7")
299+
mock_log.info.assert_called_once_with(
300+
"%s version mismatch (%s != %s), reinstalling...",
301+
"clang-format", "18.1.8", "20.1.7"
302+
)
303+
304+
305+
@pytest.mark.benchmark
306+
def test_resolve_install_tool_not_installed():
307+
"""Test _resolve_install when tool is not installed."""
308+
with patch("shutil.which", return_value=None), \
309+
patch("cpp_linter_hooks.util._install_tool", return_value=Path("/usr/bin/clang-format")) as mock_install:
310+
311+
result = _resolve_install("clang-format", "20.1.7")
312+
assert result == Path("/usr/bin/clang-format")
313+
314+
mock_install.assert_called_once_with("clang-format", "20.1.7")
315+
316+
317+
@pytest.mark.benchmark
318+
def test_resolve_install_no_version_specified():
319+
"""Test _resolve_install when no version is specified."""
320+
with patch("shutil.which", return_value=None), \
321+
patch("cpp_linter_hooks.util._install_tool", return_value=Path("/usr/bin/clang-format")) as mock_install:
322+
323+
result = _resolve_install("clang-format", None)
324+
assert result == Path("/usr/bin/clang-format")
325+
326+
mock_install.assert_called_once_with("clang-format", DEFAULT_CLANG_FORMAT_VERSION)
327+
328+
329+
@pytest.mark.benchmark
330+
def test_resolve_install_invalid_version():
331+
"""Test _resolve_install with invalid version."""
332+
with patch("shutil.which", return_value=None), \
333+
patch("cpp_linter_hooks.util._install_tool", return_value=Path("/usr/bin/clang-format")) as mock_install:
334+
335+
result = _resolve_install("clang-format", "invalid.version")
336+
assert result == Path("/usr/bin/clang-format")
337+
338+
# Should fallback to default version
339+
mock_install.assert_called_once_with("clang-format", DEFAULT_CLANG_FORMAT_VERSION)
340+
341+
342+
# Tests for ensure_installed edge cases
343+
@pytest.mark.benchmark
344+
def test_ensure_installed_version_mismatch(caplog):
345+
"""Test ensure_installed with version mismatch scenario."""
346+
mock_path = "/usr/bin/clang-format"
347+
348+
with patch("shutil.which", return_value=mock_path), \
349+
patch("cpp_linter_hooks.util._get_runtime_version", return_value="18.1.8"), \
350+
patch("cpp_linter_hooks.util._install_tool", return_value=Path(mock_path)):
351+
352+
caplog.clear()
353+
caplog.set_level(logging.INFO, logger="cpp_linter_hooks.util")
354+
355+
result = ensure_installed("clang-format", "20.1.7")
356+
assert result == "clang-format"
357+
358+
# Should log version mismatch
359+
assert any("version mismatch" in record.message for record in caplog.records)
360+
361+
362+
@pytest.mark.benchmark
363+
def test_ensure_installed_no_runtime_version():
364+
"""Test ensure_installed when runtime version cannot be determined."""
365+
mock_path = "/usr/bin/clang-format"
366+
367+
with patch("shutil.which", return_value=mock_path), \
368+
patch("cpp_linter_hooks.util._get_runtime_version", return_value=None):
369+
370+
result = ensure_installed("clang-format", "20.1.7")
371+
assert result == "clang-format"
372+
373+
374+
# Tests for constants and defaults
375+
@pytest.mark.benchmark
376+
def test_default_versions():
377+
"""Test that default versions are set correctly."""
378+
assert DEFAULT_CLANG_FORMAT_VERSION is not None
379+
assert DEFAULT_CLANG_TIDY_VERSION is not None
380+
assert isinstance(DEFAULT_CLANG_FORMAT_VERSION, str)
381+
assert isinstance(DEFAULT_CLANG_TIDY_VERSION, str)
382+
383+
384+
@pytest.mark.benchmark
385+
def test_version_lists_not_empty():
386+
"""Test that version lists are not empty."""
387+
assert len(CLANG_FORMAT_VERSIONS) > 0
388+
assert len(CLANG_TIDY_VERSIONS) > 0
389+
assert all(isinstance(v, str) for v in CLANG_FORMAT_VERSIONS)
390+
assert all(isinstance(v, str) for v in CLANG_TIDY_VERSIONS)

0 commit comments

Comments
 (0)