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