|
1 | 1 | import pytest |
2 | 2 | import subprocess |
3 | 3 | from pathlib import Path |
| 4 | +from unittest.mock import patch, MagicMock |
4 | 5 |
|
5 | 6 | from cpp_linter_hooks.clang_tidy import run_clang_tidy |
6 | 7 |
|
@@ -53,3 +54,122 @@ def test_run_clang_tidy_invalid(args, expected_retval, tmp_path): |
53 | 54 |
|
54 | 55 | ret, _ = run_clang_tidy(args + [str(test_file)]) |
55 | 56 | assert ret == expected_retval |
| 57 | + |
| 58 | + |
| 59 | +# --- compile_commands tests (all mock subprocess.run and resolve_install) --- |
| 60 | + |
| 61 | +_MOCK_RUN = MagicMock(returncode=0, stdout="", stderr="") |
| 62 | + |
| 63 | + |
| 64 | +def _patch(): |
| 65 | + return ( |
| 66 | + patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN), |
| 67 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"), |
| 68 | + ) |
| 69 | + |
| 70 | + |
| 71 | +def test_compile_commands_explicit(tmp_path): |
| 72 | + db_dir = tmp_path / "build" |
| 73 | + db_dir.mkdir() |
| 74 | + (db_dir / "compile_commands.json").write_text("[]") |
| 75 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 76 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 77 | + run_clang_tidy([f"--compile-commands={db_dir}", "dummy.cpp"]) |
| 78 | + cmd = mock_run.call_args[0][0] |
| 79 | + assert "-p" in cmd |
| 80 | + assert cmd[cmd.index("-p") + 1] == str(db_dir) |
| 81 | + |
| 82 | + |
| 83 | +def test_compile_commands_auto_detect(tmp_path, monkeypatch): |
| 84 | + monkeypatch.chdir(tmp_path) |
| 85 | + build_dir = tmp_path / "build" |
| 86 | + build_dir.mkdir() |
| 87 | + (build_dir / "compile_commands.json").write_text("[]") |
| 88 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 89 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 90 | + run_clang_tidy(["dummy.cpp"]) |
| 91 | + cmd = mock_run.call_args[0][0] |
| 92 | + assert "-p" in cmd |
| 93 | + assert cmd[cmd.index("-p") + 1] == "build" |
| 94 | + |
| 95 | + |
| 96 | +def test_compile_commands_auto_detect_fallback(tmp_path, monkeypatch): |
| 97 | + # Only ./out has compile_commands.json, not ./build |
| 98 | + monkeypatch.chdir(tmp_path) |
| 99 | + out_dir = tmp_path / "out" |
| 100 | + out_dir.mkdir() |
| 101 | + (out_dir / "compile_commands.json").write_text("[]") |
| 102 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 103 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 104 | + run_clang_tidy(["dummy.cpp"]) |
| 105 | + cmd = mock_run.call_args[0][0] |
| 106 | + assert "-p" in cmd |
| 107 | + assert cmd[cmd.index("-p") + 1] == "out" |
| 108 | + |
| 109 | + |
| 110 | +def test_compile_commands_none(tmp_path, monkeypatch): |
| 111 | + monkeypatch.chdir(tmp_path) |
| 112 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 113 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 114 | + run_clang_tidy(["dummy.cpp"]) |
| 115 | + cmd = mock_run.call_args[0][0] |
| 116 | + assert "-p" not in cmd |
| 117 | + |
| 118 | + |
| 119 | +def test_compile_commands_conflict_guard(tmp_path, monkeypatch): |
| 120 | + # -p already in args: auto-detect should NOT fire even if build/ exists |
| 121 | + monkeypatch.chdir(tmp_path) |
| 122 | + build_dir = tmp_path / "build" |
| 123 | + build_dir.mkdir() |
| 124 | + (build_dir / "compile_commands.json").write_text("[]") |
| 125 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 126 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 127 | + run_clang_tidy(["-p", "./custom", "dummy.cpp"]) |
| 128 | + cmd = mock_run.call_args[0][0] |
| 129 | + assert cmd.count("-p") == 1 |
| 130 | + assert "./custom" in cmd |
| 131 | + |
| 132 | + |
| 133 | +def test_compile_commands_no_flag(tmp_path, monkeypatch): |
| 134 | + # --no-compile-commands disables auto-detect even when build/ exists |
| 135 | + monkeypatch.chdir(tmp_path) |
| 136 | + build_dir = tmp_path / "build" |
| 137 | + build_dir.mkdir() |
| 138 | + (build_dir / "compile_commands.json").write_text("[]") |
| 139 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 140 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 141 | + run_clang_tidy(["--no-compile-commands", "dummy.cpp"]) |
| 142 | + cmd = mock_run.call_args[0][0] |
| 143 | + assert "-p" not in cmd |
| 144 | + |
| 145 | + |
| 146 | +def test_compile_commands_invalid_path(tmp_path): |
| 147 | + # Case 1: directory does not exist |
| 148 | + fake_dir = tmp_path / "nonexistent" |
| 149 | + with patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 150 | + ret, output = run_clang_tidy([f"--compile-commands={fake_dir}", "dummy.cpp"]) |
| 151 | + assert ret == 1 |
| 152 | + assert "nonexistent" in output |
| 153 | + |
| 154 | + # Case 2: directory exists but has no compile_commands.json |
| 155 | + empty_dir = tmp_path / "empty_build" |
| 156 | + empty_dir.mkdir() |
| 157 | + with patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 158 | + ret, output = run_clang_tidy([f"--compile-commands={empty_dir}", "dummy.cpp"]) |
| 159 | + assert ret == 1 |
| 160 | + assert "empty_build" in output |
| 161 | + |
| 162 | + |
| 163 | +def test_compile_commands_explicit_with_p_conflict(tmp_path, capsys): |
| 164 | + # --compile-commands + -p in args: warning printed, only the user's -p used |
| 165 | + db_dir = tmp_path / "build" |
| 166 | + db_dir.mkdir() |
| 167 | + (db_dir / "compile_commands.json").write_text("[]") |
| 168 | + with patch("cpp_linter_hooks.clang_tidy.subprocess.run", return_value=_MOCK_RUN) as mock_run, \ |
| 169 | + patch("cpp_linter_hooks.clang_tidy.resolve_install"): |
| 170 | + run_clang_tidy([f"--compile-commands={db_dir}", "-p", "./other", "dummy.cpp"]) |
| 171 | + captured = capsys.readouterr() |
| 172 | + assert "Warning" in captured.err |
| 173 | + cmd = mock_run.call_args[0][0] |
| 174 | + assert cmd.count("-p") == 1 |
| 175 | + assert "./other" in cmd |
0 commit comments