Coverage for tests / unit / tools / ruff / check / test_error_handling.py: 100%
33 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Tests for error handling in execute_ruff_check."""
3from __future__ import annotations
5import subprocess
6from unittest.mock import MagicMock, patch
8from assertpy import assert_that
10from lintro.models.core.tool_result import ToolResult
11from lintro.parsers.ruff.ruff_issue import RuffIssue
12from lintro.tools.implementations.ruff.check import execute_ruff_check
15def test_execute_ruff_check_handles_timeout(
16 mock_ruff_tool: MagicMock,
17) -> None:
18 """Handle subprocess timeout gracefully.
20 Args:
21 mock_ruff_tool: Mock RuffTool instance for testing.
22 """
23 with (
24 patch(
25 "lintro.tools.implementations.ruff.check.walk_files_with_excludes",
26 return_value=["test.py"],
27 ),
28 patch(
29 "lintro.tools.implementations.ruff.check.run_subprocess_with_timeout",
30 side_effect=subprocess.TimeoutExpired(cmd=["ruff"], timeout=30),
31 ),
32 ):
33 result = execute_ruff_check(mock_ruff_tool, ["/test/project"])
35 assert_that(result.success).is_false()
36 assert_that(result.output).contains("timed out")
37 assert_that(result.issues_count).is_equal_to(1)
40def test_execute_ruff_check_handles_format_timeout(
41 mock_ruff_tool: MagicMock,
42) -> None:
43 """Handle format check timeout gracefully.
45 Args:
46 mock_ruff_tool: Mock RuffTool instance for testing.
47 """
48 mock_ruff_tool.options["format_check"] = True
49 lint_issues = [
50 RuffIssue(file="test.py", line=1, column=1, code="F401", message="unused"),
51 ]
53 with (
54 patch(
55 "lintro.tools.implementations.ruff.check.walk_files_with_excludes",
56 return_value=["test.py"],
57 ),
58 patch(
59 "lintro.tools.implementations.ruff.check.run_subprocess_with_timeout",
60 ) as mock_subprocess,
61 patch(
62 "lintro.tools.implementations.ruff.check.parse_ruff_output",
63 return_value=lint_issues,
64 ),
65 ):
66 # First call succeeds (lint), second call times out (format)
67 mock_subprocess.side_effect = [
68 (False, "[]"),
69 subprocess.TimeoutExpired(cmd=["ruff"], timeout=30),
70 ]
72 result = execute_ruff_check(mock_ruff_tool, ["/test/project"])
74 assert_that(result.success).is_false()
75 assert_that(result.output).contains("timed out")
76 # Should include lint issues count plus timeout issue
77 assert_that(result.issues_count).is_greater_than_or_equal_to(1)
80def test_execute_ruff_check_subprocess_failure_respected(
81 mock_ruff_tool: MagicMock,
82) -> None:
83 """Return failure when subprocess fails even if no issues are parsed.
85 This is a regression test for a bug where success was determined only by
86 issue count, ignoring the subprocess exit code. If ruff returns non-zero
87 but produces no parseable output (e.g., internal error), the result should
88 still be failure.
90 Args:
91 mock_ruff_tool: Mock RuffTool instance for testing.
92 """
93 with (
94 patch(
95 "lintro.tools.implementations.ruff.check.walk_files_with_excludes",
96 return_value=["test.py"],
97 ),
98 patch(
99 "lintro.tools.implementations.ruff.check.run_subprocess_with_timeout",
100 # Subprocess fails (exit code != 0) but produces empty/no output
101 return_value=(False, "[]"),
102 ),
103 patch(
104 "lintro.tools.implementations.ruff.check.parse_ruff_output",
105 # No issues parsed from output
106 return_value=[],
107 ),
108 ):
109 result = execute_ruff_check(mock_ruff_tool, ["/test/project"])
111 # Even though no issues were parsed, subprocess failure means overall failure
112 assert_that(result.success).is_false()
113 assert_that(result.issues_count).is_equal_to(0)
116def test_execute_ruff_check_version_check_failure(
117 mock_ruff_tool: MagicMock,
118) -> None:
119 """Return early when version check fails.
121 Args:
122 mock_ruff_tool: Mock RuffTool instance for testing.
123 """
124 version_error_result = ToolResult(
125 name="ruff",
126 success=True,
127 output="Skipping ruff: version too old",
128 issues_count=0,
129 )
130 mock_ruff_tool._verify_tool_version.return_value = version_error_result
132 result = execute_ruff_check(mock_ruff_tool, ["/test/project"])
134 assert_that(result.output).is_equal_to("Skipping ruff: version too old")
135 assert_that(result.issues_count).is_equal_to(0)