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
« 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."""
3from __future__ import annotations
5from pathlib import Path
6from typing import TYPE_CHECKING
7from unittest.mock import patch
9import pytest
10from assertpy import assert_that
12from lintro.plugins.base import DEFAULT_EXCLUDE_PATTERNS
14if TYPE_CHECKING:
15 from tests.unit.plugins.conftest import FakeToolPlugin
18# =============================================================================
19# BaseToolPlugin.set_options Tests
20# =============================================================================
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.
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)
45 assert_that(fake_tool_plugin.options.get("timeout")).is_equal_to(expected)
48def test_set_options_timeout_none(fake_tool_plugin: FakeToolPlugin) -> None:
49 """Verify timeout can be set to None.
51 Args:
52 fake_tool_plugin: The fake tool plugin instance to test.
53 """
54 fake_tool_plugin.set_options(timeout=None)
56 assert_that(fake_tool_plugin.options.get("timeout")).is_none()
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.
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")
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.
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)
83 # CLI patterns are added
84 for p in cli_patterns:
85 assert_that(fake_tool_plugin.exclude_patterns).contains(p)
87 # Existing default patterns are preserved
88 for p in original_patterns:
89 assert_that(fake_tool_plugin.exclude_patterns).contains(p)
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.
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])
104 assert_that(fake_tool_plugin.exclude_patterns).is_length(original_count)
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.
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.
116 Args:
117 tmp_path: Temporary directory path for testing.
118 """
119 from tests.unit.plugins.conftest import FakeToolPlugin
121 ignore_file = tmp_path / ".lintro-ignore"
122 ignore_file.write_text("test_samples/\ncustom_dir\n")
124 with patch(
125 "lintro.plugins.file_discovery.find_lintro_ignore",
126 return_value=ignore_file,
127 ):
128 plugin = FakeToolPlugin()
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)
134 # CLI patterns are present
135 for p in cli_excludes:
136 assert_that(plugin.exclude_patterns).contains(p)
138 # .lintro-ignore patterns are preserved
139 assert_that(plugin.exclude_patterns).contains("test_samples/")
140 assert_that(plugin.exclude_patterns).contains("custom_dir")
142 # Default patterns are preserved
143 for p in DEFAULT_EXCLUDE_PATTERNS:
144 assert_that(plugin.exclude_patterns).contains(p)
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.
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")
159def test_set_options_include_venv_valid(fake_tool_plugin: FakeToolPlugin) -> None:
160 """Verify valid boolean include_venv is accepted.
162 Args:
163 fake_tool_plugin: The fake tool plugin instance to test.
164 """
165 fake_tool_plugin.set_options(include_venv=True)
167 assert_that(fake_tool_plugin.include_venv).is_true()
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.
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")
182# =============================================================================
183# BaseToolPlugin._setup_defaults Tests
184# =============================================================================
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.
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()
198 assert_that(fake_tool_plugin.exclude_patterns).is_not_empty()
201def test_setup_defaults_adds_lintro_ignore_patterns(tmp_path: Path) -> None:
202 """Verify patterns from .lintro-ignore file are added to exclude_patterns.
204 Args:
205 tmp_path: Temporary directory path for testing.
206 """
207 from tests.unit.plugins.conftest import FakeToolPlugin
209 ignore_file = tmp_path / ".lintro-ignore"
210 ignore_file.write_text("custom_pattern\n# comment\n\nother_pattern\n")
212 with patch(
213 "lintro.plugins.file_discovery.find_lintro_ignore",
214 return_value=ignore_file,
215 ):
216 plugin = FakeToolPlugin()
218 assert_that("custom_pattern" in plugin.exclude_patterns).is_true()
219 assert_that("other_pattern" in plugin.exclude_patterns).is_true()
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
226 with patch(
227 "lintro.plugins.file_discovery.find_lintro_ignore",
228 side_effect=PermissionError("Access denied"),
229 ):
230 plugin = FakeToolPlugin()
232 # Should not raise, just log debug
233 assert_that(plugin.exclude_patterns).is_not_empty()
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.
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)