| Index: tools/telemetry/third_party/coverage/coverage/files.py
|
| diff --git a/third_party/pycoverage/coverage/files.py b/tools/telemetry/third_party/coverage/coverage/files.py
|
| similarity index 59%
|
| copy from third_party/pycoverage/coverage/files.py
|
| copy to tools/telemetry/third_party/coverage/coverage/files.py
|
| index 464535a81653ca833ba33b462467941e373c0927..2b8727d43db44e087899f4733d89c6c8edabf3e8 100644
|
| --- a/third_party/pycoverage/coverage/files.py
|
| +++ b/tools/telemetry/third_party/coverage/coverage/files.py
|
| @@ -1,111 +1,123 @@
|
| +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
| +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
|
| +
|
| """File wrangling."""
|
|
|
| -from coverage.backward import to_string
|
| -from coverage.misc import CoverageException
|
| -import fnmatch, os, os.path, re, sys
|
| -import ntpath, posixpath
|
| +import fnmatch
|
| +import ntpath
|
| +import os
|
| +import os.path
|
| +import posixpath
|
| +import re
|
| +import sys
|
|
|
| -class FileLocator(object):
|
| - """Understand how filenames work."""
|
| +from coverage import env
|
| +from coverage.backward import unicode_class
|
| +from coverage.misc import CoverageException, join_regex
|
|
|
| - def __init__(self):
|
| - # The absolute path to our current directory.
|
| - self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep)
|
|
|
| - # Cache of results of calling the canonical_filename() method, to
|
| - # avoid duplicating work.
|
| - self.canonical_filename_cache = {}
|
| +RELATIVE_DIR = None
|
| +CANONICAL_FILENAME_CACHE = {}
|
|
|
| - def relative_filename(self, filename):
|
| - """Return the relative form of `filename`.
|
|
|
| - The filename will be relative to the current directory when the
|
| - `FileLocator` was constructed.
|
| +def set_relative_directory():
|
| + """Set the directory that `relative_filename` will be relative to."""
|
| + global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
|
|
|
| - """
|
| - fnorm = os.path.normcase(filename)
|
| - if fnorm.startswith(self.relative_dir):
|
| - filename = filename[len(self.relative_dir):]
|
| - return filename
|
| + # The absolute path to our current directory.
|
| + RELATIVE_DIR = os.path.normcase(abs_file(os.curdir) + os.sep)
|
|
|
| - def canonical_filename(self, filename):
|
| - """Return a canonical filename for `filename`.
|
| + # Cache of results of calling the canonical_filename() method, to
|
| + # avoid duplicating work.
|
| + CANONICAL_FILENAME_CACHE = {}
|
|
|
| - An absolute path with no redundant components and normalized case.
|
| +def relative_directory():
|
| + """Return the directory that `relative_filename` is relative to."""
|
| + return RELATIVE_DIR
|
|
|
| - """
|
| - if filename not in self.canonical_filename_cache:
|
| - if not os.path.isabs(filename):
|
| - for path in [os.curdir] + sys.path:
|
| - if path is None:
|
| - continue
|
| - f = os.path.join(path, filename)
|
| - if os.path.exists(f):
|
| - filename = f
|
| - break
|
| - cf = abs_file(filename)
|
| - self.canonical_filename_cache[filename] = cf
|
| - return self.canonical_filename_cache[filename]
|
| -
|
| - def get_zip_data(self, filename):
|
| - """Get data from `filename` if it is a zip file path.
|
| -
|
| - Returns the string data read from the zip file, or None if no zip file
|
| - could be found or `filename` isn't in it. The data returned will be
|
| - an empty string if the file is empty.
|
| +def relative_filename(filename):
|
| + """Return the relative form of `filename`.
|
|
|
| - """
|
| - import zipimport
|
| - markers = ['.zip'+os.sep, '.egg'+os.sep]
|
| - for marker in markers:
|
| - if marker in filename:
|
| - parts = filename.split(marker)
|
| - try:
|
| - zi = zipimport.zipimporter(parts[0]+marker[:-1])
|
| - except zipimport.ZipImportError:
|
| - continue
|
| - try:
|
| - data = zi.get_data(parts[1])
|
| - except IOError:
|
| + The file name will be relative to the current directory when the
|
| + `set_relative_directory` was called.
|
| +
|
| + """
|
| + fnorm = os.path.normcase(filename)
|
| + if fnorm.startswith(RELATIVE_DIR):
|
| + filename = filename[len(RELATIVE_DIR):]
|
| + return filename
|
| +
|
| +def canonical_filename(filename):
|
| + """Return a canonical file name for `filename`.
|
| +
|
| + An absolute path with no redundant components and normalized case.
|
| +
|
| + """
|
| + if filename not in CANONICAL_FILENAME_CACHE:
|
| + if not os.path.isabs(filename):
|
| + for path in [os.curdir] + sys.path:
|
| + if path is None:
|
| continue
|
| - return to_string(data)
|
| - return None
|
| + f = os.path.join(path, filename)
|
| + if os.path.exists(f):
|
| + filename = f
|
| + break
|
| + cf = abs_file(filename)
|
| + CANONICAL_FILENAME_CACHE[filename] = cf
|
| + return CANONICAL_FILENAME_CACHE[filename]
|
| +
|
| +
|
| +def flat_rootname(filename):
|
| + """A base for a flat file name to correspond to this file.
|
| +
|
| + Useful for writing files about the code where you want all the files in
|
| + the same directory, but need to differentiate same-named files from
|
| + different directories.
|
| +
|
| + For example, the file a/b/c.py will return 'a_b_c_py'
|
| +
|
| + """
|
| + name = ntpath.splitdrive(filename)[1]
|
| + return re.sub(r"[\\/.:]", "_", name)
|
|
|
|
|
| -if sys.platform == 'win32':
|
| +if env.WINDOWS:
|
| +
|
| + _ACTUAL_PATH_CACHE = {}
|
| + _ACTUAL_PATH_LIST_CACHE = {}
|
|
|
| def actual_path(path):
|
| """Get the actual path of `path`, including the correct case."""
|
| - if path in actual_path.cache:
|
| - return actual_path.cache[path]
|
| + if env.PY2 and isinstance(path, unicode_class):
|
| + path = path.encode(sys.getfilesystemencoding())
|
| + if path in _ACTUAL_PATH_CACHE:
|
| + return _ACTUAL_PATH_CACHE[path]
|
|
|
| head, tail = os.path.split(path)
|
| if not tail:
|
| - actpath = head
|
| + # This means head is the drive spec: normalize it.
|
| + actpath = head.upper()
|
| elif not head:
|
| actpath = tail
|
| else:
|
| head = actual_path(head)
|
| - if head in actual_path.list_cache:
|
| - files = actual_path.list_cache[head]
|
| + if head in _ACTUAL_PATH_LIST_CACHE:
|
| + files = _ACTUAL_PATH_LIST_CACHE[head]
|
| else:
|
| try:
|
| files = os.listdir(head)
|
| except OSError:
|
| files = []
|
| - actual_path.list_cache[head] = files
|
| + _ACTUAL_PATH_LIST_CACHE[head] = files
|
| normtail = os.path.normcase(tail)
|
| for f in files:
|
| if os.path.normcase(f) == normtail:
|
| tail = f
|
| break
|
| actpath = os.path.join(head, tail)
|
| - actual_path.cache[path] = actpath
|
| + _ACTUAL_PATH_CACHE[path] = actpath
|
| return actpath
|
|
|
| - actual_path.cache = {}
|
| - actual_path.list_cache = {}
|
| -
|
| else:
|
| def actual_path(filename):
|
| """The actual path for non-Windows platforms."""
|
| @@ -137,7 +149,7 @@ def prep_patterns(patterns):
|
| """
|
| prepped = []
|
| for p in patterns or []:
|
| - if p.startswith("*") or p.startswith("?"):
|
| + if p.startswith(("*", "?")):
|
| prepped.append(p)
|
| else:
|
| prepped.append(abs_file(p))
|
| @@ -147,7 +159,7 @@ def prep_patterns(patterns):
|
| class TreeMatcher(object):
|
| """A matcher for files in a tree."""
|
| def __init__(self, directories):
|
| - self.dirs = directories[:]
|
| + self.dirs = list(directories)
|
|
|
| def __repr__(self):
|
| return "<TreeMatcher %r>" % self.dirs
|
| @@ -156,10 +168,6 @@ class TreeMatcher(object):
|
| """A list of strings for displaying when dumping state."""
|
| return self.dirs
|
|
|
| - def add(self, directory):
|
| - """Add another directory to the list we match for."""
|
| - self.dirs.append(directory)
|
| -
|
| def match(self, fpath):
|
| """Does `fpath` indicate a file in one of our trees?"""
|
| for d in self.dirs:
|
| @@ -173,10 +181,49 @@ class TreeMatcher(object):
|
| return False
|
|
|
|
|
| +class ModuleMatcher(object):
|
| + """A matcher for modules in a tree."""
|
| + def __init__(self, module_names):
|
| + self.modules = list(module_names)
|
| +
|
| + def __repr__(self):
|
| + return "<ModuleMatcher %r>" % (self.modules)
|
| +
|
| + def info(self):
|
| + """A list of strings for displaying when dumping state."""
|
| + return self.modules
|
| +
|
| + def match(self, module_name):
|
| + """Does `module_name` indicate a module in one of our packages?"""
|
| + if not module_name:
|
| + return False
|
| +
|
| + for m in self.modules:
|
| + if module_name.startswith(m):
|
| + if module_name == m:
|
| + return True
|
| + if module_name[len(m)] == '.':
|
| + # This is a module in the package
|
| + return True
|
| +
|
| + return False
|
| +
|
| +
|
| class FnmatchMatcher(object):
|
| - """A matcher for files by filename pattern."""
|
| + """A matcher for files by file name pattern."""
|
| def __init__(self, pats):
|
| self.pats = pats[:]
|
| + # fnmatch is platform-specific. On Windows, it does the Windows thing
|
| + # of treating / and \ as equivalent. But on other platforms, we need to
|
| + # take care of that ourselves.
|
| + fnpats = (fnmatch.translate(p) for p in pats)
|
| + fnpats = (p.replace(r"\/", r"[\\/]") for p in fnpats)
|
| + if env.WINDOWS:
|
| + # Windows is also case-insensitive. BTW: the regex docs say that
|
| + # flags like (?i) have to be at the beginning, but fnmatch puts
|
| + # them at the end, and having two there seems to work fine.
|
| + fnpats = (p + "(?i)" for p in fnpats)
|
| + self.re = re.compile(join_regex(fnpats))
|
|
|
| def __repr__(self):
|
| return "<FnmatchMatcher %r>" % self.pats
|
| @@ -186,11 +233,8 @@ class FnmatchMatcher(object):
|
| return self.pats
|
|
|
| def match(self, fpath):
|
| - """Does `fpath` match one of our filename patterns?"""
|
| - for pat in self.pats:
|
| - if fnmatch.fnmatch(fpath, pat):
|
| - return True
|
| - return False
|
| + """Does `fpath` match one of our file name patterns?"""
|
| + return self.re.match(fpath) is not None
|
|
|
|
|
| def sep(s):
|
| @@ -213,12 +257,9 @@ class PathAliases(object):
|
| A `PathAliases` object tracks a list of pattern/result pairs, and can
|
| map a path through those aliases to produce a unified path.
|
|
|
| - `locator` is a FileLocator that is used to canonicalize the results.
|
| -
|
| """
|
| - def __init__(self, locator=None):
|
| + def __init__(self):
|
| self.aliases = []
|
| - self.locator = locator
|
|
|
| def add(self, pattern, result):
|
| """Add the `pattern`/`result` pair to the list of aliases.
|
| @@ -245,11 +286,10 @@ class PathAliases(object):
|
| pattern = abs_file(pattern)
|
| pattern += pattern_sep
|
|
|
| - # Make a regex from the pattern. fnmatch always adds a \Z or $ to
|
| + # Make a regex from the pattern. fnmatch always adds a \Z to
|
| # match the whole string, which we don't want.
|
| regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(')
|
| - if regex_pat.endswith("$"):
|
| - regex_pat = regex_pat[:-1]
|
| +
|
| # We want */a/b.py to match on Windows too, so change slash to match
|
| # either separator.
|
| regex_pat = regex_pat.replace(r"\/", r"[\\/]")
|
| @@ -272,6 +312,10 @@ class PathAliases(object):
|
| The separator style in the result is made to match that of the result
|
| in the alias.
|
|
|
| + Returns the mapped path. If a mapping has happened, this is a
|
| + canonical path. If no mapping has happened, it is the original value
|
| + of `path` unchanged.
|
| +
|
| """
|
| for regex, result, pattern_sep, result_sep in self.aliases:
|
| m = regex.match(path)
|
| @@ -279,8 +323,7 @@ class PathAliases(object):
|
| new = path.replace(m.group(0), result)
|
| if pattern_sep != result_sep:
|
| new = new.replace(pattern_sep, result_sep)
|
| - if self.locator:
|
| - new = self.locator.canonical_filename(new)
|
| + new = canonical_filename(new)
|
| return new
|
| return path
|
|
|
| @@ -291,7 +334,7 @@ def find_python_files(dirname):
|
| To be importable, the files have to be in a directory with a __init__.py,
|
| except for `dirname` itself, which isn't required to have one. The
|
| assumption is that `dirname` was specified directly, so the user knows
|
| - best, but subdirectories are checked for a __init__.py to be sure we only
|
| + best, but sub-directories are checked for a __init__.py to be sure we only
|
| find the importable files.
|
|
|
| """
|
|
|