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