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