Skip to content

Commit 8faac34

Browse files
committed
feat: support verbose option
1 parent 0339700 commit 8faac34

File tree

6 files changed

+368
-18
lines changed

6 files changed

+368
-18
lines changed

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,44 @@ pre-commit run --files $(git diff --name-only)
8181

8282
This approach ensures that only modified files are checked, further speeding up the linting process during development.
8383

84+
### Verbose Output and Debugging
85+
86+
For debugging issues or getting more detailed information about what the hooks are doing, you can enable verbose output:
87+
88+
```yaml
89+
repos:
90+
- repo: https://github.com/cpp-linter/cpp-linter-hooks
91+
rev: v0.8.1
92+
hooks:
93+
- id: clang-format
94+
args: [--style=file, --version=18, --verbose] # Enable verbose output
95+
- id: clang-tidy
96+
args: [--checks=.clang-tidy, --version=18, --verbose] # Enable verbose output
97+
```
98+
99+
When you run pre-commit with verbose mode, you'll get detailed debug information:
100+
101+
```bash
102+
# Run pre-commit with verbose output
103+
pre-commit run --verbose --all-files
104+
105+
# Run specific hook with verbose output
106+
pre-commit run clang-format --verbose
107+
```
108+
109+
The verbose output includes:
110+
- The exact command being executed
111+
- The path to the clang tool executable
112+
- The tool version being used
113+
- Exit codes and detailed error messages
114+
- Both stdout and stderr output from the tools
115+
116+
This is particularly useful when debugging issues like:
117+
- Exit code 247 (tool execution errors)
118+
- Missing compilation databases for clang-tidy
119+
- Configuration file loading issues
120+
- Tool installation problems
121+
84122
## Output
85123

