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