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