Coverage for tests / unit / plugins / base / test_options.py: 100%

72 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Unit tests for BaseToolPlugin set_options and _setup_defaults methods.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import TYPE_CHECKING 

7from unittest.mock import patch 

8 

9import pytest 

10from assertpy import assert_that 

11 

12from lintro.plugins.base import DEFAULT_EXCLUDE_PATTERNS 

13 

14if TYPE_CHECKING: 

15 from tests.unit.plugins.conftest import FakeToolPlugin 

16 

17 

18# ============================================================================= 

19# BaseToolPlugin.set_options Tests 

20# ============================================================================= 

21 

22 

23@pytest.mark.parametrize( 

24 ("timeout_value", "expected"), 

25 [ 

26 pytest.param(60, 60.0, id="integer_timeout"), 

27 pytest.param(45.5, 45.5, id="float_timeout"), 

28 pytest.param(0, 0.0, id="zero_timeout"), 

29 ], 

30) 

31def test_set_options_timeout_valid_values( 

32 fake_tool_plugin: FakeToolPlugin, 

33 timeout_value: int | float, 

34 expected: float, 

35) -> None: 

36 """Verify valid timeout values are accepted and stored correctly. 

37 

38 Args: 

39 fake_tool_plugin: The fake tool plugin instance to test. 

40 timeout_value: The timeout value to set. 

41 expected: The expected timeout value after setting. 

42 """ 

43 fake_tool_plugin.set_options(timeout=timeout_value) 

44 

45 assert_that(fake_tool_plugin.options.get("timeout")).is_equal_to(expected) 

46 

47 

48def test_set_options_timeout_none(fake_tool_plugin: FakeToolPlugin) -> None: 

49 """Verify timeout can be set to None. 

50 

51 Args: 

52 fake_tool_plugin: The fake tool plugin instance to test. 

53 """ 

54 fake_tool_plugin.set_options(timeout=None) 

55 

56 assert_that(fake_tool_plugin.options.get("timeout")).is_none() 

57 

58 

59def test_set_options_timeout_invalid_raises_value_error( 

60 fake_tool_plugin: FakeToolPlugin, 

61) -> None: 

62 """Verify invalid timeout type raises ValueError with descriptive message. 

63 

64 Args: 

65 fake_tool_plugin: The fake tool plugin instance to test. 

66 """ 

67 with pytest.raises(ValueError, match="Timeout must be a number"): 

68 fake_tool_plugin.set_options(timeout="invalid") 

69 

70 

71def test_set_options_exclude_patterns_merges_with_existing( 

72 fake_tool_plugin: FakeToolPlugin, 

73) -> None: 

74 """Verify CLI exclude patterns are merged with existing defaults. 

75 

76 Args: 

77 fake_tool_plugin: The fake tool plugin instance to test. 

78 """ 

79 original_patterns = list(fake_tool_plugin.exclude_patterns) 

80 cli_patterns = ["*.log", "*.tmp"] 

81 fake_tool_plugin.set_options(exclude_patterns=cli_patterns) 

82 

83 # CLI patterns are added 

84 for p in cli_patterns: 

85 assert_that(fake_tool_plugin.exclude_patterns).contains(p) 

86 

87 # Existing default patterns are preserved 

88 for p in original_patterns: 

89 assert_that(fake_tool_plugin.exclude_patterns).contains(p) 

90 

91 

92def test_set_options_exclude_patterns_does_not_duplicate( 

93 fake_tool_plugin: FakeToolPlugin, 

94) -> None: 

95 """Verify duplicate patterns are not added when merging. 

96 

97 Args: 

98 fake_tool_plugin: The fake tool plugin instance to test. 

99 """ 

100 pattern = DEFAULT_EXCLUDE_PATTERNS[0] 

101 original_count = len(fake_tool_plugin.exclude_patterns) 

102 fake_tool_plugin.set_options(exclude_patterns=[pattern]) 

103 

104 assert_that(fake_tool_plugin.exclude_patterns).is_length(original_count) 

105 

106 

107def test_set_options_exclude_patterns_preserves_lintro_ignore( 

108 tmp_path: Path, 

109) -> None: 

110 """Verify CLI --exclude merges with .lintro-ignore instead of replacing. 

111 

112 This is the exact scenario from issue #580: .lintro-ignore lists 

113 test_samples/ but CLI --exclude passes a different set of patterns. 

114 Both must be present in the final exclude list. 

115 

116 Args: 

117 tmp_path: Temporary directory path for testing. 

118 """ 

119 from tests.unit.plugins.conftest import FakeToolPlugin 

120 

121 ignore_file = tmp_path / ".lintro-ignore" 

122 ignore_file.write_text("test_samples/\ncustom_dir\n") 

123 

124 with patch( 

125 "lintro.plugins.file_discovery.find_lintro_ignore", 

126 return_value=ignore_file, 

127 ): 

