##// END OF EJS Templates
tests: require SQLite 3.8.3+ as sqlitestore relies on "WITH" clause...
Yuya Nishihara -
r40492:1bf3e604 stable
parent child Browse files
Show More
@@ -1,808 +1,812 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import errno
3 import errno
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 if sys.version_info[0] >= 3:
19 if sys.version_info[0] >= 3:
20 def _bytespath(p):
20 def _bytespath(p):
21 if p is None:
21 if p is None:
22 return p
22 return p
23 return p.encode('utf-8')
23 return p.encode('utf-8')
24
24
25 def _strpath(p):
25 def _strpath(p):
26 if p is None:
26 if p is None:
27 return p
27 return p
28 return p.decode('utf-8')
28 return p.decode('utf-8')
29 else:
29 else:
30 def _bytespath(p):
30 def _bytespath(p):
31 return p
31 return p
32
32
33 _strpath = _bytespath
33 _strpath = _bytespath
34
34
35 def check(name, desc):
35 def check(name, desc):
36 """Registers a check function for a feature."""
36 """Registers a check function for a feature."""
37 def decorator(func):
37 def decorator(func):
38 checks[name] = (func, desc)
38 checks[name] = (func, desc)
39 return func
39 return func
40 return decorator
40 return decorator
41
41
42 def checkvers(name, desc, vers):
42 def checkvers(name, desc, vers):
43 """Registers a check function for each of a series of versions.
43 """Registers a check function for each of a series of versions.
44
44
45 vers can be a list or an iterator"""
45 vers can be a list or an iterator"""
46 def decorator(func):
46 def decorator(func):
47 def funcv(v):
47 def funcv(v):
48 def f():
48 def f():
49 return func(v)
49 return func(v)
50 return f
50 return f
51 for v in vers:
51 for v in vers:
52 v = str(v)
52 v = str(v)
53 f = funcv(v)
53 f = funcv(v)
54 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
54 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
55 return func
55 return func
56 return decorator
56 return decorator
57
57
58 def checkfeatures(features):
58 def checkfeatures(features):
59 result = {
59 result = {
60 'error': [],
60 'error': [],
61 'missing': [],
61 'missing': [],
62 'skipped': [],
62 'skipped': [],
63 }
63 }
64
64
65 for feature in features:
65 for feature in features:
66 negate = feature.startswith('no-')
66 negate = feature.startswith('no-')
67 if negate:
67 if negate:
68 feature = feature[3:]
68 feature = feature[3:]
69
69
70 if feature not in checks:
70 if feature not in checks:
71 result['missing'].append(feature)
71 result['missing'].append(feature)
72 continue
72 continue
73
73
74 check, desc = checks[feature]
74 check, desc = checks[feature]
75 try:
75 try:
76 available = check()
76 available = check()
77 except Exception:
77 except Exception:
78 result['error'].append('hghave check failed: %s' % feature)
78 result['error'].append('hghave check failed: %s' % feature)
79 continue
79 continue
80
80
81 if not negate and not available:
81 if not negate and not available:
82 result['skipped'].append('missing feature: %s' % desc)
82 result['skipped'].append('missing feature: %s' % desc)
83 elif negate and available:
83 elif negate and available:
84 result['skipped'].append('system supports %s' % desc)
84 result['skipped'].append('system supports %s' % desc)
85
85
86 return result
86 return result
87
87
88 def require(features):
88 def require(features):
89 """Require that features are available, exiting if not."""
89 """Require that features are available, exiting if not."""
90 result = checkfeatures(features)
90 result = checkfeatures(features)
91
91
92 for missing in result['missing']:
92 for missing in result['missing']:
93 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
93 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
94 for msg in result['skipped']:
94 for msg in result['skipped']:
95 sys.stderr.write('skipped: %s\n' % msg)
95 sys.stderr.write('skipped: %s\n' % msg)
96 for msg in result['error']:
96 for msg in result['error']:
97 sys.stderr.write('%s\n' % msg)
97 sys.stderr.write('%s\n' % msg)
98
98
99 if result['missing']:
99 if result['missing']:
100 sys.exit(2)
100 sys.exit(2)
101
101
102 if result['skipped'] or result['error']:
102 if result['skipped'] or result['error']:
103 sys.exit(1)
103 sys.exit(1)
104
104
105 def matchoutput(cmd, regexp, ignorestatus=False):
105 def matchoutput(cmd, regexp, ignorestatus=False):
106 """Return the match object if cmd executes successfully and its output
106 """Return the match object if cmd executes successfully and its output
107 is matched by the supplied regular expression.
107 is matched by the supplied regular expression.
108 """
108 """
109 r = re.compile(regexp)
109 r = re.compile(regexp)
110 try:
110 try:
111 p = subprocess.Popen(
111 p = subprocess.Popen(
112 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
112 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
113 except OSError as e:
113 except OSError as e:
114 if e.errno != errno.ENOENT:
114 if e.errno != errno.ENOENT:
115 raise
115 raise
116 ret = -1
116 ret = -1
117 s = p.communicate()[0]
117 s = p.communicate()[0]
118 ret = p.returncode
118 ret = p.returncode
119 return (ignorestatus or not ret) and r.search(s)
119 return (ignorestatus or not ret) and r.search(s)
120
120
121 @check("baz", "GNU Arch baz client")
121 @check("baz", "GNU Arch baz client")
122 def has_baz():
122 def has_baz():
123 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
123 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
124
124
125 @check("bzr", "Canonical's Bazaar client")
125 @check("bzr", "Canonical's Bazaar client")
126 def has_bzr():
126 def has_bzr():
127 try:
127 try:
128 import bzrlib
128 import bzrlib
129 import bzrlib.bzrdir
129 import bzrlib.bzrdir
130 import bzrlib.errors
130 import bzrlib.errors
131 import bzrlib.revision
131 import bzrlib.revision
132 import bzrlib.revisionspec
132 import bzrlib.revisionspec
133 bzrlib.revisionspec.RevisionSpec
133 bzrlib.revisionspec.RevisionSpec
134 return bzrlib.__doc__ is not None
134 return bzrlib.__doc__ is not None
135 except (AttributeError, ImportError):
135 except (AttributeError, ImportError):
136 return False
136 return False
137
137
138 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
138 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
139 def has_bzr_range(v):
139 def has_bzr_range(v):
140 major, minor = v.split('.')[0:2]
140 major, minor = v.split('.')[0:2]
141 try:
141 try:
142 import bzrlib
142 import bzrlib
143 return (bzrlib.__doc__ is not None
143 return (bzrlib.__doc__ is not None
144 and bzrlib.version_info[:2] >= (int(major), int(minor)))
144 and bzrlib.version_info[:2] >= (int(major), int(minor)))
145 except ImportError:
145 except ImportError:
146 return False
146 return False
147
147
148 @check("chg", "running with chg")
148 @check("chg", "running with chg")
149 def has_chg():
149 def has_chg():
150 return 'CHGHG' in os.environ
150 return 'CHGHG' in os.environ
151
151
152 @check("cvs", "cvs client/server")
152 @check("cvs", "cvs client/server")
153 def has_cvs():
153 def has_cvs():
154 re = br'Concurrent Versions System.*?server'
154 re = br'Concurrent Versions System.*?server'
155 return matchoutput('cvs --version 2>&1', re) and not has_msys()
155 return matchoutput('cvs --version 2>&1', re) and not has_msys()
156
156
157 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
157 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
158 def has_cvs112():
158 def has_cvs112():
159 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
159 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
160 return matchoutput('cvs --version 2>&1', re) and not has_msys()
160 return matchoutput('cvs --version 2>&1', re) and not has_msys()
161
161
162 @check("cvsnt", "cvsnt client/server")
162 @check("cvsnt", "cvsnt client/server")
163 def has_cvsnt():
163 def has_cvsnt():
164 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
164 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
165 return matchoutput('cvsnt --version 2>&1', re)
165 return matchoutput('cvsnt --version 2>&1', re)
166
166
167 @check("darcs", "darcs client")
167 @check("darcs", "darcs client")
168 def has_darcs():
168 def has_darcs():
169 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
169 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
170
170
171 @check("mtn", "monotone client (>= 1.0)")
171 @check("mtn", "monotone client (>= 1.0)")
172 def has_mtn():
172 def has_mtn():
173 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
173 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
174 'mtn --version', br'monotone 0\.', True)
174 'mtn --version', br'monotone 0\.', True)
175
175
176 @check("eol-in-paths", "end-of-lines in paths")
176 @check("eol-in-paths", "end-of-lines in paths")
177 def has_eol_in_paths():
177 def has_eol_in_paths():
178 try:
178 try:
179 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
179 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
180 os.close(fd)
180 os.close(fd)
181 os.remove(path)
181 os.remove(path)
182 return True
182 return True
183 except (IOError, OSError):
183 except (IOError, OSError):
184 return False
184 return False
185
185
186 @check("execbit", "executable bit")
186 @check("execbit", "executable bit")
187 def has_executablebit():
187 def has_executablebit():
188 try:
188 try:
189 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
189 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
190 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
190 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
191 try:
191 try:
192 os.close(fh)
192 os.close(fh)
193 m = os.stat(fn).st_mode & 0o777
193 m = os.stat(fn).st_mode & 0o777
194 new_file_has_exec = m & EXECFLAGS
194 new_file_has_exec = m & EXECFLAGS
195 os.chmod(fn, m ^ EXECFLAGS)
195 os.chmod(fn, m ^ EXECFLAGS)
196 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
196 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
197 finally:
197 finally:
198 os.unlink(fn)
198 os.unlink(fn)
199 except (IOError, OSError):
199 except (IOError, OSError):
200 # we don't care, the user probably won't be able to commit anyway
200 # we don't care, the user probably won't be able to commit anyway
201 return False
201 return False
202 return not (new_file_has_exec or exec_flags_cannot_flip)
202 return not (new_file_has_exec or exec_flags_cannot_flip)
203
203
204 @check("icasefs", "case insensitive file system")
204 @check("icasefs", "case insensitive file system")
205 def has_icasefs():
205 def has_icasefs():
206 # Stolen from mercurial.util
206 # Stolen from mercurial.util
207 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
207 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
208 os.close(fd)
208 os.close(fd)
209 try:
209 try:
210 s1 = os.stat(path)
210 s1 = os.stat(path)
211 d, b = os.path.split(path)
211 d, b = os.path.split(path)
212 p2 = os.path.join(d, b.upper())
212 p2 = os.path.join(d, b.upper())
213 if path == p2:
213 if path == p2:
214 p2 = os.path.join(d, b.lower())
214 p2 = os.path.join(d, b.lower())
215 try:
215 try:
216 s2 = os.stat(p2)
216 s2 = os.stat(p2)
217 return s2 == s1
217 return s2 == s1
218 except OSError:
218 except OSError:
219 return False
219 return False
220 finally:
220 finally:
221 os.remove(path)
221 os.remove(path)
222
222
223 @check("fifo", "named pipes")
223 @check("fifo", "named pipes")
224 def has_fifo():
224 def has_fifo():
225 if getattr(os, "mkfifo", None) is None:
225 if getattr(os, "mkfifo", None) is None:
226 return False
226 return False
227 name = tempfile.mktemp(dir='.', prefix=tempprefix)
227 name = tempfile.mktemp(dir='.', prefix=tempprefix)
228 try:
228 try:
229 os.mkfifo(name)
229 os.mkfifo(name)
230 os.unlink(name)
230 os.unlink(name)
231 return True
231 return True
232 except OSError:
232 except OSError:
233 return False
233 return False
234
234
235 @check("killdaemons", 'killdaemons.py support')
235 @check("killdaemons", 'killdaemons.py support')
236 def has_killdaemons():
236 def has_killdaemons():
237 return True
237 return True
238
238
239 @check("cacheable", "cacheable filesystem")
239 @check("cacheable", "cacheable filesystem")
240 def has_cacheable_fs():
240 def has_cacheable_fs():
241 from mercurial import util
241 from mercurial import util
242
242
243 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
243 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
244 os.close(fd)
244 os.close(fd)
245 try:
245 try:
246 return util.cachestat(path).cacheable()
246 return util.cachestat(path).cacheable()
247 finally:
247 finally:
248 os.remove(path)
248 os.remove(path)
249
249
250 @check("lsprof", "python lsprof module")
250 @check("lsprof", "python lsprof module")
251 def has_lsprof():
251 def has_lsprof():
252 try:
252 try:
253 import _lsprof
253 import _lsprof
254 _lsprof.Profiler # silence unused import warning
254 _lsprof.Profiler # silence unused import warning
255 return True
255 return True
256 except ImportError:
256 except ImportError:
257 return False
257 return False
258
258
259 def gethgversion():
259 def gethgversion():
260 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
260 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
261 if not m:
261 if not m:
262 return (0, 0)
262 return (0, 0)
263 return (int(m.group(1)), int(m.group(2)))
263 return (int(m.group(1)), int(m.group(2)))
264
264
265 @checkvers("hg", "Mercurial >= %s",
265 @checkvers("hg", "Mercurial >= %s",
266 list([(1.0 * x) / 10 for x in range(9, 99)]))
266 list([(1.0 * x) / 10 for x in range(9, 99)]))
267 def has_hg_range(v):
267 def has_hg_range(v):
268 major, minor = v.split('.')[0:2]
268 major, minor = v.split('.')[0:2]
269 return gethgversion() >= (int(major), int(minor))
269 return gethgversion() >= (int(major), int(minor))
270
270
271 @check("hg08", "Mercurial >= 0.8")
271 @check("hg08", "Mercurial >= 0.8")
272 def has_hg08():
272 def has_hg08():
273 if checks["hg09"][0]():
273 if checks["hg09"][0]():
274 return True
274 return True
275 return matchoutput('hg help annotate 2>&1', '--date')
275 return matchoutput('hg help annotate 2>&1', '--date')
276
276
277 @check("hg07", "Mercurial >= 0.7")
277 @check("hg07", "Mercurial >= 0.7")
278 def has_hg07():
278 def has_hg07():
279 if checks["hg08"][0]():
279 if checks["hg08"][0]():
280 return True
280 return True
281 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
281 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
282
282
283 @check("hg06", "Mercurial >= 0.6")
283 @check("hg06", "Mercurial >= 0.6")
284 def has_hg06():
284 def has_hg06():
285 if checks["hg07"][0]():
285 if checks["hg07"][0]():
286 return True
286 return True
287 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
287 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
288
288
289 @check("gettext", "GNU Gettext (msgfmt)")
289 @check("gettext", "GNU Gettext (msgfmt)")
290 def has_gettext():
290 def has_gettext():
291 return matchoutput('msgfmt --version', br'GNU gettext-tools')
291 return matchoutput('msgfmt --version', br'GNU gettext-tools')
292
292
293 @check("git", "git command line client")
293 @check("git", "git command line client")
294 def has_git():
294 def has_git():
295 return matchoutput('git --version 2>&1', br'^git version')
295 return matchoutput('git --version 2>&1', br'^git version')
296
296
297 def getgitversion():
297 def getgitversion():
298 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
298 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
299 if not m:
299 if not m:
300 return (0, 0)
300 return (0, 0)
301 return (int(m.group(1)), int(m.group(2)))
301 return (int(m.group(1)), int(m.group(2)))
302
302
303 # https://github.com/git-lfs/lfs-test-server
303 # https://github.com/git-lfs/lfs-test-server
304 @check("lfs-test-server", "git-lfs test server")
304 @check("lfs-test-server", "git-lfs test server")
305 def has_lfsserver():
305 def has_lfsserver():
306 exe = 'lfs-test-server'
306 exe = 'lfs-test-server'
307 if has_windows():
307 if has_windows():
308 exe = 'lfs-test-server.exe'
308 exe = 'lfs-test-server.exe'
309 return any(
309 return any(
310 os.access(os.path.join(path, exe), os.X_OK)
310 os.access(os.path.join(path, exe), os.X_OK)
311 for path in os.environ["PATH"].split(os.pathsep)
311 for path in os.environ["PATH"].split(os.pathsep)
312 )
312 )
313
313
314 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
314 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
315 def has_git_range(v):
315 def has_git_range(v):
316 major, minor = v.split('.')[0:2]
316 major, minor = v.split('.')[0:2]
317 return getgitversion() >= (int(major), int(minor))
317 return getgitversion() >= (int(major), int(minor))
318
318
319 @check("docutils", "Docutils text processing library")
319 @check("docutils", "Docutils text processing library")
320 def has_docutils():
320 def has_docutils():
321 try:
321 try:
322 import docutils.core
322 import docutils.core
323 docutils.core.publish_cmdline # silence unused import
323 docutils.core.publish_cmdline # silence unused import
324 return True
324 return True
325 except ImportError:
325 except ImportError:
326 return False
326 return False
327
327
328 def getsvnversion():
328 def getsvnversion():
329 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
329 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
330 if not m:
330 if not m:
331 return (0, 0)
331 return (0, 0)
332 return (int(m.group(1)), int(m.group(2)))
332 return (int(m.group(1)), int(m.group(2)))
333
333
334 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
334 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
335 def has_svn_range(v):
335 def has_svn_range(v):
336 major, minor = v.split('.')[0:2]
336 major, minor = v.split('.')[0:2]
337 return getsvnversion() >= (int(major), int(minor))
337 return getsvnversion() >= (int(major), int(minor))
338
338
339 @check("svn", "subversion client and admin tools")
339 @check("svn", "subversion client and admin tools")
340 def has_svn():
340 def has_svn():
341 return matchoutput('svn --version 2>&1', br'^svn, version') and \
341 return matchoutput('svn --version 2>&1', br'^svn, version') and \
342 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
342 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
343
343
344 @check("svn-bindings", "subversion python bindings")
344 @check("svn-bindings", "subversion python bindings")
345 def has_svn_bindings():
345 def has_svn_bindings():
346 try:
346 try:
347 import svn.core
347 import svn.core
348 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
348 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
349 if version < (1, 4):
349 if version < (1, 4):
350 return False
350 return False
351 return True
351 return True
352 except ImportError:
352 except ImportError:
353 return False
353 return False
354
354
355 @check("p4", "Perforce server and client")
355 @check("p4", "Perforce server and client")
356 def has_p4():
356 def has_p4():
357 return (matchoutput('p4 -V', br'Rev\. P4/') and
357 return (matchoutput('p4 -V', br'Rev\. P4/') and
358 matchoutput('p4d -V', br'Rev\. P4D/'))
358 matchoutput('p4d -V', br'Rev\. P4D/'))
359
359
360 @check("symlink", "symbolic links")
360 @check("symlink", "symbolic links")
361 def has_symlink():
361 def has_symlink():
362 if getattr(os, "symlink", None) is None:
362 if getattr(os, "symlink", None) is None:
363 return False
363 return False
364 name = tempfile.mktemp(dir='.', prefix=tempprefix)
364 name = tempfile.mktemp(dir='.', prefix=tempprefix)
365 try:
365 try:
366 os.symlink(".", name)
366 os.symlink(".", name)
367 os.unlink(name)
367 os.unlink(name)
368 return True
368 return True
369 except (OSError, AttributeError):
369 except (OSError, AttributeError):
370 return False
370 return False
371
371
372 @check("hardlink", "hardlinks")
372 @check("hardlink", "hardlinks")
373 def has_hardlink():
373 def has_hardlink():
374 from mercurial import util
374 from mercurial import util
375 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
375 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
376 os.close(fh)
376 os.close(fh)
377 name = tempfile.mktemp(dir='.', prefix=tempprefix)
377 name = tempfile.mktemp(dir='.', prefix=tempprefix)
378 try:
378 try:
379 util.oslink(_bytespath(fn), _bytespath(name))
379 util.oslink(_bytespath(fn), _bytespath(name))
380 os.unlink(name)
380 os.unlink(name)
381 return True
381 return True
382 except OSError:
382 except OSError:
383 return False
383 return False
384 finally:
384 finally:
385 os.unlink(fn)
385 os.unlink(fn)
386
386
387 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
387 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
388 def has_hardlink_whitelisted():
388 def has_hardlink_whitelisted():
389 from mercurial import util
389 from mercurial import util
390 try:
390 try:
391 fstype = util.getfstype(b'.')
391 fstype = util.getfstype(b'.')
392 except OSError:
392 except OSError:
393 return False
393 return False
394 return fstype in util._hardlinkfswhitelist
394 return fstype in util._hardlinkfswhitelist
395
395
396 @check("rmcwd", "can remove current working directory")
396 @check("rmcwd", "can remove current working directory")
397 def has_rmcwd():
397 def has_rmcwd():
398 ocwd = os.getcwd()
398 ocwd = os.getcwd()
399 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
399 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
400 try:
400 try:
401 os.chdir(temp)
401 os.chdir(temp)
402 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
402 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
403 # On Solaris and Windows, the cwd can't be removed by any names.
403 # On Solaris and Windows, the cwd can't be removed by any names.
404 os.rmdir(os.getcwd())
404 os.rmdir(os.getcwd())
405 return True
405 return True
406 except OSError:
406 except OSError:
407 return False
407 return False
408 finally:
408 finally:
409 os.chdir(ocwd)
409 os.chdir(ocwd)
410 # clean up temp dir on platforms where cwd can't be removed
410 # clean up temp dir on platforms where cwd can't be removed
411 try:
411 try:
412 os.rmdir(temp)
412 os.rmdir(temp)
413 except OSError:
413 except OSError:
414 pass
414 pass
415
415
416 @check("tla", "GNU Arch tla client")
416 @check("tla", "GNU Arch tla client")
417 def has_tla():
417 def has_tla():
418 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
418 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
419
419
420 @check("gpg", "gpg client")
420 @check("gpg", "gpg client")
421 def has_gpg():
421 def has_gpg():
422 return matchoutput('gpg --version 2>&1', br'GnuPG')
422 return matchoutput('gpg --version 2>&1', br'GnuPG')
423
423
424 @check("gpg2", "gpg client v2")
424 @check("gpg2", "gpg client v2")
425 def has_gpg2():
425 def has_gpg2():
426 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
426 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
427
427
428 @check("gpg21", "gpg client v2.1+")
428 @check("gpg21", "gpg client v2.1+")
429 def has_gpg21():
429 def has_gpg21():
430 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
430 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
431
431
432 @check("unix-permissions", "unix-style permissions")
432 @check("unix-permissions", "unix-style permissions")
433 def has_unix_permissions():
433 def has_unix_permissions():
434 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
434 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
435 try:
435 try:
436 fname = os.path.join(d, 'foo')
436 fname = os.path.join(d, 'foo')
437 for umask in (0o77, 0o07, 0o22):
437 for umask in (0o77, 0o07, 0o22):
438 os.umask(umask)
438 os.umask(umask)
439 f = open(fname, 'w')
439 f = open(fname, 'w')
440 f.close()
440 f.close()
441 mode = os.stat(fname).st_mode
441 mode = os.stat(fname).st_mode
442 os.unlink(fname)
442 os.unlink(fname)
443 if mode & 0o777 != ~umask & 0o666:
443 if mode & 0o777 != ~umask & 0o666:
444 return False
444 return False
445 return True
445 return True
446 finally:
446 finally:
447 os.rmdir(d)
447 os.rmdir(d)
448
448
449 @check("unix-socket", "AF_UNIX socket family")
449 @check("unix-socket", "AF_UNIX socket family")
450 def has_unix_socket():
450 def has_unix_socket():
451 return getattr(socket, 'AF_UNIX', None) is not None
451 return getattr(socket, 'AF_UNIX', None) is not None
452
452
453 @check("root", "root permissions")
453 @check("root", "root permissions")
454 def has_root():
454 def has_root():
455 return getattr(os, 'geteuid', None) and os.geteuid() == 0
455 return getattr(os, 'geteuid', None) and os.geteuid() == 0
456
456
457 @check("pyflakes", "Pyflakes python linter")
457 @check("pyflakes", "Pyflakes python linter")
458 def has_pyflakes():
458 def has_pyflakes():
459 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
459 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
460 br"<stdin>:1: 're' imported but unused",
460 br"<stdin>:1: 're' imported but unused",
461 True)
461 True)
462
462
463 @check("pylint", "Pylint python linter")
463 @check("pylint", "Pylint python linter")
464 def has_pylint():
464 def has_pylint():
465 return matchoutput("pylint --help",
465 return matchoutput("pylint --help",
466 br"Usage: pylint",
466 br"Usage: pylint",
467 True)
467 True)
468
468
469 @check("clang-format", "clang-format C code formatter")
469 @check("clang-format", "clang-format C code formatter")
470 def has_clang_format():
470 def has_clang_format():
471 m = matchoutput('clang-format --version', br'clang-format version (\d)')
471 m = matchoutput('clang-format --version', br'clang-format version (\d)')
472 # style changed somewhere between 4.x and 6.x
472 # style changed somewhere between 4.x and 6.x
473 return m and int(m.group(1)) >= 6
473 return m and int(m.group(1)) >= 6
474
474
475 @check("jshint", "JSHint static code analysis tool")
475 @check("jshint", "JSHint static code analysis tool")
476 def has_jshint():
476 def has_jshint():
477 return matchoutput("jshint --version 2>&1", br"jshint v")
477 return matchoutput("jshint --version 2>&1", br"jshint v")
478
478
479 @check("pygments", "Pygments source highlighting library")
479 @check("pygments", "Pygments source highlighting library")
480 def has_pygments():
480 def has_pygments():
481 try:
481 try:
482 import pygments
482 import pygments
483 pygments.highlight # silence unused import warning
483 pygments.highlight # silence unused import warning
484 return True
484 return True
485 except ImportError:
485 except ImportError:
486 return False
486 return False
487
487
488 @check("outer-repo", "outer repo")
488 @check("outer-repo", "outer repo")
489 def has_outer_repo():
489 def has_outer_repo():
490 # failing for other reasons than 'no repo' imply that there is a repo
490 # failing for other reasons than 'no repo' imply that there is a repo
491 return not matchoutput('hg root 2>&1',
491 return not matchoutput('hg root 2>&1',
492 br'abort: no repository found', True)
492 br'abort: no repository found', True)
493
493
494 @check("ssl", "ssl module available")
494 @check("ssl", "ssl module available")
495 def has_ssl():
495 def has_ssl():
496 try:
496 try:
497 import ssl
497 import ssl
498 ssl.CERT_NONE
498 ssl.CERT_NONE
499 return True
499 return True
500 except ImportError:
500 except ImportError:
501 return False
501 return False
502
502
503 @check("sslcontext", "python >= 2.7.9 ssl")
503 @check("sslcontext", "python >= 2.7.9 ssl")
504 def has_sslcontext():
504 def has_sslcontext():
505 try:
505 try:
506 import ssl
506 import ssl
507 ssl.SSLContext
507 ssl.SSLContext
508 return True
508 return True
509 except (ImportError, AttributeError):
509 except (ImportError, AttributeError):
510 return False
510 return False
511
511
512 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
512 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
513 def has_defaultcacerts():
513 def has_defaultcacerts():
514 from mercurial import sslutil, ui as uimod
514 from mercurial import sslutil, ui as uimod
515 ui = uimod.ui.load()
515 ui = uimod.ui.load()
516 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
516 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
517
517
518 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
518 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
519 def has_defaultcacertsloaded():
519 def has_defaultcacertsloaded():
520 import ssl
520 import ssl
521 from mercurial import sslutil, ui as uimod
521 from mercurial import sslutil, ui as uimod
522
522
523 if not has_defaultcacerts():
523 if not has_defaultcacerts():
524 return False
524 return False
525 if not has_sslcontext():
525 if not has_sslcontext():
526 return False
526 return False
527
527
528 ui = uimod.ui.load()
528 ui = uimod.ui.load()
529 cafile = sslutil._defaultcacerts(ui)
529 cafile = sslutil._defaultcacerts(ui)
530 ctx = ssl.create_default_context()
530 ctx = ssl.create_default_context()
531 if cafile:
531 if cafile:
532 ctx.load_verify_locations(cafile=cafile)
532 ctx.load_verify_locations(cafile=cafile)
533 else:
533 else:
534 ctx.load_default_certs()
534 ctx.load_default_certs()
535
535
536 return len(ctx.get_ca_certs()) > 0
536 return len(ctx.get_ca_certs()) > 0
537
537
538 @check("tls1.2", "TLS 1.2 protocol support")
538 @check("tls1.2", "TLS 1.2 protocol support")
539 def has_tls1_2():
539 def has_tls1_2():
540 from mercurial import sslutil
540 from mercurial import sslutil
541 return 'tls1.2' in sslutil.supportedprotocols
541 return 'tls1.2' in sslutil.supportedprotocols
542
542
543 @check("windows", "Windows")
543 @check("windows", "Windows")
544 def has_windows():
544 def has_windows():
545 return os.name == 'nt'
545 return os.name == 'nt'
546
546
547 @check("system-sh", "system() uses sh")
547 @check("system-sh", "system() uses sh")
548 def has_system_sh():
548 def has_system_sh():
549 return os.name != 'nt'
549 return os.name != 'nt'
550
550
551 @check("serve", "platform and python can manage 'hg serve -d'")
551 @check("serve", "platform and python can manage 'hg serve -d'")
552 def has_serve():
552 def has_serve():
553 return True
553 return True
554
554
555 @check("test-repo", "running tests from repository")
555 @check("test-repo", "running tests from repository")
556 def has_test_repo():
556 def has_test_repo():
557 t = os.environ["TESTDIR"]
557 t = os.environ["TESTDIR"]
558 return os.path.isdir(os.path.join(t, "..", ".hg"))
558 return os.path.isdir(os.path.join(t, "..", ".hg"))
559
559
560 @check("tic", "terminfo compiler and curses module")
560 @check("tic", "terminfo compiler and curses module")
561 def has_tic():
561 def has_tic():
562 try:
562 try:
563 import curses
563 import curses
564 curses.COLOR_BLUE
564 curses.COLOR_BLUE
565 return matchoutput('test -x "`which tic`"', br'')
565 return matchoutput('test -x "`which tic`"', br'')
566 except ImportError:
566 except ImportError:
567 return False
567 return False
568
568
569 @check("msys", "Windows with MSYS")
569 @check("msys", "Windows with MSYS")
570 def has_msys():
570 def has_msys():
571 return os.getenv('MSYSTEM')
571 return os.getenv('MSYSTEM')
572
572
573 @check("aix", "AIX")
573 @check("aix", "AIX")
574 def has_aix():
574 def has_aix():
575 return sys.platform.startswith("aix")
575 return sys.platform.startswith("aix")
576
576
577 @check("osx", "OS X")
577 @check("osx", "OS X")
578 def has_osx():
578 def has_osx():
579 return sys.platform == 'darwin'
579 return sys.platform == 'darwin'
580
580
581 @check("osxpackaging", "OS X packaging tools")
581 @check("osxpackaging", "OS X packaging tools")
582 def has_osxpackaging():
582 def has_osxpackaging():
583 try:
583 try:
584 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
584 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
585 and matchoutput(
585 and matchoutput(
586 'productbuild', br'Usage: productbuild ',
586 'productbuild', br'Usage: productbuild ',
587 ignorestatus=1)
587 ignorestatus=1)
588 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
588 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
589 and matchoutput(
589 and matchoutput(
590 'xar --help', br'Usage: xar', ignorestatus=1))
590 'xar --help', br'Usage: xar', ignorestatus=1))
591 except ImportError:
591 except ImportError:
592 return False
592 return False
593
593
594 @check('linuxormacos', 'Linux or MacOS')
594 @check('linuxormacos', 'Linux or MacOS')
595 def has_linuxormacos():
595 def has_linuxormacos():
596 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
596 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
597 return sys.platform.startswith(('linux', 'darwin'))
597 return sys.platform.startswith(('linux', 'darwin'))
598
598
599 @check("docker", "docker support")
599 @check("docker", "docker support")
600 def has_docker():
600 def has_docker():
601 pat = br'A self-sufficient runtime for'
601 pat = br'A self-sufficient runtime for'
602 if matchoutput('docker --help', pat):
602 if matchoutput('docker --help', pat):
603 if 'linux' not in sys.platform:
603 if 'linux' not in sys.platform:
604 # TODO: in theory we should be able to test docker-based
604 # TODO: in theory we should be able to test docker-based
605 # package creation on non-linux using boot2docker, but in
605 # package creation on non-linux using boot2docker, but in
606 # practice that requires extra coordination to make sure
606 # practice that requires extra coordination to make sure
607 # $TESTTEMP is going to be visible at the same path to the
607 # $TESTTEMP is going to be visible at the same path to the
608 # boot2docker VM. If we figure out how to verify that, we
608 # boot2docker VM. If we figure out how to verify that, we
609 # can use the following instead of just saying False:
609 # can use the following instead of just saying False:
610 # return 'DOCKER_HOST' in os.environ
610 # return 'DOCKER_HOST' in os.environ
611 return False
611 return False
612
612
613 return True
613 return True
614 return False
614 return False
615
615
616 @check("debhelper", "debian packaging tools")
616 @check("debhelper", "debian packaging tools")
617 def has_debhelper():
617 def has_debhelper():
618 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
618 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
619 # quote), so just accept anything in that spot.
619 # quote), so just accept anything in that spot.
620 dpkg = matchoutput('dpkg --version',
620 dpkg = matchoutput('dpkg --version',
621 br"Debian .dpkg' package management program")
621 br"Debian .dpkg' package management program")
622 dh = matchoutput('dh --help',
622 dh = matchoutput('dh --help',
623 br'dh is a part of debhelper.', ignorestatus=True)
623 br'dh is a part of debhelper.', ignorestatus=True)
624 dh_py2 = matchoutput('dh_python2 --help',
624 dh_py2 = matchoutput('dh_python2 --help',
625 br'other supported Python versions')
625 br'other supported Python versions')
626 # debuild comes from the 'devscripts' package, though you might want
626 # debuild comes from the 'devscripts' package, though you might want
627 # the 'build-debs' package instead, which has a dependency on devscripts.
627 # the 'build-debs' package instead, which has a dependency on devscripts.
628 debuild = matchoutput('debuild --help',
628 debuild = matchoutput('debuild --help',
629 br'to run debian/rules with given parameter')
629 br'to run debian/rules with given parameter')
630 return dpkg and dh and dh_py2 and debuild
630 return dpkg and dh and dh_py2 and debuild
631
631
632 @check("debdeps",
632 @check("debdeps",
633 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
633 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
634 def has_debdeps():
634 def has_debdeps():
635 # just check exit status (ignoring output)
635 # just check exit status (ignoring output)
636 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
636 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
637 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
637 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
638
638
639 @check("demandimport", "demandimport enabled")
639 @check("demandimport", "demandimport enabled")
640 def has_demandimport():
640 def has_demandimport():
641 # chg disables demandimport intentionally for performance wins.
641 # chg disables demandimport intentionally for performance wins.
642 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
642 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
643
643
644 @check("py3", "running with Python 3.x")
644 @check("py3", "running with Python 3.x")
645 def has_py3():
645 def has_py3():
646 return 3 == sys.version_info[0]
646 return 3 == sys.version_info[0]
647
647
648 @check("py3exe", "a Python 3.x interpreter is available")
648 @check("py3exe", "a Python 3.x interpreter is available")
649 def has_python3exe():
649 def has_python3exe():
650 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
650 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
651
651
652 @check("pure", "running with pure Python code")
652 @check("pure", "running with pure Python code")
653 def has_pure():
653 def has_pure():
654 return any([
654 return any([
655 os.environ.get("HGMODULEPOLICY") == "py",
655 os.environ.get("HGMODULEPOLICY") == "py",
656 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
656 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
657 ])
657 ])
658
658
659 @check("slow", "allow slow tests (use --allow-slow-tests)")
659 @check("slow", "allow slow tests (use --allow-slow-tests)")
660 def has_slow():
660 def has_slow():
661 return os.environ.get('HGTEST_SLOW') == 'slow'
661 return os.environ.get('HGTEST_SLOW') == 'slow'
662
662
663 @check("hypothesis", "Hypothesis automated test generation")
663 @check("hypothesis", "Hypothesis automated test generation")
664 def has_hypothesis():
664 def has_hypothesis():
665 try:
665 try:
666 import hypothesis
666 import hypothesis
667 hypothesis.given
667 hypothesis.given
668 return True
668 return True
669 except ImportError:
669 except ImportError:
670 return False
670 return False
671
671
672 @check("unziplinks", "unzip(1) understands and extracts symlinks")
672 @check("unziplinks", "unzip(1) understands and extracts symlinks")
673 def unzip_understands_symlinks():
673 def unzip_understands_symlinks():
674 return matchoutput('unzip --help', br'Info-ZIP')
674 return matchoutput('unzip --help', br'Info-ZIP')
675
675
676 @check("zstd", "zstd Python module available")
676 @check("zstd", "zstd Python module available")
677 def has_zstd():
677 def has_zstd():
678 try:
678 try:
679 import mercurial.zstd
679 import mercurial.zstd
680 mercurial.zstd.__version__
680 mercurial.zstd.__version__
681 return True
681 return True
682 except ImportError:
682 except ImportError:
683 return False
683 return False
684
684
685 @check("devfull", "/dev/full special file")
685 @check("devfull", "/dev/full special file")
686 def has_dev_full():
686 def has_dev_full():
687 return os.path.exists('/dev/full')
687 return os.path.exists('/dev/full')
688
688
689 @check("virtualenv", "Python virtualenv support")
689 @check("virtualenv", "Python virtualenv support")
690 def has_virtualenv():
690 def has_virtualenv():
691 try:
691 try:
692 import virtualenv
692 import virtualenv
693 virtualenv.ACTIVATE_SH
693 virtualenv.ACTIVATE_SH
694 return True
694 return True
695 except ImportError:
695 except ImportError:
696 return False
696 return False
697
697
698 @check("fsmonitor", "running tests with fsmonitor")
698 @check("fsmonitor", "running tests with fsmonitor")
699 def has_fsmonitor():
699 def has_fsmonitor():
700 return 'HGFSMONITOR_TESTS' in os.environ
700 return 'HGFSMONITOR_TESTS' in os.environ
701
701
702 @check("fuzzywuzzy", "Fuzzy string matching library")
702 @check("fuzzywuzzy", "Fuzzy string matching library")
703 def has_fuzzywuzzy():
703 def has_fuzzywuzzy():
704 try:
704 try:
705 import fuzzywuzzy
705 import fuzzywuzzy
706 fuzzywuzzy.__version__
706 fuzzywuzzy.__version__
707 return True
707 return True
708 except ImportError:
708 except ImportError:
709 return False
709 return False
710
710
711 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
711 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
712 def has_clang_libfuzzer():
712 def has_clang_libfuzzer():
713 mat = matchoutput('clang --version', b'clang version (\d)')
713 mat = matchoutput('clang --version', b'clang version (\d)')
714 if mat:
714 if mat:
715 # libfuzzer is new in clang 6
715 # libfuzzer is new in clang 6
716 return int(mat.group(1)) > 5
716 return int(mat.group(1)) > 5
717 return False
717 return False
718
718
719 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
719 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
720 def has_clang60():
720 def has_clang60():
721 return matchoutput('clang-6.0 --version', b'clang version 6\.')
721 return matchoutput('clang-6.0 --version', b'clang version 6\.')
722
722
723 @check("xdiff", "xdiff algorithm")
723 @check("xdiff", "xdiff algorithm")
724 def has_xdiff():
724 def has_xdiff():
725 try:
725 try:
726 from mercurial import policy
726 from mercurial import policy
727 bdiff = policy.importmod('bdiff')
727 bdiff = policy.importmod('bdiff')
728 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
728 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
729 except (ImportError, AttributeError):
729 except (ImportError, AttributeError):
730 return False
730 return False
731
731
732 @check('extraextensions', 'whether tests are running with extra extensions')
732 @check('extraextensions', 'whether tests are running with extra extensions')
733 def has_extraextensions():
733 def has_extraextensions():
734 return 'HGTESTEXTRAEXTENSIONS' in os.environ
734 return 'HGTESTEXTRAEXTENSIONS' in os.environ
735
735
736 def getrepofeatures():
736 def getrepofeatures():
737 """Obtain set of repository features in use.
737 """Obtain set of repository features in use.
738
738
739 HGREPOFEATURES can be used to define or remove features. It contains
739 HGREPOFEATURES can be used to define or remove features. It contains
740 a space-delimited list of feature strings. Strings beginning with ``-``
740 a space-delimited list of feature strings. Strings beginning with ``-``
741 mean to remove.
741 mean to remove.
742 """
742 """
743 # Default list provided by core.
743 # Default list provided by core.
744 features = {
744 features = {
745 'bundlerepo',
745 'bundlerepo',
746 'revlogstore',
746 'revlogstore',
747 'fncache',
747 'fncache',
748 }
748 }
749
749
750 # Features that imply other features.
750 # Features that imply other features.
751 implies = {
751 implies = {
752 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
752 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
753 }
753 }
754
754
755 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
755 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
756 if not override:
756 if not override:
757 continue
757 continue
758
758
759 if override.startswith('-'):
759 if override.startswith('-'):
760 if override[1:] in features:
760 if override[1:] in features:
761 features.remove(override[1:])
761 features.remove(override[1:])
762 else:
762 else:
763 features.add(override)
763 features.add(override)
764
764
765 for imply in implies.get(override, []):
765 for imply in implies.get(override, []):
766 if imply.startswith('-'):
766 if imply.startswith('-'):
767 if imply[1:] in features:
767 if imply[1:] in features:
768 features.remove(imply[1:])
768 features.remove(imply[1:])
769 else:
769 else:
770 features.add(imply)
770 features.add(imply)
771
771
772 return features
772 return features
773
773
774 @check('reporevlogstore', 'repository using the default revlog store')
774 @check('reporevlogstore', 'repository using the default revlog store')
775 def has_reporevlogstore():
775 def has_reporevlogstore():
776 return 'revlogstore' in getrepofeatures()
776 return 'revlogstore' in getrepofeatures()
777
777
778 @check('reposimplestore', 'repository using simple storage extension')
778 @check('reposimplestore', 'repository using simple storage extension')
779 def has_reposimplestore():
779 def has_reposimplestore():
780 return 'simplestore' in getrepofeatures()
780 return 'simplestore' in getrepofeatures()
781
781
782 @check('repobundlerepo', 'whether we can open bundle files as repos')
782 @check('repobundlerepo', 'whether we can open bundle files as repos')
783 def has_repobundlerepo():
783 def has_repobundlerepo():
784 return 'bundlerepo' in getrepofeatures()
784 return 'bundlerepo' in getrepofeatures()
785
785
786 @check('repofncache', 'repository has an fncache')
786 @check('repofncache', 'repository has an fncache')
787 def has_repofncache():
787 def has_repofncache():
788 return 'fncache' in getrepofeatures()
788 return 'fncache' in getrepofeatures()
789
789
790 @check('sqlite', 'sqlite3 module is available')
790 @check('sqlite', 'sqlite3 module is available')
791 def has_sqlite():
791 def has_sqlite():
792 try:
792 try:
793 import sqlite3
793 import sqlite3
794 sqlite3.sqlite_version
794 version = sqlite3.sqlite_version_info
795 except ImportError:
795 except ImportError:
796 return False
796 return False
797
797
798 if version < (3, 8, 3):
799 # WITH clause not supported
800 return False
801
798 return matchoutput('sqlite3 -version', b'^3\.\d+')
802 return matchoutput('sqlite3 -version', b'^3\.\d+')
799
803
800 @check('vcr', 'vcr http mocking library')
804 @check('vcr', 'vcr http mocking library')
801 def has_vcr():
805 def has_vcr():
802 try:
806 try:
803 import vcr
807 import vcr
804 vcr.VCR
808 vcr.VCR
805 return True
809 return True
806 except (ImportError, AttributeError):
810 except (ImportError, AttributeError):
807 pass
811 pass
808 return False
812 return False
@@ -1,125 +1,133 b''
1 # This test verifies the conformance of various classes to various
1 # This test verifies the conformance of various classes to various
2 # storage interfaces.
2 # storage interfaces.
3 from __future__ import absolute_import
3 from __future__ import absolute_import
4
4
5 import silenttestrunner
5 import silenttestrunner
6
6
7 from mercurial import (
7 from mercurial import (
8 error,
8 error,
9 filelog,
9 filelog,
10 revlog,
10 revlog,
11 transaction,
11 transaction,
12 ui as uimod,
12 ui as uimod,
13 vfs as vfsmod,
13 vfs as vfsmod,
14 )
14 )
15
15
16 from mercurial.testing import (
16 from mercurial.testing import (
17 storage as storagetesting,
17 storage as storagetesting,
18 )
18 )
19
19
20 try:
20 try:
21 from hgext import (
21 from hgext import (
22 sqlitestore,
22 sqlitestore,
23 )
23 )
24 except ImportError:
24 except ImportError:
25 sqlitestore = None
25 sqlitestore = None
26
26
27 try:
27 try:
28 import sqlite3
29 if sqlite3.sqlite_version_info < (3, 8, 3):
30 # WITH clause not supported
31 sqlitestore = None
32 except ImportError:
33 pass
34
35 try:
28 from mercurial import zstd
36 from mercurial import zstd
29 zstd.__version__
37 zstd.__version__
30 except ImportError:
38 except ImportError:
31 zstd = None
39 zstd = None
32
40
33 STATE = {
41 STATE = {
34 'lastindex': 0,
42 'lastindex': 0,
35 'ui': uimod.ui(),
43 'ui': uimod.ui(),
36 'vfs': vfsmod.vfs(b'.', realpath=True),
44 'vfs': vfsmod.vfs(b'.', realpath=True),
37 }
45 }
38
46
39 def makefilefn(self):
47 def makefilefn(self):
40 """Factory for filelog instances."""
48 """Factory for filelog instances."""
41 fl = filelog.filelog(STATE['vfs'], b'filelog-%d' % STATE['lastindex'])
49 fl = filelog.filelog(STATE['vfs'], b'filelog-%d' % STATE['lastindex'])
42 STATE['lastindex'] += 1
50 STATE['lastindex'] += 1
43 return fl
51 return fl
44
52
45 def maketransaction(self):
53 def maketransaction(self):
46 vfsmap = {b'plain': STATE['vfs'], b'store': STATE['vfs']}
54 vfsmap = {b'plain': STATE['vfs'], b'store': STATE['vfs']}
47
55
48 return transaction.transaction(STATE['ui'].warn, STATE['vfs'], vfsmap,
56 return transaction.transaction(STATE['ui'].warn, STATE['vfs'], vfsmap,
49 b'journal', b'undo')
57 b'journal', b'undo')
50
58
51 def addrawrevision(self, fl, tr, node, p1, p2, linkrev, rawtext=None,
59 def addrawrevision(self, fl, tr, node, p1, p2, linkrev, rawtext=None,
52 delta=None, censored=False, ellipsis=False, extstored=False):
60 delta=None, censored=False, ellipsis=False, extstored=False):
53 flags = 0
61 flags = 0
54
62
55 if censored:
63 if censored:
56 flags |= revlog.REVIDX_ISCENSORED
64 flags |= revlog.REVIDX_ISCENSORED
57 if ellipsis:
65 if ellipsis:
58 flags |= revlog.REVIDX_ELLIPSIS
66 flags |= revlog.REVIDX_ELLIPSIS
59 if extstored:
67 if extstored:
60 flags |= revlog.REVIDX_EXTSTORED
68 flags |= revlog.REVIDX_EXTSTORED
61
69
62 if rawtext is not None:
70 if rawtext is not None:
63 fl._revlog.addrawrevision(rawtext, tr, linkrev, p1, p2, node, flags)
71 fl._revlog.addrawrevision(rawtext, tr, linkrev, p1, p2, node, flags)
64 elif delta is not None:
72 elif delta is not None:
65 fl._revlog.addrawrevision(rawtext, tr, linkrev, p1, p2, node, flags,
73 fl._revlog.addrawrevision(rawtext, tr, linkrev, p1, p2, node, flags,
66 cachedelta=delta)
74 cachedelta=delta)
67 else:
75 else:
68 raise error.Abort('must supply rawtext or delta arguments')
76 raise error.Abort('must supply rawtext or delta arguments')
69
77
70 # We may insert bad data. Clear caches to prevent e.g. cache hits to
78 # We may insert bad data. Clear caches to prevent e.g. cache hits to
71 # bypass hash verification.
79 # bypass hash verification.
72 fl._revlog.clearcaches()
80 fl._revlog.clearcaches()
73
81
74 # Assigning module-level attributes that inherit from unittest.TestCase
82 # Assigning module-level attributes that inherit from unittest.TestCase
75 # is all that is needed to register tests.
83 # is all that is needed to register tests.
76 filelogindextests = storagetesting.makeifileindextests(makefilefn,
84 filelogindextests = storagetesting.makeifileindextests(makefilefn,
77 maketransaction,
85 maketransaction,
78 addrawrevision)
86 addrawrevision)
79 filelogdatatests = storagetesting.makeifiledatatests(makefilefn,
87 filelogdatatests = storagetesting.makeifiledatatests(makefilefn,
80 maketransaction,
88 maketransaction,
81 addrawrevision)
89 addrawrevision)
82 filelogmutationtests = storagetesting.makeifilemutationtests(makefilefn,
90 filelogmutationtests = storagetesting.makeifilemutationtests(makefilefn,
83 maketransaction,
91 maketransaction,
84 addrawrevision)
92 addrawrevision)
85
93
86 def makesqlitefile(self):
94 def makesqlitefile(self):
87 path = STATE['vfs'].join(b'db-%d.db' % STATE['lastindex'])
95 path = STATE['vfs'].join(b'db-%d.db' % STATE['lastindex'])
88 STATE['lastindex'] += 1
96 STATE['lastindex'] += 1
89
97
90 db = sqlitestore.makedb(path)
98 db = sqlitestore.makedb(path)
91
99
92 compression = b'zstd' if zstd else b'zlib'
100 compression = b'zstd' if zstd else b'zlib'
93
101
94 return sqlitestore.sqlitefilestore(db, b'dummy-path', compression)
102 return sqlitestore.sqlitefilestore(db, b'dummy-path', compression)
95
103
96 def addrawrevisionsqlite(self, fl, tr, node, p1, p2, linkrev, rawtext=None,
104 def addrawrevisionsqlite(self, fl, tr, node, p1, p2, linkrev, rawtext=None,
97 delta=None, censored=False, ellipsis=False,
105 delta=None, censored=False, ellipsis=False,
98 extstored=False):
106 extstored=False):
99 flags = 0
107 flags = 0
100
108
101 if censored:
109 if censored:
102 flags |= sqlitestore.FLAG_CENSORED
110 flags |= sqlitestore.FLAG_CENSORED
103
111
104 if ellipsis | extstored:
112 if ellipsis | extstored:
105 raise error.Abort(b'support for ellipsis and extstored flags not '
113 raise error.Abort(b'support for ellipsis and extstored flags not '
106 b'supported')
114 b'supported')
107
115
108 if rawtext is not None:
116 if rawtext is not None:
109 fl._addrawrevision(node, rawtext, tr, linkrev, p1, p2, flags=flags)
117 fl._addrawrevision(node, rawtext, tr, linkrev, p1, p2, flags=flags)
110 elif delta is not None:
118 elif delta is not None:
111 fl._addrawrevision(node, rawtext, tr, linkrev, p1, p2,
119 fl._addrawrevision(node, rawtext, tr, linkrev, p1, p2,
112 storedelta=delta, flags=flags)
120 storedelta=delta, flags=flags)
113 else:
121 else:
114 raise error.Abort(b'must supply rawtext or delta arguments')
122 raise error.Abort(b'must supply rawtext or delta arguments')
115
123
116 if sqlitestore is not None:
124 if sqlitestore is not None:
117 sqlitefileindextests = storagetesting.makeifileindextests(
125 sqlitefileindextests = storagetesting.makeifileindextests(
118 makesqlitefile, maketransaction, addrawrevisionsqlite)
126 makesqlitefile, maketransaction, addrawrevisionsqlite)
119 sqlitefiledatatests = storagetesting.makeifiledatatests(
127 sqlitefiledatatests = storagetesting.makeifiledatatests(
120 makesqlitefile, maketransaction, addrawrevisionsqlite)
128 makesqlitefile, maketransaction, addrawrevisionsqlite)
121 sqlitefilemutationtests = storagetesting.makeifilemutationtests(
129 sqlitefilemutationtests = storagetesting.makeifilemutationtests(
122 makesqlitefile, maketransaction, addrawrevisionsqlite)
130 makesqlitefile, maketransaction, addrawrevisionsqlite)
123
131
124 if __name__ == '__main__':
132 if __name__ == '__main__':
125 silenttestrunner.main(__name__)
133 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now