| Index: third_party/coverage/data.py
|
| diff --git a/third_party/coverage/data.py b/third_party/coverage/data.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fb88c5b1e638cdd5954c3818a08d000a4ec499eb
|
| --- /dev/null
|
| +++ b/third_party/coverage/data.py
|
| @@ -0,0 +1,278 @@
|
| +"""Coverage data for Coverage."""
|
| +
|
| +import os
|
| +
|
| +from coverage.backward import iitems, pickle, sorted # pylint: disable=W0622
|
| +from coverage.files import PathAliases
|
| +from coverage.misc import file_be_gone
|
| +
|
| +
|
| +class CoverageData(object):
|
| + """Manages collected coverage data, including file storage.
|
| +
|
| + The data file format is a pickled dict, with these keys:
|
| +
|
| + * collector: a string identifying the collecting software
|
| +
|
| + * lines: a dict mapping filenames to sorted lists of line numbers
|
| + executed:
|
| + { 'file1': [17,23,45], 'file2': [1,2,3], ... }
|
| +
|
| + * arcs: a dict mapping filenames to sorted lists of line number pairs:
|
| + { 'file1': [(17,23), (17,25), (25,26)], ... }
|
| +
|
| + """
|
| +
|
| + def __init__(self, basename=None, collector=None, debug=None):
|
| + """Create a CoverageData.
|
| +
|
| + `basename` is the name of the file to use for storing data.
|
| +
|
| + `collector` is a string describing the coverage measurement software.
|
| +
|
| + `debug` is a `DebugControl` object for writing debug messages.
|
| +
|
| + """
|
| + self.collector = collector or 'unknown'
|
| + self.debug = debug
|
| +
|
| + self.use_file = True
|
| +
|
| + # Construct the filename that will be used for data file storage, if we
|
| + # ever do any file storage.
|
| + self.filename = basename or ".coverage"
|
| + self.filename = os.path.abspath(self.filename)
|
| +
|
| + # A map from canonical Python source file name to a dictionary in
|
| + # which there's an entry for each line number that has been
|
| + # executed:
|
| + #
|
| + # {
|
| + # 'filename1.py': { 12: None, 47: None, ... },
|
| + # ...
|
| + # }
|
| + #
|
| + self.lines = {}
|
| +
|
| + # A map from canonical Python source file name to a dictionary with an
|
| + # entry for each pair of line numbers forming an arc:
|
| + #
|
| + # {
|
| + # 'filename1.py': { (12,14): None, (47,48): None, ... },
|
| + # ...
|
| + # }
|
| + #
|
| + self.arcs = {}
|
| +
|
| + def usefile(self, use_file=True):
|
| + """Set whether or not to use a disk file for data."""
|
| + self.use_file = use_file
|
| +
|
| + def read(self):
|
| + """Read coverage data from the coverage data file (if it exists)."""
|
| + if self.use_file:
|
| + self.lines, self.arcs = self._read_file(self.filename)
|
| + else:
|
| + self.lines, self.arcs = {}, {}
|
| +
|
| + def write(self, suffix=None):
|
| + """Write the collected coverage data to a file.
|
| +
|
| + `suffix` is a suffix to append to the base file name. This can be used
|
| + for multiple or parallel execution, so that many coverage data files
|
| + can exist simultaneously. A dot will be used to join the base name and
|
| + the suffix.
|
| +
|
| + """
|
| + if self.use_file:
|
| + filename = self.filename
|
| + if suffix:
|
| + filename += "." + suffix
|
| + self.write_file(filename)
|
| +
|
| + def erase(self):
|
| + """Erase the data, both in this object, and from its file storage."""
|
| + if self.use_file:
|
| + if self.filename:
|
| + file_be_gone(self.filename)
|
| + self.lines = {}
|
| + self.arcs = {}
|
| +
|
| + def line_data(self):
|
| + """Return the map from filenames to lists of line numbers executed."""
|
| + return dict(
|
| + [(f, sorted(lmap.keys())) for f, lmap in iitems(self.lines)]
|
| + )
|
| +
|
| + def arc_data(self):
|
| + """Return the map from filenames to lists of line number pairs."""
|
| + return dict(
|
| + [(f, sorted(amap.keys())) for f, amap in iitems(self.arcs)]
|
| + )
|
| +
|
| + def write_file(self, filename):
|
| + """Write the coverage data to `filename`."""
|
| +
|
| + # Create the file data.
|
| + data = {}
|
| +
|
| + data['lines'] = self.line_data()
|
| + arcs = self.arc_data()
|
| + if arcs:
|
| + data['arcs'] = arcs
|
| +
|
| + if self.collector:
|
| + data['collector'] = self.collector
|
| +
|
| + if self.debug and self.debug.should('dataio'):
|
| + self.debug.write("Writing data to %r" % (filename,))
|
| +
|
| + # Write the pickle to the file.
|
| + fdata = open(filename, 'wb')
|
| + try:
|
| + pickle.dump(data, fdata, 2)
|
| + finally:
|
| + fdata.close()
|
| +
|
| + def read_file(self, filename):
|
| + """Read the coverage data from `filename`."""
|
| + self.lines, self.arcs = self._read_file(filename)
|
| +
|
| + def raw_data(self, filename):
|
| + """Return the raw pickled data from `filename`."""
|
| + if self.debug and self.debug.should('dataio'):
|
| + self.debug.write("Reading data from %r" % (filename,))
|
| + fdata = open(filename, 'rb')
|
| + try:
|
| + data = pickle.load(fdata)
|
| + finally:
|
| + fdata.close()
|
| + return data
|
| +
|
| + def _read_file(self, filename):
|
| + """Return the stored coverage data from the given file.
|
| +
|
| + Returns two values, suitable for assigning to `self.lines` and
|
| + `self.arcs`.
|
| +
|
| + """
|
| + lines = {}
|
| + arcs = {}
|
| + try:
|
| + data = self.raw_data(filename)
|
| + if isinstance(data, dict):
|
| + # Unpack the 'lines' item.
|
| + lines = dict([
|
| + (f, dict.fromkeys(linenos, None))
|
| + for f, linenos in iitems(data.get('lines', {}))
|
| + ])
|
| + # Unpack the 'arcs' item.
|
| + arcs = dict([
|
| + (f, dict.fromkeys(arcpairs, None))
|
| + for f, arcpairs in iitems(data.get('arcs', {}))
|
| + ])
|
| + except Exception:
|
| + pass
|
| + return lines, arcs
|
| +
|
| + def combine_parallel_data(self, aliases=None):
|
| + """Combine a number of data files together.
|
| +
|
| + Treat `self.filename` as a file prefix, and combine the data from all
|
| + of the data files starting with that prefix plus a dot.
|
| +
|
| + If `aliases` is provided, it's a `PathAliases` object that is used to
|
| + re-map paths to match the local machine's.
|
| +
|
| + """
|
| + aliases = aliases or PathAliases()
|
| + data_dir, local = os.path.split(self.filename)
|
| + localdot = local + '.'
|
| + for f in os.listdir(data_dir or '.'):
|
| + if f.startswith(localdot):
|
| + full_path = os.path.join(data_dir, f)
|
| + new_lines, new_arcs = self._read_file(full_path)
|
| + for filename, file_data in iitems(new_lines):
|
| + filename = aliases.map(filename)
|
| + self.lines.setdefault(filename, {}).update(file_data)
|
| + for filename, file_data in iitems(new_arcs):
|
| + filename = aliases.map(filename)
|
| + self.arcs.setdefault(filename, {}).update(file_data)
|
| + if f != local:
|
| + os.remove(full_path)
|
| +
|
| + def add_line_data(self, line_data):
|
| + """Add executed line data.
|
| +
|
| + `line_data` is { filename: { lineno: None, ... }, ...}
|
| +
|
| + """
|
| + for filename, linenos in iitems(line_data):
|
| + self.lines.setdefault(filename, {}).update(linenos)
|
| +
|
| + def add_arc_data(self, arc_data):
|
| + """Add measured arc data.
|
| +
|
| + `arc_data` is { filename: { (l1,l2): None, ... }, ...}
|
| +
|
| + """
|
| + for filename, arcs in iitems(arc_data):
|
| + self.arcs.setdefault(filename, {}).update(arcs)
|
| +
|
| + def touch_file(self, filename):
|
| + """Ensure that `filename` appears in the data, empty if needed."""
|
| + self.lines.setdefault(filename, {})
|
| +
|
| + def measured_files(self):
|
| + """A list of all files that had been measured."""
|
| + return list(self.lines.keys())
|
| +
|
| + def executed_lines(self, filename):
|
| + """A map containing all the line numbers executed in `filename`.
|
| +
|
| + If `filename` hasn't been collected at all (because it wasn't executed)
|
| + then return an empty map.
|
| +
|
| + """
|
| + return self.lines.get(filename) or {}
|
| +
|
| + def executed_arcs(self, filename):
|
| + """A map containing all the arcs executed in `filename`."""
|
| + return self.arcs.get(filename) or {}
|
| +
|
| + def add_to_hash(self, filename, hasher):
|
| + """Contribute `filename`'s data to the Md5Hash `hasher`."""
|
| + hasher.update(self.executed_lines(filename))
|
| + hasher.update(self.executed_arcs(filename))
|
| +
|
| + def summary(self, fullpath=False):
|
| + """Return a dict summarizing the coverage data.
|
| +
|
| + Keys are based on the filenames, and values are the number of executed
|
| + lines. If `fullpath` is true, then the keys are the full pathnames of
|
| + the files, otherwise they are the basenames of the files.
|
| +
|
| + """
|
| + summ = {}
|
| + if fullpath:
|
| + filename_fn = lambda f: f
|
| + else:
|
| + filename_fn = os.path.basename
|
| + for filename, lines in iitems(self.lines):
|
| + summ[filename_fn(filename)] = len(lines)
|
| + return summ
|
| +
|
| + def has_arcs(self):
|
| + """Does this data have arcs?"""
|
| + return bool(self.arcs)
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + # Ad-hoc: show the raw data in a data file.
|
| + import pprint, sys
|
| + covdata = CoverageData()
|
| + if sys.argv[1:]:
|
| + fname = sys.argv[1]
|
| + else:
|
| + fname = covdata.filename
|
| + pprint.pprint(covdata.raw_data(fname))
|
|
|