You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
11 KiB
314 lines
11 KiB
# MIT License |
|
# |
|
# Copyright (c) 2026 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
|
# |
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|
# of this software and associated documentation files (the "Software"), to deal |
|
# in the Software without restriction, including without limitation the rights |
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
# copies of the Software, and to permit persons to whom the Software is |
|
# furnished to do so, subject to the following conditions: |
|
# |
|
# The above copyright notice and this permission notice shall be included in all |
|
# copies or substantial portions of the Software. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
# SOFTWARE. |
|
|
|
import pytest |
|
import sys |
|
import os |
|
from pathlib import Path |
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../../libs/pyTermTk')) |
|
|
|
from ttkode.app.helpers.search_file import ( |
|
is_text_file, |
|
_load_gitignore_patterns, |
|
_glob_match_patterns, |
|
_custom_walk, |
|
TTKode_SearchFile |
|
) |
|
|
|
|
|
class TestIsTextFile: |
|
"""Test text file detection.""" |
|
|
|
def test_text_file_detection(self, tmp_path): |
|
"""Test detection of text files.""" |
|
text_file = tmp_path / "test.txt" |
|
text_file.write_text("This is a text file\nwith multiple lines") |
|
|
|
assert is_text_file(str(text_file)) is True |
|
|
|
def test_python_file_detection(self, tmp_path): |
|
"""Test Python files are detected as text.""" |
|
py_file = tmp_path / "test.py" |
|
py_file.write_text("print('hello world')") |
|
|
|
assert is_text_file(str(py_file)) is True |
|
|
|
def test_json_file_detection(self, tmp_path): |
|
"""Test JSON files are detected as text.""" |
|
json_file = tmp_path / "test.json" |
|
json_file.write_text('{"key": "value"}') |
|
|
|
assert is_text_file(str(json_file)) is True |
|
|
|
def test_binary_file_detection(self, tmp_path): |
|
"""Test binary files are detected correctly.""" |
|
binary_file = tmp_path / "test.bin" |
|
binary_file.write_bytes(b'\x00\x01\x02\x03\x04\x05') |
|
|
|
assert is_text_file(str(binary_file)) is False |
|
|
|
def test_empty_file(self, tmp_path): |
|
"""Test empty file detection.""" |
|
empty_file = tmp_path / "empty.txt" |
|
empty_file.write_text("") |
|
|
|
# Empty files might be considered text depending on implementation |
|
result = is_text_file(str(empty_file)) |
|
assert isinstance(result, bool) |
|
|
|
|
|
class TestLoadGitignorePatterns: |
|
"""Test loading .gitignore patterns.""" |
|
|
|
def test_load_existing_gitignore(self, tmp_path): |
|
"""Test loading an existing .gitignore file.""" |
|
gitignore = tmp_path / ".gitignore" |
|
gitignore.write_text("*.pyc\n__pycache__/\n.env\n") |
|
|
|
patterns = _load_gitignore_patterns(str(gitignore)) |
|
|
|
assert "*.pyc" in patterns |
|
assert "__pycache__/" in patterns |
|
assert ".env" in patterns |
|
|
|
def test_load_nonexistent_gitignore(self, tmp_path): |
|
"""Test loading a non-existent .gitignore file.""" |
|
gitignore_path = str(tmp_path / ".gitignore") |
|
patterns = _load_gitignore_patterns(gitignore_path) |
|
|
|
assert patterns == [] |
|
|
|
def test_empty_gitignore(self, tmp_path): |
|
"""Test loading an empty .gitignore file.""" |
|
gitignore = tmp_path / ".gitignore" |
|
gitignore.write_text("") |
|
|
|
patterns = _load_gitignore_patterns(str(gitignore)) |
|
assert patterns == [] |
|
|
|
def test_gitignore_with_comments(self, tmp_path): |
|
"""Test .gitignore with comments.""" |
|
gitignore = tmp_path / ".gitignore" |
|
gitignore.write_text("# This is a comment\n*.pyc\n# Another comment\n__pycache__/") |
|
|
|
patterns = _load_gitignore_patterns(str(gitignore)) |
|
|
|
# Comments should be included as-is (filtering happens in matching) |
|
assert len(patterns) == 4 |
|
|
|
|
|
class TestGlobMatchPatterns: |
|
"""Test glob pattern matching.""" |
|
|
|
def test_simple_pattern_match(self): |
|
"""Test simple pattern matching.""" |
|
assert _glob_match_patterns("test.pyc", ["*.pyc"]) is True |
|
assert _glob_match_patterns("test.py", ["*.pyc"]) is False |
|
|
|
def test_directory_pattern_match(self): |
|
"""Test directory pattern matching.""" |
|
assert _glob_match_patterns("./__pycache__/module.pyc", ["__pycache__"]) is True |
|
assert _glob_match_patterns("./src/__pycache__/", ["__pycache__"]) is True |
|
|
|
def test_multiple_patterns(self): |
|
"""Test matching against multiple patterns.""" |
|
patterns = ["*.pyc", "*.pyo", "__pycache__"] |
|
|
|
assert _glob_match_patterns("test.pyc", patterns) is True |
|
assert _glob_match_patterns("test.pyo", patterns) is True |
|
assert _glob_match_patterns("./__pycache__/", patterns) is True |
|
assert _glob_match_patterns("test.py", patterns) is False |
|
|
|
def test_empty_patterns(self): |
|
"""Test with empty pattern list.""" |
|
assert _glob_match_patterns("test.py", []) is False |
|
|
|
def test_current_directory(self): |
|
"""Test matching with current directory.""" |
|
result = _glob_match_patterns(".", ["*.pyc"]) |
|
assert isinstance(result, bool) |
|
|
|
def test_relative_path_matching(self): |
|
"""Test matching relative paths.""" |
|
assert _glob_match_patterns("./src/module.pyc", ["*.pyc"]) is True |
|
assert _glob_match_patterns("src/module.py", ["*.pyc"]) is False |
|
|
|
|
|
class TestCustomWalk: |
|
"""Test custom directory walking.""" |
|
|
|
def test_walk_simple_directory(self, tmp_path): |
|
"""Test walking a simple directory structure.""" |
|
# Create test structure |
|
(tmp_path / "file1.py").write_text("print('file1')") |
|
(tmp_path / "file2.txt").write_text("text") |
|
|
|
results = list(_custom_walk(str(tmp_path))) |
|
|
|
assert len(results) == 2 |
|
filenames = [entry[1] for entry in results] |
|
assert "file1.py" in filenames |
|
assert "file2.txt" in filenames |
|
|
|
def test_walk_nested_directories(self, tmp_path): |
|
"""Test walking nested directories.""" |
|
# Create nested structure |
|
subdir = tmp_path / "subdir" |
|
subdir.mkdir() |
|
(tmp_path / "root.py").write_text("root") |
|
(subdir / "nested.py").write_text("nested") |
|
|
|
results = list(_custom_walk(str(tmp_path))) |
|
|
|
assert len(results) == 2 |
|
filenames = [entry[1] for entry in results] |
|
assert "root.py" in filenames |
|
assert "nested.py" in filenames |
|
|
|
def test_walk_with_exclude_patterns(self, tmp_path): |
|
"""Test walking with exclusion patterns.""" |
|
# Create files |
|
(tmp_path / "include.py").write_text("include") |
|
(tmp_path / "exclude.pyc").write_text("exclude") |
|
|
|
results = list(_custom_walk(str(tmp_path), exclude_patterns=["*.pyc"])) |
|
|
|
filenames = [entry[1] for entry in results] |
|
assert "include.py" in filenames |
|
assert "exclude.pyc" not in filenames |
|
|
|
def test_walk_with_include_patterns(self, tmp_path): |
|
"""Test walking with inclusion patterns.""" |
|
# Create files |
|
(tmp_path / "test.py").write_text("python") |
|
(tmp_path / "test.txt").write_text("text") |
|
|
|
results = list(_custom_walk(str(tmp_path), include_patterns=["*.py"])) |
|
|
|
filenames = [entry[1] for entry in results] |
|
assert "test.py" in filenames |
|
assert "test.txt" not in filenames |
|
|
|
def test_walk_ignores_git_directory(self, tmp_path): |
|
"""Test that .git directories are ignored.""" |
|
# Create .git directory |
|
git_dir = tmp_path / ".git" |
|
git_dir.mkdir() |
|
(git_dir / "config").write_text("git config") |
|
(tmp_path / "normal.py").write_text("normal file") |
|
|
|
results = list(_custom_walk(str(tmp_path))) |
|
|
|
filenames = [entry[1] for entry in results] |
|
assert "normal.py" in filenames |
|
assert "config" not in filenames |
|
|
|
def test_walk_respects_gitignore(self, tmp_path): |
|
"""Test that .gitignore patterns are respected.""" |
|
# Create .gitignore |
|
gitignore = tmp_path / ".gitignore" |
|
gitignore.write_text("*.pyc\n__pycache__/") |
|
|
|
# Create files |
|
(tmp_path / "normal.py").write_text("include") |
|
(tmp_path / "compiled.pyc").write_text("exclude") |
|
|
|
results = list(_custom_walk(str(tmp_path))) |
|
|
|
filenames = [entry[1] for entry in results] |
|
assert "normal.py" in filenames |
|
assert ".gitignore" in filenames |
|
assert "compiled.pyc" not in filenames |
|
|
|
|
|
class TestTTKodeSearchFile: |
|
"""Test the file search functionality.""" |
|
|
|
def test_search_by_pattern(self, tmp_path): |
|
"""Test searching files by pattern.""" |
|
# Create test files |
|
pass |
|
(tmp_path / "test_Eugenio_ABC_file.py").write_text("test") |
|
(tmp_path / "other.txt").write_text("other") |
|
(tmp_path / "test_Eugenio_ABC_data.json").write_text("{}") |
|
|
|
results = list(TTKode_SearchFile.getFilesFromPattern(tmp_path, "test_Eugenio_ABC")) |
|
|
|
assert len(results) == 2 |
|
result_names = [r.name for r in results] |
|
assert "test_Eugenio_ABC_file.py" in result_names |
|
assert "test_Eugenio_ABC_data.json" in result_names |
|
assert "other.txt" not in result_names |
|
|
|
def test_search_empty_pattern(self, tmp_path): |
|
"""Test searching with empty pattern matches all files.""" |
|
(tmp_path / "file1.py").write_text("1") |
|
(tmp_path / "file2.txt").write_text("2") |
|
|
|
results = list(TTKode_SearchFile.getFilesFromPattern(tmp_path, "")) |
|
|
|
# Empty pattern should match all files |
|
assert len(results) >= 2 |
|
|
|
def test_search_no_matches(self, tmp_path): |
|
"""Test searching with pattern that matches nothing.""" |
|
(tmp_path / "file1.py").write_text("1") |
|
|
|
results = list(TTKode_SearchFile.getFilesFromPattern(tmp_path, "nonexistent_pattern_xyz")) |
|
|
|
assert len(results) == 0 |
|
|
|
def test_search_nested_directories(self, tmp_path): |
|
"""Test searching in nested directories.""" |
|
# Create nested structure |
|
subdir = tmp_path / "subdir" |
|
subdir.mkdir() |
|
(tmp_path / "root_test.py").write_text("root") |
|
(subdir / "nested_test.py").write_text("nested") |
|
|
|
results = list(TTKode_SearchFile.getFilesFromPattern(tmp_path, "test")) |
|
|
|
assert len(results) == 2 |
|
result_names = [r.name for r in results] |
|
assert "root_test.py" in result_names |
|
assert "nested_test.py" in result_names |
|
|
|
def test_search_returns_path_objects(self, tmp_path): |
|
"""Test that search returns Path objects.""" |
|
(tmp_path / "test.py").write_text("test") |
|
|
|
results = list(TTKode_SearchFile.getFilesFromPattern(tmp_path, "test")) |
|
|
|
assert len(results) > 0 |
|
for result in results: |
|
assert isinstance(result, Path) |
|
|
|
def test_search_case_sensitivity(self, tmp_path): |
|
"""Test search pattern case sensitivity.""" |
|
(tmp_path / "TestFile.py").write_text("test") |
|
(tmp_path / "testfile.txt").write_text("test") |
|
|
|
results = list(TTKode_SearchFile.getFilesFromPattern(tmp_path, "test")) |
|
|
|
# Should match both (case-insensitive glob matching) |
|
assert len(results) >= 2
|
|
|