Coverage for tests\test_lmcat.py: 94%
192 statements
« prev ^ index » next coverage.py v7.6.10, created at 2024-12-31 20:31 -0700
« prev ^ index » next coverage.py v7.6.10, created at 2024-12-31 20:31 -0700
1import sys
2import os
3import shutil
4import subprocess
5from pathlib import Path
7from lmcat.lmcat import (
8 LMCatConfig,
9 IgnoreHandler,
10 walk_dir,
11 walk_and_collect,
12)
14# We will store all test directories under this path:
15TEMP_PATH: Path = Path("tests/_temp")
18def ensure_clean_dir(dirpath: Path) -> None:
19 """Remove `dirpath` if it exists, then re-create it."""
20 if dirpath.is_dir():
21 shutil.rmtree(dirpath)
22 dirpath.mkdir(parents=True, exist_ok=True)
25# Test LMCatConfig - these tests remain largely unchanged
26def test_lmcat_config_defaults():
27 config = LMCatConfig()
28 assert config.tree_divider == "│ "
29 assert config.indent == " "
30 assert config.file_divider == "├── "
31 assert config.content_divider == "``````"
34def test_lmcat_config_load_partial():
35 data = {"tree_divider": "|---"}
36 config = LMCatConfig.load(data)
37 assert config.tree_divider == "|---"
38 assert config.indent == " "
39 assert config.file_divider == "├── "
40 assert config.content_divider == "``````"
43def test_lmcat_config_load_all():
44 data = {
45 "tree_divider": "XX",
46 "indent": "YY",
47 "file_divider": "ZZ",
48 "content_divider": "@@@",
49 }
50 config = LMCatConfig.load(data)
51 assert config.tree_divider == "XX"
52 assert config.indent == "YY"
53 assert config.file_divider == "ZZ"
54 assert config.content_divider == "@@@"
57# Test IgnoreHandler class
58def test_ignore_handler_init():
59 """Test basic initialization of IgnoreHandler"""
60 test_dir = TEMP_PATH / "test_ignore_handler_init"
61 ensure_clean_dir(test_dir)
62 config = LMCatConfig()
63 handler = IgnoreHandler(test_dir, config)
64 assert handler.root_dir == test_dir
65 assert handler.config == config
68def test_ignore_handler_basic_ignore():
69 """Test basic ignore patterns"""
70 test_dir = TEMP_PATH / "test_ignore_handler_basic_ignore"
71 ensure_clean_dir(test_dir)
73 # Create test files
74 (test_dir / "file1.txt").write_text("content1")
75 (test_dir / "file2.log").write_text("content2")
76 (test_dir / ".lmignore").write_text("*.log\n")
78 config = LMCatConfig()
79 handler = IgnoreHandler(test_dir, config)
81 # Test matches
82 assert not handler.is_ignored(test_dir / "file1.txt")
83 assert handler.is_ignored(test_dir / "file2.log")
86def test_ignore_handler_directory_patterns():
87 """Test directory ignore patterns"""
88 test_dir = TEMP_PATH / "test_ignore_handler_directory"
89 ensure_clean_dir(test_dir)
91 # Create test structure
92 (test_dir / "subdir1").mkdir()
93 (test_dir / "subdir2").mkdir()
94 (test_dir / "subdir1/file1.txt").write_text("content1")
95 (test_dir / "subdir2/file2.txt").write_text("content2")
96 (test_dir / ".lmignore").write_text("subdir2/\n")
98 config = LMCatConfig()
99 handler = IgnoreHandler(test_dir, config)
101 # Test matches
102 assert not handler.is_ignored(test_dir / "subdir1")
103 assert handler.is_ignored(test_dir / "subdir2")
104 assert not handler.is_ignored(test_dir / "subdir1/file1.txt")
105 assert handler.is_ignored(test_dir / "subdir2/file2.txt")
108def test_ignore_handler_negation():
109 """Test negation patterns"""
110 test_dir = TEMP_PATH / "test_ignore_handler_negation"
111 ensure_clean_dir(test_dir)
113 # Create test files
114 (test_dir / "file1.txt").write_text("content1")
115 (test_dir / "file2.txt").write_text("content2")
116 (test_dir / ".gitignore").write_text("*.txt\n")
117 (test_dir / ".lmignore").write_text("!file2.txt\n")
119 config = LMCatConfig()
120 handler = IgnoreHandler(test_dir, config)
122 # Test matches - file2.txt should be unignored by the negation
123 assert handler.is_ignored(test_dir / "file1.txt")
124 assert not handler.is_ignored(test_dir / "file2.txt")
127def test_ignore_handler_nested_ignore_files():
128 """Test nested ignore files with different patterns"""
129 test_dir = TEMP_PATH / "test_ignore_handler_nested"
130 ensure_clean_dir(test_dir)
132 # Create test structure
133 (test_dir / "subdir").mkdir()
134 (test_dir / "subdir/file1.txt").write_text("content1")
135 (test_dir / "subdir/file2.log").write_text("content2")
137 # Root ignores .txt, subdir ignores .log
138 (test_dir / ".lmignore").write_text("*.txt\n")
139 (test_dir / "subdir/.lmignore").write_text("*.log\n")
141 config = LMCatConfig()
142 handler = IgnoreHandler(test_dir, config)
144 # Test both patterns are active
145 assert handler.is_ignored(test_dir / "subdir/file1.txt")
146 assert handler.is_ignored(test_dir / "subdir/file2.log")
149def test_ignore_handler_gitignore_disabled():
150 """Test that gitignore patterns are ignored when disabled"""
151 test_dir = TEMP_PATH / "test_ignore_handler_gitignore_disabled"
152 ensure_clean_dir(test_dir)
154 # Create test files
155 (test_dir / "file1.txt").write_text("content1")
156 (test_dir / ".gitignore").write_text("*.txt\n")
158 config = LMCatConfig(include_gitignore=False)
159 handler = IgnoreHandler(test_dir, config)
161 # File should not be ignored since gitignore is disabled
162 assert not handler.is_ignored(test_dir / "file1.txt")
165# Test walking functions with new IgnoreHandler
166def test_walk_dir_basic():
167 """Test basic directory walking with no ignore patterns"""
168 test_dir = TEMP_PATH / "test_walk_dir_basic"
169 ensure_clean_dir(test_dir)
171 # Create test structure
172 (test_dir / "subdir1").mkdir()
173 (test_dir / "subdir2").mkdir()
174 (test_dir / "subdir1/file1.txt").write_text("content1")
175 (test_dir / "subdir2/file2.txt").write_text("content2")
176 (test_dir / "file3.txt").write_text("content3")
178 config = LMCatConfig()
179 handler = IgnoreHandler(test_dir, config)
181 tree_output, files = walk_dir(test_dir, handler, config)
182 joined_output = "\n".join(tree_output)
184 # Check output contains all entries
185 assert "subdir1" in joined_output
186 assert "subdir2" in joined_output
187 assert "file1.txt" in joined_output
188 assert "file2.txt" in joined_output
189 assert "file3.txt" in joined_output
191 # Check collected files
192 assert len(files) == 3
193 file_names = {f.name for f in files}
194 assert file_names == {"file1.txt", "file2.txt", "file3.txt"}
197def test_walk_dir_with_ignore():
198 """Test directory walking with ignore patterns"""
199 test_dir = TEMP_PATH / "test_walk_dir_with_ignore"
200 ensure_clean_dir(test_dir)
202 # Create test structure
203 (test_dir / "subdir1").mkdir()
204 (test_dir / "subdir2").mkdir()
205 (test_dir / "subdir1/file1.txt").write_text("content1")
206 (test_dir / "subdir2/file2.log").write_text("content2")
207 (test_dir / "file3.txt").write_text("content3")
209 # Ignore .log files
210 (test_dir / ".lmignore").write_text("*.log\n")
212 config = LMCatConfig()
213 handler = IgnoreHandler(test_dir, config)
215 tree_output, files = walk_dir(test_dir, handler, config)
216 joined_output = "\n".join(tree_output)
218 # Check output excludes .log file
219 assert "file2.log" not in joined_output
220 assert "file1.txt" in joined_output
221 assert "file3.txt" in joined_output
223 # Check collected files
224 assert len(files) == 2
225 file_names = {f.name for f in files}
226 assert file_names == {"file1.txt", "file3.txt"}
229def test_walk_and_collect_complex():
230 """Test full directory walking with multiple ignore patterns"""
231 test_dir = TEMP_PATH / "test_walk_and_collect_complex"
232 ensure_clean_dir(test_dir)
234 # Create complex directory structure
235 (test_dir / "subdir1/nested").mkdir(parents=True)
236 (test_dir / "subdir2/nested").mkdir(parents=True)
237 (test_dir / "subdir1/file1.txt").write_text("content1")
238 (test_dir / "subdir1/nested/file2.log").write_text("content2")
239 (test_dir / "subdir2/file3.txt").write_text("content3")
240 (test_dir / "subdir2/nested/file4.log").write_text("content4")
242 # Root ignores .log files
243 (test_dir / ".lmignore").write_text("*.log\n")
244 # subdir2 ignores nested dir
245 (test_dir / "subdir2/.lmignore").write_text("nested/\n")
247 config = LMCatConfig()
248 tree_output, files = walk_and_collect(test_dir, config)
249 joined_output = "\n".join(tree_output)
251 # Check correct files are excluded
252 assert "file1.txt" in joined_output
253 assert "file2.log" not in joined_output
254 assert "file3.txt" in joined_output
255 assert "file4.log" not in joined_output
256 assert "nested" not in joined_output.split("\n")[-5:] # Check last few lines
258 # Check collected files
259 assert len(files) == 2
260 file_names = {f.name for f in files}
261 assert file_names == {"file1.txt", "file3.txt"}
264# Test CLI functionality
265def test_cli_output_file():
266 """Test writing output to a file"""
267 test_dir = TEMP_PATH / "test_cli_output_file"
268 ensure_clean_dir(test_dir)
270 # Create test files
271 (test_dir / "file1.txt").write_text("content1")
272 output_file = test_dir / "output.md"
274 original_cwd = os.getcwd()
275 try:
276 os.chdir(test_dir)
277 subprocess.run(
278 ["uv", "run", "python", "-m", "lmcat", "--output", str(output_file)],
279 check=True,
280 )
282 # Check output file exists and contains expected content
283 assert output_file.is_file()
284 content = output_file.read_text()
285 assert "# File Tree" in content
286 assert "file1.txt" in content
287 assert "content1" in content
288 except subprocess.CalledProcessError as e:
289 print(f"{e = }", file=sys.stderr)
290 print(e.stdout, file=sys.stderr)
291 print(e.stderr, file=sys.stderr)
292 raise e
293 finally:
294 os.chdir(original_cwd)
297def test_cli_tree_only():
298 """Test --tree-only option"""
299 test_dir = TEMP_PATH / "test_cli_tree_only"
300 ensure_clean_dir(test_dir)
302 # Create test file
303 (test_dir / "file1.txt").write_text("content1")
305 original_cwd = os.getcwd()
306 try:
307 os.chdir(test_dir)
308 result = subprocess.run(
309 ["uv", "run", "python", "-m", "lmcat", "--tree-only"],
310 capture_output=True,
311 text=True,
312 check=True,
313 )
315 # Check output has tree but not content
316 assert "# File Tree" in result.stdout
317 assert "file1.txt" in result.stdout
318 assert "# File Contents" not in result.stdout
319 assert "content1" not in result.stdout
320 except subprocess.CalledProcessError as e:
321 print(f"{e = }", file=sys.stderr)
322 print(e.stdout, file=sys.stderr)
323 print(e.stderr, file=sys.stderr)
324 raise e
325 finally:
326 os.chdir(original_cwd)