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

1"""Tests for error handling in execute_ruff_check.""" 

2 

3from __future__ import annotations 

4 

5import subprocess 

6from unittest.mock import MagicMock, patch 

7 

8from assertpy import assert_that 

9 

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 

13 

14 

15def test_execute_ruff_check_handles_timeout( 

16 mock_ruff_tool: MagicMock, 

17) -> None: 

18 """Handle subprocess timeout gracefully. 

19 

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"]) 

34 

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) 

38 

39 

40def test_execute_ruff_check_handles_format_timeout( 

41 mock_ruff_tool: MagicMock, 

42) -> None: 

43 """Handle format check timeout gracefully. 

44 

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 ] 

52 

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 ] 

71 

72 result = execute_ruff_check(mock_ruff_tool, ["/test/project"]) 

73 

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) 

78 

79 

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. 

84 

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. 

89 

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"]) 

110 

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) 

114 

115 

116def test_execute_ruff_check_version_check_failure( 

117 mock_ruff_tool: MagicMock, 

118) -> None: 

119 """Return early when version check fails. 

120 

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 

131 

132 result = execute_ruff_check(mock_ruff_tool, ["/test/project"]) 

133 

134 assert_that(result.output).is_equal_to("Skipping ruff: version too old") 

135 assert_that(result.issues_count).is_equal_to(0)