From 7efd4f50062f750df145102fc07c87fc49599bbe Mon Sep 17 00:00:00 2001 From: Christine Lytwynec Date: Tue, 21 Apr 2015 11:28:13 -0400 Subject: Added ability to combine coverage data files from multiple directories into one file via command line args. --- AUTHORS.txt | 1 + coverage/cmdline.py | 5 +++-- coverage/control.py | 4 ++-- coverage/data.py | 40 +++++++++++++++++++++++++--------------- tests/test_cmdline.py | 4 ++-- tests/test_data.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 21 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index fb2f0bc..2b84a1b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -16,6 +16,7 @@ Catherine Proulx Chris Adams Chris Rose Christian Heimes +Christine Lytwynec Christoph Zwerschke Danek Duvall Danny Allen diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 2be3294..c611e03 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -249,7 +249,7 @@ CMDS = { ), 'combine': CmdOptionParser("combine", GLOBAL_ARGS, - usage = " ", + usage = " ... ", description = "Combine data from multiple coverage files collected " "with 'run -p'. The combined results are written to a single " "file representing the union of the data." @@ -430,7 +430,8 @@ class CoverageScript(object): self.do_run(options, args) if options.action == "combine": - self.coverage.combine() + data_dirs = argv if argv else None + self.coverage.combine(data_dirs) self.coverage.save() # Remaining actions are reporting, with some common options. diff --git a/coverage/control.py b/coverage/control.py index 563925e..4a9ac72 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -717,7 +717,7 @@ class Coverage(object): self._harvest_data() self.data.write(suffix=data_suffix) - def combine(self): + def combine(self, data_dirs=None): """Combine together a number of similarly-named coverage data files. All coverage data files whose name starts with `data_file` (from the @@ -733,7 +733,7 @@ class Coverage(object): result = paths[0] for pattern in paths[1:]: aliases.add(pattern, result) - self.data.combine_parallel_data(aliases=aliases) + self.data.combine_parallel_data(aliases=aliases, data_dirs=data_dirs) def _harvest_data(self): """Get the collected data and reset the collector. diff --git a/coverage/data.py b/coverage/data.py index 2c5d351..ed79f79 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -1,5 +1,6 @@ """Coverage data for Coverage.""" +import glob import os from coverage.backward import iitems, pickle @@ -190,7 +191,7 @@ class CoverageData(object): pass return lines, arcs, plugins - def combine_parallel_data(self, aliases=None): + def combine_parallel_data(self, aliases=None, data_dirs=None): """Combine a number of data files together. Treat `self.filename` as a file prefix, and combine the data from all @@ -199,23 +200,32 @@ class CoverageData(object): If `aliases` is provided, it's a `PathAliases` object that is used to re-map paths to match the local machine's. + If `data_dirs` is provided, then it combines the data files from each + directory into a single file. + """ 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, new_plugins = 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) - self.plugins.update(new_plugins) - if f != local: - os.remove(full_path) + localdot = local + '.*' + + data_dirs = data_dirs or [data_dir] or ['.'] + files_to_combine = [] + for d in data_dirs: + pattern = os.path.join(os.path.abspath(d), localdot) + files_to_combine.extend(glob.glob(pattern)) + + for f in files_to_combine: + new_lines, new_arcs, new_plugins = self._read_file(f) + 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) + self.plugins.update(new_plugins) + + if os.path.basename(f) != local: + os.remove(f) def add_line_data(self, line_data): """Add executed line data. diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 775e003..54d8419 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -214,10 +214,10 @@ class CmdLineTest(BaseCmdLineTest): def test_combine(self): # coverage combine - self.cmd_executes("combine", """\ + self.cmd_executes("combine datadir1", """\ .coverage() .load() - .combine() + .combine(["datadir1"]) .save() """) diff --git a/tests/test_data.py b/tests/test_data.py index 0549a3c..9156e5a 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,5 +1,8 @@ """Tests for coverage.data""" +import os +import shutil + from coverage.backward import pickle from coverage.data import CoverageData from coverage.files import PathAliases @@ -154,3 +157,29 @@ class DataTest(CoverageTest): covdata3, {'./a.py': 4, './sub/b.py': 2}, fullpath=True ) self.assert_measured_files(covdata3, ['./a.py', './sub/b.py']) + + def test_combining_from_different_directories(self): + try: + covdata1 = CoverageData() + covdata1.add_line_data(DATA_1) + os.makedirs('cov1') + covdata1.write_file('cov1/.coverage.1') + + covdata2 = CoverageData() + covdata2.add_line_data(DATA_2) + os.makedirs('cov2') + covdata2.write_file('cov2/.coverage.2') + + covdata3 = CoverageData() + covdata3.combine_parallel_data(data_dirs=[ + 'cov1/', + 'cov2/', + ]) + + self.assert_summary(covdata3, SUMMARY_1_2) + self.assert_measured_files(covdata3, MEASURED_FILES_1_2) + finally: + # Use shutil here because if something goes wrong above, these + # dirs may not be empty and os.rmdir would fail to remove them. + shutil.rmtree('cov1') + shutil.rmtree('cov2') -- cgit v1.2.1 From 161556f47ec6b8f7c0232c21fdbdd7cc25bd3d8e Mon Sep 17 00:00:00 2001 From: Christine Lytwynec Date: Wed, 22 Apr 2015 11:45:55 -0400 Subject: Update docstring and command line help text. --- coverage/cmdline.py | 5 ++++- coverage/control.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index c611e03..66a76fa 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -252,7 +252,10 @@ CMDS = { usage = " ... ", description = "Combine data from multiple coverage files collected " "with 'run -p'. The combined results are written to a single " - "file representing the union of the data." + "file representing the union of the data. The positional " + "arguments are directories from which the data files should be " + "combined. By default, only data files in the current directory " + "are combined." ), 'debug': CmdOptionParser("debug", GLOBAL_ARGS, diff --git a/coverage/control.py b/coverage/control.py index 4a9ac72..2c8d384 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -724,6 +724,10 @@ class Coverage(object): coverage() constructor) will be read, and combined together into the current measurements. + `data_dirs` is a list of directories from which data files should be + combined. If no list is passed, then the data files from the current + directory will be combined. + """ self._init() aliases = None -- cgit v1.2.1 From aee7fba6b17bbcf9510e5891d154a52069b783b3 Mon Sep 17 00:00:00 2001 From: Christine Lytwynec Date: Thu, 23 Apr 2015 16:25:08 -0400 Subject: Update tests --- coverage/data.py | 2 +- tests/test_cmdline.py | 9 ++++++++- tests/test_data.py | 49 +++++++++++++++++++++++++------------------------ 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/coverage/data.py b/coverage/data.py index ed79f79..fa43ff7 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -208,7 +208,7 @@ class CoverageData(object): data_dir, local = os.path.split(self.filename) localdot = local + '.*' - data_dirs = data_dirs or [data_dir] or ['.'] + data_dirs = data_dirs or [data_dir] files_to_combine = [] for d in data_dirs: pattern = os.path.join(os.path.abspath(d), localdot) diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 54d8419..b616ed5 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -213,13 +213,20 @@ class CmdLineTest(BaseCmdLineTest): """) def test_combine(self): - # coverage combine + # coverage combine with args self.cmd_executes("combine datadir1", """\ .coverage() .load() .combine(["datadir1"]) .save() """) + # coverage combine without args + self.cmd_executes("combine", """\ + .coverage() + .load() + .combine(None) + .save() + """) def test_debug(self): self.cmd_help("debug", "What information would you like: data, sys?") diff --git a/tests/test_data.py b/tests/test_data.py index 9156e5a..ef57f0c 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -158,28 +158,29 @@ class DataTest(CoverageTest): ) self.assert_measured_files(covdata3, ['./a.py', './sub/b.py']) + +class DataTestInTempDir(DataTest): + """Test cases for coverage.data.""" + + run_in_temp_dir = True + def test_combining_from_different_directories(self): - try: - covdata1 = CoverageData() - covdata1.add_line_data(DATA_1) - os.makedirs('cov1') - covdata1.write_file('cov1/.coverage.1') - - covdata2 = CoverageData() - covdata2.add_line_data(DATA_2) - os.makedirs('cov2') - covdata2.write_file('cov2/.coverage.2') - - covdata3 = CoverageData() - covdata3.combine_parallel_data(data_dirs=[ - 'cov1/', - 'cov2/', - ]) - - self.assert_summary(covdata3, SUMMARY_1_2) - self.assert_measured_files(covdata3, MEASURED_FILES_1_2) - finally: - # Use shutil here because if something goes wrong above, these - # dirs may not be empty and os.rmdir would fail to remove them. - shutil.rmtree('cov1') - shutil.rmtree('cov2') + covdata1 = CoverageData() + covdata1.add_line_data(DATA_1) + os.makedirs('cov1') + covdata1.write_file('cov1/.coverage.1') + + covdata2 = CoverageData() + covdata2.add_line_data(DATA_2) + os.makedirs('cov2') + covdata2.write_file('cov2/.coverage.2') + + covdata3 = CoverageData() + covdata3.combine_parallel_data(data_dirs=[ + 'cov1/', + 'cov2/', + ]) + + self.assert_summary(covdata3, SUMMARY_1_2) + self.assert_measured_files(covdata3, MEASURED_FILES_1_2) + -- cgit v1.2.1 From e114efc35ad2c2134f7d4c24a7a4fac286f9e50a Mon Sep 17 00:00:00 2001 From: Christine Lytwynec Date: Fri, 24 Apr 2015 12:46:32 -0400 Subject: remove unneeded conditional --- coverage/data.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/coverage/data.py b/coverage/data.py index fa43ff7..8a699b5 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -223,9 +223,7 @@ class CoverageData(object): filename = aliases.map(filename) self.arcs.setdefault(filename, {}).update(file_data) self.plugins.update(new_plugins) - - if os.path.basename(f) != local: - os.remove(f) + os.remove(f) def add_line_data(self, line_data): """Add executed line data. -- cgit v1.2.1