Coverage for tests / unit / utils / test_path_filtering.py: 100%
101 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 path_filtering module."""
3from __future__ import annotations
5from pathlib import Path
6from unittest.mock import patch
8import pytest
9from assertpy import assert_that
11from lintro.utils.path_filtering import (
12 _is_venv_directory,
13 should_exclude_path,
14 walk_files_with_excludes,
15)
17# =============================================================================
18# Tests for should_exclude_path
19# =============================================================================
22def test_should_exclude_path_empty_patterns() -> None:
23 """Return False for empty patterns list."""
24 result = should_exclude_path("src/main.py", [])
25 assert_that(result).is_false()
28def test_should_exclude_path_simple_glob_match() -> None:
29 """Match simple glob pattern."""
30 result = should_exclude_path("test.pyc", ["*.pyc"])
31 assert_that(result).is_true()
34def test_should_exclude_path_no_match() -> None:
35 """Return False when no pattern matches."""
36 result = should_exclude_path("src/main.py", ["*.pyc", "*.log"])
37 assert_that(result).is_false()
40def test_should_exclude_path_directory_pattern_with_slash() -> None:
41 """Match directory pattern ending with /*."""
42 result = should_exclude_path("test_samples/file.py", ["test_samples/*"])
43 assert_that(result).is_true()
46def test_should_exclude_path_directory_pattern_nested() -> None:
47 """Match nested path under directory pattern."""
48 result = should_exclude_path(
49 "project/test_samples/subdir/file.py",
50 ["test_samples/*"],
51 )
52 assert_that(result).is_true()
55def test_should_exclude_path_simple_directory_pattern() -> None:
56 """Match simple directory name without wildcards."""
57 result = should_exclude_path("project/build/output.js", ["build"])
58 assert_that(result).is_true()
61def test_should_exclude_path_simple_directory_not_in_path() -> None:
62 """Don't match when directory not in path."""
63 result = should_exclude_path("src/main.py", ["build"])
64 assert_that(result).is_false()
67def test_should_exclude_path_empty_pattern_ignored() -> None:
68 """Ignore empty patterns in list."""
69 result = should_exclude_path("src/main.py", ["", " ", "*.pyc"])
70 assert_that(result).is_false()
73def test_should_exclude_path_path_part_match() -> None:
74 """Match pattern against path parts."""
75 result = should_exclude_path("src/__pycache__/module.pyc", ["__pycache__"])
76 assert_that(result).is_true()
79def test_should_exclude_path_normalization_error() -> None:
80 """Handle path normalization errors gracefully."""
81 with patch("os.path.abspath", side_effect=ValueError("Invalid path")):
82 result = should_exclude_path("some/path", ["*.py"])
83 # Should still work with original path and not match since path doesn't end in .py
84 assert_that(result).is_false()
87# =============================================================================
88# Tests for walk_files_with_excludes
89# =============================================================================
92@pytest.fixture
93def src_dir_with_files(tmp_path: Path) -> Path:
94 """Create a source directory with Python and text files.
96 Args:
97 tmp_path: Temporary directory path.
99 Returns:
100 Path to the created source directory.
101 """
102 src_dir = tmp_path / "src"
103 src_dir.mkdir()
104 (src_dir / "main.py").write_text("print('main')")
105 (src_dir / "utils.py").write_text("print('utils')")
106 (src_dir / "readme.txt").write_text("readme")
107 return src_dir
110@pytest.fixture
111def project_with_venv(tmp_path: Path) -> Path:
112 """Create a project directory with main.py and .venv directory.
114 Args:
115 tmp_path: Temporary directory path.
117 Returns:
118 Path to the created project directory.
119 """
120 src_dir = tmp_path / "project"
121 src_dir.mkdir()
122 (src_dir / "main.py").write_text("print('main')")
123 venv_dir = src_dir / ".venv"
124 venv_dir.mkdir()
125 (venv_dir / "lib.py").write_text("lib")
126 return src_dir
129def test_walk_files_single_file_match(tmp_path: Path) -> None:
130 """Include single file matching pattern.
132 Args:
133 tmp_path: Temporary directory path for test files.
134 """
135 test_file = tmp_path / "main.py"
136 test_file.write_text("print('hello')")
138 result = walk_files_with_excludes(
139 paths=[str(test_file)],
140 file_patterns=["*.py"],
141 exclude_patterns=[],
142 )
143 assert_that(result).is_length(1)
144 assert_that(result[0]).ends_with("main.py")
147def test_walk_files_single_file_no_match(tmp_path: Path) -> None:
148 """Exclude single file not matching pattern.
150 Args:
151 tmp_path: Temporary directory path for test files.
152 """
153 test_file = tmp_path / "main.txt"
154 test_file.write_text("hello")
156 result = walk_files_with_excludes(
157 paths=[str(test_file)],
158 file_patterns=["*.py"],
159 exclude_patterns=[],
160 )
161 assert_that(result).is_empty()
164def test_walk_files_directory_walk(src_dir_with_files: Path) -> None:
165 """Walk directory and find matching files.
167 Args:
168 src_dir_with_files: Directory containing test files.
169 """
170 result = walk_files_with_excludes(
171 paths=[str(src_dir_with_files)],
172 file_patterns=["*.py"],
173 exclude_patterns=[],
174 )
175 assert_that(result).is_length(2)
178def test_walk_files_excludes_patterns(tmp_path: Path) -> None:
179 """Exclude files matching exclude patterns.
181 Args:
182 tmp_path: Temporary directory path for test files.
183 """
184 src_dir = tmp_path / "src"
185 src_dir.mkdir()
186 (src_dir / "main.py").write_text("print('main')")
187 cache_dir = src_dir / "__pycache__"
188 cache_dir.mkdir()
189 (cache_dir / "main.pyc").write_text("bytecode")
191 result = walk_files_with_excludes(
192 paths=[str(src_dir)],
193 file_patterns=["*.py", "*.pyc"],
194 exclude_patterns=["__pycache__"],
195 )
196 assert_that(result).is_length(1)
197 assert_that(result[0]).ends_with("main.py")
200def test_walk_files_excludes_venv_by_default(project_with_venv: Path) -> None:
201 """Exclude virtual environment directories by default.
203 Args:
204 project_with_venv: Project directory with virtual environment.
205 """
206 result = walk_files_with_excludes(
207 paths=[str(project_with_venv)],
208 file_patterns=["*.py"],
209 exclude_patterns=[],
210 include_venv=False,
211 )
212 assert_that(result).is_length(1)
215def test_walk_files_includes_venv_when_requested(project_with_venv: Path) -> None:
216 """Include virtual environment when include_venv=True.
218 Args:
219 project_with_venv: Project directory with virtual environment.
220 """
221 result = walk_files_with_excludes(
222 paths=[str(project_with_venv)],
223 file_patterns=["*.py"],
224 exclude_patterns=[],
225 include_venv=True,
226 )
227 assert_that(result).is_length(2)
230def test_walk_files_returns_sorted_results(tmp_path: Path) -> None:
231 """Return sorted file paths.
233 Args:
234 tmp_path: Temporary directory path for test files.
235 """
236 src_dir = tmp_path / "src"
237 src_dir.mkdir()
238 (src_dir / "z_file.py").write_text("")
239 (src_dir / "a_file.py").write_text("")
240 (src_dir / "m_file.py").write_text("")
242 result = walk_files_with_excludes(
243 paths=[str(src_dir)],
244 file_patterns=["*.py"],
245 exclude_patterns=[],
246 )
247 assert_that(result).is_equal_to(sorted(result))
250def test_walk_files_single_file_excluded(tmp_path: Path) -> None:
251 """Exclude single file matching exclude pattern.
253 Args:
254 tmp_path: Temporary directory path for test files.
255 """
256 test_file = tmp_path / "test.pyc"
257 test_file.write_text("bytecode")
259 result = walk_files_with_excludes(
260 paths=[str(test_file)],
261 file_patterns=["*.pyc"],
262 exclude_patterns=["*.pyc"],
263 )
264 assert_that(result).is_empty()
267# =============================================================================
268# Tests for _is_venv_directory
269# =============================================================================
272@pytest.mark.parametrize(
273 ("dirname", "expected"),
274 [
275 (".venv", True),
276 ("venv", True),
277 ("env", True),
278 ("src", False),
279 ("environment", False),
280 ],
281 ids=[
282 "dot_venv",
283 "venv_without_dot",
284 "env_directory",
285 "regular_directory",
286 "similar_name",
287 ],
288)
289def test_is_venv_directory(dirname: str, expected: bool) -> None:
290 """Test virtual environment directory detection.
292 Args:
293 dirname: Description of dirname (str).
294 expected: Description of expected (bool).
295 """
296 result = _is_venv_directory(dirname)
297 assert_that(result).is_equal_to(expected)