Skip to content

Commit 879fd5e

Browse files
committed
feat: add wheel support for clang-include-cleaner and clang-apply-replacements
- Add version lists for clang-include-cleaner (22.1.7) and clang-apply-replacements (16.0.0, 17.0.6) in versions.py - Extend _versions_for_tool and _default_version_for_tool in util.py to handle the new tools via lookup maps - Add comprehensive tests for version resolution, default versions, install dispatch, and diagnostic messages
1 parent 6f66123 commit 879fd5e

3 files changed

Lines changed: 245 additions & 8 deletions

File tree

cpp_linter_hooks/util.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
else:
1313
import tomli as tomllib
1414

15-
from cpp_linter_hooks.versions import CLANG_FORMAT_VERSIONS, CLANG_TIDY_VERSIONS
15+
from cpp_linter_hooks.versions import (
16+
CLANG_FORMAT_VERSIONS,
17+
CLANG_TIDY_VERSIONS,
18+
CLANG_INCLUDE_CLEANER_VERSIONS,
19+
CLANG_APPLY_REPLACEMENTS_VERSIONS,
20+
)
1621

1722
LOG = logging.getLogger(__name__)
1823

@@ -34,20 +39,30 @@ def get_version_from_dependency(tool: str) -> Optional[str]:
3439

3540
DEFAULT_CLANG_FORMAT_VERSION = CLANG_FORMAT_VERSIONS[-1] # latest from versions.py
3641
DEFAULT_CLANG_TIDY_VERSION = CLANG_TIDY_VERSIONS[-1] # latest from versions.py
42+
DEFAULT_CLANG_INCLUDE_CLEANER_VERSION = CLANG_INCLUDE_CLEANER_VERSIONS[-1]
43+
DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION = CLANG_APPLY_REPLACEMENTS_VERSIONS[-1]
3744

3845

3946
def _versions_for_tool(tool: str) -> List[str]:
4047
"""Return supported Python wheel versions for a clang tool."""
41-
return CLANG_FORMAT_VERSIONS if tool == "clang-format" else CLANG_TIDY_VERSIONS
48+
_tool_map = {
49+
"clang-format": CLANG_FORMAT_VERSIONS,
50+
"clang-tidy": CLANG_TIDY_VERSIONS,
51+
"clang-include-cleaner": CLANG_INCLUDE_CLEANER_VERSIONS,
52+
"clang-apply-replacements": CLANG_APPLY_REPLACEMENTS_VERSIONS,
53+
}
54+
return _tool_map[tool]
4255

4356

4457
def _default_version_for_tool(tool: str) -> Optional[str]:
4558
"""Return the default Python wheel version for a clang tool."""
46-
return (
47-
DEFAULT_CLANG_FORMAT_VERSION
48-
if tool == "clang-format"
49-
else DEFAULT_CLANG_TIDY_VERSION
50-
)
59+
_default_map = {
60+
"clang-format": DEFAULT_CLANG_FORMAT_VERSION,
61+
"clang-tidy": DEFAULT_CLANG_TIDY_VERSION,
62+
"clang-include-cleaner": DEFAULT_CLANG_INCLUDE_CLEANER_VERSION,
63+
"clang-apply-replacements": DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION,
64+
}
65+
return _default_map[tool]
5166

5267

5368
def _supported_versions_message(tool: str) -> str:

cpp_linter_hooks/versions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,14 @@
104104
"22.1.0",
105105
"22.1.0.1",
106106
]
107+
108+
# Updated automatically by GitHub Actions - DO NOT EDIT MANUALLY
109+
CLANG_INCLUDE_CLEANER_VERSIONS = [
110+
"22.1.7",
111+
]
112+
113+
# Updated automatically by GitHub Actions - DO NOT EDIT MANUALLY
114+
CLANG_APPLY_REPLACEMENTS_VERSIONS = [
115+
"16.0.0",
116+
"17.0.6",
117+
]

tests/test_util.py

Lines changed: 212 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
resolve_install,
1414
DEFAULT_CLANG_FORMAT_VERSION,
1515
DEFAULT_CLANG_TIDY_VERSION,
16+
DEFAULT_CLANG_INCLUDE_CLEANER_VERSION,
17+
DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION,
18+
)
19+
from cpp_linter_hooks.versions import (
20+
CLANG_FORMAT_VERSIONS,
21+
CLANG_TIDY_VERSIONS,
22+
CLANG_INCLUDE_CLEANER_VERSIONS,
23+
CLANG_APPLY_REPLACEMENTS_VERSIONS,
1624
)
17-
from cpp_linter_hooks.versions import CLANG_FORMAT_VERSIONS, CLANG_TIDY_VERSIONS
1825

