Deskaid
by ezyang
- codemcp
- tests
"""
Test for the glob module's pattern matching functionality.
"""
from codemcp import glob
def test_gitignore_simple_asterisk():
"""Test simple * pattern with gitignore behavior."""
# * should match anything except slash
assert glob.match("*.txt", "file1.txt")
assert glob.match("*.txt", "a.txt")
assert not glob.match("*.txt", "dir/file.txt")
assert not glob.match("*.txt", "file.py")
def test_gitignore_question_mark():
"""Test ? pattern with gitignore behavior."""
# ? should match any single character except slash
assert glob.match("file?.txt", "file1.txt")
assert glob.match("file?.txt", "fileA.txt")
assert not glob.match("file?.txt", "file12.txt")
assert not glob.match("file?.txt", "file.txt")
assert not glob.match("file?.txt", "dir/file1.txt")
def test_gitignore_character_class():
"""Test character class patterns with gitignore behavior."""
# [abc] should match any character in the brackets
assert glob.match("file[123].txt", "file1.txt")
assert glob.match("file[123].txt", "file2.txt")
assert not glob.match("file[123].txt", "file4.txt")
# [!abc] should match any character not in the brackets
assert glob.match("file[!123].txt", "file4.txt")
assert glob.match("file[!123].txt", "fileA.txt")
assert not glob.match("file[!123].txt", "file1.txt")
# [a-z] should match any character in the range
assert glob.match("file[a-z].txt", "filea.txt")
assert glob.match("file[a-z].txt", "filez.txt")
assert not glob.match("file[a-z].txt", "file1.txt")
assert not glob.match("file[a-z].txt", "fileA.txt")
def test_gitignore_leading_double_asterisk():
"""Test leading **/ pattern with gitignore behavior."""
# **/ should match in all directories
assert glob.match("**/file.txt", "file.txt")
assert glob.match("**/file.txt", "a/file.txt")
assert glob.match("**/file.txt", "a/b/file.txt")
assert not glob.match("**/file.txt", "file1.txt")
assert not glob.match("**/file.txt", "a/file1.txt")
# **/foo/bar should match bar anywhere directly under foo
assert glob.match("**/a/file.txt", "a/file.txt")
assert glob.match("**/a/file.txt", "x/a/file.txt")
assert not glob.match("**/a/file.txt", "a/b/file.txt")
def test_gitignore_trailing_double_asterisk():
"""Test trailing /** pattern with gitignore behavior."""
# /** should match everything inside
assert glob.match("a/**", "a/file.txt")
assert glob.match("a/**", "a/b/file.txt")
assert glob.match("a/**", "a/b/c/deep.txt")
assert not glob.match("a/**", "file.txt")
assert not glob.match("a/**", "x/file.txt")
def test_gitignore_middle_double_asterisk():
"""Test /**/pattern with gitignore behavior."""
# /**/ should match zero or more directories
assert glob.match("a/**/file.txt", "a/file.txt")
assert glob.match("a/**/file.txt", "a/b/file.txt")
assert glob.match("a/**/file.txt", "a/b/c/file.txt")
assert not glob.match("a/**/file.txt", "file.txt")
assert not glob.match("a/**/file.txt", "a/file.py")
def test_editorconfig_braces():
"""Test editorconfig brace expansion."""
# {s1,s2,s3} should match any of the strings
assert glob.match("file.{txt,py}", "file.txt", editorconfig_braces=True)
assert glob.match("file.{txt,py}", "file.py", editorconfig_braces=True)
assert not glob.match("file.{txt,py}", "file.md", editorconfig_braces=True)
# {num1..num2} should match any integer in the range
assert glob.match("file{1..3}.txt", "file1.txt", editorconfig_braces=True)
assert glob.match("file{1..3}.txt", "file2.txt", editorconfig_braces=True)
assert glob.match("file{1..3}.txt", "file3.txt", editorconfig_braces=True)
assert not glob.match("file{1..3}.txt", "file4.txt", editorconfig_braces=True)
assert not glob.match("file{1..3}.txt", "file0.txt", editorconfig_braces=True)
# Negative ranges
assert glob.match("file{-1..1}.txt", "file-1.txt", editorconfig_braces=True)
assert glob.match("file{-1..1}.txt", "file0.txt", editorconfig_braces=True)
assert glob.match("file{-1..1}.txt", "file1.txt", editorconfig_braces=True)
# Braces can be nested
assert glob.match("file{a,{b,c}}.txt", "filea.txt", editorconfig_braces=True)
assert glob.match("file{a,{b,c}}.txt", "fileb.txt", editorconfig_braces=True)
assert glob.match("file{a,{b,c}}.txt", "filec.txt", editorconfig_braces=True)
def test_editorconfig_asterisk():
"""Test editorconfig asterisk behavior."""
# * should match any string including path separators
assert glob.match("*.txt", "file.txt", editorconfig_asterisk=True)
assert glob.match("*.txt", "dir/file.txt", editorconfig_asterisk=True)
assert not glob.match("*.txt", "file.py", editorconfig_asterisk=True)
def test_editorconfig_double_asterisk():
"""Test editorconfig ** behavior."""
# ** should match any string
assert glob.match("**", "file.txt", editorconfig_double_asterisk=True)
assert glob.match("**", "dir/file.txt", editorconfig_double_asterisk=True)
assert glob.match("**", "dir/subdir/file.txt", editorconfig_double_asterisk=True)
# More specific pattern with **
assert glob.match("a/**/file.txt", "a/file.txt", editorconfig_double_asterisk=True)
assert glob.match(
"a/**/file.txt", "a/b/file.txt", editorconfig_double_asterisk=True
)
assert glob.match(
"a/**/file.txt", "a/b/c/file.txt", editorconfig_double_asterisk=True
)
def test_escaped_characters():
"""Test escaped special characters in patterns."""
# Escaped special characters should be treated as literals
assert glob.match(r"\*.txt", "*.txt")
assert not glob.match(r"\*.txt", "a.txt")
assert glob.match(r"\?.txt", "?.txt")
assert not glob.match(r"\?.txt", "a.txt")
assert glob.match(r"\[abc\].txt", "[abc].txt")
assert not glob.match(r"\[abc\].txt", "a.txt")
def test_combined_features():
"""Test combining different pattern features."""
# Combining various features
assert glob.match(
"**/[a-z]/{file,test}.{txt,py}", "a/file.txt", editorconfig_braces=True
)
assert glob.match(
"**/[a-z]/{file,test}.{txt,py}", "x/y/z/test.py", editorconfig_braces=True
)
assert not glob.match(
"**/[a-z]/{file,test}.{txt,py}", "1/file.txt", editorconfig_braces=True
)
assert not glob.match(
"**/[a-z]/{file,test}.{txt,py}", "a/other.txt", editorconfig_braces=True
)
def test_filter_function():
"""Test the filter function."""
paths = [
"file1.txt",
"file2.py",
"dir/file.txt",
"dir/file.py",
"dir/subdir/file.txt",
]
# Filter with a single pattern
result = glob.filter(["*.txt"], paths)
assert result == ["file1.txt"]
# Filter with multiple patterns
result = glob.filter(["*.txt", "*.py"], paths)
assert result == ["file1.txt", "file2.py"]
# Filter with gitignore-specific pattern
result = glob.filter(["**/file.txt"], paths)
assert result == ["dir/file.txt", "dir/subdir/file.txt"]
def test_find_function():
"""Test the find function with a list of paths."""
# Define test paths
test_paths = [
"file1.txt",
"file2.py",
"a/file.txt",
"a/file.py",
"a/b/file.txt",
"a/b/file.py",
"a/b/c/deep.txt",
"a/d/file.txt",
"a/d/e/deep.py",
"x/file.txt",
"x/y/file.py",
"x/y/z/deep.txt",
]
# Find with a simple pattern
result = glob.find(["*.txt"], "", paths=test_paths)
assert len(result) == 1
assert result[0] == "file1.txt"
# Find with gitignore-specific pattern
result = glob.find(["**/file.txt"], "", paths=test_paths)
# Should find a/file.txt, a/b/file.txt, a/d/file.txt, x/file.txt
assert len(result) == 4
assert all(path.endswith("/file.txt") or path == "file.txt" for path in result)
# Find with combination of patterns
result = glob.find(["a/**/*.py"], "", paths=test_paths)
assert len(result) == 3
assert "a/file.py" in result
assert "a/b/file.py" in result
assert "a/d/e/deep.py" in result
def test_make_matcher_function():
"""Test the make_matcher function."""
# Create a matcher function
matcher = glob.make_matcher("*.txt")
# Test the matcher
assert matcher("file.txt")
assert not matcher("file.py")
assert not matcher("dir/file.txt")
# Create a matcher with gitignore-specific behavior
matcher = glob.make_matcher("**/file.txt")
# Test the matcher
assert matcher("file.txt")
assert matcher("dir/file.txt")
assert matcher("dir/subdir/file.txt")
assert not matcher("file.py")
def test_complex_patterns():
"""Test more complex pattern combinations."""
# Mix of ** and character classes
assert glob.match("**/*[0-9].txt", "file1.txt")
assert glob.match("**/*[0-9].txt", "dir/file2.txt")
assert glob.match("**/*[0-9].txt", "a/b/c/file3.txt")
assert not glob.match("**/*[0-9].txt", "file.txt")
# Multiple ** patterns
assert glob.match("**/a/**/b/**/c.txt", "a/b/c.txt")
assert glob.match("**/a/**/b/**/c.txt", "x/a/y/b/z/c.txt")
assert glob.match("**/a/**/b/**/c.txt", "a/1/2/b/c.txt")
assert not glob.match("**/a/**/b/**/c.txt", "a/b/d.txt")
# Combinations with editorconfig features
assert glob.match(
"**/{a,b}/**/*.{txt,md}", "a/x/y/file.txt", editorconfig_braces=True
)
assert glob.match("**/{a,b}/**/*.{txt,md}", "b/file.md", editorconfig_braces=True)
assert not glob.match(
"**/{a,b}/**/*.{txt,md}", "c/file.txt", editorconfig_braces=True
)
assert not glob.match(
"**/{a,b}/**/*.{txt,md}", "a/file.py", editorconfig_braces=True
)
def test_edge_cases():
"""Test edge cases and corner cases."""
# Empty pattern
assert glob.match("", "")
assert not glob.match("", "file.txt")
# Empty path
assert not glob.match("*.txt", "")
# Just asterisks
assert glob.match("*", "file.txt")
assert glob.match("*", "directory")
assert not glob.match("*", "nested/file.txt")
# Just double asterisks
assert glob.match("**", "file.txt")
assert glob.match("**", "nested/file.txt", editorconfig_double_asterisk=True)
# Pattern with just a slash
assert glob.match("/", "/")
assert not glob.match("/", "file.txt")
# Pattern with trailing slash
assert glob.match("dir/", "dir/")
assert not glob.match("dir/", "dir")
assert not glob.match("dir/", "dir/file.txt")
# Invalid character class that isn't closed
assert not glob.match("file[abc.txt", "fileabc.txt")
# Escaping backslashes
assert glob.match(r"file\\.txt", "file\\.txt")
assert not glob.match(r"file\\.txt", "file.txt")