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

1"""Unit tests for path_filtering module.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from unittest.mock import patch 

7 

8import pytest 

9from assertpy import assert_that 

10 

11from lintro.utils.path_filtering import ( 

12 _is_venv_directory, 

13 should_exclude_path, 

14 walk_files_with_excludes, 

15) 

16 

17# ============================================================================= 

18# Tests for should_exclude_path 

19# ============================================================================= 

20 

21 

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() 

26 

27 

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() 

32 

33 

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() 

38 

39 

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() 

44 

45 

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() 

53 

54 

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() 

59 

60 

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() 

65 

66 

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() 

71 

72 

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() 

77 

78 

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() 

85 

86 

87# ============================================================================= 

88# Tests for walk_files_with_excludes 

89# ============================================================================= 

90 

91 

92@pytest.fixture 

93def src_dir_with_files(tmp_path: Path) -> Path: 

94 """Create a source directory with Python and text files. 

95 

96 Args: 

97 tmp_path: Temporary directory path. 

98 

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 

108 

109 

110@pytest.fixture 

111def project_with_venv(tmp_path: Path) -> Path: 

112 """Create a project directory with main.py and .venv directory. 

113 

114 Args: 

115 tmp_path: Temporary directory path. 

116 

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 

127 

128 

129def test_walk_files_single_file_match(tmp_path: Path) -> None: 

130 """Include single file matching pattern. 

131 

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

137 

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

145 

146 

147def test_walk_files_single_file_no_match(tmp_path: Path) -> None: 

148 """Exclude single file not matching pattern. 

149 

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

155 

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() 

162 

163 

164def test_walk_files_directory_walk(src_dir_with_files: Path) -> None: 

165 """Walk directory and find matching files. 

166 

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) 

176 

177 

178def test_walk_files_excludes_patterns(tmp_path: Path) -> None: 

179 """Exclude files matching exclude patterns. 

180 

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

190 

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

198 

199 

200def test_walk_files_excludes_venv_by_default(project_with_venv: Path) -> None: 

201 """Exclude virtual environment directories by default. 

202 

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) 

213 

214 

215def test_walk_files_includes_venv_when_requested(project_with_venv: Path) -> None: 

216 """Include virtual environment when include_venv=True. 

217 

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) 

228 

229 

230def test_walk_files_returns_sorted_results(tmp_path: Path) -> None: 

231 """Return sorted file paths. 

232 

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

241 

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

248 

249 

250def test_walk_files_single_file_excluded(tmp_path: Path) -> None: 

251 """Exclude single file matching exclude pattern. 

252 

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

258 

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() 

265 

266 

267# ============================================================================= 

268# Tests for _is_venv_directory 

269# ============================================================================= 

270 

271 

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. 

291 

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)