##// END OF EJS Templates
hghave: move feature checking into hghave.py...
Gregory Szorc -
r26067:8107c308 default
parent child Browse files
Show More
@@ -1,99 +1,67 b''
1 1 #!/usr/bin/env python
2 2 """Test the running system for features availability. Exit with zero
3 3 if all features are there, non-zero otherwise. If a feature name is
4 4 prefixed with "no-", the absence of feature is tested.
5 5 """
6 6 import optparse
7 7 import os, sys
8 8 import hghave
9 9
10 10 checks = hghave.checks
11 11
12 12 def list_features():
13 13 for name, feature in sorted(checks.iteritems()):
14 14 desc = feature[1]
15 15 print name + ':', desc
16 16
17 17 def test_features():
18 18 failed = 0
19 19 for name, feature in checks.iteritems():
20 20 check, _ = feature
21 21 try:
22 22 check()
23 23 except Exception, e:
24 24 print "feature %s failed: %s" % (name, e)
25 25 failed += 1
26 26 return failed
27 27
28 28 parser = optparse.OptionParser("%prog [options] [features]")
29 29 parser.add_option("--test-features", action="store_true",
30 30 help="test available features")
31 31 parser.add_option("--list-features", action="store_true",
32 32 help="list available features")
33 33 parser.add_option("-q", "--quiet", action="store_true",
34 34 help="check features silently")
35 35
36 36 def _loadaddon(quiet):
37 37 if 'TESTDIR' in os.environ:
38 38 # loading from '.' isn't needed, because `hghave` should be
39 39 # running at TESTTMP in this case
40 40 path = os.environ['TESTDIR']
41 41 else:
42 42 path = '.'
43 43
44 44 if not os.path.exists(os.path.join(path, 'hghaveaddon.py')):
45 45 return
46 46
47 47 sys.path.insert(0, path)
48 48 try:
49 49 import hghaveaddon
50 50 except BaseException, inst:
51 51 if not quiet:
52 52 sys.stderr.write('failed to import hghaveaddon.py from %r: %s\n'
53 53 % (path, inst))
54 54 sys.exit(2)
55 55 sys.path.pop(0)
56 56
57 57 if __name__ == '__main__':
58 58 options, args = parser.parse_args()
59 59 _loadaddon(options.quiet)
60 60 if options.list_features:
61 61 list_features()
62 62 sys.exit(0)
63 63
64 64 if options.test_features:
65 65 sys.exit(test_features())
66 66
67 quiet = options.quiet
68
69 failures = 0
70
71 def error(msg):
72 global failures
73 if not quiet:
74 sys.stderr.write(msg + '\n')
75 failures += 1
76
77 for feature in args:
78 negate = feature.startswith('no-')
79 if negate:
80 feature = feature[3:]
81
82 if feature not in checks:
83 error('skipped: unknown feature: ' + feature)
84 sys.exit(2)
85
86 check, desc = checks[feature]
87 try:
88 available = check()
89 except Exception, e:
90 error('hghave check failed: ' + feature)
91 continue
92
93 if not negate and not available:
94 error('skipped: missing feature: ' + desc)
95 elif negate and available:
96 error('skipped: system supports %s' % desc)
97
98 if failures != 0:
99 sys.exit(1)
67 hghave.require(args, options.quiet)
@@ -1,382 +1,430 b''
1 1 import os, stat
2 2 import re
3 3 import socket
4 4 import sys
5 5 import tempfile
6 6
7 7 tempprefix = 'hg-hghave-'
8 8
9 9 checks = {
10 10 "true": (lambda: True, "yak shaving"),
11 11 "false": (lambda: False, "nail clipper"),
12 12 }
13 13
14 14 def check(name, desc):
15 15 def decorator(func):
16 16 checks[name] = (func, desc)
17 17 return func
18 18 return decorator
19 19
20 def checkfeatures(features):
21 result = {
22 'error': [],
23 'missing': [],
24 'skipped': [],
25 }
26
27 for feature in features:
28 negate = feature.startswith('no-')
29 if negate:
30 feature = feature[3:]
31
32 if feature not in checks:
33 result['missing'].append(feature)
34 continue
35
36 check, desc = checks[feature]
37 try:
38 available = check()
39 except Exception:
40 result['error'].append('hghave check failed: %s' % feature)
41 continue
42
43 if not negate and not available:
44 result['skipped'].append('missing feature: %s' % desc)
45 elif negate and available:
46 result['skipped'].append('system supports %s' % desc)
47
48 return result
49
50 def require(features, quiet=False):
51 """Require that features are available, exiting if not."""
52 result = checkfeatures(features)
53
54 if not quiet:
55 for missing in result['missing']:
56 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
57 for msg in result['skipped']:
58 sys.stderr.write('skipped: %s\n' % msg)
59 for msg in result['error']:
60 sys.stderr.write('%s\n' % msg)
61
62 if result['missing']:
63 sys.exit(2)
64
65 if result['skipped'] or result['error']:
66 sys.exit(1)
67
20 68 def matchoutput(cmd, regexp, ignorestatus=False):
21 69 """Return True if cmd executes successfully and its output
22 70 is matched by the supplied regular expression.
23 71 """
24 72 r = re.compile(regexp)
25 73 fh = os.popen(cmd)
26 74 s = fh.read()
27 75 try:
28 76 ret = fh.close()
29 77 except IOError:
30 78 # Happen in Windows test environment
31 79 ret = 1
32 80 return (ignorestatus or ret is None) and r.search(s)
33 81
34 82 @check("baz", "GNU Arch baz client")
35 83 def has_baz():
36 84 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
37 85
38 86 @check("bzr", "Canonical's Bazaar client")
39 87 def has_bzr():
40 88 try:
41 89 import bzrlib
42 90 return bzrlib.__doc__ is not None
43 91 except ImportError:
44 92 return False
45 93
46 94 @check("bzr114", "Canonical's Bazaar client >= 1.14")
47 95 def has_bzr114():
48 96 try:
49 97 import bzrlib
50 98 return (bzrlib.__doc__ is not None
51 99 and bzrlib.version_info[:2] >= (1, 14))
52 100 except ImportError:
53 101 return False
54 102
55 103 @check("cvs", "cvs client/server")
56 104 def has_cvs():
57 105 re = r'Concurrent Versions System.*?server'
58 106 return matchoutput('cvs --version 2>&1', re) and not has_msys()
59 107
60 108 @check("cvs112", "cvs client/server >= 1.12")
61 109 def has_cvs112():
62 110 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
63 111 return matchoutput('cvs --version 2>&1', re) and not has_msys()
64 112
65 113 @check("darcs", "darcs client")
66 114 def has_darcs():
67 115 return matchoutput('darcs --version', r'2\.[2-9]', True)
68 116
69 117 @check("mtn", "monotone client (>= 1.0)")
70 118 def has_mtn():
71 119 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
72 120 'mtn --version', r'monotone 0\.', True)
73 121
74 122 @check("eol-in-paths", "end-of-lines in paths")
75 123 def has_eol_in_paths():
76 124 try:
77 125 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
78 126 os.close(fd)
79 127 os.remove(path)
80 128 return True
81 129 except (IOError, OSError):
82 130 return False
83 131
84 132 @check("execbit", "executable bit")
85 133 def has_executablebit():
86 134 try:
87 135 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
88 136 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
89 137 try:
90 138 os.close(fh)
91 139 m = os.stat(fn).st_mode & 0o777
92 140 new_file_has_exec = m & EXECFLAGS
93 141 os.chmod(fn, m ^ EXECFLAGS)
94 142 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
95 143 finally:
96 144 os.unlink(fn)
97 145 except (IOError, OSError):
98 146 # we don't care, the user probably won't be able to commit anyway
99 147 return False
100 148 return not (new_file_has_exec or exec_flags_cannot_flip)
101 149
102 150 @check("icasefs", "case insensitive file system")
103 151 def has_icasefs():
104 152 # Stolen from mercurial.util
105 153 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
106 154 os.close(fd)
107 155 try:
108 156 s1 = os.stat(path)
109 157 d, b = os.path.split(path)
110 158 p2 = os.path.join(d, b.upper())
111 159 if path == p2:
112 160 p2 = os.path.join(d, b.lower())
113 161 try:
114 162 s2 = os.stat(p2)
115 163 return s2 == s1
116 164 except OSError:
117 165 return False
118 166 finally:
119 167 os.remove(path)
120 168
121 169 @check("fifo", "named pipes")
122 170 def has_fifo():
123 171 if getattr(os, "mkfifo", None) is None:
124 172 return False
125 173 name = tempfile.mktemp(dir='.', prefix=tempprefix)
126 174 try:
127 175 os.mkfifo(name)
128 176 os.unlink(name)
129 177 return True
130 178 except OSError:
131 179 return False
132 180
133 181 @check("killdaemons", 'killdaemons.py support')
134 182 def has_killdaemons():
135 183 return True
136 184
137 185 @check("cacheable", "cacheable filesystem")
138 186 def has_cacheable_fs():
139 187 from mercurial import util
140 188
141 189 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
142 190 os.close(fd)
143 191 try:
144 192 return util.cachestat(path).cacheable()
145 193 finally:
146 194 os.remove(path)
147 195
148 196 @check("lsprof", "python lsprof module")
149 197 def has_lsprof():
150 198 try:
151 199 import _lsprof
152 200 _lsprof.Profiler # silence unused import warning
153 201 return True
154 202 except ImportError:
155 203 return False
156 204
157 205 @check("gettext", "GNU Gettext (msgfmt)")
158 206 def has_gettext():
159 207 return matchoutput('msgfmt --version', 'GNU gettext-tools')
160 208
161 209 @check("git", "git command line client")
162 210 def has_git():
163 211 return matchoutput('git --version 2>&1', r'^git version')
164 212
165 213 @check("docutils", "Docutils text processing library")
166 214 def has_docutils():
167 215 try:
168 216 from docutils.core import publish_cmdline
169 217 publish_cmdline # silence unused import
170 218 return True
171 219 except ImportError:
172 220 return False
173 221
174 222 def getsvnversion():
175 223 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
176 224 if not m:
177 225 return (0, 0)
178 226 return (int(m.group(1)), int(m.group(2)))
179 227
180 228 @check("svn15", "subversion client and admin tools >= 1.5")
181 229 def has_svn15():
182 230 return getsvnversion() >= (1, 5)
183 231
184 232 @check("svn13", "subversion client and admin tools >= 1.3")
185 233 def has_svn13():
186 234 return getsvnversion() >= (1, 3)
187 235
188 236 @check("svn", "subversion client and admin tools")
189 237 def has_svn():
190 238 return matchoutput('svn --version 2>&1', r'^svn, version') and \
191 239 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
192 240
193 241 @check("svn-bindings", "subversion python bindings")
194 242 def has_svn_bindings():
195 243 try:
196 244 import svn.core
197 245 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
198 246 if version < (1, 4):
199 247 return False
200 248 return True
201 249 except ImportError:
202 250 return False
203 251
204 252 @check("p4", "Perforce server and client")
205 253 def has_p4():
206 254 return (matchoutput('p4 -V', r'Rev\. P4/') and
207 255 matchoutput('p4d -V', r'Rev\. P4D/'))
208 256
209 257 @check("symlink", "symbolic links")
210 258 def has_symlink():
211 259 if getattr(os, "symlink", None) is None:
212 260 return False
213 261 name = tempfile.mktemp(dir='.', prefix=tempprefix)
214 262 try:
215 263 os.symlink(".", name)
216 264 os.unlink(name)
217 265 return True
218 266 except (OSError, AttributeError):
219 267 return False
220 268
221 269 @check("hardlink", "hardlinks")
222 270 def has_hardlink():
223 271 from mercurial import util
224 272 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
225 273 os.close(fh)
226 274 name = tempfile.mktemp(dir='.', prefix=tempprefix)
227 275 try:
228 276 util.oslink(fn, name)
229 277 os.unlink(name)
230 278 return True
231 279 except OSError:
232 280 return False
233 281 finally:
234 282 os.unlink(fn)
235 283
236 284 @check("tla", "GNU Arch tla client")
237 285 def has_tla():
238 286 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
239 287
240 288 @check("gpg", "gpg client")
241 289 def has_gpg():
242 290 return matchoutput('gpg --version 2>&1', r'GnuPG')
243 291
244 292 @check("unix-permissions", "unix-style permissions")
245 293 def has_unix_permissions():
246 294 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
247 295 try:
248 296 fname = os.path.join(d, 'foo')
249 297 for umask in (0o77, 0o07, 0o22):
250 298 os.umask(umask)
251 299 f = open(fname, 'w')
252 300 f.close()
253 301 mode = os.stat(fname).st_mode
254 302 os.unlink(fname)
255 303 if mode & 0o777 != ~umask & 0o666:
256 304 return False
257 305 return True
258 306 finally:
259 307 os.rmdir(d)
260 308
261 309 @check("unix-socket", "AF_UNIX socket family")
262 310 def has_unix_socket():
263 311 return getattr(socket, 'AF_UNIX', None) is not None
264 312
265 313 @check("root", "root permissions")
266 314 def has_root():
267 315 return getattr(os, 'geteuid', None) and os.geteuid() == 0
268 316
269 317 @check("pyflakes", "Pyflakes python linter")
270 318 def has_pyflakes():
271 319 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
272 320 r"<stdin>:1: 're' imported but unused",
273 321 True)
274 322
275 323 @check("pygments", "Pygments source highlighting library")
276 324 def has_pygments():
277 325 try:
278 326 import pygments
279 327 pygments.highlight # silence unused import warning
280 328 return True
281 329 except ImportError:
282 330 return False
283 331
284 332 @check("json", "some json module available")
285 333 def has_json():
286 334 try:
287 335 import json
288 336 json.dumps
289 337 return True
290 338 except ImportError:
291 339 try:
292 340 import simplejson as json
293 341 json.dumps
294 342 return True
295 343 except ImportError:
296 344 pass
297 345 return False
298 346
299 347 @check("outer-repo", "outer repo")
300 348 def has_outer_repo():
301 349 # failing for other reasons than 'no repo' imply that there is a repo
302 350 return not matchoutput('hg root 2>&1',
303 351 r'abort: no repository found', True)
304 352
305 353 @check("ssl", ("(python >= 2.6 ssl module and python OpenSSL) "
306 354 "OR python >= 2.7.9 ssl"))
307 355 def has_ssl():
308 356 try:
309 357 import ssl
310 358 if getattr(ssl, 'create_default_context', False):
311 359 return True
312 360 import OpenSSL
313 361 OpenSSL.SSL.Context
314 362 return True
315 363 except ImportError:
316 364 return False
317 365
318 366 @check("sslcontext", "python >= 2.7.9 ssl")
319 367 def has_sslcontext():
320 368 try:
321 369 import ssl
322 370 ssl.SSLContext
323 371 return True
324 372 except (ImportError, AttributeError):
325 373 return False
326 374
327 375 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
328 376 def has_defaultcacerts():
329 377 from mercurial import sslutil
330 378 return sslutil._defaultcacerts() != '!'
331 379
332 380 @check("windows", "Windows")
333 381 def has_windows():
334 382 return os.name == 'nt'
335 383
336 384 @check("system-sh", "system() uses sh")
337 385 def has_system_sh():
338 386 return os.name != 'nt'
339 387
340 388 @check("serve", "platform and python can manage 'hg serve -d'")
341 389 def has_serve():
342 390 return os.name != 'nt' # gross approximation
343 391
344 392 @check("test-repo", "running tests from repository")
345 393 def has_test_repo():
346 394 t = os.environ["TESTDIR"]
347 395 return os.path.isdir(os.path.join(t, "..", ".hg"))
348 396
349 397 @check("tic", "terminfo compiler and curses module")
350 398 def has_tic():
351 399 try:
352 400 import curses
353 401 curses.COLOR_BLUE
354 402 return matchoutput('test -x "`which tic`"', '')
355 403 except ImportError:
356 404 return False
357 405
358 406 @check("msys", "Windows with MSYS")
359 407 def has_msys():
360 408 return os.getenv('MSYSTEM')
361 409
362 410 @check("aix", "AIX")
363 411 def has_aix():
364 412 return sys.platform.startswith("aix")
365 413
366 414 @check("osx", "OS X")
367 415 def has_osx():
368 416 return sys.platform == 'darwin'
369 417
370 418 @check("absimport", "absolute_import in __future__")
371 419 def has_absimport():
372 420 import __future__
373 421 from mercurial import util
374 422 return util.safehasattr(__future__, "absolute_import")
375 423
376 424 @check("py3k", "running with Python 3.x")
377 425 def has_py3k():
378 426 return 3 == sys.version_info[0]
379 427
380 428 @check("pure", "running with pure Python code")
381 429 def has_pure():
382 430 return os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure"
General Comments 0
You need to be logged in to leave comments. Login now