128 plugin = FakeToolPlugin() 

129 

130 # Simulate CLI --exclude (same flow as tool_configuration.py) 

131 cli_excludes = [".pytest_cache", ".mypy_cache", "htmlcov"] 

132 plugin.set_options(exclude_patterns=cli_excludes) 

133 

134 # CLI patterns are present 

135 for p in cli_excludes: 

136 assert_that(plugin.exclude_patterns).contains(p) 

137 

138 # .lintro-ignore patterns are preserved 

139 assert_that(plugin.exclude_patterns).contains("test_samples/") 

140 assert_that(plugin.exclude_patterns).contains("custom_dir") 

141 

142 # Default patterns are preserved 

143 for p in DEFAULT_EXCLUDE_PATTERNS: 

144 assert_that(plugin.exclude_patterns).contains(p) 

145 

146 

147def test_set_options_exclude_patterns_invalid_raises_value_error( 

148 fake_tool_plugin: FakeToolPlugin, 

149) -> None: 

150 """Verify non-list exclude patterns raises ValueError. 

151 

152 Args: 

153 fake_tool_plugin: The fake tool plugin instance to test. 

154 """ 

155 with pytest.raises(ValueError, match="Exclude patterns must be a list"): 

156 fake_tool_plugin.set_options(exclude_patterns="*.log") 

157 

158 

159def test_set_options_include_venv_valid(fake_tool_plugin: FakeToolPlugin) -> None: 

160 """Verify valid boolean include_venv is accepted. 

161 

162 Args: 

163 fake_tool_plugin: The fake tool plugin instance to test. 

164 """ 

165 fake_tool_plugin.set_options(include_venv=True) 

166 

167 assert_that(fake_tool_plugin.include_venv).is_true() 

168 

169 

170def test_set_options_include_venv_invalid_raises_value_error( 

171 fake_tool_plugin: FakeToolPlugin, 

172) -> None: 

173 """Verify non-boolean include_venv raises ValueError. 

174 

175 Args: 

176 fake_tool_plugin: The fake tool plugin instance to test. 

177 """ 

178 with pytest.raises(ValueError, match="Include venv must be a boolean"): 

179 fake_tool_plugin.set_options(include_venv="yes") 

180 

181 

182# ============================================================================= 

183# BaseToolPlugin._setup_defaults Tests 

184# ============================================================================= 

185 

186 

187def test_setup_defaults_adds_default_exclude_patterns( 

188 fake_tool_plugin: FakeToolPlugin, 

189) -> None: 

190 """Verify default exclude patterns are added to plugin's exclude_patterns. 

191 

192 Args: 

193 fake_tool_plugin: The fake tool plugin instance to test. 

194 """ 

195 for pattern in DEFAULT_EXCLUDE_PATTERNS: 

196 assert_that(pattern in fake_tool_plugin.exclude_patterns).is_true() 

197 

198 assert_that(fake_tool_plugin.exclude_patterns).is_not_empty() 

199 

200 

201def test_setup_defaults_adds_lintro_ignore_patterns(tmp_path: Path) -> None: 

202 """Verify patterns from .lintro-ignore file are added to exclude_patterns. 

203 

204 Args: 

205 tmp_path: Temporary directory path for testing. 

206 """ 

207 from tests.unit.plugins.conftest import FakeToolPlugin 

208 

209 ignore_file = tmp_path / ".lintro-ignore" 

210 ignore_file.write_text("custom_pattern\n# comment\n\nother_pattern\n") 

211 

212 with patch( 

213 "lintro.plugins.file_discovery.find_lintro_ignore", 

214 return_value=ignore_file, 

215 ): 

216 plugin = FakeToolPlugin() 

217 

218 assert_that("custom_pattern" in plugin.exclude_patterns).is_true() 

219 assert_that("other_pattern" in plugin.exclude_patterns).is_true() 

220 

221 

222def test_setup_defaults_handles_lintro_ignore_read_error_gracefully() -> None: 

223 """Verify .lintro-ignore read errors are handled without raising.""" 

224 from tests.unit.plugins.conftest import FakeToolPlugin 

225 

226 with patch( 

227 "lintro.plugins.file_discovery.find_lintro_ignore", 

228 side_effect=PermissionError("Access denied"), 

229 ): 

230 plugin = FakeToolPlugin() 

231 

232 # Should not raise, just log debug 

233 assert_that(plugin.exclude_patterns).is_not_empty() 

234 

235 

236def test_setup_defaults_sets_default_timeout_from_definition( 

237 fake_tool_plugin: FakeToolPlugin, 

238) -> None: 

239 """Verify default timeout is set from tool definition. 

240 

241 Args: 

242 fake_tool_plugin: The fake tool plugin instance to test. 

243 """ 

244 assert_that(fake_tool_plugin.options.get("timeout")).is_equal_to(30)