summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2015-07-26 13:20:17 -0400
committerNed Batchelder <ned@nedbatchelder.com>2015-07-26 13:20:17 -0400
commit34f6b60c9da1eadf9ed8af3a0c289309a028d92c (patch)
tree43f08c976b879de62603b340c4a1ade9fa791443
parentf84849b705b108f6e40ca938c14ae1a0bfbd751e (diff)
downloadpython-coveragepy-git-34f6b60c9da1eadf9ed8af3a0c289309a028d92c.tar.gz
Combine run_infos when combining data.
-rw-r--r--coverage/data.py63
-rw-r--r--tests/test_data.py40
2 files changed, 73 insertions, 30 deletions
diff --git a/coverage/data.py b/coverage/data.py
index d3178668..977e6a07 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -29,7 +29,7 @@ class CoverageData(object):
the future, in possibly complicated ways. Use this API to avoid
disruption.
- There are three kinds of data that can be collected:
+ There are a number of kinds of data that can be collected:
* **lines**: the line numbers of source lines that were executed.
These are always available.
@@ -41,10 +41,14 @@ class CoverageData(object):
* **file tracer names**: the module names of the file tracer plugins that
handled each file in the data.
+ * **run information**: information about the program execution. This is
+ written during "coverage run", and then accumulated during "coverage
+ combine".
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
file tracer data with :meth:`lines`, :meth:`arcs`, or :meth:`file_tracer`.
+ Run information is available with :meth:`run_infos`.
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`.
@@ -56,8 +60,10 @@ class CoverageData(object):
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:`set_lines`,
:meth:`set_arcs`, and :meth:`set_file_tracers` methods add data, in ways
- that are convenient for coverage.py. To add a file without any measured
- data, use :meth:`touch_file`.
+ that are convenient for coverage.py. The :meth:`add_run_info` method adds
+ key-value pairs to the run information.
+
+ 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`.
@@ -73,22 +79,20 @@ class CoverageData(object):
# * lines: a dict mapping filenames to lists of line numbers
# executed::
#
- # { 'file1': [17,23,45], 'file2': [1,2,3], ... }
+ # { "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]], ... }
+ # { "file1": [[17,23], [17,25], [25,26]], ... }
#
# * file_tracers: a dict mapping filenames to plugin names::
#
- # { 'file1': "django.coverage", ... }
+ # { "file1": "django.coverage", ... }
#
- # * run: a dict of information about the coverage.py run::
+ # * runs: a list of dicts of information about the coverage.py runs
+ # contributing to the data::
#
- # { 'collector': 'coverage.py',
- # 'collected': '20150724T162717',
- # ...
- # }
+ # [ { "briefsys": "CPython 2.7.10 Darwin" }, ... ]
#
# 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
@@ -124,8 +128,8 @@ class CoverageData(object):
#
self._file_tracers = {}
- # A dict of information about the coverage.py run.
- self._run_info = {}
+ # A list of dicts of information about the coverage.py runs.
+ self._runs = []
##
## Reading data
@@ -196,9 +200,15 @@ class CoverageData(object):
return self._file_tracers.get(filename, "")
return None
- def run_info(self):
- """Return the dict of run information."""
- return self._run_info
+ def run_infos(self):
+ """Return the list of dicts of run information.
+
+ For data collected during a single run, this will be a one-element
+ list. If data has been combined, there will be one element for each
+ original data file.
+
+ """
+ return self._runs
def measured_files(self):
"""A list of all files that had been measured."""
@@ -242,7 +252,7 @@ class CoverageData(object):
for fname, arcs in iitems(data.get('arcs', {}))
)
self._file_tracers = data.get('file_tracers', {})
- self._run_info = data.get('run', {})
+ self._runs = data.get('runs', [])
self._validate()
@@ -357,7 +367,9 @@ class CoverageData(object):
but repeated keywords overwrite each other.
"""
- self._run_info.update(kwargs)
+ if not self._runs:
+ self._runs = [{}]
+ self._runs[0].update(kwargs)
self._validate()
def touch_file(self, filename):
@@ -379,8 +391,8 @@ class CoverageData(object):
if self._file_tracers:
file_data['file_tracers'] = self._file_tracers
- if self._run_info:
- file_data['run'] = self._run_info
+ if self._runs:
+ file_data['runs'] = self._runs
# Write the data to the file.
file_obj.write(self._GO_AWAY)
@@ -398,7 +410,7 @@ class CoverageData(object):
self._lines = {}
self._arcs = {}
self._file_tracers = {}
- self._run_info = {}
+ self._runs = []
self._validate()
def update(self, other_data, aliases=None):
@@ -432,6 +444,9 @@ class CoverageData(object):
)
)
+ # _runs: add the new runs to these runs.
+ self._runs.extend(other_data._runs)
+
# _lines: merge dicts.
for filename, file_lines in iitems(other_data._lines):
filename = aliases.map(filename)
@@ -491,6 +506,12 @@ class CoverageData(object):
"_file_tracers[%r] shoudn't be %r" % (fname, plugin)
)
+ # _runs should be a list of dicts.
+ for val in self._runs:
+ assert isinstance(val, dict)
+ for key in val:
+ assert isinstance(key, string_class), "Key in _runs shouldn't be %r" % (key,)
+
def add_to_hash(self, filename, hasher):
"""Contribute `filename`'s data to the `hasher`.
diff --git a/tests/test_data.py b/tests/test_data.py
index 8d385ee7..c2bb9a0f 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -153,11 +153,11 @@ class CoverageDataTest(DataTestHelpers, CoverageTest):
def test_run_info(self):
covdata = CoverageData()
- self.assertEqual(covdata.run_info(), {})
+ self.assertEqual(covdata.run_infos(), [])
covdata.add_run_info(hello="there")
- self.assertEqual(covdata.run_info(), {"hello": "there"})
+ self.assertEqual(covdata.run_infos(), [{"hello": "there"}])
covdata.add_run_info(count=17)
- self.assertEqual(covdata.run_info(), {"hello": "there", "count": 17})
+ self.assertEqual(covdata.run_infos(), [{"hello": "there", "count": 17}])
def test_no_arcs_vs_unmeasured_file(self):
covdata = CoverageData()
@@ -212,6 +212,7 @@ class CoverageDataTest(DataTestHelpers, CoverageTest):
self.assert_line_counts(covdata3, SUMMARY_1_2)
self.assert_measured_files(covdata3, MEASURED_FILES_1_2)
+ self.assertEqual(covdata3.run_infos(), [])
def test_update_arcs(self):
covdata1 = CoverageData()
@@ -226,6 +227,25 @@ class CoverageDataTest(DataTestHelpers, CoverageTest):
self.assert_line_counts(covdata3, SUMMARY_3_4)
self.assert_measured_files(covdata3, MEASURED_FILES_3_4)
+ self.assertEqual(covdata3.run_infos(), [])
+
+ def test_update_run_info(self):
+ covdata1 = CoverageData()
+ covdata1.set_arcs(ARCS_3)
+ covdata1.add_run_info(hello="there", count=17)
+
+ covdata2 = CoverageData()
+ covdata2.set_arcs(ARCS_4)
+ covdata2.add_run_info(hello="goodbye", count=23)
+
+ covdata3 = CoverageData()
+ covdata3.update(covdata1)
+ covdata3.update(covdata2)
+
+ self.assertEqual(covdata3.run_infos(), [
+ {'hello': 'there', 'count': 17},
+ {'hello': 'goodbye', 'count': 23},
+ ])
def test_update_cant_mix_lines_and_arcs(self):
covdata1 = CoverageData()
@@ -341,7 +361,7 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest):
self.assert_line_counts(covdata2, SUMMARY_1)
self.assert_measured_files(covdata2, MEASURED_FILES_1)
self.assertCountEqual(covdata2.lines("a.py"), A_PY_LINES_1)
- self.assertEqual(covdata2.run_info(), {})
+ self.assertEqual(covdata2.run_infos(), [])
def test_read_write_arcs(self):
covdata1 = CoverageData()
@@ -356,7 +376,7 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest):
self.assertCountEqual(covdata2.arcs("x.py"), X_PY_ARCS_3)
self.assertCountEqual(covdata2.lines("y.py"), Y_PY_LINES_3)
self.assertCountEqual(covdata2.arcs("y.py"), Y_PY_ARCS_3)
- self.assertEqual(covdata2.run_info(), {})
+ self.assertEqual(covdata2.run_infos(), [])
def test_read_errors(self):
covdata = CoverageData()
@@ -409,10 +429,12 @@ class CoverageDataTestInTempDir(DataTestHelpers, CoverageTest):
"y.py": [[-1, 17], [17, 23], [23, -1]],
},
"file_tracers": {"y.py": "magic_plugin"},
- "run": {
- "chunks": ["z", "a"],
- "version": "v3.14",
- },
+ "runs": [
+ {
+ "chunks": ["z", "a"],
+ "version": "v3.14",
+ },
+ ],
},
"empty.dat": {"lines": {}},
}