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