diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2015-07-20 17:49:39 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2015-07-20 17:49:39 -0400 |
commit | caae3057c6e8ac32ca7a6e306d131f181df8686f (patch) | |
tree | 8d7b175408bdfa4e17f55814cc7bd2794d26a1f8 /coverage/data.py | |
parent | 40ea0f9d70519a3c5645bee934921986cceb1f32 (diff) | |
download | python-coveragepy-caae3057c6e8ac32ca7a6e306d131f181df8686f.tar.gz |
Add docs to CoverageData.
Diffstat (limited to 'coverage/data.py')
-rw-r--r-- | coverage/data.py | 241 |
1 files changed, 157 insertions, 84 deletions
diff --git a/coverage/data.py b/coverage/data.py index 9a8a397..9cf52a3 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -14,34 +14,80 @@ from coverage.misc import CoverageException, file_be_gone class CoverageData(object): """Manages collected coverage data, including file storage. - The data file format is a pickled dict, with these keys: + This class is the public supported API to coverage.py's data. - * collector: a string identifying the collecting software + .. note:: - * lines: a dict mapping filenames to lists of line numbers - executed:: + The file format is not documented or guaranteed. It will change in + the future, in possibly complicated ways. Use this API to avoid + disruption. - { 'file1': [17,23,45], 'file2': [1,2,3], ... } + There are three kinds of data that can be collected: - * arcs: a dict mapping filenames to lists of line number pairs:: + * **lines**: the line numbers of source lines that were executed. + These are always available. - { 'file1': [(17,23), (17,25), (25,26)], ... } + * **arcs**: pairs of source and destination line numbers for transitions + between source lines. These are only available if branch coverage was + used. - * plugins: a dict mapping filenames to plugin names:: + * **plugin names**: the module names of the plugin that handled each file + in the data. - { 'file1': "django.coverage", ... } - Only one of `lines` or `arcs` will be present: with branch coverage, data - is stored as arcs. Without branch coverage, it is stored as lines. The - line data is easily recovered from the arcs: it is all the first elements - of the pairs that are greater than zero. + To read a coverage.py data file, use :meth:`read_file`, or :meth:`read` if + you have an already-opened file. You can then access the line, arc, or + plugin data with :meth:`lines`, :meth:`arcs`, or :meth:`plugin_name`. + + The :meth:`has_arcs` method indicates whether arc data is available. You + can get a list of the files in the data with :meth:`measured_files`. + A summary of the line data is available from :meth:`line_counts`. As with + most Python containers, you can determine if there is any data at all by + using this object as a boolean value. + + + Most data files will be created by coverage.py itself, but you can use + methods here to create data files if you like. The :meth:`add_lines`, + :meth:`add_arcs`, and :meth:`add_plugins` methods add data, in ways that + are convenient for coverage.py. To add a file without any measured data, + use :meth:`touch_file`. + + You write to a named file with :meth:`write_file`, or to an already opened + file with :meth:`write`. + + You can clear the data in memory with :meth:`erase`. Two data collections + can be combined by using :meth:`update` on one `CoverageData`, passing it + the other. """ + # The data file format is a pickled dict, with these keys: + # + # * collector: a string identifying the collecting software + # + # * lines: a dict mapping filenames to lists of line numbers + # executed:: + # + # { 'file1': [17,23,45], 'file2': [1,2,3], ... } + # + # * arcs: a dict mapping filenames to lists of line number pairs:: + # + # { 'file1': [(17,23), (17,25), (25,26)], ... } + # + # * plugins: a dict mapping filenames to plugin names:: + # + # { 'file1': "django.coverage", ... } + # + # Only one of `lines` or `arcs` will be present: with branch coverage, data + # is stored as arcs. Without branch coverage, it is stored as lines. The + # line data is easily recovered from the arcs: it is all the first elements + # of the pairs that are greater than zero. + def __init__(self, collector=None, debug=None): """Create a CoverageData. - `collector` is a string describing the coverage measurement software. + `collector` is a string describing the coverage measurement software, + for example, `"coverage.py v3.14"`. `debug` is a `DebugControl` object for writing debug messages. @@ -79,11 +125,20 @@ class CoverageData(object): # self._plugins = {} - def erase(self): - """Erase the data in this object.""" - self._lines = {} - self._arcs = {} - self._plugins = {} + ## + ## Reading data + ## + + def has_arcs(self): + """Does this data have arcs? + + Arc data is only available if branch coverage was used during + collection. + + Returns a boolean. + + """ + return self._has_arcs() def lines(self, filename): """Get the list of lines executed for a file. @@ -130,6 +185,35 @@ class CoverageData(object): return self._plugins.get(filename, "") return None + def measured_files(self): + """A list of all files that had been measured.""" + return list(self._arcs or self._lines) + + def line_counts(self, fullpath=False): + """Return a dict summarizing the line 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. + + Returns: + dict mapping filenames to counts of lines. + + """ + summ = {} + if fullpath: + filename_fn = lambda f: f + else: + filename_fn = os.path.basename + for filename in self.measured_files(): + summ[filename_fn(filename)] = len(self.lines(filename)) + return summ + + def __nonzero__(self): + return bool(self._lines) or bool(self._arcs) + + __bool__ = __nonzero__ + def read(self, file_obj): """Read the coverage data from the given file object. @@ -151,7 +235,7 @@ class CoverageData(object): self._plugins = data.get('plugins', {}) def read_file(self, filename): - """Read the coverage data from `filename`.""" + """Read the coverage data from `filename` into this object.""" if self._debug and self._debug.should('dataio'): self._debug.write("Reading data from %r" % (filename,)) try: @@ -164,31 +248,9 @@ class CoverageData(object): ) ) - def write(self, file_obj): - """Write the coverage data to `file_obj`.""" - - # Create the file data. - file_data = {} - - if self._arcs: - file_data['arcs'] = dict((f, list(amap.keys())) for f, amap in iitems(self._arcs)) - else: - file_data['lines'] = dict((f, list(lmap.keys())) for f, lmap in iitems(self._lines)) - - if self._collector: - file_data['collector'] = self._collector - - file_data['plugins'] = self._plugins - - # Write the pickle to the file. - pickle.dump(file_data, file_obj, 2) - - def write_file(self, filename): - """Write the coverage data to `filename`.""" - if self._debug and self._debug.should('dataio'): - self._debug.write("Writing data to %r" % (filename,)) - with open(filename, 'wb') as fdata: - self.write(fdata) + ## + ## Writing data + ## def add_lines(self, line_data): """Add executed line data. @@ -196,7 +258,7 @@ class CoverageData(object): `line_data` is { filename: { lineno: None, ... }, ...} """ - if self.has_arcs(): + if self._has_arcs(): raise CoverageException("Can't add lines to existing arc data") for filename, linenos in iitems(line_data): @@ -208,7 +270,7 @@ class CoverageData(object): `arc_data` is { filename: { (l1,l2): None, ... }, ...} """ - if self.has_lines(): + if self._has_lines(): raise CoverageException("Can't add arcs to existing line data") for filename, arcs in iitems(arc_data): @@ -235,6 +297,42 @@ class CoverageData(object): ) self._plugins[filename] = plugin_name + def touch_file(self, filename): + """Ensure that `filename` appears in the data, empty if needed.""" + (self._arcs or self._lines).setdefault(filename, {}) + + def write(self, file_obj): + """Write the coverage data to `file_obj`.""" + + # Create the file data. + file_data = {} + + if self._arcs: + file_data['arcs'] = dict((f, list(amap.keys())) for f, amap in iitems(self._arcs)) + else: + file_data['lines'] = dict((f, list(lmap.keys())) for f, lmap in iitems(self._lines)) + + if self._collector: + file_data['collector'] = self._collector + + file_data['plugins'] = self._plugins + + # Write the pickle to the file. + pickle.dump(file_data, file_obj, 2) + + def write_file(self, filename): + """Write the coverage data to `filename`.""" + if self._debug and self._debug.should('dataio'): + self._debug.write("Writing data to %r" % (filename,)) + with open(filename, 'wb') as fdata: + self.write(fdata) + + def erase(self): + """Erase the data in this object.""" + self._lines = {} + self._arcs = {} + self._plugins = {} + def update(self, other_data, aliases=None): """Update this data with data from another `CoverageData`. @@ -242,9 +340,9 @@ class CoverageData(object): re-map paths to match the local machine's. """ - if self.has_lines() and other_data.has_arcs(): + if self._has_lines() and other_data._has_arcs(): raise CoverageException("Can't combine arc data with line data") - if self.has_arcs() and other_data.has_lines(): + if self._has_arcs() and other_data._has_lines(): raise CoverageException("Can't combine line data with arc data") aliases = aliases or PathAliases() @@ -275,13 +373,9 @@ class CoverageData(object): filename = aliases.map(filename) self._arcs.setdefault(filename, {}).update(file_data) - def touch_file(self, filename): - """Ensure that `filename` appears in the data, empty if needed.""" - (self._arcs or self._lines).setdefault(filename, {}) - - def measured_files(self): - """A list of all files that had been measured.""" - return list(self._arcs or self._lines) + ## + ## Miscellaneous + ## def add_to_hash(self, filename, hasher): """Contribute `filename`'s data to the `hasher`. @@ -298,37 +392,16 @@ class CoverageData(object): hasher.update(sorted(self.lines(filename))) hasher.update(self.plugin_name(filename)) - def line_counts(self, fullpath=False): - """Return a dict summarizing the line 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. - - Returns: - dict mapping filenames to counts of lines. - - """ - summ = {} - if fullpath: - filename_fn = lambda f: f - else: - filename_fn = os.path.basename - for filename in self.measured_files(): - summ[filename_fn(filename)] = len(self.lines(filename)) - return summ - - def __nonzero__(self): - return bool(self._lines) or bool(self._arcs) - - __bool__ = __nonzero__ + ## + ## Internal + ## - def has_lines(self): - """Does this data have lines?""" + def _has_lines(self): + """Do we have data in self._lines?""" return bool(self._lines) - def has_arcs(self): - """Does this data have arcs?""" + def _has_arcs(self): + """Do we have data in self._arcs?""" return bool(self._arcs) |