selection: add docstring
[git-cola.git] / test / git_test.py
blobdfe49a248e62306b0ac0376a1d36315591b636b8
1 """Test the cola.git module"""
2 import os
3 import pathlib
5 from cola import git
6 from cola.git import STDOUT
8 from .helper import patch
11 # 16k+1 bytes to exhaust any output buffers.
12 BUFFER_SIZE = (16 * 1024) + 1
15 @patch('cola.git.is_git_dir')
16 def test_find_git_dir_None(is_git_dir):
17 paths = git.find_git_directory(None)
19 assert not is_git_dir.called
20 assert paths.git_dir is None
21 assert paths.git_file is None
22 assert paths.worktree is None
25 @patch('cola.git.is_git_dir')
26 def test_find_git_dir_empty_string(is_git_dir):
27 paths = git.find_git_directory('')
29 assert not is_git_dir.called
30 assert paths.git_dir is None
31 assert paths.git_file is None
32 assert paths.worktree is None
35 @patch('cola.git.is_git_dir')
36 def test_find_git_dir_never_found(is_git_dir):
37 is_git_dir.return_value = False
39 paths = git.find_git_directory(str(pathlib.Path('/does/not/exist').resolve()))
41 assert is_git_dir.called
42 assert paths.git_dir is None
43 assert paths.git_file is None
44 assert paths.worktree is None
46 expect = 8
47 actual = is_git_dir.call_count
48 assert expect == actual
49 is_git_dir.assert_has_calls([
50 ((str(pathlib.Path('/does/not/exist').resolve()),), {}),
51 ((str(pathlib.Path('/does/not/exist/.git').resolve()),), {}),
52 ((str(pathlib.Path('/does/not').resolve()),), {}),
53 ((str(pathlib.Path('/does/not/.git').resolve()),), {}),
54 ((str(pathlib.Path('/does').resolve()),), {}),
55 ((str(pathlib.Path('/does/.git').resolve()),), {}),
56 ((str(pathlib.Path('/').resolve()),), {}),
57 ((str(pathlib.Path('/.git').resolve()),), {}),
61 @patch('cola.git.is_git_dir')
62 def test_find_git_dir_found_right_away(is_git_dir):
63 git_dir = str(pathlib.Path('/seems/to/exist/.git').resolve())
64 worktree = str(pathlib.Path('/seems/to/exist').resolve())
65 is_git_dir.return_value = True
67 paths = git.find_git_directory(git_dir)
69 assert is_git_dir.called
70 assert git_dir == paths.git_dir
71 assert paths.git_file is None
72 assert worktree == paths.worktree
75 @patch('cola.git.is_git_dir')
76 def test_find_git_does_discovery(is_git_dir):
77 git_dir = str(pathlib.Path('/the/root/.git').resolve())
78 worktree = str(pathlib.Path('/the/root').resolve())
79 is_git_dir.side_effect = lambda x: x == git_dir
81 paths = git.find_git_directory('/the/root/sub/dir')
83 assert git_dir == paths.git_dir
84 assert paths.git_file is None
85 assert worktree == paths.worktree
88 @patch('cola.git.read_git_file')
89 @patch('cola.git.is_git_file')
90 @patch('cola.git.is_git_dir')
91 def test_find_git_honors_git_files(is_git_dir, is_git_file, read_git_file):
92 git_file = str(pathlib.Path('/the/root/.git').resolve())
93 worktree = str(pathlib.Path('/the/root').resolve())
94 git_dir = str(pathlib.Path('/super/module/.git/modules/root').resolve())
96 is_git_dir.side_effect = lambda x: x == git_file
97 is_git_file.side_effect = lambda x: x == git_file
98 read_git_file.return_value = git_dir
100 paths = git.find_git_directory(str(pathlib.Path('/the/root/sub/dir').resolve()))
102 assert git_dir == paths.git_dir
103 assert git_file == paths.git_file
104 assert worktree == paths.worktree
106 expect = 6
107 actual = is_git_dir.call_count
108 assert expect == actual
109 is_git_dir.assert_has_calls([
110 ((str(pathlib.Path('/the/root/sub/dir').resolve()),), {}),
111 ((str(pathlib.Path('/the/root/sub/dir/.git').resolve()),), {}),
112 ((str(pathlib.Path('/the/root/sub').resolve()),), {}),
113 ((str(pathlib.Path('/the/root/sub/.git').resolve()),), {}),
114 ((str(pathlib.Path('/the/root').resolve()),), {}),
115 ((str(pathlib.Path('/the/root/.git').resolve()),), {}),
117 read_git_file.assert_called_once_with(git_file)
120 @patch('cola.core.getenv')
121 @patch('cola.git.is_git_dir')
122 def test_find_git_honors_ceiling_dirs(is_git_dir, getenv):
123 git_dir = str(pathlib.Path('/ceiling/.git').resolve())
124 ceiling = os.pathsep.join(
125 str(pathlib.Path(path).resolve())
126 for path in ('/tmp', '/ceiling', '/other/ceiling')
128 is_git_dir.side_effect = lambda x: x == git_dir
130 def mock_getenv(k, v=None):
131 if k == 'GIT_CEILING_DIRECTORIES':
132 return ceiling
133 return v
135 getenv.side_effect = mock_getenv
136 paths = git.find_git_directory(str(pathlib.Path('/ceiling/sub/dir').resolve()))
138 assert paths.git_dir is None
139 assert paths.git_file is None
140 assert paths.worktree is None
141 assert is_git_dir.call_count == 4
142 is_git_dir.assert_has_calls([
143 ((str(pathlib.Path('/ceiling/sub/dir').resolve()),), {}),
144 ((str(pathlib.Path('/ceiling/sub/dir/.git').resolve()),), {}),
145 ((str(pathlib.Path('/ceiling/sub').resolve()),), {}),
146 ((str(pathlib.Path('/ceiling/sub/.git').resolve()),), {}),
150 @patch('cola.core.islink')
151 @patch('cola.core.isdir')
152 @patch('cola.core.isfile')
153 def test_is_git_dir_finds_linked_repository(isfile, isdir, islink):
154 dirs = {
155 str(pathlib.Path(directory).resolve())
156 for directory in [
157 '/foo',
158 '/foo/.git',
159 '/foo/.git/refs',
160 '/foo/.git/objects',
161 '/foo/.git/worktrees',
162 '/foo/.git/worktrees/foo',
165 files = {
166 str(pathlib.Path(file).resolve())
167 for file in [
168 '/foo/.git/HEAD',
169 '/foo/.git/worktrees/foo/HEAD',
170 '/foo/.git/worktrees/foo/index',
171 '/foo/.git/worktrees/foo/commondir',
172 '/foo/.git/worktrees/foo/gitdir',
175 islink.return_value = False
176 isfile.side_effect = lambda x: x in files
177 isdir.side_effect = lambda x: x in dirs
179 assert git.is_git_dir(str(pathlib.Path('/foo/.git/worktrees/foo').resolve()))
180 assert git.is_git_dir(str(pathlib.Path('/foo/.git').resolve()))
183 @patch('cola.core.getenv')
184 @patch('cola.git.is_git_dir')
185 def test_find_git_worktree_from_GIT_DIR(is_git_dir, getenv):
186 git_dir = str(pathlib.Path('/repo/.git').resolve())
187 worktree = str(pathlib.Path('/repo').resolve())
188 is_git_dir.return_value = True
189 getenv.side_effect = lambda x: x == 'GIT_DIR' and git_dir or None
191 paths = git.find_git_directory(git_dir)
192 assert is_git_dir.called
193 assert git_dir == paths.git_dir
194 assert paths.git_file is None
195 assert worktree == paths.worktree
198 @patch('cola.git.is_git_dir')
199 def test_finds_no_worktree_from_bare_repo(is_git_dir):
200 git_dir = str(pathlib.Path('/repos/bare.git').resolve())
201 worktree = None
202 is_git_dir.return_value = True
204 paths = git.find_git_directory(git_dir)
205 assert is_git_dir.called
206 assert git_dir == paths.git_dir
207 assert paths.git_file is None
208 assert worktree == paths.worktree
211 @patch('cola.core.getenv')
212 @patch('cola.git.is_git_dir')
213 def test_find_git_directory_uses_GIT_WORK_TREE(is_git_dir, getenv):
214 git_dir = str(pathlib.Path('/repo/worktree/.git').resolve())
215 worktree = str(pathlib.Path('/repo/worktree').resolve())
217 def is_git_dir_func(path):
218 return path == git_dir
220 is_git_dir.side_effect = is_git_dir_func
222 def getenv_func(name):
223 if name == 'GIT_WORK_TREE':
224 return worktree
225 return None
227 getenv.side_effect = getenv_func
229 paths = git.find_git_directory(worktree)
230 assert is_git_dir.called
231 assert git_dir == paths.git_dir
232 assert paths.git_file is None
233 assert worktree == paths.worktree
236 @patch('cola.core.getenv')
237 @patch('cola.git.is_git_dir')
238 def test_uses_cwd_for_worktree_with_GIT_DIR(is_git_dir, getenv):
239 git_dir = str(pathlib.Path('/repo/.yadm/repo.git').resolve())
240 worktree = str(pathlib.Path('/repo').resolve())
242 def getenv_func(name):
243 if name == 'GIT_DIR':
244 return git_dir
245 return None
247 getenv.side_effect = getenv_func
249 def is_git_dir_func(path):
250 return path == git_dir
252 is_git_dir.side_effect = is_git_dir_func
254 paths = git.find_git_directory(worktree)
255 assert is_git_dir.called
256 assert getenv.called
257 assert git_dir == paths.git_dir
258 assert paths.git_file is None
259 assert worktree == paths.worktree
262 def test_transform_kwargs_empty():
263 expect = []
264 actual = git.transform_kwargs(foo=None, bar=False)
265 assert expect == actual
268 def test_transform_kwargs_single_dash_from_True():
269 """Single dash for one-character True"""
270 expect = ['-a']
271 actual = git.transform_kwargs(a=True)
272 assert expect == actual
275 def test_transform_kwargs_no_single_dash_from_False():
276 """No single-dash for False"""
277 expect = []
278 actual = git.transform_kwargs(a=False)
279 assert expect == actual
282 def test_transform_kwargs_double_dash_from_True():
283 """Double-dash for longer True"""
284 expect = ['--abc']
285 actual = git.transform_kwargs(abc=True)
286 assert expect == actual
289 def test_transform_kwargs_no_double_dash_from_True():
290 """No double-dash for False"""
291 expect = []
292 actual = git.transform_kwargs(abc=False)
293 assert expect == actual
296 def test_transform_kwargs_single_dash_int():
297 expect = ['-a1']
298 actual = git.transform_kwargs(a=1)
299 assert expect == actual
302 def test_transform_kwargs_double_dash_int():
303 expect = ['--abc=1']
304 actual = git.transform_kwargs(abc=1)
305 assert expect == actual
308 def test_transform_kwargs_single_dash_float():
309 expect = ['-a1.5']
310 actual = git.transform_kwargs(a=1.5)
311 assert expect == actual
314 def test_transform_kwargs_double_dash_float():
315 expect = ['--abc=1.5']
316 actual = git.transform_kwargs(abc=1.5)
317 assert expect == actual
320 def test_transform_kwargs_single_dash_string():
321 expect = ['-abc']
322 actual = git.transform_kwargs(a='bc')
323 assert expect == actual
326 def test_transform_double_single_dash_string():
327 expect = ['--abc=def']
328 actual = git.transform_kwargs(abc='def')
329 assert expect == actual
332 def test_version():
333 """Test running 'git version'"""
334 gitcmd = git.Git()
335 version = gitcmd.version()[STDOUT]
336 assert version.startswith('git version')
339 def test_stdout():
340 """Test overflowing the stdout buffer"""
341 # Write to stdout only
342 code = r'import sys; value = "\0" * %d; sys.stdout.write(value);' % BUFFER_SIZE
343 status, out, err = git.Git.execute(['python', '-c', code], _raw=True)
345 assert status == 0
346 expect = BUFFER_SIZE
347 actual = len(out)
348 assert expect == actual
350 expect = 0
351 actual = len(err)
352 assert expect == actual
355 def test_stderr():
356 """Test that stderr is seen"""
357 # Write to stderr and capture it
358 code = (
359 r'import sys;' r'value = "\0" * %d;' r'sys.stderr.write(value);'
360 ) % BUFFER_SIZE
362 status, out, err = git.Git.execute(['python', '-c', code], _raw=True)
364 expect = 0
365 actual = status
366 assert expect == actual
368 expect = 0
369 actual = len(out)
370 assert expect == actual
372 expect = BUFFER_SIZE
373 actual = len(err)
374 assert expect == actual
377 def test_stdout_and_stderr():
378 """Test ignoring stderr when stdout+stderr are provided (v2)"""
379 # Write to stdout and stderr but only capture stdout
380 code = (
381 r'import sys;'
382 r'value = "\0" * %d;'
383 r'sys.stdout.write(value);'
384 r'sys.stderr.write(value);'
385 ) % BUFFER_SIZE
387 status, out, err = git.Git.execute(['python', '-c', code], _raw=True)
389 expect = 0
390 actual = status
391 assert expect == actual
393 expect = BUFFER_SIZE
394 actual = len(out)
395 assert expect == actual
397 actual = len(err)
398 assert expect == actual
401 def test_it_doesnt_deadlock():
402 """Test that we don't deadlock with both stderr and stdout"""
403 code = (
404 r'import sys;'
405 r'value = "\0" * %d;'
406 r'sys.stderr.write(value);'
407 r'sys.stdout.write(value);'
408 ) % BUFFER_SIZE
410 status, out, err = git.Git.execute(['python', '-c', code], _raw=True)
412 expect = 0
413 actual = status
414 assert expect == actual
416 expect = '\0' * BUFFER_SIZE
417 actual = out
418 assert expect == actual
420 actual = err
421 assert expect == actual