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