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