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