diff options
-rw-r--r-- | coverage/data.py | 63 | ||||
-rw-r--r-- | tests/test_data.py | 40 |
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": {}}, } |