Mercurial > hg-git
comparison tests/hghave.py @ 1106:cca56bbea143
tests: copy hg test infra from hg repo @5cfdf6137af8
author | Tony Tung <ttung@chanzuckerberg.com <mailto:ttung@chanzuckerberg.com>> |
---|---|
date | Tue, 06 Feb 2018 16:34:28 -0800 |
parents | 8c6dc6a6f5d8 |
children |
comparison
equal
deleted
inserted
replaced
1105:bb255eaf3d14 | 1106:cca56bbea143 |
---|---|
1 import os, stat, socket | 1 from __future__ import absolute_import |
2 | |
3 import errno | |
4 import os | |
2 import re | 5 import re |
6 import socket | |
7 import stat | |
8 import subprocess | |
3 import sys | 9 import sys |
4 import tempfile | 10 import tempfile |
5 | 11 |
6 tempprefix = 'hg-hghave-' | 12 tempprefix = 'hg-hghave-' |
7 | 13 |
14 checks = { | |
15 "true": (lambda: True, "yak shaving"), | |
16 "false": (lambda: False, "nail clipper"), | |
17 } | |
18 | |
19 def check(name, desc): | |
20 """Registers a check function for a feature.""" | |
21 def decorator(func): | |
22 checks[name] = (func, desc) | |
23 return func | |
24 return decorator | |
25 | |
26 def checkvers(name, desc, vers): | |
27 """Registers a check function for each of a series of versions. | |
28 | |
29 vers can be a list or an iterator""" | |
30 def decorator(func): | |
31 def funcv(v): | |
32 def f(): | |
33 return func(v) | |
34 return f | |
35 for v in vers: | |
36 v = str(v) | |
37 f = funcv(v) | |
38 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v) | |
39 return func | |
40 return decorator | |
41 | |
42 def checkfeatures(features): | |
43 result = { | |
44 'error': [], | |
45 'missing': [], | |
46 'skipped': [], | |
47 } | |
48 | |
49 for feature in features: | |
50 negate = feature.startswith('no-') | |
51 if negate: | |
52 feature = feature[3:] | |
53 | |
54 if feature not in checks: | |
55 result['missing'].append(feature) | |
56 continue | |
57 | |
58 check, desc = checks[feature] | |
59 try: | |
60 available = check() | |
61 except Exception: | |
62 result['error'].append('hghave check failed: %s' % feature) | |
63 continue | |
64 | |
65 if not negate and not available: | |
66 result['skipped'].append('missing feature: %s' % desc) | |
67 elif negate and available: | |
68 result['skipped'].append('system supports %s' % desc) | |
69 | |
70 return result | |
71 | |
72 def require(features): | |
73 """Require that features are available, exiting if not.""" | |
74 result = checkfeatures(features) | |
75 | |
76 for missing in result['missing']: | |
77 sys.stderr.write('skipped: unknown feature: %s\n' % missing) | |
78 for msg in result['skipped']: | |
79 sys.stderr.write('skipped: %s\n' % msg) | |
80 for msg in result['error']: | |
81 sys.stderr.write('%s\n' % msg) | |
82 | |
83 if result['missing']: | |
84 sys.exit(2) | |
85 | |
86 if result['skipped'] or result['error']: | |
87 sys.exit(1) | |
88 | |
8 def matchoutput(cmd, regexp, ignorestatus=False): | 89 def matchoutput(cmd, regexp, ignorestatus=False): |
9 """Return True if cmd executes successfully and its output | 90 """Return the match object if cmd executes successfully and its output |
10 is matched by the supplied regular expression. | 91 is matched by the supplied regular expression. |
11 """ | 92 """ |
12 r = re.compile(regexp) | 93 r = re.compile(regexp) |
13 fh = os.popen(cmd) | 94 try: |
14 s = fh.read() | 95 p = subprocess.Popen( |
15 try: | 96 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
16 ret = fh.close() | 97 except OSError as e: |
17 except IOError: | 98 if e.errno != errno.ENOENT: |
18 # Happen in Windows test environment | 99 raise |
19 ret = 1 | 100 ret = -1 |
20 return (ignorestatus or ret is None) and r.search(s) | 101 ret = p.wait() |
21 | 102 s = p.stdout.read() |
103 return (ignorestatus or not ret) and r.search(s) | |
104 | |
105 @check("baz", "GNU Arch baz client") | |
22 def has_baz(): | 106 def has_baz(): |
23 return matchoutput('baz --version 2>&1', r'baz Bazaar version') | 107 return matchoutput('baz --version 2>&1', br'baz Bazaar version') |
24 | 108 |
109 @check("bzr", "Canonical's Bazaar client") | |
25 def has_bzr(): | 110 def has_bzr(): |
26 try: | 111 try: |
27 import bzrlib | 112 import bzrlib |
113 import bzrlib.bzrdir | |
114 import bzrlib.errors | |
115 import bzrlib.revision | |
116 import bzrlib.revisionspec | |
117 bzrlib.revisionspec.RevisionSpec | |
28 return bzrlib.__doc__ is not None | 118 return bzrlib.__doc__ is not None |
29 except ImportError: | 119 except (AttributeError, ImportError): |
30 return False | 120 return False |
31 | 121 |
32 def has_bzr114(): | 122 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,)) |
123 def has_bzr_range(v): | |
124 major, minor = v.split('.')[0:2] | |
33 try: | 125 try: |
34 import bzrlib | 126 import bzrlib |
35 return (bzrlib.__doc__ is not None | 127 return (bzrlib.__doc__ is not None |
36 and bzrlib.version_info[:2] >= (1, 14)) | 128 and bzrlib.version_info[:2] >= (int(major), int(minor))) |
37 except ImportError: | 129 except ImportError: |
38 return False | 130 return False |
39 | 131 |
132 @check("chg", "running with chg") | |
133 def has_chg(): | |
134 return 'CHGHG' in os.environ | |
135 | |
136 @check("cvs", "cvs client/server") | |
40 def has_cvs(): | 137 def has_cvs(): |
41 re = r'Concurrent Versions System.*?server' | 138 re = br'Concurrent Versions System.*?server' |
42 return matchoutput('cvs --version 2>&1', re) and not has_msys() | 139 return matchoutput('cvs --version 2>&1', re) and not has_msys() |
43 | 140 |
141 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)") | |
142 def has_cvs112(): | |
143 re = br'Concurrent Versions System \(CVS\) 1.12.*?server' | |
144 return matchoutput('cvs --version 2>&1', re) and not has_msys() | |
145 | |
146 @check("cvsnt", "cvsnt client/server") | |
147 def has_cvsnt(): | |
148 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)' | |
149 return matchoutput('cvsnt --version 2>&1', re) | |
150 | |
151 @check("darcs", "darcs client") | |
44 def has_darcs(): | 152 def has_darcs(): |
45 return matchoutput('darcs --version', r'2\.[2-9]', True) | 153 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True) |
46 | 154 |
155 @check("mtn", "monotone client (>= 1.0)") | |
47 def has_mtn(): | 156 def has_mtn(): |
48 return matchoutput('mtn --version', r'monotone', True) and not matchoutput( | 157 return matchoutput('mtn --version', br'monotone', True) and not matchoutput( |
49 'mtn --version', r'monotone 0\.', True) | 158 'mtn --version', br'monotone 0\.', True) |
50 | 159 |
160 @check("eol-in-paths", "end-of-lines in paths") | |
51 def has_eol_in_paths(): | 161 def has_eol_in_paths(): |
52 try: | 162 try: |
53 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r') | 163 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r') |
54 os.close(fd) | 164 os.close(fd) |
55 os.remove(path) | 165 os.remove(path) |
56 return True | 166 return True |
57 except (IOError, OSError): | 167 except (IOError, OSError): |
58 return False | 168 return False |
59 | 169 |
170 @check("execbit", "executable bit") | |
60 def has_executablebit(): | 171 def has_executablebit(): |
61 try: | 172 try: |
62 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH | 173 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
63 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) | 174 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) |
64 try: | 175 try: |
65 os.close(fh) | 176 os.close(fh) |
66 m = os.stat(fn).st_mode & 0777 | 177 m = os.stat(fn).st_mode & 0o777 |
67 new_file_has_exec = m & EXECFLAGS | 178 new_file_has_exec = m & EXECFLAGS |
68 os.chmod(fn, m ^ EXECFLAGS) | 179 os.chmod(fn, m ^ EXECFLAGS) |
69 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m) | 180 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m) |
70 finally: | 181 finally: |
71 os.unlink(fn) | 182 os.unlink(fn) |
72 except (IOError, OSError): | 183 except (IOError, OSError): |
73 # we don't care, the user probably won't be able to commit anyway | 184 # we don't care, the user probably won't be able to commit anyway |
74 return False | 185 return False |
75 return not (new_file_has_exec or exec_flags_cannot_flip) | 186 return not (new_file_has_exec or exec_flags_cannot_flip) |
76 | 187 |
188 @check("icasefs", "case insensitive file system") | |
77 def has_icasefs(): | 189 def has_icasefs(): |
78 # Stolen from mercurial.util | 190 # Stolen from mercurial.util |
79 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) | 191 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) |
80 os.close(fd) | 192 os.close(fd) |
81 try: | 193 try: |
90 except OSError: | 202 except OSError: |
91 return False | 203 return False |
92 finally: | 204 finally: |
93 os.remove(path) | 205 os.remove(path) |
94 | 206 |
95 def has_inotify(): | 207 @check("fifo", "named pipes") |
96 try: | |
97 import hgext.inotify.linux.watcher | |
98 except ImportError: | |
99 return False | |
100 name = tempfile.mktemp(dir='.', prefix=tempprefix) | |
101 sock = socket.socket(socket.AF_UNIX) | |
102 try: | |
103 sock.bind(name) | |
104 except socket.error, err: | |
105 return False | |
106 sock.close() | |
107 os.unlink(name) | |
108 return True | |
109 | |
110 def has_fifo(): | 208 def has_fifo(): |
111 if getattr(os, "mkfifo", None) is None: | 209 if getattr(os, "mkfifo", None) is None: |
112 return False | 210 return False |
113 name = tempfile.mktemp(dir='.', prefix=tempprefix) | 211 name = tempfile.mktemp(dir='.', prefix=tempprefix) |
114 try: | 212 try: |
116 os.unlink(name) | 214 os.unlink(name) |
117 return True | 215 return True |
118 except OSError: | 216 except OSError: |
119 return False | 217 return False |
120 | 218 |
219 @check("killdaemons", 'killdaemons.py support') | |
220 def has_killdaemons(): | |
221 return True | |
222 | |
223 @check("cacheable", "cacheable filesystem") | |
121 def has_cacheable_fs(): | 224 def has_cacheable_fs(): |
122 from mercurial import util | 225 from mercurial import util |
123 | 226 |
124 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) | 227 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) |
125 os.close(fd) | 228 os.close(fd) |
126 try: | 229 try: |
127 return util.cachestat(path).cacheable() | 230 return util.cachestat(path).cacheable() |
128 finally: | 231 finally: |
129 os.remove(path) | 232 os.remove(path) |
130 | 233 |
234 @check("lsprof", "python lsprof module") | |
131 def has_lsprof(): | 235 def has_lsprof(): |
132 try: | 236 try: |
133 import _lsprof | 237 import _lsprof |
134 return True | 238 _lsprof.Profiler # silence unused import warning |
135 except ImportError: | 239 return True |
136 return False | 240 except ImportError: |
137 | 241 return False |
138 def has_gettext(): | 242 |
139 return matchoutput('msgfmt --version', 'GNU gettext-tools') | 243 def gethgversion(): |
140 | 244 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)') |
141 def has_git(): | |
142 return matchoutput('git --version 2>&1', r'^git version') | |
143 | |
144 def has_docutils(): | |
145 try: | |
146 from docutils.core import publish_cmdline | |
147 return True | |
148 except ImportError: | |
149 return False | |
150 | |
151 def getsvnversion(): | |
152 m = matchoutput('svn --version 2>&1', r'^svn,\s+version\s+(\d+)\.(\d+)') | |
153 if not m: | 245 if not m: |
154 return (0, 0) | 246 return (0, 0) |
155 return (int(m.group(1)), int(m.group(2))) | 247 return (int(m.group(1)), int(m.group(2))) |
156 | 248 |
157 def has_svn15(): | 249 @checkvers("hg", "Mercurial >= %s", |
158 return getsvnversion() >= (1, 5) | 250 list([(1.0 * x) / 10 for x in range(9, 99)])) |
159 | 251 def has_hg_range(v): |
160 def has_svn13(): | 252 major, minor = v.split('.')[0:2] |
161 return getsvnversion() >= (1, 3) | 253 return gethgversion() >= (int(major), int(minor)) |
162 | 254 |
255 @check("hg08", "Mercurial >= 0.8") | |
256 def has_hg08(): | |
257 if checks["hg09"][0](): | |
258 return True | |
259 return matchoutput('hg help annotate 2>&1', '--date') | |
260 | |
261 @check("hg07", "Mercurial >= 0.7") | |
262 def has_hg07(): | |
263 if checks["hg08"][0](): | |
264 return True | |
265 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM') | |
266 | |
267 @check("hg06", "Mercurial >= 0.6") | |
268 def has_hg06(): | |
269 if checks["hg07"][0](): | |
270 return True | |
271 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version') | |
272 | |
273 @check("gettext", "GNU Gettext (msgfmt)") | |
274 def has_gettext(): | |
275 return matchoutput('msgfmt --version', br'GNU gettext-tools') | |
276 | |
277 @check("git", "git command line client") | |
278 def has_git(): | |
279 return matchoutput('git --version 2>&1', br'^git version') | |
280 | |
281 def getgitversion(): | |
282 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)') | |
283 if not m: | |
284 return (0, 0) | |
285 return (int(m.group(1)), int(m.group(2))) | |
286 | |
287 # https://github.com/git-lfs/lfs-test-server <https://github.com/git-lfs/lfs-test-server> | |
288 @check("lfs-test-server", "git-lfs test server") | |
289 def has_lfsserver(): | |
290 exe = 'lfs-test-server' | |
291 if has_windows(): | |
292 exe = 'lfs-test-server.exe' | |
293 return any( | |
294 os.access(os.path.join(path, exe), os.X_OK) | |
295 for path in os.environ["PATH"].split(os.pathsep) | |
296 ) | |
297 | |
298 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,)) | |
299 def has_git_range(v): | |
300 major, minor = v.split('.')[0:2] | |
301 return getgitversion() >= (int(major), int(minor)) | |
302 | |
303 @check("docutils", "Docutils text processing library") | |
304 def has_docutils(): | |
305 try: | |
306 import docutils.core | |
307 docutils.core.publish_cmdline # silence unused import | |
308 return True | |
309 except ImportError: | |
310 return False | |
311 | |
312 def getsvnversion(): | |
313 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)') | |
314 if not m: | |
315 return (0, 0) | |
316 return (int(m.group(1)), int(m.group(2))) | |
317 | |
318 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5)) | |
319 def has_svn_range(v): | |
320 major, minor = v.split('.')[0:2] | |
321 return getsvnversion() >= (int(major), int(minor)) | |
322 | |
323 @check("svn", "subversion client and admin tools") | |
163 def has_svn(): | 324 def has_svn(): |
164 return matchoutput('svn --version 2>&1', r'^svn, version') and \ | 325 return matchoutput('svn --version 2>&1', br'^svn, version') and \ |
165 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version') | 326 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version') |
166 | 327 |
328 @check("svn-bindings", "subversion python bindings") | |
167 def has_svn_bindings(): | 329 def has_svn_bindings(): |
168 try: | 330 try: |
169 import svn.core | 331 import svn.core |
170 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR | 332 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR |
171 if version < (1, 4): | 333 if version < (1, 4): |
172 return False | 334 return False |
173 return True | 335 return True |
174 except ImportError: | 336 except ImportError: |
175 return False | 337 return False |
176 | 338 |
339 @check("p4", "Perforce server and client") | |
177 def has_p4(): | 340 def has_p4(): |
178 return (matchoutput('p4 -V', r'Rev\. P4/') and | 341 return (matchoutput('p4 -V', br'Rev\. P4/') and |
179 matchoutput('p4d -V', r'Rev\. P4D/')) | 342 matchoutput('p4d -V', br'Rev\. P4D/')) |
180 | 343 |
344 @check("symlink", "symbolic links") | |
181 def has_symlink(): | 345 def has_symlink(): |
182 if getattr(os, "symlink", None) is None: | 346 if getattr(os, "symlink", None) is None: |
183 return False | 347 return False |
184 name = tempfile.mktemp(dir='.', prefix=tempprefix) | 348 name = tempfile.mktemp(dir='.', prefix=tempprefix) |
185 try: | 349 try: |
187 os.unlink(name) | 351 os.unlink(name) |
188 return True | 352 return True |
189 except (OSError, AttributeError): | 353 except (OSError, AttributeError): |
190 return False | 354 return False |
191 | 355 |
356 @check("hardlink", "hardlinks") | |
192 def has_hardlink(): | 357 def has_hardlink(): |
193 from mercurial import util | 358 from mercurial import util |
194 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) | 359 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) |
195 os.close(fh) | 360 os.close(fh) |
196 name = tempfile.mktemp(dir='.', prefix=tempprefix) | 361 name = tempfile.mktemp(dir='.', prefix=tempprefix) |
197 try: | 362 try: |
198 try: | 363 util.oslink(fn, name) |
199 util.oslink(fn, name) | 364 os.unlink(name) |
200 os.unlink(name) | 365 return True |
201 return True | 366 except OSError: |
202 except OSError: | 367 return False |
203 return False | |
204 finally: | 368 finally: |
205 os.unlink(fn) | 369 os.unlink(fn) |
206 | 370 |
371 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems") | |
372 def has_hardlink_whitelisted(): | |
373 from mercurial import util | |
374 try: | |
375 fstype = util.getfstype('.') | |
376 except OSError: | |
377 return False | |
378 return fstype in util._hardlinkfswhitelist | |
379 | |
380 @check("rmcwd", "can remove current working directory") | |
381 def has_rmcwd(): | |
382 ocwd = os.getcwd() | |
383 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix) | |
384 try: | |
385 os.chdir(temp) | |
386 # On Linux, 'rmdir .' isn't allowed, but the other names are okay. | |
387 # On Solaris and Windows, the cwd can't be removed by any names. | |
388 os.rmdir(os.getcwd()) | |
389 return True | |
390 except OSError: | |
391 return False | |
392 finally: | |
393 os.chdir(ocwd) | |
394 # clean up temp dir on platforms where cwd can't be removed | |
395 try: | |
396 os.rmdir(temp) | |
397 except OSError: | |
398 pass | |
399 | |
400 @check("tla", "GNU Arch tla client") | |
207 def has_tla(): | 401 def has_tla(): |
208 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision') | 402 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision') |
209 | 403 |
404 @check("gpg", "gpg client") | |
210 def has_gpg(): | 405 def has_gpg(): |
211 return matchoutput('gpg --version 2>&1', r'GnuPG') | 406 return matchoutput('gpg --version 2>&1', br'GnuPG') |
212 | 407 |
408 @check("gpg2", "gpg client v2") | |
409 def has_gpg2(): | |
410 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.') | |
411 | |
412 @check("gpg21", "gpg client v2.1+") | |
413 def has_gpg21(): | |
414 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)') | |
415 | |
416 @check("unix-permissions", "unix-style permissions") | |
213 def has_unix_permissions(): | 417 def has_unix_permissions(): |
214 d = tempfile.mkdtemp(dir='.', prefix=tempprefix) | 418 d = tempfile.mkdtemp(dir='.', prefix=tempprefix) |
215 try: | 419 try: |
216 fname = os.path.join(d, 'foo') | 420 fname = os.path.join(d, 'foo') |
217 for umask in (077, 007, 022): | 421 for umask in (0o77, 0o07, 0o22): |
218 os.umask(umask) | 422 os.umask(umask) |
219 f = open(fname, 'w') | 423 f = open(fname, 'w') |
220 f.close() | 424 f.close() |
221 mode = os.stat(fname).st_mode | 425 mode = os.stat(fname).st_mode |
222 os.unlink(fname) | 426 os.unlink(fname) |
223 if mode & 0777 != ~umask & 0666: | 427 if mode & 0o777 != ~umask & 0o666: |
224 return False | 428 return False |
225 return True | 429 return True |
226 finally: | 430 finally: |
227 os.rmdir(d) | 431 os.rmdir(d) |
228 | 432 |
433 @check("unix-socket", "AF_UNIX socket family") | |
434 def has_unix_socket(): | |
435 return getattr(socket, 'AF_UNIX', None) is not None | |
436 | |
437 @check("root", "root permissions") | |
438 def has_root(): | |
439 return getattr(os, 'geteuid', None) and os.geteuid() == 0 | |
440 | |
441 @check("pyflakes", "Pyflakes python linter") | |
229 def has_pyflakes(): | 442 def has_pyflakes(): |
230 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"", | 443 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"", |
231 r"<stdin>:1: 're' imported but unused", | 444 br"<stdin>:1: 're' imported but unused", |
232 True) | 445 True) |
233 | 446 |
447 @check("pylint", "Pylint python linter") | |
448 def has_pylint(): | |
449 return matchoutput("pylint --help", | |
450 br"Usage: pylint", | |
451 True) | |
452 | |
453 @check("clang-format", "clang-format C code formatter") | |
454 def has_clang_format(): | |
455 return matchoutput("clang-format --help", | |
456 br"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.") | |
457 | |
458 @check("jshint", "JSHint static code analysis tool") | |
459 def has_jshint(): | |
460 return matchoutput("jshint --version 2>&1", br"jshint v") | |
461 | |
462 @check("pygments", "Pygments source highlighting library") | |
234 def has_pygments(): | 463 def has_pygments(): |
235 try: | 464 try: |
236 import pygments | 465 import pygments |
237 return True | 466 pygments.highlight # silence unused import warning |
238 except ImportError: | 467 return True |
239 return False | 468 except ImportError: |
240 | 469 return False |
470 | |
471 @check("outer-repo", "outer repo") | |
241 def has_outer_repo(): | 472 def has_outer_repo(): |
242 # failing for other reasons than 'no repo' imply that there is a repo | 473 # failing for other reasons than 'no repo' imply that there is a repo |
243 return not matchoutput('hg root 2>&1', | 474 return not matchoutput('hg root 2>&1', |
244 r'abort: no repository found', True) | 475 br'abort: no repository found', True) |
245 | 476 |
477 @check("ssl", "ssl module available") | |
246 def has_ssl(): | 478 def has_ssl(): |
247 try: | 479 try: |
248 import ssl | 480 import ssl |
249 import OpenSSL | 481 ssl.CERT_NONE |
250 OpenSSL.SSL.Context | 482 return True |
251 return True | 483 except ImportError: |
252 except ImportError: | 484 return False |
253 return False | 485 |
254 | 486 @check("sslcontext", "python >= 2.7.9 ssl") |
487 def has_sslcontext(): | |
488 try: | |
489 import ssl | |
490 ssl.SSLContext | |
491 return True | |
492 except (ImportError, AttributeError): | |
493 return False | |
494 | |
495 @check("defaultcacerts", "can verify SSL certs by system's CA certs store") | |
496 def has_defaultcacerts(): | |
497 from mercurial import sslutil, ui as uimod | |
498 ui = uimod.ui.load() | |
499 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts | |
500 | |
501 @check("defaultcacertsloaded", "detected presence of loaded system CA certs") | |
502 def has_defaultcacertsloaded(): | |
503 import ssl | |
504 from mercurial import sslutil, ui as uimod | |
505 | |
506 if not has_defaultcacerts(): | |
507 return False | |
508 if not has_sslcontext(): | |
509 return False | |
510 | |
511 ui = uimod.ui.load() | |
512 cafile = sslutil._defaultcacerts(ui) | |
513 ctx = ssl.create_default_context() | |
514 if cafile: | |
515 ctx.load_verify_locations(cafile=cafile) | |
516 else: | |
517 ctx.load_default_certs() | |
518 | |
519 return len(ctx.get_ca_certs()) > 0 | |
520 | |
521 @check("tls1.2", "TLS 1.2 protocol support") | |
522 def has_tls1_2(): | |
523 from mercurial import sslutil | |
524 return 'tls1.2' in sslutil.supportedprotocols | |
525 | |
526 @check("windows", "Windows") | |
255 def has_windows(): | 527 def has_windows(): |
256 return os.name == 'nt' | 528 return os.name == 'nt' |
257 | 529 |
530 @check("system-sh", "system() uses sh") | |
258 def has_system_sh(): | 531 def has_system_sh(): |
259 return os.name != 'nt' | 532 return os.name != 'nt' |
260 | 533 |
534 @check("serve", "platform and python can manage 'hg serve -d'") | |
261 def has_serve(): | 535 def has_serve(): |
262 return os.name != 'nt' # gross approximation | 536 return True |
263 | 537 |
538 @check("test-repo", "running tests from repository") | |
539 def has_test_repo(): | |
540 t = os.environ["TESTDIR"] | |
541 return os.path.isdir(os.path.join(t, "..", ".hg")) | |
542 | |
543 @check("tic", "terminfo compiler and curses module") | |
264 def has_tic(): | 544 def has_tic(): |
265 return matchoutput('test -x "`which tic`"', '') | 545 try: |
266 | 546 import curses |
547 curses.COLOR_BLUE | |
548 return matchoutput('test -x "`which tic`"', br'') | |
549 except ImportError: | |
550 return False | |
551 | |
552 @check("msys", "Windows with MSYS") | |
267 def has_msys(): | 553 def has_msys(): |
268 return os.getenv('MSYSTEM') | 554 return os.getenv('MSYSTEM') |
269 | 555 |
270 checks = { | 556 @check("aix", "AIX") |
271 "true": (lambda: True, "yak shaving"), | 557 def has_aix(): |
272 "false": (lambda: False, "nail clipper"), | 558 return sys.platform.startswith("aix") |
273 "baz": (has_baz, "GNU Arch baz client"), | 559 |
274 "bzr": (has_bzr, "Canonical's Bazaar client"), | 560 @check("osx", "OS X") |
275 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"), | 561 def has_osx(): |
276 "cacheable": (has_cacheable_fs, "cacheable filesystem"), | 562 return sys.platform == 'darwin' |
277 "cvs": (has_cvs, "cvs client/server"), | 563 |
278 "darcs": (has_darcs, "darcs client"), | 564 @check("osxpackaging", "OS X packaging tools") |
279 "docutils": (has_docutils, "Docutils text processing library"), | 565 def has_osxpackaging(): |
280 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"), | 566 try: |
281 "execbit": (has_executablebit, "executable bit"), | 567 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1) |
282 "fifo": (has_fifo, "named pipes"), | 568 and matchoutput( |
283 "gettext": (has_gettext, "GNU Gettext (msgfmt)"), | 569 'productbuild', br'Usage: productbuild ', |
284 "git": (has_git, "git command line client"), | 570 ignorestatus=1) |
285 "gpg": (has_gpg, "gpg client"), | 571 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1) |
286 "hardlink": (has_hardlink, "hardlinks"), | 572 and matchoutput( |
287 "icasefs": (has_icasefs, "case insensitive file system"), | 573 'xar --help', br'Usage: xar', ignorestatus=1)) |
288 "inotify": (has_inotify, "inotify extension support"), | 574 except ImportError: |
289 "lsprof": (has_lsprof, "python lsprof module"), | 575 return False |
290 "mtn": (has_mtn, "monotone client (>= 1.0)"), | 576 |
291 "outer-repo": (has_outer_repo, "outer repo"), | 577 @check('linuxormacos', 'Linux or MacOS') |
292 "p4": (has_p4, "Perforce server and client"), | 578 def has_linuxormacos(): |
293 "pyflakes": (has_pyflakes, "Pyflakes python linter"), | 579 # This isn't a perfect test for MacOS. But it is sufficient for our needs. |
294 "pygments": (has_pygments, "Pygments source highlighting library"), | 580 return sys.platform.startswith(('linux', 'darwin')) |
295 "serve": (has_serve, "platform and python can manage 'hg serve -d'"), | 581 |
296 "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"), | 582 @check("docker", "docker support") |
297 "svn": (has_svn, "subversion client and admin tools"), | 583 def has_docker(): |
298 "svn13": (has_svn13, "subversion client and admin tools >= 1.3"), | 584 pat = br'A self-sufficient runtime for' |
299 "svn15": (has_svn15, "subversion client and admin tools >= 1.5"), | 585 if matchoutput('docker --help', pat): |
300 "svn-bindings": (has_svn_bindings, "subversion python bindings"), | 586 if 'linux' not in sys.platform: |
301 "symlink": (has_symlink, "symbolic links"), | 587 # TODO: in theory we should be able to test docker-based |
302 "system-sh": (has_system_sh, "system() uses sh"), | 588 # package creation on non-linux using boot2docker, but in |
303 "tic": (has_tic, "terminfo compiler"), | 589 # practice that requires extra coordination to make sure |
304 "tla": (has_tla, "GNU Arch tla client"), | 590 # $TESTTEMP is going to be visible at the same path to the |
305 "unix-permissions": (has_unix_permissions, "unix-style permissions"), | 591 # boot2docker VM. If we figure out how to verify that, we |
306 "windows": (has_windows, "Windows"), | 592 # can use the following instead of just saying False: |
307 "msys": (has_msys, "Windows with MSYS"), | 593 # return 'DOCKER_HOST' in os.environ |
308 } | 594 return False |
595 | |
596 return True | |
597 return False | |
598 | |
599 @check("debhelper", "debian packaging tools") | |
600 def has_debhelper(): | |
601 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first | |
602 # quote), so just accept anything in that spot. | |
603 dpkg = matchoutput('dpkg --version', | |
604 br"Debian .dpkg' package management program") | |
605 dh = matchoutput('dh --help', | |
606 br'dh is a part of debhelper.', ignorestatus=True) | |
607 dh_py2 = matchoutput('dh_python2 --help', | |
608 br'other supported Python versions') | |
609 # debuild comes from the 'devscripts' package, though you might want | |
610 # the 'build-debs' package instead, which has a dependency on devscripts. | |
611 debuild = matchoutput('debuild --help', | |
612 br'to run debian/rules with given parameter') | |
613 return dpkg and dh and dh_py2 and debuild | |
614 | |
615 @check("debdeps", | |
616 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)") | |
617 def has_debdeps(): | |
618 # just check exit status (ignoring output) | |
619 path = '%s/../contrib/debian/control' % os.environ['TESTDIR'] | |
620 return matchoutput('dpkg-checkbuilddeps %s' % path, br'') | |
621 | |
622 @check("demandimport", "demandimport enabled") | |
623 def has_demandimport(): | |
624 # chg disables demandimport intentionally for performance wins. | |
625 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable') | |
626 | |
627 @check("py3k", "running with Python 3.x") | |
628 def has_py3k(): | |
629 return 3 == sys.version_info[0] | |
630 | |
631 @check("py3exe", "a Python 3.x interpreter is available") | |
632 def has_python3exe(): | |
633 return 'PYTHON3' in os.environ | |
634 | |
635 @check("py3pygments", "Pygments available on Python 3.x") | |
636 def has_py3pygments(): | |
637 if has_py3k(): | |
638 return has_pygments() | |
639 elif has_python3exe(): | |
640 # just check exit status (ignoring output) | |
641 py3 = os.environ['PYTHON3'] | |
642 return matchoutput('%s -c "import pygments"' % py3, br'') | |
643 return False | |
644 | |
645 @check("pure", "running with pure Python code") | |
646 def has_pure(): | |
647 return any([ | |
648 os.environ.get("HGMODULEPOLICY") == "py", | |
649 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure", | |
650 ]) | |
651 | |
652 @check("slow", "allow slow tests (use --allow-slow-tests)") | |
653 def has_slow(): | |
654 return os.environ.get('HGTEST_SLOW') == 'slow' | |
655 | |
656 @check("hypothesis", "Hypothesis automated test generation") | |
657 def has_hypothesis(): | |
658 try: | |
659 import hypothesis | |
660 hypothesis.given | |
661 return True | |
662 except ImportError: | |
663 return False | |
664 | |
665 @check("unziplinks", "unzip(1) understands and extracts symlinks") | |
666 def unzip_understands_symlinks(): | |
667 return matchoutput('unzip --help', br'Info-ZIP') | |
668 | |
669 @check("zstd", "zstd Python module available") | |
670 def has_zstd(): | |
671 try: | |
672 import mercurial.zstd | |
673 mercurial.zstd.__version__ | |
674 return True | |
675 except ImportError: | |
676 return False | |
677 | |
678 @check("devfull", "/dev/full special file") | |
679 def has_dev_full(): | |
680 return os.path.exists('/dev/full') | |
681 | |
682 @check("virtualenv", "Python virtualenv support") | |
683 def has_virtualenv(): | |
684 try: | |
685 import virtualenv | |
686 virtualenv.ACTIVATE_SH | |
687 return True | |
688 except ImportError: | |
689 return False | |
690 | |
691 @check("fsmonitor", "running tests with fsmonitor") | |
692 def has_fsmonitor(): | |
693 return 'HGFSMONITOR_TESTS' in os.environ | |
694 | |
695 @check("fuzzywuzzy", "Fuzzy string matching library") | |
696 def has_fuzzywuzzy(): | |
697 try: | |
698 import fuzzywuzzy | |
699 fuzzywuzzy.__version__ | |
700 return True | |
701 except ImportError: | |
702 return False | |
703 | |
704 @check("clang-libfuzzer", "clang new enough to include libfuzzer") | |
705 def has_clang_libfuzzer(): | |
706 mat = matchoutput('clang --version', 'clang version (\d)') | |
707 if mat: | |
708 # libfuzzer is new in clang 6 | |
709 return int(mat.group(1)) > 5 | |
710 return False |