11from unittest .mock import patch , mock_open
22from pathlib import Path
33from tempfile import TemporaryDirectory
4+ from shutil import which
5+ from subprocess import run
6+
47
58from gitignore_parser import parse_gitignore
69
7- from unittest import TestCase , main , SkipTest
10+ from unittest import TestCase , main , SkipTest , skipUnless
811
912
1013class Test (TestCase ):
1114 def test_simple (self ):
12- matches = _parse_gitignore_string (
15+ matches = self . _parse_gitignore_string (
1316 '__pycache__/\n '
1417 '*.py[cod]' ,
1518 fake_base_dir = '/home/michael'
@@ -18,9 +21,10 @@ def test_simple(self):
1821 self .assertTrue (matches ('/home/michael/main.pyc' ))
1922 self .assertTrue (matches ('/home/michael/dir/main.pyc' ))
2023 self .assertTrue (matches ('/home/michael/__pycache__' ))
24+ self .assertTrue (matches ('/home/michael/__pycache__/' ))
2125
2226 def test_incomplete_filename (self ):
23- matches = _parse_gitignore_string ('o.py' , fake_base_dir = '/home/michael' )
27+ matches = self . _parse_gitignore_string ('o.py' , fake_base_dir = '/home/michael' )
2428 self .assertTrue (matches ('/home/michael/o.py' ))
2529 self .assertFalse (matches ('/home/michael/foo.py' ))
2630 self .assertFalse (matches ('/home/michael/o.pyc' ))
@@ -29,7 +33,7 @@ def test_incomplete_filename(self):
2933 self .assertFalse (matches ('/home/michael/dir/o.pyc' ))
3034
3135 def test_wildcard (self ):
32- matches = _parse_gitignore_string (
36+ matches = self . _parse_gitignore_string (
3337 'hello.*' ,
3438 fake_base_dir = '/home/michael'
3539 )
@@ -41,7 +45,7 @@ def test_wildcard(self):
4145 self .assertFalse (matches ('/home/michael/helloX' ))
4246
4347 def test_anchored_wildcard (self ):
44- matches = _parse_gitignore_string (
48+ matches = self . _parse_gitignore_string (
4549 '/hello.*' ,
4650 fake_base_dir = '/home/michael'
4751 )
@@ -50,7 +54,7 @@ def test_anchored_wildcard(self):
5054 self .assertFalse (matches ('/home/michael/a/hello.java' ))
5155
5256 def test_trailingspaces (self ):
53- matches = _parse_gitignore_string (
57+ matches = self . _parse_gitignore_string (
5458 'ignoretrailingspace \n '
5559 'notignoredspace\\ \n '
5660 'partiallyignoredspace\\ \n '
@@ -73,7 +77,7 @@ def test_trailingspaces(self):
7377 self .assertFalse (matches ('/home/michael/notignoredmultiplespace' ))
7478
7579 def test_comment (self ):
76- matches = _parse_gitignore_string (
80+ matches = self . _parse_gitignore_string (
7781 'somematch\n '
7882 '#realcomment\n '
7983 'othermatch\n '
@@ -86,21 +90,21 @@ def test_comment(self):
8690 self .assertTrue (matches ('/home/michael/#imnocomment' ))
8791
8892 def test_ignore_directory (self ):
89- matches = _parse_gitignore_string ('.venv/' , fake_base_dir = '/home/michael' )
93+ matches = self . parse_gitignore_string ('.venv/' , fake_base_dir = '/home/michael' )
9094 self .assertTrue (matches ('/home/michael/.venv' ))
9195 self .assertTrue (matches ('/home/michael/.venv/folder' ))
9296 self .assertTrue (matches ('/home/michael/.venv/file.txt' ))
9397 self .assertFalse (matches ('/home/michael/.venv_other_folder' ))
9498 self .assertFalse (matches ('/home/michael/.venv_no_folder.py' ))
9599
96100 def test_ignore_directory_asterisk (self ):
97- matches = _parse_gitignore_string ('.venv/*' , fake_base_dir = '/home/michael' )
101+ matches = self . _parse_gitignore_string ('.venv/*' , fake_base_dir = '/home/michael' )
98102 self .assertFalse (matches ('/home/michael/.venv' ))
99103 self .assertTrue (matches ('/home/michael/.venv/folder' ))
100104 self .assertTrue (matches ('/home/michael/.venv/file.txt' ))
101105
102106 def test_negation (self ):
103- matches = _parse_gitignore_string (
107+ matches = self . _parse_gitignore_string (
104108 '''
105109*.ignore
106110!keep.ignore
@@ -112,20 +116,20 @@ def test_negation(self):
112116 self .assertTrue (matches ('/home/michael/waste.ignore' ))
113117
114118 def test_literal_exclamation_mark (self ):
115- matches = _parse_gitignore_string ('\\ !ignore_me!' , fake_base_dir = '/home/michael' )
119+ matches = self . _parse_gitignore_string ('\\ !ignore_me!' , fake_base_dir = '/home/michael' )
116120 self .assertTrue (matches ('/home/michael/!ignore_me!' ))
117121 self .assertFalse (matches ('/home/michael/ignore_me!' ))
118122 self .assertFalse (matches ('/home/michael/ignore_me' ))
119123
120124 def test_double_asterisks (self ):
121- matches = _parse_gitignore_string ('foo/**/Bar' , fake_base_dir = '/home/michael' )
125+ matches = self . _parse_gitignore_string ('foo/**/Bar' , fake_base_dir = '/home/michael' )
122126 self .assertTrue (matches ('/home/michael/foo/hello/Bar' ))
123127 self .assertTrue (matches ('/home/michael/foo/world/Bar' ))
124128 self .assertTrue (matches ('/home/michael/foo/Bar' ))
125129 self .assertFalse (matches ('/home/michael/foo/BarBar' ))
126130
127131 def test_double_asterisk_without_slashes_handled_like_single_asterisk (self ):
128- matches = _parse_gitignore_string ('a/b**c/d' , fake_base_dir = '/home/michael' )
132+ matches = self . _parse_gitignore_string ('a/b**c/d' , fake_base_dir = '/home/michael' )
129133 self .assertTrue (matches ('/home/michael/a/bc/d' ))
130134 self .assertTrue (matches ('/home/michael/a/bXc/d' ))
131135 self .assertTrue (matches ('/home/michael/a/bbc/d' ))
@@ -136,15 +140,15 @@ def test_double_asterisk_without_slashes_handled_like_single_asterisk(self):
136140 self .assertFalse (matches ('/home/michael/a/bb/XX/cc/d' ))
137141
138142 def test_more_asterisks_handled_like_single_asterisk (self ):
139- matches = _parse_gitignore_string ('***a/b' , fake_base_dir = '/home/michael' )
143+ matches = self . _parse_gitignore_string ('***a/b' , fake_base_dir = '/home/michael' )
140144 self .assertTrue (matches ('/home/michael/XYZa/b' ))
141145 self .assertFalse (matches ('/home/michael/foo/a/b' ))
142- matches = _parse_gitignore_string ('a/b***' , fake_base_dir = '/home/michael' )
146+ matches = self . _parse_gitignore_string ('a/b***' , fake_base_dir = '/home/michael' )
143147 self .assertTrue (matches ('/home/michael/a/bXYZ' ))
144148 self .assertFalse (matches ('/home/michael/a/b/foo' ))
145149
146150 def test_directory_only_negation (self ):
147- matches = _parse_gitignore_string ('''
151+ matches = self . _parse_gitignore_string ('''
148152data/**
149153!data/**/
150154!.gitkeep
@@ -160,18 +164,18 @@ def test_directory_only_negation(self):
160164 self .assertTrue (matches ('/home/michael/data/02_processed/processed_file.csv' ))
161165
162166 def test_single_asterisk (self ):
163- matches = _parse_gitignore_string ('*' , fake_base_dir = '/home/michael' )
167+ matches = self . _parse_gitignore_string ('*' , fake_base_dir = '/home/michael' )
164168 self .assertTrue (matches ('/home/michael/file.txt' ))
165169 self .assertTrue (matches ('/home/michael/directory' ))
166170 self .assertTrue (matches ('/home/michael/directory-trailing/' ))
167171
168172 def test_supports_path_type_argument (self ):
169- matches = _parse_gitignore_string ('file1\n !file2' , fake_base_dir = '/home/michael' )
173+ matches = self . _parse_gitignore_string ('file1\n !file2' , fake_base_dir = '/home/michael' )
170174 self .assertTrue (matches (Path ('/home/michael/file1' )))
171175 self .assertFalse (matches (Path ('/home/michael/file2' )))
172176
173177 def test_slash_in_range_does_not_match_dirs (self ):
174- matches = _parse_gitignore_string ('abc[X-Z/]def' , fake_base_dir = '/home/michael' )
178+ matches = self . _parse_gitignore_string ('abc[X-Z/]def' , fake_base_dir = '/home/michael' )
175179 self .assertFalse (matches ('/home/michael/abcdef' ))
176180 self .assertTrue (matches ('/home/michael/abcXdef' ))
177181 self .assertTrue (matches ('/home/michael/abcYdef' ))
@@ -190,7 +194,7 @@ def test_symlink_to_another_directory(self):
190194 with TemporaryDirectory () as project_dir , TemporaryDirectory () as another_dir :
191195 project_dir = Path (project_dir ).resolve ()
192196 another_dir = Path (another_dir ).resolve ()
193- matches = _parse_gitignore_string ('link' , fake_base_dir = project_dir )
197+ matches = self . _parse_gitignore_string ('link' , fake_base_dir = project_dir )
194198
195199 # Create a symlink to another directory.
196200 link = project_dir / 'link'
@@ -207,10 +211,32 @@ def test_symlink_to_another_directory(self):
207211 # files.
208212 self .assertTrue (matches (link ))
209213
210- def _parse_gitignore_string (data : str , fake_base_dir : str = None ):
211- with patch ('builtins.open' , mock_open (read_data = data )):
212- success = parse_gitignore (f'{ fake_base_dir } /.gitignore' , fake_base_dir )
213- return success
214+ def _parse_gitignore_string (self , data : str , fake_base_dir : str | None = None ):
215+ with patch ('builtins.open' , mock_open (read_data = data )):
216+ success = parse_gitignore (f'{ fake_base_dir } /.gitignore' , fake_base_dir )
217+ return success
218+
219+ @skipUnless (which ("git" ), "Git not installed" )
220+ class TestAgainstGit (Test ):
221+ """Testcase running tests against `git check-ignore`.
222+ This is not to test if git is right, this is to validate that all
223+ tests from the Test class are valid according to git.
224+ """
225+ def setUp (self ):
226+ self .tmpdir = tempfile .TemporaryDirectory ()
227+ run (["git" , "init" ], cwd = self .tmpdir .name )
228+
229+ def tearDown (self ):
230+ self .tmpdir .cleanup ()
231+
232+ def _parse_gitignore_string (self , data : str , fake_base_dir : Optional [str ] = None ):
233+ (Path (self .tmpdir .name ) / ".gitignore" ).write_text (data , encoding = "UTF-8" )
234+
235+ def matcher (path ):
236+ path = str (path ).replace (fake_base_dir + "/" , "" )
237+ return run (["git" , "check-ignore" , path ], cwd = self .tmpdir .name , check = False ).returncode == 0
238+
239+ return matcher
214240
215241if __name__ == '__main__' :
216242 main ()
0 commit comments