##// END OF EJS Templates
tests: check availability of pyflakes by trying to import pyflakes module...
Manuel Jacob -
r44987:aa0e1341 default
parent child Browse files
Show More
@@ -1,1073 +1,1076 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import distutils.version
3 import distutils.version
4 import os
4 import os
5 import re
5 import re
6 import socket
6 import socket
7 import stat
7 import stat
8 import subprocess
8 import subprocess
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 checks = {
14 checks = {
15 "true": (lambda: True, "yak shaving"),
15 "true": (lambda: True, "yak shaving"),
16 "false": (lambda: False, "nail clipper"),
16 "false": (lambda: False, "nail clipper"),
17 }
17 }
18
18
19 try:
19 try:
20 import msvcrt
20 import msvcrt
21
21
22 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
22 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
23 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
23 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
24 except ImportError:
24 except ImportError:
25 pass
25 pass
26
26
27 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
27 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
28 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
28 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
29
29
30 if sys.version_info[0] >= 3:
30 if sys.version_info[0] >= 3:
31
31
32 def _sys2bytes(p):
32 def _sys2bytes(p):
33 if p is None:
33 if p is None:
34 return p
34 return p
35 return p.encode('utf-8')
35 return p.encode('utf-8')
36
36
37 def _bytes2sys(p):
37 def _bytes2sys(p):
38 if p is None:
38 if p is None:
39 return p
39 return p
40 return p.decode('utf-8')
40 return p.decode('utf-8')
41
41
42
42
43 else:
43 else:
44
44
45 def _sys2bytes(p):
45 def _sys2bytes(p):
46 return p
46 return p
47
47
48 _bytes2sys = _sys2bytes
48 _bytes2sys = _sys2bytes
49
49
50
50
51 def check(name, desc):
51 def check(name, desc):
52 """Registers a check function for a feature."""
52 """Registers a check function for a feature."""
53
53
54 def decorator(func):
54 def decorator(func):
55 checks[name] = (func, desc)
55 checks[name] = (func, desc)
56 return func
56 return func
57
57
58 return decorator
58 return decorator
59
59
60
60
61 def checkvers(name, desc, vers):
61 def checkvers(name, desc, vers):
62 """Registers a check function for each of a series of versions.
62 """Registers a check function for each of a series of versions.
63
63
64 vers can be a list or an iterator.
64 vers can be a list or an iterator.
65
65
66 Produces a series of feature checks that have the form <name><vers> without
66 Produces a series of feature checks that have the form <name><vers> without
67 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
67 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
68 'py38', not 'py3.8' or 'py-38')."""
68 'py38', not 'py3.8' or 'py-38')."""
69
69
70 def decorator(func):
70 def decorator(func):
71 def funcv(v):
71 def funcv(v):
72 def f():
72 def f():
73 return func(v)
73 return func(v)
74
74
75 return f
75 return f
76
76
77 for v in vers:
77 for v in vers:
78 v = str(v)
78 v = str(v)
79 f = funcv(v)
79 f = funcv(v)
80 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
80 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
81 return func
81 return func
82
82
83 return decorator
83 return decorator
84
84
85
85
86 def checkfeatures(features):
86 def checkfeatures(features):
87 result = {
87 result = {
88 'error': [],
88 'error': [],
89 'missing': [],
89 'missing': [],
90 'skipped': [],
90 'skipped': [],
91 }
91 }
92
92
93 for feature in features:
93 for feature in features:
94 negate = feature.startswith('no-')
94 negate = feature.startswith('no-')
95 if negate:
95 if negate:
96 feature = feature[3:]
96 feature = feature[3:]
97
97
98 if feature not in checks:
98 if feature not in checks:
99 result['missing'].append(feature)
99 result['missing'].append(feature)
100 continue
100 continue
101
101
102 check, desc = checks[feature]
102 check, desc = checks[feature]
103 try:
103 try:
104 available = check()
104 available = check()
105 except Exception:
105 except Exception:
106 result['error'].append('hghave check failed: %s' % feature)
106 result['error'].append('hghave check failed: %s' % feature)
107 continue
107 continue
108
108
109 if not negate and not available:
109 if not negate and not available:
110 result['skipped'].append('missing feature: %s' % desc)
110 result['skipped'].append('missing feature: %s' % desc)
111 elif negate and available:
111 elif negate and available:
112 result['skipped'].append('system supports %s' % desc)
112 result['skipped'].append('system supports %s' % desc)
113
113
114 return result
114 return result
115
115
116
116
117 def require(features):
117 def require(features):
118 """Require that features are available, exiting if not."""
118 """Require that features are available, exiting if not."""
119 result = checkfeatures(features)
119 result = checkfeatures(features)
120
120
121 for missing in result['missing']:
121 for missing in result['missing']:
122 stderr.write(
122 stderr.write(
123 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
123 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
124 )
124 )
125 for msg in result['skipped']:
125 for msg in result['skipped']:
126 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
126 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
127 for msg in result['error']:
127 for msg in result['error']:
128 stderr.write(('%s\n' % msg).encode('utf-8'))
128 stderr.write(('%s\n' % msg).encode('utf-8'))
129
129
130 if result['missing']:
130 if result['missing']:
131 sys.exit(2)
131 sys.exit(2)
132
132
133 if result['skipped'] or result['error']:
133 if result['skipped'] or result['error']:
134 sys.exit(1)
134 sys.exit(1)
135
135
136
136
137 def matchoutput(cmd, regexp, ignorestatus=False):
137 def matchoutput(cmd, regexp, ignorestatus=False):
138 """Return the match object if cmd executes successfully and its output
138 """Return the match object if cmd executes successfully and its output
139 is matched by the supplied regular expression.
139 is matched by the supplied regular expression.
140 """
140 """
141 r = re.compile(regexp)
141 r = re.compile(regexp)
142 p = subprocess.Popen(
142 p = subprocess.Popen(
143 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
143 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
144 )
144 )
145 s = p.communicate()[0]
145 s = p.communicate()[0]
146 ret = p.returncode
146 ret = p.returncode
147 return (ignorestatus or not ret) and r.search(s)
147 return (ignorestatus or not ret) and r.search(s)
148
148
149
149
150 @check("baz", "GNU Arch baz client")
150 @check("baz", "GNU Arch baz client")
151 def has_baz():
151 def has_baz():
152 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
152 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
153
153
154
154
155 @check("bzr", "Canonical's Bazaar client")
155 @check("bzr", "Canonical's Bazaar client")
156 def has_bzr():
156 def has_bzr():
157 try:
157 try:
158 import bzrlib
158 import bzrlib
159 import bzrlib.bzrdir
159 import bzrlib.bzrdir
160 import bzrlib.errors
160 import bzrlib.errors
161 import bzrlib.revision
161 import bzrlib.revision
162 import bzrlib.revisionspec
162 import bzrlib.revisionspec
163
163
164 bzrlib.revisionspec.RevisionSpec
164 bzrlib.revisionspec.RevisionSpec
165 return bzrlib.__doc__ is not None
165 return bzrlib.__doc__ is not None
166 except (AttributeError, ImportError):
166 except (AttributeError, ImportError):
167 return False
167 return False
168
168
169
169
170 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
170 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
171 def has_bzr_range(v):
171 def has_bzr_range(v):
172 major, minor = v.split('rc')[0].split('.')[0:2]
172 major, minor = v.split('rc')[0].split('.')[0:2]
173 try:
173 try:
174 import bzrlib
174 import bzrlib
175
175
176 return bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= (
176 return bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= (
177 int(major),
177 int(major),
178 int(minor),
178 int(minor),
179 )
179 )
180 except ImportError:
180 except ImportError:
181 return False
181 return False
182
182
183
183
184 @check("chg", "running with chg")
184 @check("chg", "running with chg")
185 def has_chg():
185 def has_chg():
186 return 'CHGHG' in os.environ
186 return 'CHGHG' in os.environ
187
187
188
188
189 @check("cvs", "cvs client/server")
189 @check("cvs", "cvs client/server")
190 def has_cvs():
190 def has_cvs():
191 re = br'Concurrent Versions System.*?server'
191 re = br'Concurrent Versions System.*?server'
192 return matchoutput('cvs --version 2>&1', re) and not has_msys()
192 return matchoutput('cvs --version 2>&1', re) and not has_msys()
193
193
194
194
195 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
195 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
196 def has_cvs112():
196 def has_cvs112():
197 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
197 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
198 return matchoutput('cvs --version 2>&1', re) and not has_msys()
198 return matchoutput('cvs --version 2>&1', re) and not has_msys()
199
199
200
200
201 @check("cvsnt", "cvsnt client/server")
201 @check("cvsnt", "cvsnt client/server")
202 def has_cvsnt():
202 def has_cvsnt():
203 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
203 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
204 return matchoutput('cvsnt --version 2>&1', re)
204 return matchoutput('cvsnt --version 2>&1', re)
205
205
206
206
207 @check("darcs", "darcs client")
207 @check("darcs", "darcs client")
208 def has_darcs():
208 def has_darcs():
209 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
209 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
210
210
211
211
212 @check("mtn", "monotone client (>= 1.0)")
212 @check("mtn", "monotone client (>= 1.0)")
213 def has_mtn():
213 def has_mtn():
214 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
214 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
215 'mtn --version', br'monotone 0\.', True
215 'mtn --version', br'monotone 0\.', True
216 )
216 )
217
217
218
218
219 @check("eol-in-paths", "end-of-lines in paths")
219 @check("eol-in-paths", "end-of-lines in paths")
220 def has_eol_in_paths():
220 def has_eol_in_paths():
221 try:
221 try:
222 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
222 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
223 os.close(fd)
223 os.close(fd)
224 os.remove(path)
224 os.remove(path)
225 return True
225 return True
226 except (IOError, OSError):
226 except (IOError, OSError):
227 return False
227 return False
228
228
229
229
230 @check("execbit", "executable bit")
230 @check("execbit", "executable bit")
231 def has_executablebit():
231 def has_executablebit():
232 try:
232 try:
233 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
233 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
234 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
234 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
235 try:
235 try:
236 os.close(fh)
236 os.close(fh)
237 m = os.stat(fn).st_mode & 0o777
237 m = os.stat(fn).st_mode & 0o777
238 new_file_has_exec = m & EXECFLAGS
238 new_file_has_exec = m & EXECFLAGS
239 os.chmod(fn, m ^ EXECFLAGS)
239 os.chmod(fn, m ^ EXECFLAGS)
240 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
240 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
241 finally:
241 finally:
242 os.unlink(fn)
242 os.unlink(fn)
243 except (IOError, OSError):
243 except (IOError, OSError):
244 # we don't care, the user probably won't be able to commit anyway
244 # we don't care, the user probably won't be able to commit anyway
245 return False
245 return False
246 return not (new_file_has_exec or exec_flags_cannot_flip)
246 return not (new_file_has_exec or exec_flags_cannot_flip)
247
247
248
248
249 @check("icasefs", "case insensitive file system")
249 @check("icasefs", "case insensitive file system")
250 def has_icasefs():
250 def has_icasefs():
251 # Stolen from mercurial.util
251 # Stolen from mercurial.util
252 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
252 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
253 os.close(fd)
253 os.close(fd)
254 try:
254 try:
255 s1 = os.stat(path)
255 s1 = os.stat(path)
256 d, b = os.path.split(path)
256 d, b = os.path.split(path)
257 p2 = os.path.join(d, b.upper())
257 p2 = os.path.join(d, b.upper())
258 if path == p2:
258 if path == p2:
259 p2 = os.path.join(d, b.lower())
259 p2 = os.path.join(d, b.lower())
260 try:
260 try:
261 s2 = os.stat(p2)
261 s2 = os.stat(p2)
262 return s2 == s1
262 return s2 == s1
263 except OSError:
263 except OSError:
264 return False
264 return False
265 finally:
265 finally:
266 os.remove(path)
266 os.remove(path)
267
267
268
268
269 @check("fifo", "named pipes")
269 @check("fifo", "named pipes")
270 def has_fifo():
270 def has_fifo():
271 if getattr(os, "mkfifo", None) is None:
271 if getattr(os, "mkfifo", None) is None:
272 return False
272 return False
273 name = tempfile.mktemp(dir='.', prefix=tempprefix)
273 name = tempfile.mktemp(dir='.', prefix=tempprefix)
274 try:
274 try:
275 os.mkfifo(name)
275 os.mkfifo(name)
276 os.unlink(name)
276 os.unlink(name)
277 return True
277 return True
278 except OSError:
278 except OSError:
279 return False
279 return False
280
280
281
281
282 @check("killdaemons", 'killdaemons.py support')
282 @check("killdaemons", 'killdaemons.py support')
283 def has_killdaemons():
283 def has_killdaemons():
284 return True
284 return True
285
285
286
286
287 @check("cacheable", "cacheable filesystem")
287 @check("cacheable", "cacheable filesystem")
288 def has_cacheable_fs():
288 def has_cacheable_fs():
289 from mercurial import util
289 from mercurial import util
290
290
291 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
291 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
292 os.close(fd)
292 os.close(fd)
293 try:
293 try:
294 return util.cachestat(path).cacheable()
294 return util.cachestat(path).cacheable()
295 finally:
295 finally:
296 os.remove(path)
296 os.remove(path)
297
297
298
298
299 @check("lsprof", "python lsprof module")
299 @check("lsprof", "python lsprof module")
300 def has_lsprof():
300 def has_lsprof():
301 try:
301 try:
302 import _lsprof
302 import _lsprof
303
303
304 _lsprof.Profiler # silence unused import warning
304 _lsprof.Profiler # silence unused import warning
305 return True
305 return True
306 except ImportError:
306 except ImportError:
307 return False
307 return False
308
308
309
309
310 def _gethgversion():
310 def _gethgversion():
311 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
311 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
312 if not m:
312 if not m:
313 return (0, 0)
313 return (0, 0)
314 return (int(m.group(1)), int(m.group(2)))
314 return (int(m.group(1)), int(m.group(2)))
315
315
316
316
317 _hgversion = None
317 _hgversion = None
318
318
319
319
320 def gethgversion():
320 def gethgversion():
321 global _hgversion
321 global _hgversion
322 if _hgversion is None:
322 if _hgversion is None:
323 _hgversion = _gethgversion()
323 _hgversion = _gethgversion()
324 return _hgversion
324 return _hgversion
325
325
326
326
327 @checkvers(
327 @checkvers(
328 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
328 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
329 )
329 )
330 def has_hg_range(v):
330 def has_hg_range(v):
331 major, minor = v.split('.')[0:2]
331 major, minor = v.split('.')[0:2]
332 return gethgversion() >= (int(major), int(minor))
332 return gethgversion() >= (int(major), int(minor))
333
333
334
334
335 @check("rust", "Using the Rust extensions")
335 @check("rust", "Using the Rust extensions")
336 def has_rust():
336 def has_rust():
337 """Check is the mercurial currently running is using some rust code"""
337 """Check is the mercurial currently running is using some rust code"""
338 cmd = b'hg debuginstall --quiet 2>&1'
338 cmd = b'hg debuginstall --quiet 2>&1'
339 match = br'checking module policy \(([^)]+)\)'
339 match = br'checking module policy \(([^)]+)\)'
340 policy = matchoutput(cmd, match)
340 policy = matchoutput(cmd, match)
341 if not policy:
341 if not policy:
342 return False
342 return False
343 return b'rust' in policy.group(1)
343 return b'rust' in policy.group(1)
344
344
345
345
346 @check("hg08", "Mercurial >= 0.8")
346 @check("hg08", "Mercurial >= 0.8")
347 def has_hg08():
347 def has_hg08():
348 if checks["hg09"][0]():
348 if checks["hg09"][0]():
349 return True
349 return True
350 return matchoutput('hg help annotate 2>&1', '--date')
350 return matchoutput('hg help annotate 2>&1', '--date')
351
351
352
352
353 @check("hg07", "Mercurial >= 0.7")
353 @check("hg07", "Mercurial >= 0.7")
354 def has_hg07():
354 def has_hg07():
355 if checks["hg08"][0]():
355 if checks["hg08"][0]():
356 return True
356 return True
357 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
357 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
358
358
359
359
360 @check("hg06", "Mercurial >= 0.6")
360 @check("hg06", "Mercurial >= 0.6")
361 def has_hg06():
361 def has_hg06():
362 if checks["hg07"][0]():
362 if checks["hg07"][0]():
363 return True
363 return True
364 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
364 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
365
365
366
366
367 @check("gettext", "GNU Gettext (msgfmt)")
367 @check("gettext", "GNU Gettext (msgfmt)")
368 def has_gettext():
368 def has_gettext():
369 return matchoutput('msgfmt --version', br'GNU gettext-tools')
369 return matchoutput('msgfmt --version', br'GNU gettext-tools')
370
370
371
371
372 @check("git", "git command line client")
372 @check("git", "git command line client")
373 def has_git():
373 def has_git():
374 return matchoutput('git --version 2>&1', br'^git version')
374 return matchoutput('git --version 2>&1', br'^git version')
375
375
376
376
377 def getgitversion():
377 def getgitversion():
378 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
378 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
379 if not m:
379 if not m:
380 return (0, 0)
380 return (0, 0)
381 return (int(m.group(1)), int(m.group(2)))
381 return (int(m.group(1)), int(m.group(2)))
382
382
383
383
384 @check("pygit2", "pygit2 Python library")
384 @check("pygit2", "pygit2 Python library")
385 def has_git():
385 def has_git():
386 try:
386 try:
387 import pygit2
387 import pygit2
388
388
389 pygit2.Oid # silence unused import
389 pygit2.Oid # silence unused import
390 return True
390 return True
391 except ImportError:
391 except ImportError:
392 return False
392 return False
393
393
394
394
395 # https://github.com/git-lfs/lfs-test-server
395 # https://github.com/git-lfs/lfs-test-server
396 @check("lfs-test-server", "git-lfs test server")
396 @check("lfs-test-server", "git-lfs test server")
397 def has_lfsserver():
397 def has_lfsserver():
398 exe = 'lfs-test-server'
398 exe = 'lfs-test-server'
399 if has_windows():
399 if has_windows():
400 exe = 'lfs-test-server.exe'
400 exe = 'lfs-test-server.exe'
401 return any(
401 return any(
402 os.access(os.path.join(path, exe), os.X_OK)
402 os.access(os.path.join(path, exe), os.X_OK)
403 for path in os.environ["PATH"].split(os.pathsep)
403 for path in os.environ["PATH"].split(os.pathsep)
404 )
404 )
405
405
406
406
407 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
407 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
408 def has_git_range(v):
408 def has_git_range(v):
409 major, minor = v.split('.')[0:2]
409 major, minor = v.split('.')[0:2]
410 return getgitversion() >= (int(major), int(minor))
410 return getgitversion() >= (int(major), int(minor))
411
411
412
412
413 @check("docutils", "Docutils text processing library")
413 @check("docutils", "Docutils text processing library")
414 def has_docutils():
414 def has_docutils():
415 try:
415 try:
416 import docutils.core
416 import docutils.core
417
417
418 docutils.core.publish_cmdline # silence unused import
418 docutils.core.publish_cmdline # silence unused import
419 return True
419 return True
420 except ImportError:
420 except ImportError:
421 return False
421 return False
422
422
423
423
424 def getsvnversion():
424 def getsvnversion():
425 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
425 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
426 if not m:
426 if not m:
427 return (0, 0)
427 return (0, 0)
428 return (int(m.group(1)), int(m.group(2)))
428 return (int(m.group(1)), int(m.group(2)))
429
429
430
430
431 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
431 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
432 def has_svn_range(v):
432 def has_svn_range(v):
433 major, minor = v.split('.')[0:2]
433 major, minor = v.split('.')[0:2]
434 return getsvnversion() >= (int(major), int(minor))
434 return getsvnversion() >= (int(major), int(minor))
435
435
436
436
437 @check("svn", "subversion client and admin tools")
437 @check("svn", "subversion client and admin tools")
438 def has_svn():
438 def has_svn():
439 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
439 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
440 'svnadmin --version 2>&1', br'^svnadmin, version'
440 'svnadmin --version 2>&1', br'^svnadmin, version'
441 )
441 )
442
442
443
443
444 @check("svn-bindings", "subversion python bindings")
444 @check("svn-bindings", "subversion python bindings")
445 def has_svn_bindings():
445 def has_svn_bindings():
446 try:
446 try:
447 import svn.core
447 import svn.core
448
448
449 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
449 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
450 if version < (1, 4):
450 if version < (1, 4):
451 return False
451 return False
452 return True
452 return True
453 except ImportError:
453 except ImportError:
454 return False
454 return False
455
455
456
456
457 @check("p4", "Perforce server and client")
457 @check("p4", "Perforce server and client")
458 def has_p4():
458 def has_p4():
459 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
459 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
460 'p4d -V', br'Rev\. P4D/'
460 'p4d -V', br'Rev\. P4D/'
461 )
461 )
462
462
463
463
464 @check("symlink", "symbolic links")
464 @check("symlink", "symbolic links")
465 def has_symlink():
465 def has_symlink():
466 # mercurial.windows.checklink() is a hard 'no' at the moment
466 # mercurial.windows.checklink() is a hard 'no' at the moment
467 if os.name == 'nt' or getattr(os, "symlink", None) is None:
467 if os.name == 'nt' or getattr(os, "symlink", None) is None:
468 return False
468 return False
469 name = tempfile.mktemp(dir='.', prefix=tempprefix)
469 name = tempfile.mktemp(dir='.', prefix=tempprefix)
470 try:
470 try:
471 os.symlink(".", name)
471 os.symlink(".", name)
472 os.unlink(name)
472 os.unlink(name)
473 return True
473 return True
474 except (OSError, AttributeError):
474 except (OSError, AttributeError):
475 return False
475 return False
476
476
477
477
478 @check("hardlink", "hardlinks")
478 @check("hardlink", "hardlinks")
479 def has_hardlink():
479 def has_hardlink():
480 from mercurial import util
480 from mercurial import util
481
481
482 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
482 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
483 os.close(fh)
483 os.close(fh)
484 name = tempfile.mktemp(dir='.', prefix=tempprefix)
484 name = tempfile.mktemp(dir='.', prefix=tempprefix)
485 try:
485 try:
486 util.oslink(_sys2bytes(fn), _sys2bytes(name))
486 util.oslink(_sys2bytes(fn), _sys2bytes(name))
487 os.unlink(name)
487 os.unlink(name)
488 return True
488 return True
489 except OSError:
489 except OSError:
490 return False
490 return False
491 finally:
491 finally:
492 os.unlink(fn)
492 os.unlink(fn)
493
493
494
494
495 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
495 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
496 def has_hardlink_whitelisted():
496 def has_hardlink_whitelisted():
497 from mercurial import util
497 from mercurial import util
498
498
499 try:
499 try:
500 fstype = util.getfstype(b'.')
500 fstype = util.getfstype(b'.')
501 except OSError:
501 except OSError:
502 return False
502 return False
503 return fstype in util._hardlinkfswhitelist
503 return fstype in util._hardlinkfswhitelist
504
504
505
505
506 @check("rmcwd", "can remove current working directory")
506 @check("rmcwd", "can remove current working directory")
507 def has_rmcwd():
507 def has_rmcwd():
508 ocwd = os.getcwd()
508 ocwd = os.getcwd()
509 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
509 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
510 try:
510 try:
511 os.chdir(temp)
511 os.chdir(temp)
512 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
512 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
513 # On Solaris and Windows, the cwd can't be removed by any names.
513 # On Solaris and Windows, the cwd can't be removed by any names.
514 os.rmdir(os.getcwd())
514 os.rmdir(os.getcwd())
515 return True
515 return True
516 except OSError:
516 except OSError:
517 return False
517 return False
518 finally:
518 finally:
519 os.chdir(ocwd)
519 os.chdir(ocwd)
520 # clean up temp dir on platforms where cwd can't be removed
520 # clean up temp dir on platforms where cwd can't be removed
521 try:
521 try:
522 os.rmdir(temp)
522 os.rmdir(temp)
523 except OSError:
523 except OSError:
524 pass
524 pass
525
525
526
526
527 @check("tla", "GNU Arch tla client")
527 @check("tla", "GNU Arch tla client")
528 def has_tla():
528 def has_tla():
529 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
529 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
530
530
531
531
532 @check("gpg", "gpg client")
532 @check("gpg", "gpg client")
533 def has_gpg():
533 def has_gpg():
534 return matchoutput('gpg --version 2>&1', br'GnuPG')
534 return matchoutput('gpg --version 2>&1', br'GnuPG')
535
535
536
536
537 @check("gpg2", "gpg client v2")
537 @check("gpg2", "gpg client v2")
538 def has_gpg2():
538 def has_gpg2():
539 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
539 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
540
540
541
541
542 @check("gpg21", "gpg client v2.1+")
542 @check("gpg21", "gpg client v2.1+")
543 def has_gpg21():
543 def has_gpg21():
544 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
544 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
545
545
546
546
547 @check("unix-permissions", "unix-style permissions")
547 @check("unix-permissions", "unix-style permissions")
548 def has_unix_permissions():
548 def has_unix_permissions():
549 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
549 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
550 try:
550 try:
551 fname = os.path.join(d, 'foo')
551 fname = os.path.join(d, 'foo')
552 for umask in (0o77, 0o07, 0o22):
552 for umask in (0o77, 0o07, 0o22):
553 os.umask(umask)
553 os.umask(umask)
554 f = open(fname, 'w')
554 f = open(fname, 'w')
555 f.close()
555 f.close()
556 mode = os.stat(fname).st_mode
556 mode = os.stat(fname).st_mode
557 os.unlink(fname)
557 os.unlink(fname)
558 if mode & 0o777 != ~umask & 0o666:
558 if mode & 0o777 != ~umask & 0o666:
559 return False
559 return False
560 return True
560 return True
561 finally:
561 finally:
562 os.rmdir(d)
562 os.rmdir(d)
563
563
564
564
565 @check("unix-socket", "AF_UNIX socket family")
565 @check("unix-socket", "AF_UNIX socket family")
566 def has_unix_socket():
566 def has_unix_socket():
567 return getattr(socket, 'AF_UNIX', None) is not None
567 return getattr(socket, 'AF_UNIX', None) is not None
568
568
569
569
570 @check("root", "root permissions")
570 @check("root", "root permissions")
571 def has_root():
571 def has_root():
572 return getattr(os, 'geteuid', None) and os.geteuid() == 0
572 return getattr(os, 'geteuid', None) and os.geteuid() == 0
573
573
574
574
575 @check("pyflakes", "Pyflakes python linter")
575 @check("pyflakes", "Pyflakes python linter")
576 def has_pyflakes():
576 def has_pyflakes():
577 return matchoutput(
577 try:
578 "sh -c \"echo 'import re' 2>&1 | $PYTHON -m pyflakes\"",
578 import pyflakes
579 br"<stdin>:1: 're' imported but unused",
579
580 True,
580 pyflakes.__version__
581 )
581 except ImportError:
582 return False
583 else:
584 return True
582
585
583
586
584 @check("pylint", "Pylint python linter")
587 @check("pylint", "Pylint python linter")
585 def has_pylint():
588 def has_pylint():
586 return matchoutput("pylint --help", br"Usage: pylint", True)
589 return matchoutput("pylint --help", br"Usage: pylint", True)
587
590
588
591
589 @check("clang-format", "clang-format C code formatter")
592 @check("clang-format", "clang-format C code formatter")
590 def has_clang_format():
593 def has_clang_format():
591 m = matchoutput('clang-format --version', br'clang-format version (\d)')
594 m = matchoutput('clang-format --version', br'clang-format version (\d)')
592 # style changed somewhere between 4.x and 6.x
595 # style changed somewhere between 4.x and 6.x
593 return m and int(m.group(1)) >= 6
596 return m and int(m.group(1)) >= 6
594
597
595
598
596 @check("jshint", "JSHint static code analysis tool")
599 @check("jshint", "JSHint static code analysis tool")
597 def has_jshint():
600 def has_jshint():
598 return matchoutput("jshint --version 2>&1", br"jshint v")
601 return matchoutput("jshint --version 2>&1", br"jshint v")
599
602
600
603
601 @check("pygments", "Pygments source highlighting library")
604 @check("pygments", "Pygments source highlighting library")
602 def has_pygments():
605 def has_pygments():
603 try:
606 try:
604 import pygments
607 import pygments
605
608
606 pygments.highlight # silence unused import warning
609 pygments.highlight # silence unused import warning
607 return True
610 return True
608 except ImportError:
611 except ImportError:
609 return False
612 return False
610
613
611
614
612 @check("pygments25", "Pygments version >= 2.5")
615 @check("pygments25", "Pygments version >= 2.5")
613 def pygments25():
616 def pygments25():
614 try:
617 try:
615 import pygments
618 import pygments
616
619
617 v = pygments.__version__
620 v = pygments.__version__
618 except ImportError:
621 except ImportError:
619 return False
622 return False
620
623
621 parts = v.split(".")
624 parts = v.split(".")
622 major = int(parts[0])
625 major = int(parts[0])
623 minor = int(parts[1])
626 minor = int(parts[1])
624
627
625 return (major, minor) >= (2, 5)
628 return (major, minor) >= (2, 5)
626
629
627
630
628 @check("outer-repo", "outer repo")
631 @check("outer-repo", "outer repo")
629 def has_outer_repo():
632 def has_outer_repo():
630 # failing for other reasons than 'no repo' imply that there is a repo
633 # failing for other reasons than 'no repo' imply that there is a repo
631 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
634 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
632
635
633
636
634 @check("ssl", "ssl module available")
637 @check("ssl", "ssl module available")
635 def has_ssl():
638 def has_ssl():
636 try:
639 try:
637 import ssl
640 import ssl
638
641
639 ssl.CERT_NONE
642 ssl.CERT_NONE
640 return True
643 return True
641 except ImportError:
644 except ImportError:
642 return False
645 return False
643
646
644
647
645 @check("sslcontext", "python >= 2.7.9 ssl")
648 @check("sslcontext", "python >= 2.7.9 ssl")
646 def has_sslcontext():
649 def has_sslcontext():
647 try:
650 try:
648 import ssl
651 import ssl
649
652
650 ssl.SSLContext
653 ssl.SSLContext
651 return True
654 return True
652 except (ImportError, AttributeError):
655 except (ImportError, AttributeError):
653 return False
656 return False
654
657
655
658
656 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
659 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
657 def has_defaultcacerts():
660 def has_defaultcacerts():
658 from mercurial import sslutil, ui as uimod
661 from mercurial import sslutil, ui as uimod
659
662
660 ui = uimod.ui.load()
663 ui = uimod.ui.load()
661 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
664 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
662
665
663
666
664 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
667 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
665 def has_defaultcacertsloaded():
668 def has_defaultcacertsloaded():
666 import ssl
669 import ssl
667 from mercurial import sslutil, ui as uimod
670 from mercurial import sslutil, ui as uimod
668
671
669 if not has_defaultcacerts():
672 if not has_defaultcacerts():
670 return False
673 return False
671 if not has_sslcontext():
674 if not has_sslcontext():
672 return False
675 return False
673
676
674 ui = uimod.ui.load()
677 ui = uimod.ui.load()
675 cafile = sslutil._defaultcacerts(ui)
678 cafile = sslutil._defaultcacerts(ui)
676 ctx = ssl.create_default_context()
679 ctx = ssl.create_default_context()
677 if cafile:
680 if cafile:
678 ctx.load_verify_locations(cafile=cafile)
681 ctx.load_verify_locations(cafile=cafile)
679 else:
682 else:
680 ctx.load_default_certs()
683 ctx.load_default_certs()
681
684
682 return len(ctx.get_ca_certs()) > 0
685 return len(ctx.get_ca_certs()) > 0
683
686
684
687
685 @check("tls1.2", "TLS 1.2 protocol support")
688 @check("tls1.2", "TLS 1.2 protocol support")
686 def has_tls1_2():
689 def has_tls1_2():
687 from mercurial import sslutil
690 from mercurial import sslutil
688
691
689 return b'tls1.2' in sslutil.supportedprotocols
692 return b'tls1.2' in sslutil.supportedprotocols
690
693
691
694
692 @check("windows", "Windows")
695 @check("windows", "Windows")
693 def has_windows():
696 def has_windows():
694 return os.name == 'nt'
697 return os.name == 'nt'
695
698
696
699
697 @check("system-sh", "system() uses sh")
700 @check("system-sh", "system() uses sh")
698 def has_system_sh():
701 def has_system_sh():
699 return os.name != 'nt'
702 return os.name != 'nt'
700
703
701
704
702 @check("serve", "platform and python can manage 'hg serve -d'")
705 @check("serve", "platform and python can manage 'hg serve -d'")
703 def has_serve():
706 def has_serve():
704 return True
707 return True
705
708
706
709
707 @check("test-repo", "running tests from repository")
710 @check("test-repo", "running tests from repository")
708 def has_test_repo():
711 def has_test_repo():
709 t = os.environ["TESTDIR"]
712 t = os.environ["TESTDIR"]
710 return os.path.isdir(os.path.join(t, "..", ".hg"))
713 return os.path.isdir(os.path.join(t, "..", ".hg"))
711
714
712
715
713 @check("tic", "terminfo compiler and curses module")
716 @check("tic", "terminfo compiler and curses module")
714 def has_tic():
717 def has_tic():
715 try:
718 try:
716 import curses
719 import curses
717
720
718 curses.COLOR_BLUE
721 curses.COLOR_BLUE
719 return matchoutput('test -x "`which tic`"', br'')
722 return matchoutput('test -x "`which tic`"', br'')
720 except (ImportError, AttributeError):
723 except (ImportError, AttributeError):
721 return False
724 return False
722
725
723
726
724 @check("xz", "xz compression utility")
727 @check("xz", "xz compression utility")
725 def has_xz():
728 def has_xz():
726 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
729 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
727 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
730 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
728 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
731 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
729
732
730
733
731 @check("msys", "Windows with MSYS")
734 @check("msys", "Windows with MSYS")
732 def has_msys():
735 def has_msys():
733 return os.getenv('MSYSTEM')
736 return os.getenv('MSYSTEM')
734
737
735
738
736 @check("aix", "AIX")
739 @check("aix", "AIX")
737 def has_aix():
740 def has_aix():
738 return sys.platform.startswith("aix")
741 return sys.platform.startswith("aix")
739
742
740
743
741 @check("osx", "OS X")
744 @check("osx", "OS X")
742 def has_osx():
745 def has_osx():
743 return sys.platform == 'darwin'
746 return sys.platform == 'darwin'
744
747
745
748
746 @check("osxpackaging", "OS X packaging tools")
749 @check("osxpackaging", "OS X packaging tools")
747 def has_osxpackaging():
750 def has_osxpackaging():
748 try:
751 try:
749 return (
752 return (
750 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
753 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
751 and matchoutput(
754 and matchoutput(
752 'productbuild', br'Usage: productbuild ', ignorestatus=1
755 'productbuild', br'Usage: productbuild ', ignorestatus=1
753 )
756 )
754 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
757 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
755 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
758 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
756 )
759 )
757 except ImportError:
760 except ImportError:
758 return False
761 return False
759
762
760
763
761 @check('linuxormacos', 'Linux or MacOS')
764 @check('linuxormacos', 'Linux or MacOS')
762 def has_linuxormacos():
765 def has_linuxormacos():
763 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
766 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
764 return sys.platform.startswith(('linux', 'darwin'))
767 return sys.platform.startswith(('linux', 'darwin'))
765
768
766
769
767 @check("docker", "docker support")
770 @check("docker", "docker support")
768 def has_docker():
771 def has_docker():
769 pat = br'A self-sufficient runtime for'
772 pat = br'A self-sufficient runtime for'
770 if matchoutput('docker --help', pat):
773 if matchoutput('docker --help', pat):
771 if 'linux' not in sys.platform:
774 if 'linux' not in sys.platform:
772 # TODO: in theory we should be able to test docker-based
775 # TODO: in theory we should be able to test docker-based
773 # package creation on non-linux using boot2docker, but in
776 # package creation on non-linux using boot2docker, but in
774 # practice that requires extra coordination to make sure
777 # practice that requires extra coordination to make sure
775 # $TESTTEMP is going to be visible at the same path to the
778 # $TESTTEMP is going to be visible at the same path to the
776 # boot2docker VM. If we figure out how to verify that, we
779 # boot2docker VM. If we figure out how to verify that, we
777 # can use the following instead of just saying False:
780 # can use the following instead of just saying False:
778 # return 'DOCKER_HOST' in os.environ
781 # return 'DOCKER_HOST' in os.environ
779 return False
782 return False
780
783
781 return True
784 return True
782 return False
785 return False
783
786
784
787
785 @check("debhelper", "debian packaging tools")
788 @check("debhelper", "debian packaging tools")
786 def has_debhelper():
789 def has_debhelper():
787 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
790 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
788 # quote), so just accept anything in that spot.
791 # quote), so just accept anything in that spot.
789 dpkg = matchoutput(
792 dpkg = matchoutput(
790 'dpkg --version', br"Debian .dpkg' package management program"
793 'dpkg --version', br"Debian .dpkg' package management program"
791 )
794 )
792 dh = matchoutput(
795 dh = matchoutput(
793 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
796 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
794 )
797 )
795 dh_py2 = matchoutput(
798 dh_py2 = matchoutput(
796 'dh_python2 --help', br'other supported Python versions'
799 'dh_python2 --help', br'other supported Python versions'
797 )
800 )
798 # debuild comes from the 'devscripts' package, though you might want
801 # debuild comes from the 'devscripts' package, though you might want
799 # the 'build-debs' package instead, which has a dependency on devscripts.
802 # the 'build-debs' package instead, which has a dependency on devscripts.
800 debuild = matchoutput(
803 debuild = matchoutput(
801 'debuild --help', br'to run debian/rules with given parameter'
804 'debuild --help', br'to run debian/rules with given parameter'
802 )
805 )
803 return dpkg and dh and dh_py2 and debuild
806 return dpkg and dh and dh_py2 and debuild
804
807
805
808
806 @check(
809 @check(
807 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
810 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
808 )
811 )
809 def has_debdeps():
812 def has_debdeps():
810 # just check exit status (ignoring output)
813 # just check exit status (ignoring output)
811 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
814 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
812 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
815 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
813
816
814
817
815 @check("demandimport", "demandimport enabled")
818 @check("demandimport", "demandimport enabled")
816 def has_demandimport():
819 def has_demandimport():
817 # chg disables demandimport intentionally for performance wins.
820 # chg disables demandimport intentionally for performance wins.
818 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
821 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
819
822
820
823
821 # Add "py27", "py35", ... as possible feature checks. Note that there's no
824 # Add "py27", "py35", ... as possible feature checks. Note that there's no
822 # punctuation here.
825 # punctuation here.
823 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
826 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
824 def has_python_range(v):
827 def has_python_range(v):
825 major, minor = v.split('.')[0:2]
828 major, minor = v.split('.')[0:2]
826 py_major, py_minor = sys.version_info.major, sys.version_info.minor
829 py_major, py_minor = sys.version_info.major, sys.version_info.minor
827
830
828 return (py_major, py_minor) >= (int(major), int(minor))
831 return (py_major, py_minor) >= (int(major), int(minor))
829
832
830
833
831 @check("py3", "running with Python 3.x")
834 @check("py3", "running with Python 3.x")
832 def has_py3():
835 def has_py3():
833 return 3 == sys.version_info[0]
836 return 3 == sys.version_info[0]
834
837
835
838
836 @check("py3exe", "a Python 3.x interpreter is available")
839 @check("py3exe", "a Python 3.x interpreter is available")
837 def has_python3exe():
840 def has_python3exe():
838 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
841 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
839
842
840
843
841 @check("pure", "running with pure Python code")
844 @check("pure", "running with pure Python code")
842 def has_pure():
845 def has_pure():
843 return any(
846 return any(
844 [
847 [
845 os.environ.get("HGMODULEPOLICY") == "py",
848 os.environ.get("HGMODULEPOLICY") == "py",
846 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
849 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
847 ]
850 ]
848 )
851 )
849
852
850
853
851 @check("slow", "allow slow tests (use --allow-slow-tests)")
854 @check("slow", "allow slow tests (use --allow-slow-tests)")
852 def has_slow():
855 def has_slow():
853 return os.environ.get('HGTEST_SLOW') == 'slow'
856 return os.environ.get('HGTEST_SLOW') == 'slow'
854
857
855
858
856 @check("hypothesis", "Hypothesis automated test generation")
859 @check("hypothesis", "Hypothesis automated test generation")
857 def has_hypothesis():
860 def has_hypothesis():
858 try:
861 try:
859 import hypothesis
862 import hypothesis
860
863
861 hypothesis.given
864 hypothesis.given
862 return True
865 return True
863 except ImportError:
866 except ImportError:
864 return False
867 return False
865
868
866
869
867 @check("unziplinks", "unzip(1) understands and extracts symlinks")
870 @check("unziplinks", "unzip(1) understands and extracts symlinks")
868 def unzip_understands_symlinks():
871 def unzip_understands_symlinks():
869 return matchoutput('unzip --help', br'Info-ZIP')
872 return matchoutput('unzip --help', br'Info-ZIP')
870
873
871
874
872 @check("zstd", "zstd Python module available")
875 @check("zstd", "zstd Python module available")
873 def has_zstd():
876 def has_zstd():
874 try:
877 try:
875 import mercurial.zstd
878 import mercurial.zstd
876
879
877 mercurial.zstd.__version__
880 mercurial.zstd.__version__
878 return True
881 return True
879 except ImportError:
882 except ImportError:
880 return False
883 return False
881
884
882
885
883 @check("devfull", "/dev/full special file")
886 @check("devfull", "/dev/full special file")
884 def has_dev_full():
887 def has_dev_full():
885 return os.path.exists('/dev/full')
888 return os.path.exists('/dev/full')
886
889
887
890
888 @check("ensurepip", "ensurepip module")
891 @check("ensurepip", "ensurepip module")
889 def has_ensurepip():
892 def has_ensurepip():
890 try:
893 try:
891 import ensurepip
894 import ensurepip
892
895
893 ensurepip.bootstrap
896 ensurepip.bootstrap
894 return True
897 return True
895 except ImportError:
898 except ImportError:
896 return False
899 return False
897
900
898
901
899 @check("virtualenv", "Python virtualenv support")
902 @check("virtualenv", "Python virtualenv support")
900 def has_virtualenv():
903 def has_virtualenv():
901 try:
904 try:
902 import virtualenv
905 import virtualenv
903
906
904 virtualenv.ACTIVATE_SH
907 virtualenv.ACTIVATE_SH
905 return True
908 return True
906 except ImportError:
909 except ImportError:
907 return False
910 return False
908
911
909
912
910 @check("fsmonitor", "running tests with fsmonitor")
913 @check("fsmonitor", "running tests with fsmonitor")
911 def has_fsmonitor():
914 def has_fsmonitor():
912 return 'HGFSMONITOR_TESTS' in os.environ
915 return 'HGFSMONITOR_TESTS' in os.environ
913
916
914
917
915 @check("fuzzywuzzy", "Fuzzy string matching library")
918 @check("fuzzywuzzy", "Fuzzy string matching library")
916 def has_fuzzywuzzy():
919 def has_fuzzywuzzy():
917 try:
920 try:
918 import fuzzywuzzy
921 import fuzzywuzzy
919
922
920 fuzzywuzzy.__version__
923 fuzzywuzzy.__version__
921 return True
924 return True
922 except ImportError:
925 except ImportError:
923 return False
926 return False
924
927
925
928
926 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
929 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
927 def has_clang_libfuzzer():
930 def has_clang_libfuzzer():
928 mat = matchoutput('clang --version', br'clang version (\d)')
931 mat = matchoutput('clang --version', br'clang version (\d)')
929 if mat:
932 if mat:
930 # libfuzzer is new in clang 6
933 # libfuzzer is new in clang 6
931 return int(mat.group(1)) > 5
934 return int(mat.group(1)) > 5
932 return False
935 return False
933
936
934
937
935 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
938 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
936 def has_clang60():
939 def has_clang60():
937 return matchoutput('clang-6.0 --version', br'clang version 6\.')
940 return matchoutput('clang-6.0 --version', br'clang version 6\.')
938
941
939
942
940 @check("xdiff", "xdiff algorithm")
943 @check("xdiff", "xdiff algorithm")
941 def has_xdiff():
944 def has_xdiff():
942 try:
945 try:
943 from mercurial import policy
946 from mercurial import policy
944
947
945 bdiff = policy.importmod('bdiff')
948 bdiff = policy.importmod('bdiff')
946 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
949 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
947 except (ImportError, AttributeError):
950 except (ImportError, AttributeError):
948 return False
951 return False
949
952
950
953
951 @check('extraextensions', 'whether tests are running with extra extensions')
954 @check('extraextensions', 'whether tests are running with extra extensions')
952 def has_extraextensions():
955 def has_extraextensions():
953 return 'HGTESTEXTRAEXTENSIONS' in os.environ
956 return 'HGTESTEXTRAEXTENSIONS' in os.environ
954
957
955
958
956 def getrepofeatures():
959 def getrepofeatures():
957 """Obtain set of repository features in use.
960 """Obtain set of repository features in use.
958
961
959 HGREPOFEATURES can be used to define or remove features. It contains
962 HGREPOFEATURES can be used to define or remove features. It contains
960 a space-delimited list of feature strings. Strings beginning with ``-``
963 a space-delimited list of feature strings. Strings beginning with ``-``
961 mean to remove.
964 mean to remove.
962 """
965 """
963 # Default list provided by core.
966 # Default list provided by core.
964 features = {
967 features = {
965 'bundlerepo',
968 'bundlerepo',
966 'revlogstore',
969 'revlogstore',
967 'fncache',
970 'fncache',
968 }
971 }
969
972
970 # Features that imply other features.
973 # Features that imply other features.
971 implies = {
974 implies = {
972 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
975 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
973 }
976 }
974
977
975 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
978 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
976 if not override:
979 if not override:
977 continue
980 continue
978
981
979 if override.startswith('-'):
982 if override.startswith('-'):
980 if override[1:] in features:
983 if override[1:] in features:
981 features.remove(override[1:])
984 features.remove(override[1:])
982 else:
985 else:
983 features.add(override)
986 features.add(override)
984
987
985 for imply in implies.get(override, []):
988 for imply in implies.get(override, []):
986 if imply.startswith('-'):
989 if imply.startswith('-'):
987 if imply[1:] in features:
990 if imply[1:] in features:
988 features.remove(imply[1:])
991 features.remove(imply[1:])
989 else:
992 else:
990 features.add(imply)
993 features.add(imply)
991
994
992 return features
995 return features
993
996
994
997
995 @check('reporevlogstore', 'repository using the default revlog store')
998 @check('reporevlogstore', 'repository using the default revlog store')
996 def has_reporevlogstore():
999 def has_reporevlogstore():
997 return 'revlogstore' in getrepofeatures()
1000 return 'revlogstore' in getrepofeatures()
998
1001
999
1002
1000 @check('reposimplestore', 'repository using simple storage extension')
1003 @check('reposimplestore', 'repository using simple storage extension')
1001 def has_reposimplestore():
1004 def has_reposimplestore():
1002 return 'simplestore' in getrepofeatures()
1005 return 'simplestore' in getrepofeatures()
1003
1006
1004
1007
1005 @check('repobundlerepo', 'whether we can open bundle files as repos')
1008 @check('repobundlerepo', 'whether we can open bundle files as repos')
1006 def has_repobundlerepo():
1009 def has_repobundlerepo():
1007 return 'bundlerepo' in getrepofeatures()
1010 return 'bundlerepo' in getrepofeatures()
1008
1011
1009
1012
1010 @check('repofncache', 'repository has an fncache')
1013 @check('repofncache', 'repository has an fncache')
1011 def has_repofncache():
1014 def has_repofncache():
1012 return 'fncache' in getrepofeatures()
1015 return 'fncache' in getrepofeatures()
1013
1016
1014
1017
1015 @check('sqlite', 'sqlite3 module is available')
1018 @check('sqlite', 'sqlite3 module is available')
1016 def has_sqlite():
1019 def has_sqlite():
1017 try:
1020 try:
1018 import sqlite3
1021 import sqlite3
1019
1022
1020 version = sqlite3.sqlite_version_info
1023 version = sqlite3.sqlite_version_info
1021 except ImportError:
1024 except ImportError:
1022 return False
1025 return False
1023
1026
1024 if version < (3, 8, 3):
1027 if version < (3, 8, 3):
1025 # WITH clause not supported
1028 # WITH clause not supported
1026 return False
1029 return False
1027
1030
1028 return matchoutput('sqlite3 -version', br'^3\.\d+')
1031 return matchoutput('sqlite3 -version', br'^3\.\d+')
1029
1032
1030
1033
1031 @check('vcr', 'vcr http mocking library')
1034 @check('vcr', 'vcr http mocking library')
1032 def has_vcr():
1035 def has_vcr():
1033 try:
1036 try:
1034 import vcr
1037 import vcr
1035
1038
1036 vcr.VCR
1039 vcr.VCR
1037 return True
1040 return True
1038 except (ImportError, AttributeError):
1041 except (ImportError, AttributeError):
1039 pass
1042 pass
1040 return False
1043 return False
1041
1044
1042
1045
1043 @check('emacs', 'GNU Emacs')
1046 @check('emacs', 'GNU Emacs')
1044 def has_emacs():
1047 def has_emacs():
1045 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1048 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1046 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1049 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1047 # 24 release)
1050 # 24 release)
1048 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1051 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1049
1052
1050
1053
1051 @check('black', 'the black formatter for python')
1054 @check('black', 'the black formatter for python')
1052 def has_black():
1055 def has_black():
1053 blackcmd = 'black --version'
1056 blackcmd = 'black --version'
1054 version_regex = b'black, version ([0-9a-b.]+)'
1057 version_regex = b'black, version ([0-9a-b.]+)'
1055 version = matchoutput(blackcmd, version_regex)
1058 version = matchoutput(blackcmd, version_regex)
1056 sv = distutils.version.StrictVersion
1059 sv = distutils.version.StrictVersion
1057 return version and sv(_bytes2sys(version.group(1))) >= sv('19.10b0')
1060 return version and sv(_bytes2sys(version.group(1))) >= sv('19.10b0')
1058
1061
1059
1062
1060 @check('pytype', 'the pytype type checker')
1063 @check('pytype', 'the pytype type checker')
1061 def has_pytype():
1064 def has_pytype():
1062 pytypecmd = 'pytype --version'
1065 pytypecmd = 'pytype --version'
1063 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1066 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1064 sv = distutils.version.StrictVersion
1067 sv = distutils.version.StrictVersion
1065 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1068 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1066
1069
1067
1070
1068 @check("rustfmt", "rustfmt tool")
1071 @check("rustfmt", "rustfmt tool")
1069 def has_rustfmt():
1072 def has_rustfmt():
1070 # We use Nightly's rustfmt due to current unstable config options.
1073 # We use Nightly's rustfmt due to current unstable config options.
1071 return matchoutput(
1074 return matchoutput(
1072 '`rustup which --toolchain nightly rustfmt` --version', b'rustfmt'
1075 '`rustup which --toolchain nightly rustfmt` --version', b'rustfmt'
1073 )
1076 )
General Comments 0
You need to be logged in to leave comments. Login now