Skip to content

Commit 48b848b

Browse files
committed
refactor new changes
1 parent a6544ff commit 48b848b

File tree

5 files changed

+166
-160
lines changed

5 files changed

+166
-160
lines changed

cpp_linter_hooks/clang_format.py

Lines changed: 15 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from argparse import ArgumentParser
44
from typing import Tuple
55

6-
from .util import ensure_installed, DEFAULT_CLANG_VERSION
6+
from .util import (
7+
ensure_installed,
8+
DEFAULT_CLANG_VERSION,
9+
debug_print,
10+
run_subprocess_with_logging,
11+
)
712

813

914
parser = ArgumentParser()
@@ -21,71 +26,17 @@ def run_clang_format(args=None) -> Tuple[int, str]:
2126

2227
verbose = hook_args.verbose
2328

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-
29-
retval = 0
30-
output = ""
31-
stderr_output = ""
32-
33-
try:
34-
if "--dry-run" in command:
35-
sp = subprocess.run(
36-
command,
37-
stdout=subprocess.PIPE,
38-
stderr=subprocess.PIPE,
39-
encoding="utf-8",
40-
)
41-
retval = -1 # Not a fail just identify it's a dry-run.
42-
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-
)
51-
else:
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:
81-
retval = 1
82-
error_msg = f"clang-format executable not found: {error}"
29+
# Log initial debug information
30+
debug_print(f"clang-format version: {hook_args.version}", verbose)
31+
debug_print(f"clang-format executable: {path}", verbose)
8332

84-
if verbose:
85-
print(f"[DEBUG] FileNotFoundError: {error}", file=sys.stderr)
86-
print(f"[DEBUG] Command attempted: {' '.join(command)}", file=sys.stderr)
33+
# Check for dry-run mode
34+
dry_run = "--dry-run" in command
8735

88-
return retval, error_msg
36+
# Use the utility function for subprocess execution
37+
return run_subprocess_with_logging(
38+
command=command, tool_name="clang-format", verbose=verbose, dry_run=dry_run
39+
)
8940

9041

9142
def main() -> int:

cpp_linter_hooks/clang_tidy.py

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from argparse import ArgumentParser
44
from typing import Tuple
55

6-
from .util import ensure_installed, DEFAULT_CLANG_VERSION
6+
from .util import (
7+
ensure_installed,
8+
DEFAULT_CLANG_VERSION,
9+
debug_print,
10+
run_subprocess_with_logging,
11+
)
712

813

914
parser = ArgumentParser()
@@ -21,57 +26,14 @@ def run_clang_tidy(args=None) -> Tuple[int, str]:
2126

2227
verbose = hook_args.verbose
2328

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-
29-
retval = 0
30-
output = ""
31-
stderr_output = ""
32-
33-
try:
34-
sp = subprocess.run(
35-
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8"
36-
)
37-
retval = sp.returncode
38-
output = sp.stdout
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:
57-
retval = 1
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:
67-
retval = 1
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)
29+
# Log initial debug information
30+
debug_print(f"clang-tidy version: {hook_args.version}", verbose)
31+
debug_print(f"clang-tidy executable: {path}", verbose)
7332

74-
return retval, error_msg
33+
# Use the utility function for subprocess execution
34+
return run_subprocess_with_logging(
35+
command=command, tool_name="clang-tidy", verbose=verbose, dry_run=False
36+
)
7537

7638

7739
def main() -> int:

cpp_linter_hooks/util.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import sys
22
from pathlib import Path
33
import logging
4-
from typing import Optional
4+
import subprocess
5+
from typing import Optional, Tuple, List
56

67
from clang_tools.util import Version
78
from clang_tools.install import is_installed as _is_installed, install_tool
@@ -13,6 +14,80 @@
1314
DEFAULT_CLANG_VERSION = "18" # Default version for clang tools, can be overridden
1415

1516

