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