1926

2027
VERSIONS = [None, "20"]
@@ -377,3 +384,207 @@ def test_resolve_install_with_none_default_version():
377384

378385
# Should fallback to hardcoded version when DEFAULT is None
379386
mock_install.assert_called_once_with("clang-format", None)
387+
388+
389+
# ---------------------------------------------------------------------------
390+
# Tests for new wheel tools: clang-include-cleaner and clang-apply-replacements
391+
# ---------------------------------------------------------------------------
392+
393+
394+
@pytest.mark.benchmark
395+
def test_clang_include_cleaner_versions_not_empty():
396+
"""Test that clang-include-cleaner version list is not empty."""
397+
assert len(CLANG_INCLUDE_CLEANER_VERSIONS) > 0
398+
assert all(isinstance(v, str) for v in CLANG_INCLUDE_CLEANER_VERSIONS)
399+
400+
401+
@pytest.mark.benchmark
402+
def test_clang_apply_replacements_versions_not_empty():
403+
"""Test that clang-apply-replacements version list is not empty."""
404+
assert len(CLANG_APPLY_REPLACEMENTS_VERSIONS) > 0
405+
assert all(isinstance(v, str) for v in CLANG_APPLY_REPLACEMENTS_VERSIONS)
406+
407+
408+
@pytest.mark.benchmark
409+
def test_default_clang_include_cleaner_version():
410+
"""Test that default version for clang-include-cleaner is set."""
411+
assert DEFAULT_CLANG_INCLUDE_CLEANER_VERSION is not None
412+
assert isinstance(DEFAULT_CLANG_INCLUDE_CLEANER_VERSION, str)
413+
assert DEFAULT_CLANG_INCLUDE_CLEANER_VERSION == CLANG_INCLUDE_CLEANER_VERSIONS[-1]
414+
415+
416+
@pytest.mark.benchmark
417+
def test_default_clang_apply_replacements_version():
418+
"""Test that default version for clang-apply-replacements is set."""
419+
assert DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION is not None
420+
assert isinstance(DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION, str)
421+
assert DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION == CLANG_APPLY_REPLACEMENTS_VERSIONS[-1]
422+
423+
424+
@pytest.mark.benchmark
425+
def test_resolve_install_include_cleaner():
426+
"""Test resolve_install for clang-include-cleaner."""
427+
mock_path = "/usr/bin/clang-include-cleaner"
428+
with (
429+
patch("shutil.which", return_value=None),
430+
patch(
431+
"cpp_linter_hooks.util._install_tool",
432+
return_value=Path(mock_path),
433+
) as mock_install,
434+
):
435+
result = resolve_install("clang-include-cleaner", "22.1.7")
436+
assert Path(result) == Path(mock_path)
437+
mock_install.assert_called_once_with("clang-include-cleaner", "22.1.7")
438+
439+
440+
@pytest.mark.benchmark
441+
def test_resolve_install_include_cleaner_no_version():
442+
"""Test resolve_install for clang-include-cleaner with no version (uses default)."""
443+
with (
444+
patch("shutil.which", return_value=None),
445+
patch(
446+
"cpp_linter_hooks.util._install_tool",
447+
return_value=Path("/usr/bin/clang-include-cleaner"),
448+
) as mock_install,
449+
):
450+
result = resolve_install("clang-include-cleaner", None)
451+
assert result == Path("/usr/bin/clang-include-cleaner")
452+
mock_install.assert_called_once_with(
453+
"clang-include-cleaner", DEFAULT_CLANG_INCLUDE_CLEANER_VERSION
454+
)
455+
456+
457+
@pytest.mark.benchmark
458+
def test_resolve_install_include_cleaner_invalid_version():
459+
"""Test resolve_install for clang-include-cleaner with invalid version."""
460+
with (
461+
patch("shutil.which", return_value=None),
462+
patch(
463+
"cpp_linter_hooks.util._install_tool",
464+
return_value=Path("/usr/bin/clang-include-cleaner"),
465+
) as mock_install,
466+
):
467+
result = resolve_install("clang-include-cleaner", "99.0.0")
468+
assert result is None
469+
mock_install.assert_not_called()
470+
471+
472+
@pytest.mark.benchmark
473+
def test_resolve_install_apply_replacements():
474+
"""Test resolve_install for clang-apply-replacements."""
475+
mock_path = "/usr/bin/clang-apply-replacements"
476+
with (
477+
patch("shutil.which", return_value=None),
478+
patch(
479+
"cpp_linter_hooks.util._install_tool",
480+
return_value=Path(mock_path),
481+
) as mock_install,
482+
):
483+
result = resolve_install("clang-apply-replacements", "17.0.6")
484+
assert Path(result) == Path(mock_path)
485+
mock_install.assert_called_once_with("clang-apply-replacements", "17.0.6")
486+
487+
488+
@pytest.mark.benchmark
489+
def test_resolve_install_apply_replacements_no_version():
490+
"""Test resolve_install for clang-apply-replacements with no version (uses default)."""
491+
with (
492+
patch("shutil.which", return_value=None),
493+
patch(
494+
"cpp_linter_hooks.util._install_tool",
495+
return_value=Path("/usr/bin/clang-apply-replacements"),
496+
) as mock_install,
497+
):
498+
result = resolve_install("clang-apply-replacements", None)
499+
assert result == Path("/usr/bin/clang-apply-replacements")
500+
mock_install.assert_called_once_with(
501+
"clang-apply-replacements", DEFAULT_CLANG_APPLY_REPLACEMENTS_VERSION
502+
)
503+
504+
505+
@pytest.mark.benchmark
506+
def test_resolve_install_apply_replacements_invalid_version():
507+
"""Test resolve_install for clang-apply-replacements with invalid version."""
508+
with (
509+
patch("shutil.which", return_value=None),
510+
patch(
511+
"cpp_linter_hooks.util._install_tool",
512+
return_value=Path("/usr/bin/clang-apply-replacements"),
513+
) as mock_install,
514+
):
515+
result = resolve_install("clang-apply-replacements", "99.0.0")
516+
assert result is None
517+
mock_install.assert_not_called()
518+
519+
520+
@pytest.mark.benchmark
521+
@pytest.mark.parametrize(
522+
"user_input,expected",
523+
[
524+
("22", "22.1.7"),
525+
("22.1", "22.1.7"),
526+
("22.1.7", "22.1.7"),
527+
("99", None),
528+
],
529+
)
530+
def test_resolve_version_clang_include_cleaner(user_input, expected):
531+
"""Test _resolve_version for clang-include-cleaner versions."""
532+
result = _resolve_version(CLANG_INCLUDE_CLEANER_VERSIONS, user_input)
533+
assert result == expected
534+
535+
536+
@pytest.mark.benchmark
537+
@pytest.mark.parametrize(
538+
"user_input,expected",
539+
[
540+
("16", "16.0.0"),
541+
("17", "17.0.6"),
542+
("17.0.6", "17.0.6"),
543+
("99", None),
544+
],
545+
)
546+
def test_resolve_version_clang_apply_replacements(user_input, expected):
547+
"""Test _resolve_version for clang-apply-replacements versions."""
548+
result = _resolve_version(CLANG_APPLY_REPLACEMENTS_VERSIONS, user_input)
549+
assert result == expected
550+
551+
552+
@pytest.mark.benchmark
553+
def test_resolve_install_with_diagnostics_include_cleaner_invalid():
554+
path, error = resolve_install_with_diagnostics("clang-include-cleaner", "99")
555+
556+
assert path is None
557+
assert error is not None
558+
assert "Unsupported clang-include-cleaner version '99'" in error
559+
assert "Supported clang-include-cleaner wheel versions:" in error
560+
assert CLANG_INCLUDE_CLEANER_VERSIONS[-1] in error
561+
562+
563+
@pytest.mark.benchmark
564+
def test_resolve_install_with_diagnostics_apply_replacements_invalid():
565+
path, error = resolve_install_with_diagnostics("clang-apply-replacements", "99")
566+
567+
assert path is None
568+
assert error is not None
569+
assert "Unsupported clang-apply-replacements version '99'" in error
570+
assert "Supported clang-apply-replacements wheel versions:" in error
571+
assert CLANG_APPLY_REPLACEMENTS_VERSIONS[-1] in error
572+
573+
574+
@pytest.mark.benchmark
575+
def test_resolve_install_with_diagnostics_include_cleaner_verbose(capsys):
576+
with (
577+
patch("shutil.which", return_value=None),
578+
patch(
579+
"cpp_linter_hooks.util._install_tool",
580+
return_value=Path("/usr/bin/clang-include-cleaner"),
581+
),
582+
):
583+
path, error = resolve_install_with_diagnostics("clang-include-cleaner", "22", True)
584+
585+
assert path == Path("/usr/bin/clang-include-cleaner")
586+
assert error is None
587+
assert (
588+
"Resolved clang-include-cleaner --version=22 to Python wheel version 22.1.7"
589+
in capsys.readouterr().err
590+
)

0 commit comments

Comments
 (0)