Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit adc35f2

Browse filesBrowse files
committed
test: rework git-daemon helper wrapper
+ Extensive use of contextlib `ExitStack`. - DO NOT LEAVE DIRS ON DISK on failures (impossible now that using tempfile standard files). + cmd: Use subprocess.DEVNULL instead of opening file. + PY2: Add `contextlib2` and `backports.tempfile` deps.
1 parent 6310480 commit adc35f2
Copy full SHA for adc35f2

9 files changed

+261
-235
lines changed

‎.appveyor.yml

Copy file name to clipboardExpand all lines: .appveyor.yml
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ install:
5252
conda info -a &
5353
conda install --yes --quiet pip
5454
)
55-
- pip install nose ddt wheel codecov
55+
- pip install nose ddt wheel codecov
5656
- IF "%PYTHON_VERSION%"=="2.7" (
57-
pip install mock
57+
pip install mock contextlib2 backports.tempfile
5858
)
5959

6060
## Copied from `init-tests-after-clone.sh`.

‎.travis.yml

Copy file name to clipboardExpand all lines: .travis.yml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ install:
1818
- git submodule update --init --recursive
1919
- git fetch --tags
2020
- pip install codecov flake8 ddt sphinx
21+
- if [ "$TRAVIS_PYTHON_VERSION" == '2.7' ]; then pip install mock contextlib2 backports.tempfile; fi
2122

2223
# generate some reflog as git-python tests need it (in master)
2324
- ./init-tests-after-clone.sh

‎git/test/lib/helper.py

Copy file name to clipboardExpand all lines: git/test/lib/helper.py
+143-133Lines changed: 143 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,39 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
from __future__ import print_function
77

8+
import contextlib
89
from functools import wraps
910
import io
1011
import logging
1112
import os
12-
import tempfile
13+
import tempfile # @UnusedImport
1314
import textwrap
1415
import time
1516
from unittest import TestCase
1617

1718
from git.compat import string_types, is_win
18-
from git.util import rmtree
19-
19+
from git.util import rmtree, cwd
2020
import os.path as osp
2121

2222

23+
try:
24+
from unittest import mock
25+
from contextlib import ExitStack
26+
from tempfile import TemporaryDirectory
27+
except ImportError: # PY2
28+
import mock
29+
from contextlib2 import ExitStack # @UnusedImport
30+
from backports.tempfile import TemporaryDirectory # @UnusedImport
31+
32+
2333
ospd = osp.dirname
2434

2535
GIT_REPO = os.environ.get("GIT_PYTHON_TEST_GIT_REPO_BASE", ospd(ospd(ospd(ospd(__file__)))))
2636
GIT_DAEMON_PORT = os.environ.get("GIT_PYTHON_TEST_GIT_DAEMON_PORT", "19418")
2737

2838
__all__ = (
2939
'fixture_path', 'fixture', 'StringProcessAdapter',
30-
'with_rw_directory', 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase',
40+
'with_rw_directory', 'with_rw_repo', 'rw_and_rw_remote_repos', 'TestBase', 'TestCase',
3141
'GIT_REPO', 'GIT_DAEMON_PORT'
3242
)
3343

@@ -162,39 +172,91 @@ def repo_creator(self):
162172
return argument_passer
163173

164174

