| Index: third_party/pycoverage/coverage/misc.py
|
| diff --git a/third_party/pycoverage/coverage/misc.py b/third_party/pycoverage/coverage/misc.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0378173fcc3ddc92d00ca94d88d7ef3fec746a25
|
| --- /dev/null
|
| +++ b/third_party/pycoverage/coverage/misc.py
|
| @@ -0,0 +1,167 @@
|
| +"""Miscellaneous stuff for Coverage."""
|
| +
|
| +import errno
|
| +import inspect
|
| +import os
|
| +import sys
|
| +
|
| +from coverage.backward import md5, sorted # pylint: disable=W0622
|
| +from coverage.backward import string_class, to_bytes
|
| +
|
| +
|
| +def nice_pair(pair):
|
| + """Make a nice string representation of a pair of numbers.
|
| +
|
| + If the numbers are equal, just return the number, otherwise return the pair
|
| + with a dash between them, indicating the range.
|
| +
|
| + """
|
| + start, end = pair
|
| + if start == end:
|
| + return "%d" % start
|
| + else:
|
| + return "%d-%d" % (start, end)
|
| +
|
| +
|
| +def format_lines(statements, lines):
|
| + """Nicely format a list of line numbers.
|
| +
|
| + Format a list of line numbers for printing by coalescing groups of lines as
|
| + long as the lines represent consecutive statements. This will coalesce
|
| + even if there are gaps between statements.
|
| +
|
| + For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
|
| + `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
|
| +
|
| + """
|
| + pairs = []
|
| + i = 0
|
| + j = 0
|
| + start = None
|
| + statements = sorted(statements)
|
| + lines = sorted(lines)
|
| + while i < len(statements) and j < len(lines):
|
| + if statements[i] == lines[j]:
|
| + if start == None:
|
| + start = lines[j]
|
| + end = lines[j]
|
| + j += 1
|
| + elif start:
|
| + pairs.append((start, end))
|
| + start = None
|
| + i += 1
|
| + if start:
|
| + pairs.append((start, end))
|
| + ret = ', '.join(map(nice_pair, pairs))
|
| + return ret
|
| +
|
| +
|
| +def short_stack():
|
| + """Return a string summarizing the call stack."""
|
| + stack = inspect.stack()[:0:-1]
|
| + return "\n".join(["%30s : %s @%d" % (t[3],t[1],t[2]) for t in stack])
|
| +
|
| +
|
| +def expensive(fn):
|
| + """A decorator to cache the result of an expensive operation.
|
| +
|
| + Only applies to methods with no arguments.
|
| +
|
| + """
|
| + attr = "_cache_" + fn.__name__
|
| + def _wrapped(self):
|
| + """Inner fn that checks the cache."""
|
| + if not hasattr(self, attr):
|
| + setattr(self, attr, fn(self))
|
| + return getattr(self, attr)
|
| + return _wrapped
|
| +
|
| +
|
| +def bool_or_none(b):
|
| + """Return bool(b), but preserve None."""
|
| + if b is None:
|
| + return None
|
| + else:
|
| + return bool(b)
|
| +
|
| +
|
| +def join_regex(regexes):
|
| + """Combine a list of regexes into one that matches any of them."""
|
| + if len(regexes) > 1:
|
| + return "|".join(["(%s)" % r for r in regexes])
|
| + elif regexes:
|
| + return regexes[0]
|
| + else:
|
| + return ""
|
| +
|
| +
|
| +def file_be_gone(path):
|
| + """Remove a file, and don't get annoyed if it doesn't exist."""
|
| + try:
|
| + os.remove(path)
|
| + except OSError:
|
| + _, e, _ = sys.exc_info()
|
| + if e.errno != errno.ENOENT:
|
| + raise
|
| +
|
| +
|
| +class Hasher(object):
|
| + """Hashes Python data into md5."""
|
| + def __init__(self):
|
| + self.md5 = md5()
|
| +
|
| + def update(self, v):
|
| + """Add `v` to the hash, recursively if needed."""
|
| + self.md5.update(to_bytes(str(type(v))))
|
| + if isinstance(v, string_class):
|
| + self.md5.update(to_bytes(v))
|
| + elif v is None:
|
| + pass
|
| + elif isinstance(v, (int, float)):
|
| + self.md5.update(to_bytes(str(v)))
|
| + elif isinstance(v, (tuple, list)):
|
| + for e in v:
|
| + self.update(e)
|
| + elif isinstance(v, dict):
|
| + keys = v.keys()
|
| + for k in sorted(keys):
|
| + self.update(k)
|
| + self.update(v[k])
|
| + else:
|
| + for k in dir(v):
|
| + if k.startswith('__'):
|
| + continue
|
| + a = getattr(v, k)
|
| + if inspect.isroutine(a):
|
| + continue
|
| + self.update(k)
|
| + self.update(a)
|
| +
|
| + def digest(self):
|
| + """Retrieve the digest of the hash."""
|
| + return self.md5.digest()
|
| +
|
| +
|
| +class CoverageException(Exception):
|
| + """An exception specific to Coverage."""
|
| + pass
|
| +
|
| +class NoSource(CoverageException):
|
| + """We couldn't find the source for a module."""
|
| + pass
|
| +
|
| +class NoCode(NoSource):
|
| + """We couldn't find any code at all."""
|
| + pass
|
| +
|
| +class NotPython(CoverageException):
|
| + """A source file turned out not to be parsable Python."""
|
| + pass
|
| +
|
| +class ExceptionDuringRun(CoverageException):
|
| + """An exception happened while running customer code.
|
| +
|
| + Construct it with three arguments, the values from `sys.exc_info`.
|
| +
|
| + """
|
| + pass
|
|
|