|
1 | 1 | # tests/test_coverage_gaps.py |
2 | 2 | import sys |
3 | | -from pathlib import Path |
4 | 3 |
|
5 | 4 | import pytest |
6 | 5 |
|
@@ -316,6 +315,51 @@ def test_yaml_with_special_unicode_nel(tmp_path): |
316 | 315 | assert parsed is not None |
317 | 316 |
|
318 | 317 |
|
| 318 | +def test_yaml_with_unicode_line_separators(tmp_path): |
| 319 | + import yaml |
| 320 | + |
| 321 | + project = tmp_path / "project" |
| 322 | + project.mkdir() |
| 323 | + |
| 324 | + ls_char = "\u2028" |
| 325 | + ps_char = "\u2029" |
| 326 | + (project / "line_sep.txt").write_text(f"line1{ls_char}line2") |
| 327 | + (project / "para_sep.txt").write_text(f"para1{ps_char}para2") |
| 328 | + |
| 329 | + tree = map_directory(project) |
| 330 | + yaml_output = to_yaml(tree) |
| 331 | + |
| 332 | + parsed = yaml.safe_load(yaml_output) |
| 333 | + assert parsed is not None |
| 334 | + |
| 335 | + line_sep_node = find_node_by_path(parsed, ["line_sep.txt"]) |
| 336 | + para_sep_node = find_node_by_path(parsed, ["para_sep.txt"]) |
| 337 | + |
| 338 | + assert line_sep_node is not None |
| 339 | + assert ls_char in line_sep_node.get("content", "") |
| 340 | + assert para_sep_node is not None |
| 341 | + assert ps_char in para_sep_node.get("content", "") |
| 342 | + |
| 343 | + |
| 344 | +def test_yaml_literal_style_without_trailing_newline(tmp_path): |
| 345 | + project = tmp_path / "project" |
| 346 | + project.mkdir() |
| 347 | + |
| 348 | + (project / "no_newline.txt").write_text("content without newline") |
| 349 | + |
| 350 | + tree = map_directory(project) |
| 351 | + yaml_output = to_yaml(tree) |
| 352 | + |
| 353 | + assert "no_newline.txt" in yaml_output |
| 354 | + |
| 355 | + import yaml |
| 356 | + |
| 357 | + parsed = yaml.safe_load(yaml_output) |
| 358 | + node = find_node_by_path(parsed, ["no_newline.txt"]) |
| 359 | + assert node is not None |
| 360 | + assert "content without newline" in node.get("content", "") |
| 361 | + |
| 362 | + |
319 | 363 | def test_empty_directory_handling(tmp_path): |
320 | 364 | project = tmp_path / "project" |
321 | 365 | project.mkdir() |
@@ -363,3 +407,76 @@ def test_all_verbosity_levels(tmp_path): |
363 | 407 | finally: |
364 | 408 | root_logger.setLevel(original_level) |
365 | 409 | root_logger.handlers = original_handlers |
| 410 | + |
| 411 | + |
| 412 | +def test_binary_detection_at_exact_boundary(tmp_path): |
| 413 | + from treemapper.tree import BINARY_DETECTION_SAMPLE_SIZE |
| 414 | + |
| 415 | + project = tmp_path / "project" |
| 416 | + project.mkdir() |
| 417 | + |
| 418 | + null_at_boundary = project / "null_at_boundary.bin" |
| 419 | + content = b"x" * (BINARY_DETECTION_SAMPLE_SIZE - 1) + b"\x00" + b"y" * 100 |
| 420 | + null_at_boundary.write_bytes(content) |
| 421 | + |
| 422 | + null_after_boundary = project / "null_after_boundary.txt" |
| 423 | + content2 = b"x" * BINARY_DETECTION_SAMPLE_SIZE + b"\x00" + b"y" * 100 |
| 424 | + null_after_boundary.write_bytes(content2) |
| 425 | + |
| 426 | + tree = map_directory(project) |
| 427 | + |
| 428 | + boundary_node = find_node_by_path(tree, ["null_at_boundary.bin"]) |
| 429 | + after_node = find_node_by_path(tree, ["null_after_boundary.txt"]) |
| 430 | + |
| 431 | + assert boundary_node is not None |
| 432 | + assert "<binary file:" in boundary_node.get("content", "") |
| 433 | + |
| 434 | + assert after_node is not None |
| 435 | + assert "x" * 100 in after_node.get("content", "") |
| 436 | + |
| 437 | + |
| 438 | +def test_deep_nesting_with_ignore_patterns(tmp_path): |
| 439 | + project = tmp_path / "project" |
| 440 | + project.mkdir() |
| 441 | + |
| 442 | + depth = 12 |
| 443 | + current = project |
| 444 | + for i in range(depth): |
| 445 | + current = current / f"level{i}" |
| 446 | + current.mkdir() |
| 447 | + (current / f"keep{i}.txt").write_text(f"keep{i}") |
| 448 | + (current / f"ignore{i}.bak").write_text(f"ignore{i}") |
| 449 | + |
| 450 | + (project / "level0" / ".gitignore").write_text("*.bak\n") |
| 451 | + |
| 452 | + tree = map_directory(project) |
| 453 | + names = get_all_files_in_tree(tree) |
| 454 | + |
| 455 | + for i in range(depth): |
| 456 | + assert f"keep{i}.txt" in names |
| 457 | + |
| 458 | + assert "ignore0.bak" not in names |
| 459 | + assert "ignore5.bak" not in names |
| 460 | + assert "ignore11.bak" not in names |
| 461 | + |
| 462 | + |
| 463 | +def test_middle_slash_pattern_is_anchored(tmp_path): |
| 464 | + project = tmp_path / "project" |
| 465 | + project.mkdir() |
| 466 | + |
| 467 | + (project / "subdir").mkdir() |
| 468 | + (project / "subdir" / "docs").mkdir() |
| 469 | + (project / "subdir" / "docs" / "api.txt").write_text("should be ignored") |
| 470 | + (project / "subdir" / "other").mkdir() |
| 471 | + (project / "subdir" / "other" / "docs").mkdir() |
| 472 | + (project / "subdir" / "other" / "docs" / "api.txt").write_text("should be kept") |
| 473 | + |
| 474 | + (project / "subdir" / ".gitignore").write_text("docs/api.txt\n") |
| 475 | + |
| 476 | + tree = map_directory(project) |
| 477 | + |
| 478 | + direct_node = find_node_by_path(tree, ["subdir", "docs", "api.txt"]) |
| 479 | + nested_node = find_node_by_path(tree, ["subdir", "other", "docs", "api.txt"]) |
| 480 | + |
| 481 | + assert direct_node is None |
| 482 | + assert nested_node is not None |
0 commit comments