175+
@contextlib.contextmanager
165176
def launch_git_daemon(base_path, ip, port):
166-
from git import Git
167-
if is_win:
168-
## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
169-
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
170-
# and then CANNOT DIE!
171-
# So, invoke it as a single command.
172-
## Cygwin-git has no daemon. But it can use MINGW's.
173-
#
174-
daemon_cmd = ['git-daemon',
175-
'--enable=receive-pack',
176-
'--listen=%s' % ip,
177-
'--port=%s' % port,
178-
'--base-path=%s' % base_path,
179-
base_path]
180-
gd = Git().execute(daemon_cmd, as_process=True)
181-
else:
182-
gd = Git().daemon(base_path,
183-
enable='receive-pack',
184-
listen=ip,
185-
port=port,
186-
base_path=base_path,
187-
as_process=True)
188-
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
189-
time.sleep(0.5)
190-
return gd
191-
192-
193-
def with_rw_and_rw_remote_repo(working_tree_ref):
177+
from git import Git # Avoid circular deps.
178+
179+
gd_launched = False
180+
try:
181+
if is_win:
182+
## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
183+
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
184+
# and then CANNOT DIE!
185+
# So, invoke it as a single command.
186+
## Cygwin-git has no daemon. But it can use MINGW's.
187+
#
188+
daemon_cmd = ['git-daemon',
189+
'--enable=receive-pack',
190+
'--listen=%s' % ip,
191+
'--port=%s' % port,
192+
'--base-path=%s' % base_path,
193+
base_path]
194+
gd = Git().execute(daemon_cmd, as_process=True)
195+
else:
196+
gd = Git().daemon(base_path,
197+
enable='receive-pack',
198+
listen=ip,
199+
port=port,
200+
base_path=base_path,
201+
as_process=True)
202+
gd_launched = True
203+
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
204+
time.sleep(0.5 * (1 + is_win))
205+
206+
yield gd
207+
208+
except Exception as ex:
209+
msg = textwrap.dedent("""
210+
Launching git-daemon failed due to: %s
211+
Probably test will fail subsequently.
212+
213+
BUT you may start *git-daemon* manually with this command:"
214+
git daemon --enable=receive-pack --listen=%s --port=%s --base-path=%s %s
215+
You may also run the daemon on a different port by passing --port=<port>"
216+
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
217+
""")
218+
if is_win:
219+
msg += textwrap.dedent("""
220+
221+
On Windows,
222+
the `git-daemon.exe` must be in PATH.
223+
For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
224+
CYGWIN has no daemon, but if one exists, it gets along fine (but has also paths problems).""")
225+
log.warning(msg, ex, ip, port, base_path, base_path, exc_info=1)
226+
227+
yield mock.MagicMock() # @UndefinedVariable
228+
229+
finally:
230+
if gd_launched:
231+
try:
232+
log.debug("Killing git-daemon...")
233+
gd.proc.kill()
234+
except Exception as ex:
235+
## Either it has died (and we're here), or it won't die, again here...
236+
log.debug("Hidden error while Killing git-daemon: %s", ex, exc_info=1)
237+
238+
239+
@contextlib.contextmanager
240+
def tmp_clone(repo, clone_prefix, **clone_kwargs):
241+
def cleanup_clone(repo):
242+
repo.git.clear_cache()
243+
import gc
244+
gc.collect()
245+
246+
with ExitStack() as stack:
247+
clone_dir = stack.enter_context(TemporaryDirectory(prefix=clone_prefix))
248+
clone = repo.clone(clone_dir, **clone_kwargs)
249+
stack.callback(cleanup_clone, clone)
250+
251+
yield clone
252+
253+
254+
@contextlib.contextmanager
255+
def rw_and_rw_remote_repos(repo, working_tree_ref):
194256
"""
195-
Same as with_rw_repo, but also provides a writable remote repository from which the
196-
rw_repo has been forked as well as a handle for a git-daemon that may be started to
197-
run the remote_repo.
257+
A context-manager creating the same temporary-repo as `with_rw_repo` and in addition
258+
a writable remote non-bare repository from which the rw_repo has been forked as well as a handle
259+
for a git-daemon that may be started to run the remote_repo.
198260
The remote repository was cloned as bare repository from the rorepo, wheras
199261
the rw repo has a working tree and was cloned from the remote repository.
200262
@@ -203,11 +265,13 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
203265
and should be an inetd service that serves tempdir.gettempdir() and all
204266
directories in it.
205267
206-
The following scetch demonstrates this::
207-
rorepo ---<bare clone>---> rw_remote_repo ---<clone>---> rw_repo
268+
The following sketch demonstrates this::
269+
rorepo ---<bare clone>---> remote_repo ---<clone>---> rw_repo
270+
271+
It is used like that::
208272
209-
The test case needs to support the following signature::
210-
def case(self, rw_repo, rw_remote_repo)
273+
with rw_and_rw_remote_repos(origin_repo) as (rw_repo, remote_repo):
274+
...
211275
212276
This setup allows you to test push and pull scenarios and hooks nicely.
213277
@@ -218,105 +282,51 @@ def case(self, rw_repo, rw_remote_repo)
218282

219283
assert isinstance(working_tree_ref, string_types), "Decorator requires ref name for working tree checkout"
220284

221-
def argument_passer(func):
222-
223-
@wraps(func)
224-
def remote_repo_creator(self):
225-
remote_repo_dir = _mktemp("remote_repo_%s" % func.__name__)
226-
repo_dir = _mktemp("remote_clone_non_bare_repo")
227-
228-
rw_remote_repo = self.rorepo.clone(remote_repo_dir, shared=True, bare=True)
229-
# recursive alternates info ?
230-
rw_repo = rw_remote_repo.clone(repo_dir, shared=True, bare=False, n=True)
231-
rw_repo.head.commit = working_tree_ref
232-
rw_repo.head.reference.checkout()
233-
234-
# prepare for git-daemon
235-
rw_remote_repo.daemon_export = True
236-
237-
# this thing is just annoying !
238-
with rw_remote_repo.config_writer() as crw:
239-
section = "daemon"
240-
try:
241-
crw.add_section(section)
242-
except Exception:
243-
pass
244-
crw.set(section, "receivepack", True)
245-
246-
# Initialize the remote - first do it as local remote and pull, then
247-
# we change the url to point to the daemon.
248-
d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir)
249-
d_remote.fetch()
250-
251-
base_path, rel_repo_dir = osp.split(remote_repo_dir)
252-
253-
remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir))
254-
with d_remote.config_writer as cw:
255-
cw.set('url', remote_repo_url)
256-
285+
with ExitStack() as stack:
286+
rw_remote_repo = stack.enter_context(tmp_clone(repo,
287+
clone_prefix="remote_bare_repo_%s",
288+
shared=True,
289+
bare=True))
290+
rw_repo = stack.enter_context(tmp_clone(rw_remote_repo,
291+
clone_prefix="remote_clone_non_bare_repo_",
292+
shared=True,
293+
bare=False,
294+
n=True))
295+
remote_repo_dir = rw_remote_repo.working_dir
296+
297+
# recursive alternates info ?
298+
rw_repo.head.commit = working_tree_ref
299+
rw_repo.head.reference.checkout()
300+
301+
# Allow git-daemon in bare-repo (https://git-scm.com/book/en/v2/Git-on-the-Server-Git-Daemon).
302+
rw_remote_repo.daemon_export = True
303+
304+
section = "daemon"
305+
with rw_remote_repo.config_writer() as crw:
257306
try:
258-
gd = launch_git_daemon(Git.polish_url(base_path), '127.0.0.1', GIT_DAEMON_PORT)
259-
except Exception as ex:
260-
if is_win:
261-
msg = textwrap.dedent("""
262-
The `git-daemon.exe` must be in PATH.
263-
For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
264-
CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
265-
Anyhow, alternatively try starting `git-daemon` manually:""")
266-
else:
267-
msg = "Please try starting `git-daemon` manually:"
268-
msg += textwrap.dedent("""
269-
git daemon --enable=receive-pack --base-path=%s %s
270-
You can also run the daemon on a different port by passing --port=<port>"
271-
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
272-
""" % (base_path, base_path))
273-
raise AssertionError(ex, msg)
274-
# END make assertion
275-
else:
276-
# Try listing remotes, to diagnose whether the daemon is up.
277-
rw_repo.git.ls_remote(d_remote)
278-
279-
# adjust working dir
280-
prev_cwd = os.getcwd()
281-
os.chdir(rw_repo.working_dir)
282-
283-
try:
284-
return func(self, rw_repo, rw_remote_repo)
285-
except:
286-
log.info("Keeping repos after failure: repo_dir = %s, remote_repo_dir = %s",
287-
repo_dir, remote_repo_dir)
288-
repo_dir = remote_repo_dir = None
289-
raise
290-
finally:
291-
os.chdir(prev_cwd)
307+
crw.add_section(section) # TODO: Add section if not exists.
308+
except Exception:
309+
pass
310+
crw.set(section, "receivepack", True)
292311

293-
finally:
294-
try:
295-
log.debug("Killing git-daemon...")
296-
gd.proc.kill()
297-
except:
298-
## Either it has died (and we're here), or it won't die, again here...
299-
pass
312+
# Initialize the non-bare repo - first do it as local remote and pull, then
313+
# we change the URL to point to the "relative" against "daemon's `--base-path`.
314+
#
315+
d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir)
316+
d_remote.fetch()
317+
base_path, rel_repo_dir = osp.split(remote_repo_dir)
318+
remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir))
319+
with d_remote.config_writer as cw:
320+
cw.set('url', remote_repo_url)
300321

301-
rw_repo.git.clear_cache()
302-
rw_remote_repo.git.clear_cache()
303-
rw_repo = rw_remote_repo = None
304-
import gc
305-
gc.collect()
306-
if repo_dir:
307-
rmtree(repo_dir)
308-
if remote_repo_dir:
309-
rmtree(remote_repo_dir)
322+
stack.enter_context(launch_git_daemon(Git.polish_url(base_path), '127.0.0.1', GIT_DAEMON_PORT))
310323

311-
if gd is not None:
312-
gd.proc.wait()
313-
# END cleanup
314-
# END bare repo creator
315-
return remote_repo_creator
316-
# END remote repo creator
317-
# END argument parser
324+
# Try listing remotes, to diagnose whether the daemon is up.
325+
rw_repo.git.ls_remote(d_remote)
318326

319-
return argument_passer
327+
# adjust working dir
328+
stack.enter_context(cwd(rw_repo.working_dir))
329+
yield rw_repo, rw_remote_repo
320330

321331
#} END decorators
322332

‎git/test/test_base.py

Copy file name to clipboardExpand all lines: git/test/test_base.py
+8-6Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
TestBase,
1515
assert_raises,
1616
with_rw_repo,
17-
with_rw_and_rw_remote_repo
17+
rw_and_rw_remote_repos
1818
)
1919
from git import (
2020
Blob,
@@ -25,6 +25,7 @@
2525
from git.objects.util import get_object_type_by_name
2626
from gitdb.util import hex_to_bin
2727
from git.compat import is_win
28+
from git.util import HIDE_WINDOWS_KNOWN_ERRORS
2829

2930

3031
class TestBase(TestBase):
@@ -110,11 +111,12 @@ def test_with_rw_repo(self, rw_repo):
110111
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
111112
assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib'))
112113

113-
@with_rw_and_rw_remote_repo('0.1.6')
114-
def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo):
115-
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
116-
assert rw_remote_repo.config_reader("repository").getboolean("core", "bare")
117-
assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib'))
114+
@skipIf(HIDE_WINDOWS_KNOWN_ERRORS, "FIXME: Freezes!")
115+
def test_with_rw_remote_and_rw_repo(self):
116+
with rw_and_rw_remote_repos(self.rorepo, '0.1.6') as (rw_repo, rw_remote_repo):
117+
assert not rw_repo.config_reader("repository").getboolean("core", "bare")
118+
assert rw_remote_repo.config_reader("repository").getboolean("core", "bare")
119+
assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib'))
118120

119121
@skipIf(sys.version_info < (3,) and is_win,
120122
"Unicode woes, see https://github.com/gitpython-developers/GitPython/pull/519")

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.