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