17+
def debug_print(message: str, verbose: bool = False) -> None:
18+
"""Print debug message to stderr if verbose mode is enabled."""
19+
if verbose:
20+
print(f"[DEBUG] {message}", file=sys.stderr)
21+
22+
23+
def run_subprocess_with_logging(
24+
command: List[str], tool_name: str, verbose: bool = False, dry_run: bool = False
25+
) -> Tuple[int, str]:
26+
"""
27+
Run subprocess with comprehensive logging and error handling.
28+
29+
Args:
30+
command: Command list to execute
31+
tool_name: Name of the tool (for logging)
32+
verbose: Enable verbose debug output
33+
dry_run: Whether this is a dry run (affects return code for clang-format)
34+
35+
Returns:
36+
Tuple of (return_code, combined_output)
37+
"""
38+
debug_print(f"{tool_name} command: {' '.join(command)}", verbose)
39+
40+
retval = 0
41+
output = ""
42+
stderr_output = ""
43+
44+
try:
45+
sp = subprocess.run(
46+
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8"
47+
)
48+
49+
output = sp.stdout
50+
stderr_output = sp.stderr
51+
retval = sp.returncode
52+
53+
if dry_run and tool_name == "clang-format":
54+
debug_print("Dry-run mode detected", verbose)
55+
debug_print(f"Exit code: {retval} (converted to -1 for dry-run)", verbose)
56+
retval = -1 # Special handling for clang-format dry-run
57+
else:
58+
debug_print(f"Exit code: {retval}", verbose)
59+
60+
# Log outputs if verbose
61+
if verbose and (output or stderr_output):
62+
debug_print(f"stdout: {repr(output)}", verbose)
63+
debug_print(f"stderr: {repr(stderr_output)}", verbose)
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+
# Special handling for clang-tidy warnings/errors
75+
if tool_name == "clang-tidy" and (
76+
"warning:" in combined_output or "error:" in combined_output
77+
):
78+
if retval == 0: # Only override if it was successful
79+
retval = 1
80+
debug_print("Found warnings/errors, setting exit code to 1", verbose)
81+
82+
return retval, combined_output
83+
84+
except FileNotFoundError as error:
85+
error_msg = f"{tool_name} executable not found: {error}"
86+
debug_print(f"FileNotFoundError: {error}", verbose)
87+
debug_print(f"Command attempted: {' '.join(command)}", verbose)
88+
return 1, error_msg
89+
90+
1691
def is_installed(tool_name: str, version: str) -> Optional[Path]:
1792
"""Check if tool is installed.
1893

tests/test_clang_format.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ def test_verbose_output(tmp_path, capsys):
9090

9191
with patch("cpp_linter_hooks.clang_format.ensure_installed") as mock_ensure:
9292
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="")
93+
with patch(
94+
"cpp_linter_hooks.clang_format.run_subprocess_with_logging"
95+
) as mock_run:
96+
mock_run.return_value = (0, "")
9597

9698
# Test verbose mode
9799
retval, output = run_clang_format(
@@ -100,10 +102,14 @@ def test_verbose_output(tmp_path, capsys):
100102

101103
# Check that debug messages were printed to stderr
102104
captured = capsys.readouterr()
103-
assert "[DEBUG] clang-format command:" in captured.err
104105
assert "[DEBUG] clang-format version:" in captured.err
105106
assert "[DEBUG] clang-format executable:" in captured.err
106-
assert "[DEBUG] Exit code:" in captured.err
107+
108+
# Verify that run_subprocess_with_logging was called correctly
109+
mock_run.assert_called_once()
110+
call_args = mock_run.call_args
111+
assert call_args[1]["tool_name"] == "clang-format"
112+
assert call_args[1]["verbose"] == True
107113

108114

109115
def test_verbose_with_error(tmp_path, capsys):
@@ -112,10 +118,10 @@ def test_verbose_with_error(tmp_path, capsys):
112118

113119
with patch("cpp_linter_hooks.clang_format.ensure_installed") as mock_ensure:
114120
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-
)
121+
with patch(
122+
"cpp_linter_hooks.clang_format.run_subprocess_with_logging"
123+
) as mock_run:
124+
mock_run.return_value = (1, "error output\nerror in stderr")
119125

120126
# Test verbose mode with error
121127
retval, output = run_clang_format(
@@ -127,11 +133,11 @@ def test_verbose_with_error(tmp_path, capsys):
127133
assert "error output" in output
128134
assert "error in stderr" in output
129135

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
136+
# Verify that run_subprocess_with_logging was called correctly
137+
mock_run.assert_called_once()
138+
call_args = mock_run.call_args
139+
assert call_args[1]["tool_name"] == "clang-format"
140+
assert call_args[1]["verbose"] == True
135141

136142

137143
def test_verbose_dry_run(tmp_path, capsys):
@@ -141,10 +147,10 @@ def test_verbose_dry_run(tmp_path, capsys):
141147

142148
with patch("cpp_linter_hooks.clang_format.ensure_installed") as mock_ensure:
143149
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-
)
150+
with patch(
151+
"cpp_linter_hooks.clang_format.run_subprocess_with_logging"
152+
) as mock_run:
153+
mock_run.return_value = (-1, "dry run output")
148154

149155
# Test verbose dry-run mode
150156
retval, output = run_clang_format(
@@ -155,7 +161,9 @@ def test_verbose_dry_run(tmp_path, capsys):
155161
assert retval == -1
156162
assert "dry run output" in output
157163

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
164+
# Verify that run_subprocess_with_logging was called correctly
165+
mock_run.assert_called_once()
166+
call_args = mock_run.call_args
167+
assert call_args[1]["tool_name"] == "clang-format"
168+
assert call_args[1]["verbose"] == True
169+
assert call_args[1]["dry_run"] == True

0 commit comments

Comments
 (0)