Skip to content

Commit 5c3fe73

Browse files
committed
test: add pytest tests for template validator
Comprehensive test suite covering glob validation, trailing whitespace detection, duplicate and conflict detection, coverage analysis, and report formatting. Signed-off-by: Srikanth Patchava <spatchava@meta.com>
1 parent be26f4f commit 5c3fe73

1 file changed

Lines changed: 189 additions & 0 deletions

File tree

scripts/test_validate_templates.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env python3
2+
"""Tests for the gitignore template validator."""
3+
4+
import os
5+
import tempfile
6+
import pytest
7+
8+
from validate_templates import (
9+
Issue,
10+
Severity,
11+
ValidationReport,
12+
analyze_coverage,
13+
check_trailing_whitespace,
14+
discover_templates,
15+
find_conflicts,
16+
find_duplicates,
17+
format_report,
18+
is_valid_glob_pattern,
19+
validate_template,
20+
)
21+
22+
23+
class TestIsValidGlobPattern:
24+
def test_simple_extension(self):
25+
assert is_valid_glob_pattern("*.py") is True
26+
27+
def test_directory_pattern(self):
28+
assert is_valid_glob_pattern("build/") is True
29+
30+
def test_negation(self):
31+
assert is_valid_glob_pattern("!important.txt") is True
32+
33+
def test_double_star(self):
34+
assert is_valid_glob_pattern("**/logs") is True
35+
36+
def test_bracket_pattern(self):
37+
assert is_valid_glob_pattern("*.[oa]") is True
38+
39+
def test_unmatched_bracket(self):
40+
assert is_valid_glob_pattern("*.[o") is False
41+
42+
def test_empty_after_strip(self):
43+
assert is_valid_glob_pattern("!") is False
44+
45+
def test_rooted_pattern(self):
46+
assert is_valid_glob_pattern("/build") is True
47+
48+
49+
class TestCheckTrailingWhitespace:
50+
def test_no_trailing(self):
51+
assert check_trailing_whitespace("*.py", 1) is None
52+
53+
def test_with_trailing_space(self):
54+
issue = check_trailing_whitespace("*.py ", 5)
55+
assert issue is not None
56+
assert issue.severity == Severity.WARNING
57+
assert issue.line_number == 5
58+
59+
def test_blank_line_ignored(self):
60+
assert check_trailing_whitespace(" ", 1) is None
61+
62+
63+
class TestFindDuplicates:
64+
def test_no_duplicates(self):
65+
patterns = [(1, "*.py"), (2, "*.js"), (3, "build/")]
66+
assert find_duplicates(patterns) == []
67+
68+
def test_exact_duplicate(self):
69+
patterns = [(1, "*.py"), (2, "*.js"), (3, "*.py")]
70+
issues = find_duplicates(patterns)
71+
assert len(issues) == 1
72+
assert issues[0].line_number == 3
73+
74+
def test_trailing_slash_normalization(self):
75+
patterns = [(1, "build"), (2, "build/")]
76+
issues = find_duplicates(patterns)
77+
assert len(issues) == 1
78+
79+
80+
class TestFindConflicts:
81+
def test_no_conflicts(self):
82+
patterns = [(1, "*.log"), (2, "*.tmp")]
83+
assert find_conflicts(patterns) == []
84+
85+
def test_include_then_negate(self):
86+
patterns = [(1, "*.log"), (2, "!*.log")]
87+
issues = find_conflicts(patterns)
88+
assert len(issues) == 1
89+
assert issues[0].severity == Severity.WARNING
90+
91+
def test_negate_without_include(self):
92+
patterns = [(1, "!important.txt")]
93+
issues = find_conflicts(patterns)
94+
assert len(issues) == 0
95+
96+
97+
class TestAnalyzeCoverage:
98+
def test_many_wildcards(self):
99+
patterns = [(i, f"*.ext{i}") for i in range(12)]
100+
issues = analyze_coverage(patterns)
101+
msgs = [i.message for i in issues]
102+
assert any("wildcard" in m for m in msgs)
103+
104+
def test_no_directory_patterns(self):
105+
patterns = [(i, f"*.ext{i}") for i in range(6)]
106+
issues = analyze_coverage(patterns)
107+
msgs = [i.message for i in issues]
108+
assert any("directory" in m.lower() for m in msgs)
109+
110+
def test_duplicate_extension_suggestion(self):
111+
patterns = [(1, "*.py"), (2, "*.py")]
112+
issues = analyze_coverage(patterns)
113+
msgs = [i.message for i in issues]
114+
assert any(".py" in m for m in msgs)
115+
116+
117+
class TestValidateTemplate:
118+
def test_valid_template(self):
119+
with tempfile.NamedTemporaryFile(
120+
mode="w", suffix=".gitignore", delete=False
121+
) as f:
122+
f.write("# Build output\nbuild/\n*.o\n*.a\n")
123+
f.flush()
124+
report = validate_template(f.name)
125+
os.unlink(f.name)
126+
assert report.error_count == 0
127+
assert report.pattern_count == 3
128+
assert report.comment_count == 1
129+
130+
def test_invalid_pattern(self):
131+
with tempfile.NamedTemporaryFile(
132+
mode="w", suffix=".gitignore", delete=False
133+
) as f:
134+
f.write("*.[invalid\n")
135+
f.flush()
136+
report = validate_template(f.name)
137+
os.unlink(f.name)
138+
assert report.error_count > 0
139+
140+
def test_file_not_found(self):
141+
report = validate_template("/nonexistent/file.gitignore")
142+
assert report.error_count > 0
143+
144+
145+
class TestFormatReport:
146+
def test_clean_report(self):
147+
report = ValidationReport(
148+
template_file="test.gitignore",
149+
total_lines=5,
150+
pattern_count=3,
151+
comment_count=1,
152+
blank_count=1,
153+
)
154+
output = format_report(report)
155+
assert "No issues found" in output
156+
157+
def test_report_with_issues(self):
158+
report = ValidationReport(
159+
template_file="test.gitignore",
160+
total_lines=5,
161+
pattern_count=3,
162+
)
163+
report.issues.append(
164+
Issue(
165+
severity=Severity.ERROR,
166+
line_number=2,
167+
line_content="*.[bad",
168+
message="Invalid glob pattern",
169+
)
170+
)
171+
output = format_report(report, verbose=True)
172+
assert "ERROR" in output
173+
assert "*.[bad" in output
174+
175+
176+
class TestDiscoverTemplates:
177+
def test_discover_in_directory(self):
178+
with tempfile.TemporaryDirectory() as tmpdir:
179+
# Create fake gitignore files
180+
for name in ["Python.gitignore", "Java.gitignore", "README.md"]:
181+
with open(os.path.join(tmpdir, name), "w") as f:
182+
f.write("# test\n")
183+
templates = discover_templates(tmpdir)
184+
assert len(templates) == 2
185+
assert all(t.endswith(".gitignore") for t in templates)
186+
187+
188+
if __name__ == "__main__":
189+
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)