86124
### clang-format Example
@@ -148,7 +186,6 @@ Use -header-filter=.* to display errors from all non-system headers. Use -system
148186
for (;;)
149187
^
150188
{
151-
152189
```
153190

154191
## Contributing

cpp_linter_hooks/clang_format.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import subprocess
2+
import sys
23
from argparse import ArgumentParser
34
from typing import Tuple
45

@@ -7,6 +8,9 @@
78

89
parser = ArgumentParser()
910
parser.add_argument("--version", default=DEFAULT_CLANG_VERSION)
11+
parser.add_argument(
12+
"-v", "--verbose", action="store_true", help="Enable verbose output"
13+
)
1014

1115

1216
def run_clang_format(args=None) -> Tuple[int, str]:
@@ -15,27 +19,81 @@ def run_clang_format(args=None) -> Tuple[int, str]:
1519
command = [str(path), "-i"]
1620
command.extend(other_args)
1721

22+
verbose = hook_args.verbose
23+
24+
if verbose:
25+
print(f"[DEBUG] clang-format command: {' '.join(command)}", file=sys.stderr)
26+
print(f"[DEBUG] clang-format version: {hook_args.version}", file=sys.stderr)
27+
print(f"[DEBUG] clang-format executable: {path}", file=sys.stderr)
28+
1829
retval = 0
1930
output = ""
31+
stderr_output = ""
32+
2033
try:
2134
if "--dry-run" in command:
22-
sp = subprocess.run(command, stdout=subprocess.PIPE, encoding="utf-8")
35+
sp = subprocess.run(
36+
command,
37+
stdout=subprocess.PIPE,
38+
stderr=subprocess.PIPE,
39+
encoding="utf-8",
40+
)
2341
retval = -1 # Not a fail just identify it's a dry-run.
2442
output = sp.stdout
43+
stderr_output = sp.stderr
44+
45+
if verbose:
46+
print("[DEBUG] Dry-run mode detected", file=sys.stderr)
47+
print(
48+
f"[DEBUG] Exit code: {sp.returncode} (converted to -1 for dry-run)",
49+
file=sys.stderr,
50+
)
2551
else:
26-
retval = subprocess.run(command, stdout=subprocess.PIPE).returncode
27-
return retval, output
28-
except FileNotFoundError as stderr:
52+
sp = subprocess.run(
53+
command,
54+
stdout=subprocess.PIPE,
55+
stderr=subprocess.PIPE,
56+
encoding="utf-8",
57+
)
58+
retval = sp.returncode
59+
output = sp.stdout
60+
stderr_output = sp.stderr
61+
62+
if verbose:
63+
print(f"[DEBUG] Exit code: {retval}", file=sys.stderr)
64+
65+
# Combine stdout and stderr for comprehensive output
66+
combined_output = ""
67+
if output:
68+
combined_output += output
69+
if stderr_output:
70+
if combined_output:
71+
combined_output += "\n"
72+
combined_output += stderr_output
73+
74+
if verbose and (output or stderr_output):
75+
print(f"[DEBUG] stdout: {repr(output)}", file=sys.stderr)
76+
print(f"[DEBUG] stderr: {repr(stderr_output)}", file=sys.stderr)
77+
78+
return retval, combined_output
79+
80+
except FileNotFoundError as error:
2981
retval = 1
30-
return retval, str(stderr)
82+
error_msg = f"clang-format executable not found: {error}"
83+
84+
if verbose:
85+
print(f"[DEBUG] FileNotFoundError: {error}", file=sys.stderr)
86+
print(f"[DEBUG] Command attempted: {' '.join(command)}", file=sys.stderr)
87+
88+
return retval, error_msg
3189

3290

3391
def main() -> int:
3492
retval, output = run_clang_format()
35-
if retval != 0:
36-
print(output)
93+
if retval != 0: # pragma: no cover
94+
print(output) # pragma: no cover
3795
return retval
3896

3997

40-
if __name__ == "__main__":
98+
if __name__ == "__main__": # pragma: no cover
4199
raise SystemExit(main())

cpp_linter_hooks/clang_tidy.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import subprocess
2+
import sys
23
from argparse import ArgumentParser
34
from typing import Tuple
45

@@ -7,6 +8,9 @@
78

89
parser = ArgumentParser()
910
parser.add_argument("--version", default=DEFAULT_CLANG_VERSION)
11+
parser.add_argument(
12+
"-v", "--verbose", action="store_true", help="Enable verbose output"
13+
)
1014

1115

1216
def run_clang_tidy(args=None) -> Tuple[int, str]:
@@ -15,26 +19,67 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
1519
command = [str(path)]
1620
command.extend(other_args)
1721

22+
verbose = hook_args.verbose
23+
24+
if verbose:
25+
print(f"[DEBUG] clang-tidy command: {' '.join(command)}", file=sys.stderr)
26+
print(f"[DEBUG] clang-tidy version: {hook_args.version}", file=sys.stderr)
27+
print(f"[DEBUG] clang-tidy executable: {path}", file=sys.stderr)
28+
1829
retval = 0
1930
output = ""
31+
stderr_output = ""
32+
2033
try:
21-
sp = subprocess.run(command, stdout=subprocess.PIPE, encoding="utf-8")
34+
sp = subprocess.run(
35+
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8"
36+
)
2237
retval = sp.returncode
2338
output = sp.stdout
24-
if "warning:" in output or "error:" in output:
39+
stderr_output = sp.stderr
40+
41+
if verbose:
42+
print(f"[DEBUG] Exit code: {retval}", file=sys.stderr)
43+
print(f"[DEBUG] stdout: {repr(output)}", file=sys.stderr)
44+
print(f"[DEBUG] stderr: {repr(stderr_output)}", file=sys.stderr)
45+
46+
# Combine stdout and stderr for comprehensive output
47+
combined_output = ""
48+
if output:
49+
combined_output += output
50+
if stderr_output:
51+
if combined_output:
52+
combined_output += "\n"
53+
combined_output += stderr_output
54+
55+
# Check for warnings or errors in the combined output
56+
if "warning:" in combined_output or "error:" in combined_output:
2557
retval = 1
26-
return retval, output
27-
except FileNotFoundError as stderr:
58+
if verbose:
59+
print(
60+
"[DEBUG] Found warnings/errors, setting exit code to 1",
61+
file=sys.stderr,
62+
)
63+
64+
return retval, combined_output
65+
66+
except FileNotFoundError as error:
2867
retval = 1
29-
return retval, str(stderr)
68+
error_msg = f"clang-tidy executable not found: {error}"
69+
70+
if verbose:
71+
print(f"[DEBUG] FileNotFoundError: {error}", file=sys.stderr)
72+
print(f"[DEBUG] Command attempted: {' '.join(command)}", file=sys.stderr)
73+
74+
return retval, error_msg
3075

3176

3277
def main() -> int:
3378
retval, output = run_clang_tidy()
34-
if retval != 0:
35-
print(output)
79+
if retval != 0: # pragma: no cover
80+
print(output) # pragma: no cover
3681
return retval
3782

3883

39-
if __name__ == "__main__":
84+
if __name__ == "__main__": # pragma: no cover
4085
raise SystemExit(main())
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Example .pre-commit-config.yaml with verbose debugging enabled
2+
3+
repos:
4+
- repo: https://github.com/cpp-linter/cpp-linter-hooks
5+
rev: v0.8.1 # Use the tag or commit you want
6+
hooks:
7+
- id: clang-format
8+
args: [--style=file, --version=18, --verbose] # Added --verbose
9+
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$
10+
- id: clang-tidy
11+
args: [--checks=.clang-tidy, --version=18, --verbose] # Added --verbose
12+
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$
13+
14+
# To use this configuration:
15+
# 1. Save this as .pre-commit-config.yaml in your project root
16+
# 2. Run: pre-commit run --verbose --all-files
17+
# 3. The --verbose flag to pre-commit combined with --verbose in args
18+
# will give you maximum debugging information
19+
20+
# For your specific exit code 247 issue, you'll now see:
21+
# - The exact clang-format command being executed
22+
# - The path to the clang-format executable
23+
# - The version being used
24+
# - Both stdout and stderr output
25+
# - The actual exit code from clang-format

tests/test_clang_format.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import pytest
22
from pathlib import Path
3+
from unittest.mock import patch, MagicMock
34

4-
from cpp_linter_hooks.clang_format import run_clang_format
5+
from cpp_linter_hooks.clang_format import run_clang_format, main
56

67

78
@pytest.mark.parametrize(
@@ -64,3 +65,97 @@ def test_run_clang_format_dry_run(args, expected_retval, tmp_path):
6465
test_file = tmp_path / "main.c"
6566
ret, _ = run_clang_format(["--dry-run", str(test_file)])
6667
assert ret == -1 # Dry run should not fail
68+
69+
70+
def test_main_empty_output():
71+
"""Test main() function when clang-format returns error with empty output"""
72+
with patch(
73+
"cpp_linter_hooks.clang_format.run_clang_format"
74+
) as mock_run_clang_format:
75+
# Mock failed run with empty output
76+
mock_run_clang_format.return_value = (1, "")
77+
78+
with patch("builtins.print") as mock_print:
79+
result = main()
80+
81+
# Should return 1 and print empty string
82+
assert result == 1
83+
mock_print.assert_called_once_with("")
84+
85+
86+
def test_verbose_output(tmp_path, capsys):
87+
"""Test that verbose mode produces debug output to stderr"""
88+
test_file = tmp_path / "test.c"
89+
test_file.write_text("#include <stdio.h>\nint main(){return 0;}")
90+
91+
with patch("cpp_linter_hooks.clang_format.ensure_installed") as mock_ensure:
92+
mock_ensure.return_value = "/fake/clang-format"
93+
with patch("subprocess.run") as mock_run:
94+
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
95+
96+
# Test verbose mode
97+
retval, output = run_clang_format(
98+
["--verbose", "--style=Google", str(test_file)]
99+
)
100+
101+
# Check that debug messages were printed to stderr
102+
captured = capsys.readouterr()
103+
assert "[DEBUG] clang-format command:" in captured.err
104+
assert "[DEBUG] clang-format version:" in captured.err
105+
assert "[DEBUG] clang-format executable:" in captured.err
106+
assert "[DEBUG] Exit code:" in captured.err
107+
108+
109+
def test_verbose_with_error(tmp_path, capsys):
110+
"""Test verbose output when there's an error"""
111+
test_file = tmp_path / "test.c"
112+
113+
with patch("cpp_linter_hooks.clang_format.ensure_installed") as mock_ensure:
114+
mock_ensure.return_value = "/fake/clang-format"
115+
with patch("subprocess.run") as mock_run:
116+
mock_run.return_value = MagicMock(
117+
returncode=1, stdout="error output", stderr="error in stderr"
118+
)
119+
120+
# Test verbose mode with error
121+
retval, output = run_clang_format(
122+
["--verbose", "--style=Google", str(test_file)]
123+
)
124+
125+
# Check return values
126+
assert retval == 1
127+
assert "error output" in output
128+
assert "error in stderr" in output
129+
130+
# Check debug output
131+
captured = capsys.readouterr()
132+
assert "[DEBUG] Exit code: 1" in captured.err
133+
assert "[DEBUG] stdout:" in captured.err
134+
assert "[DEBUG] stderr:" in captured.err
135+
136+
137+
def test_verbose_dry_run(tmp_path, capsys):
138+
"""Test verbose output in dry-run mode"""
139+
test_file = tmp_path / "test.c"
140+
test_file.write_text("#include <stdio.h>\nint main(){return 0;}")
141+
142+
with patch("cpp_linter_hooks.clang_format.ensure_installed") as mock_ensure:
143+
mock_ensure.return_value = "/fake/clang-format"
144+
with patch("subprocess.run") as mock_run:
145+
mock_run.return_value = MagicMock(
146+
returncode=0, stdout="dry run output", stderr=""
147+
)
148+
149+
# Test verbose dry-run mode
150+
retval, output = run_clang_format(
151+
["--verbose", "--dry-run", str(test_file)]
152+
)
153+
154+
# Check return values (dry-run should return -1)
155+
assert retval == -1
156+
assert "dry run output" in output
157+
158+
# Check debug output
159+
captured = capsys.readouterr()
160+
assert "[DEBUG] Dry-run mode detected" in captured.err
161+
assert "[DEBUG] Exit code: 0 (converted to -1 for dry-run)" in captured.err

0 commit comments

Comments
 (0)