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