-
Notifications
You must be signed in to change notification settings - Fork 1
/
test_gitignorant.py
242 lines (209 loc) · 7.06 KB
/
test_gitignorant.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
from typing import List
import pytest
from gitignorant import (
Rule,
check_match,
check_path_match,
parse_gitignore_file,
try_parse_rule,
)
GITIGNORE_STRING = (
r"""
# Hello! this line is ignored.
# comment
#comment
comment_after.file # a comment must be on its own line
*.peasoup
a?.john
zz*
!/[a-f]*.peasoup
# world
!booze/*.peasoup
!/scaffolding/*.peasoup
# Directory testing
recipes/
!/other/recipes/
/other/recipes/work_in_progress.txt
notes/private
!recipes/include_anyway
# Escape testing
\!important*
"""
r"spaced_out\ " # this is separate to avoid trimming whitespace
)
@pytest.fixture(scope="session")
def rules() -> List[Rule]:
return list(parse_gitignore_file(GITIGNORE_STRING.strip().splitlines()))
TEST_CASES = [
(False, "hello"),
(True, "hello.peasoup"),
(False, "hello.peasoupiness"),
(True, "ballo/allo.peasoup"),
(False, "allo.peasoup"),
(False, "cullo.peasoup"),
(True, "bazze/allo.peasoup"),
(False, "booze/allo.peasoup"),
(True, "booze/scaffolding/allo.peasoup"),
(False, "scaffolding/allo.peasoup"),
(True, "asdf/ab.john"),
(False, "asdf/aba.john"),
(True, "ab.john"),
(False, "asdf/cb.john"),
(True, "!important_1*"),
(True, "spaced_out "),
(False, "spaced_out"),
(True, "zztop"),
(False, "jazztop"),
(False, "# comment"),
(False, "#comment"),
(False, "comment"),
(False, "comment_after.file"),
(True, "comment_after.file # a comment must be on its own line"),
]
@pytest.mark.parametrize(["expected", "path"], TEST_CASES)
@pytest.mark.parametrize("func", ["check_match", "check_path_match"])
def test_gitignorant_files(
rules: List[Rule],
path: str,
expected: bool,
func: str,
) -> None:
if func == "check_match":
assert check_match(rules, path, is_dir=False) == expected
elif func == "check_path_match":
assert check_path_match(rules, path) == expected
else:
raise NotImplementedError("...")
@pytest.mark.parametrize(
"expected,path",
[
(True, "foo/recipes"),
(False, "other/recipes"),
(True, "/recipes"),
],
)
def test_gitignorant_dirs(rules: List[Rule], path: str, expected: bool) -> None:
assert check_match(rules, path, is_dir=True) == expected
@pytest.mark.parametrize(
"expected, path",
[
(True, "a/b"),
(True, "a/x/b"),
(True, "a/x/y/b"),
],
)
def test_spec_internal_doublestar(path: str, expected: bool) -> None:
# * A slash followed by two consecutive asterisks then a slash matches
# zero or more directories. For example, "a/**/b"
# matches "a/b", "a/x/b", "a/x/y/b" and so on.
r = try_parse_rule("a/**/b")
assert r
assert r.matches(path) == expected
@pytest.mark.parametrize(
"expected, path",
[
(True, "abc/a"),
(True, "abc/x/b"),
(True, "abc/x/y/b"),
],
)
def test_spec_trailing_doublestar(path: str, expected: bool) -> None:
# * A trailing "/**" matches everything inside. For example, "abc/**"
# matches all files inside directory "abc", relative to the location
# of the .gitignore file, with infinite depth.
r = try_parse_rule("abc/**")
assert r
assert r.matches(path) == expected
@pytest.mark.parametrize(
"expected, path",
[
(True, "doop/foo"),
(True, "abc/bloop/buup/foo"),
(False, "doop/foo/zoop"),
(False, "abc/bloop/buup/foro"),
],
)
def test_spec_leading_doublestar(path: str, expected: bool) -> None:
# * A leading "**" followed by a slash means match in all directories.
# For example, "**/foo" matches file or directory "foo" anywhere, the
# same as pattern "foo". "**/foo/bar" matches file or directory "bar"
# anywhere that is directly under directory "foo".
r = try_parse_rule("**/foo")
assert r
assert r.matches(path) == expected
def test_spec_trailing_dir_magic() -> None:
# * For example, a pattern doc/frotz/ matches doc/frotz directory, but not
# a/doc/frotz directory; however frotz/ matches frotz and a/frotz that
# is a directory (all paths are relative from the .gitignore file).
r1 = try_parse_rule("doc/frotz/")
assert r1
assert r1.matches("doc/frotz", is_dir=True)
assert not r1.matches("a/doc/frotz", is_dir=True)
r2 = try_parse_rule("frotz/")
assert r2
assert r2.matches("frotz", is_dir=True)
assert r2.matches("a/frotz", is_dir=True)
def test_unfinished_group_parsing() -> None:
r1 = try_parse_rule("unfinished/symp[athy")
assert r1
assert r1.matches("unfinished/sympa", is_dir=False)
assert r1.matches("unfinished/sympt", is_dir=False)
assert r1.matches("unfinished/symph", is_dir=False)
assert r1.matches("unfinished/sympy", is_dir=False)
assert not r1.matches("unfinished/sympathy", is_dir=False)
CHECK_PATH_MATCH_CASES = [
# These should match since `recipes/` is in the list,
# and it's not anchored to the root
("recipes/zep", True),
("splop/recipes/zep", True),
# all subdirectories and files inside subdirectories too
("recipes/xoop/", True),
("recipes/xoop/zep", True),
("recipes/xoop/zep/", True),
("recipes/pkex/pox/ioa/hai/kxr/aha", True),
("splop/recipes/xoop/xep", True),
# Subdirectory match (`notes/private`) also catches everything under it,
# including further subdirectories
("notes/private", True),
("notes/private/ramblings/trees", True),
("notes/private/free_stuff", True),
# It is not possible to explicitly include files from within an ignored directory
# (Git doesn't descend into ignored directories)
("recipes/include_anyway/delicious_plum_pie.txt", True),
("recipes/include_anyway/more_pie_recipes/rhubarb_pie.txt", True),
# However, subdirectory match is only relative to the .gitignore location and
# not any subdirectories
("splop/notes/private/notes_on_splop", False),
# This should not match, since `/other/recipes/` is explicitly negated
("other/recipes/zep", False),
# We can however re-ignore a file within an explicitly included directory
("other/recipes/work_in_progress.txt", True),
# This too should match, since it's trying to ignore the whole folder
("recipes/", True),
]
@pytest.mark.parametrize(["path", "expected"], CHECK_PATH_MATCH_CASES)
def test_check_path_match(rules: List[Rule], path: str, expected: bool) -> None:
assert check_path_match(rules, path) == expected
IGNORE_ALL_BY_DEFAULT_RULES = list(
parse_gitignore_file(
[
"*",
"!include_this",
"!included_subdir/",
"!included_subdir/*",
"!*wildcard",
]
)
)
@pytest.mark.parametrize(
"path, expected",
[
("ignore_this", True),
("include_this", False),
("included_subdir/include_file_in_subdir", False),
("include_with_wildcard", False),
],
)
def test_ignore_all_but_excluded(path: str, expected: bool) -> None:
"""Test that we can ignore all files except those explicitly included"""
assert check_path_match(IGNORE_ALL_BY_DEFAULT_RULES, path) == expected