gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / tests / compat / utils.py @ 959
History | View | Annotate | Download (8.69 KB)
1 |
# utils.py -- Git compatibility utilities
|
---|---|
2 |
# Copyright (C) 2010 Google, Inc.
|
3 |
#
|
4 |
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
|
5 |
# General Public License as public by the Free Software Foundation; version 2.0
|
6 |
# or (at your option) any later version. You can redistribute it and/or
|
7 |
# modify it under the terms of either of these two licenses.
|
8 |
#
|
9 |
# Unless required by applicable law or agreed to in writing, software
|
10 |
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
# See the License for the specific language governing permissions and
|
13 |
# limitations under the License.
|
14 |
#
|
15 |
# You should have received a copy of the licenses; if not, see
|
16 |
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
|
17 |
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
|
18 |
# License, Version 2.0.
|
19 |
#
|
20 |
|
21 |
"""Utilities for interacting with cgit."""
|
22 |
|
23 |
import errno |
24 |
import functools |
25 |
import os |
26 |
import shutil |
27 |
import socket |
28 |
import stat |
29 |
import subprocess |
30 |
import sys |
31 |
import tempfile |
32 |
import time |
33 |
|
34 |
from dulwich.repo import Repo |
35 |
from dulwich.protocol import TCP_GIT_PORT |
36 |
|
37 |
from dulwich.tests import ( |
38 |
SkipTest, |
39 |
TestCase, |
40 |
) |
41 |
|
42 |
_DEFAULT_GIT = 'git'
|
43 |
_VERSION_LEN = 4
|
44 |
_REPOS_DATA_DIR = os.path.abspath(os.path.join( |
45 |
os.path.dirname(__file__), os.pardir, 'data', 'repos')) |
46 |
|
47 |
|
48 |
def git_version(git_path=_DEFAULT_GIT): |
49 |
"""Attempt to determine the version of git currently installed.
|
50 |
|
51 |
:param git_path: Path to the git executable; defaults to the version in
|
52 |
the system path.
|
53 |
:return: A tuple of ints of the form (major, minor, point, sub-point), or
|
54 |
None if no git installation was found.
|
55 |
"""
|
56 |
try:
|
57 |
output = run_git_or_fail(['--version'], git_path=git_path)
|
58 |
except OSError: |
59 |
return None |
60 |
version_prefix = b'git version '
|
61 |
if not output.startswith(version_prefix): |
62 |
return None |
63 |
|
64 |
parts = output[len(version_prefix):].split(b'.') |
65 |
nums = [] |
66 |
for part in parts: |
67 |
try:
|
68 |
nums.append(int(part))
|
69 |
except ValueError: |
70 |
break
|
71 |
|
72 |
while len(nums) < _VERSION_LEN: |
73 |
nums.append(0)
|
74 |
return tuple(nums[:_VERSION_LEN]) |
75 |
|
76 |
|
77 |
def require_git_version(required_version, git_path=_DEFAULT_GIT): |
78 |
"""Require git version >= version, or skip the calling test.
|
79 |
|
80 |
:param required_version: A tuple of ints of the form (major, minor, point,
|
81 |
sub-point); ommitted components default to 0.
|
82 |
:param git_path: Path to the git executable; defaults to the version in
|
83 |
the system path.
|
84 |
:raise ValueError: if the required version tuple has too many parts.
|
85 |
:raise SkipTest: if no suitable git version was found at the given path.
|
86 |
"""
|
87 |
found_version = git_version(git_path=git_path) |
88 |
if found_version is None: |
89 |
raise SkipTest('Test requires git >= %s, but c git not found' % |
90 |
(required_version, )) |
91 |
|
92 |
if len(required_version) > _VERSION_LEN: |
93 |
raise ValueError('Invalid version tuple %s, expected %i parts' % |
94 |
(required_version, _VERSION_LEN)) |
95 |
|
96 |
required_version = list(required_version)
|
97 |
while len(found_version) < len(required_version): |
98 |
required_version.append(0)
|
99 |
required_version = tuple(required_version)
|
100 |
|
101 |
if found_version < required_version:
|
102 |
required_version = '.'.join(map(str, required_version)) |
103 |
found_version = '.'.join(map(str, found_version)) |
104 |
raise SkipTest('Test requires git >= %s, found %s' % |
105 |
(required_version, found_version)) |
106 |
|
107 |
|
108 |
def run_git(args, git_path=_DEFAULT_GIT, input=None, capture_stdout=False, |
109 |
**popen_kwargs): |
110 |
"""Run a git command.
|
111 |
|
112 |
Input is piped from the input parameter and output is sent to the standard
|
113 |
streams, unless capture_stdout is set.
|
114 |
|
115 |
:param args: A list of args to the git command.
|
116 |
:param git_path: Path to to the git executable.
|
117 |
:param input: Input data to be sent to stdin.
|
118 |
:param capture_stdout: Whether to capture and return stdout.
|
119 |
:param popen_kwargs: Additional kwargs for subprocess.Popen;
|
120 |
stdin/stdout args are ignored.
|
121 |
:return: A tuple of (returncode, stdout contents). If capture_stdout is
|
122 |
False, None will be returned as stdout contents.
|
123 |
:raise OSError: if the git executable was not found.
|
124 |
"""
|
125 |
|
126 |
env = popen_kwargs.pop('env', {})
|
127 |
env['LC_ALL'] = env['LANG'] = 'C' |
128 |
|
129 |
args = [git_path] + args |
130 |
popen_kwargs['stdin'] = subprocess.PIPE
|
131 |
if capture_stdout:
|
132 |
popen_kwargs['stdout'] = subprocess.PIPE
|
133 |
else:
|
134 |
popen_kwargs.pop('stdout', None) |
135 |
p = subprocess.Popen(args, env=env, **popen_kwargs) |
136 |
stdout, stderr = p.communicate(input=input)
|
137 |
return (p.returncode, stdout)
|
138 |
|
139 |
|
140 |
def run_git_or_fail(args, git_path=_DEFAULT_GIT, input=None, **popen_kwargs): |
141 |
"""Run a git command, capture stdout/stderr, and fail if git fails."""
|
142 |
if 'stderr' not in popen_kwargs: |
143 |
popen_kwargs['stderr'] = subprocess.STDOUT
|
144 |
returncode, stdout = run_git(args, git_path=git_path, input=input,
|
145 |
capture_stdout=True, **popen_kwargs)
|
146 |
if returncode != 0: |
147 |
raise AssertionError("git with args %r failed with %d: %r" % ( |
148 |
args, returncode, stdout)) |
149 |
return stdout
|
150 |
|
151 |
|
152 |
def import_repo_to_dir(name): |
153 |
"""Import a repo from a fast-export file in a temporary directory.
|
154 |
|
155 |
These are used rather than binary repos for compat tests because they are
|
156 |
more compact and human-editable, and we already depend on git.
|
157 |
|
158 |
:param name: The name of the repository export file, relative to
|
159 |
dulwich/tests/data/repos.
|
160 |
:returns: The path to the imported repository.
|
161 |
"""
|
162 |
temp_dir = tempfile.mkdtemp() |
163 |
export_path = os.path.join(_REPOS_DATA_DIR, name) |
164 |
temp_repo_dir = os.path.join(temp_dir, name) |
165 |
export_file = open(export_path, 'rb') |
166 |
run_git_or_fail(['init', '--quiet', '--bare', temp_repo_dir]) |
167 |
run_git_or_fail(['fast-import'], input=export_file.read(),
|
168 |
cwd=temp_repo_dir) |
169 |
export_file.close() |
170 |
return temp_repo_dir
|
171 |
|
172 |
|
173 |
def check_for_daemon(limit=10, delay=0.1, timeout=0.1, port=TCP_GIT_PORT): |
174 |
"""Check for a running TCP daemon.
|
175 |
|
176 |
Defaults to checking 10 times with a delay of 0.1 sec between tries.
|
177 |
|
178 |
:param limit: Number of attempts before deciding no daemon is running.
|
179 |
:param delay: Delay between connection attempts.
|
180 |
:param timeout: Socket timeout for connection attempts.
|
181 |
:param port: Port on which we expect the daemon to appear.
|
182 |
:returns: A boolean, true if a daemon is running on the specified port,
|
183 |
false if not.
|
184 |
"""
|
185 |
for _ in range(limit): |
186 |
time.sleep(delay) |
187 |
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
188 |
s.settimeout(delay) |
189 |
try:
|
190 |
s.connect(('localhost', port))
|
191 |
return True |
192 |
except socket.timeout:
|
193 |
pass
|
194 |
except socket.error as e: |
195 |
if getattr(e, 'errno', False) and e.errno != errno.ECONNREFUSED: |
196 |
raise
|
197 |
elif e.args[0] != errno.ECONNREFUSED: |
198 |
raise
|
199 |
finally:
|
200 |
s.close() |
201 |
return False |
202 |
|
203 |
|
204 |
class CompatTestCase(TestCase): |
205 |
"""Test case that requires git for compatibility checks.
|
206 |
|
207 |
Subclasses can change the git version required by overriding
|
208 |
min_git_version.
|
209 |
"""
|
210 |
|
211 |
min_git_version = (1, 5, 0) |
212 |
|
213 |
def setUp(self): |
214 |
super(CompatTestCase, self).setUp() |
215 |
require_git_version(self.min_git_version)
|
216 |
|
217 |
def assertObjectStoreEqual(self, store1, store2): |
218 |
self.assertEqual(sorted(set(store1)), sorted(set(store2))) |
219 |
|
220 |
def assertReposEqual(self, repo1, repo2): |
221 |
self.assertEqual(repo1.get_refs(), repo2.get_refs())
|
222 |
self.assertObjectStoreEqual(repo1.object_store, repo2.object_store)
|
223 |
|
224 |
def assertReposNotEqual(self, repo1, repo2): |
225 |
refs1 = repo1.get_refs() |
226 |
objs1 = set(repo1.object_store)
|
227 |
refs2 = repo2.get_refs() |
228 |
objs2 = set(repo2.object_store)
|
229 |
self.assertFalse(refs1 == refs2 and objs1 == objs2) |
230 |
|
231 |
def import_repo(self, name): |
232 |
"""Import a repo from a fast-export file in a temporary directory.
|
233 |
|
234 |
:param name: The name of the repository export file, relative to
|
235 |
dulwich/tests/data/repos.
|
236 |
:returns: An initialized Repo object that lives in a temporary directory.
|
237 |
"""
|
238 |
path = import_repo_to_dir(name) |
239 |
repo = Repo(path) |
240 |
def cleanup(): |
241 |
repo.close() |
242 |
rmtree_ro(os.path.dirname(path.rstrip(os.sep))) |
243 |
self.addCleanup(cleanup)
|
244 |
return repo
|
245 |
|
246 |
|
247 |
if sys.platform == 'win32': |
248 |
def remove_ro(action, name, exc): |
249 |
os.chmod(name, stat.S_IWRITE) |
250 |
os.remove(name) |
251 |
|
252 |
rmtree_ro = functools.partial(shutil.rmtree, onerror=remove_ro) |
253 |
else:
|
254 |
rmtree_ro = shutil.rmtree |