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