diff --git a/coverage/control.py b/coverage/control.py index 2e58ad85c..b033071a9 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -733,6 +733,18 @@ def save(self): data = self.get_data() data.write() + def _make_aliases(self): + """Create a PathAliases from our configuration.""" + aliases = PathAliases( + debugfn=(self._debug.write if self._debug.should("pathmap") else None), + relative=self.config.relative_files, + ) + for paths in self.config.paths.values(): + result = paths[0] + for pattern in paths[1:]: + aliases.add(pattern, result) + return aliases + def combine(self, data_paths=None, strict=False, keep=False): """Combine together a number of similarly-named coverage data files. @@ -764,18 +776,9 @@ def combine(self, data_paths=None, strict=False, keep=False): self._post_init() self.get_data() - aliases = PathAliases( - debugfn=(self._debug.write if self._debug.should("pathmap") else None), - relative=self.config.relative_files, - ) - for paths in self.config.paths.values(): - result = paths[0] - for pattern in paths[1:]: - aliases.add(pattern, result) - combine_parallel_data( self._data, - aliases=aliases, + aliases=self._make_aliases(), data_paths=data_paths, strict=strict, keep=keep, @@ -925,6 +928,13 @@ def _get_file_reporters(self, morfs=None): file_reporters = [self._get_file_reporter(morf) for morf in morfs] return file_reporters + def _prepare_data_for_reporting(self): + """Re-map data before reporting, to get implicit 'combine' behavior.""" + if self.config.paths: + mapped_data = CoverageData(warn=self._warn, debug=self._debug, no_disk=True) + mapped_data.update(self._data, aliases=self._make_aliases()) + self._data = mapped_data + def report( self, morfs=None, @@ -990,6 +1000,7 @@ def report( The `format` parameter. """ + self._prepare_data_for_reporting() with override_config( self, ignore_errors=ignore_errors, diff --git a/coverage/sqldata.py b/coverage/sqldata.py index ea6b1199f..68663715c 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -648,7 +648,12 @@ def update(self, other_data, aliases=None): "inner join file on file.id = line_bits.file_id " + "inner join context on context.id = line_bits.context_id" ) - lines = {(files[path], context): numbits for (path, context, numbits) in cur} + lines = {} + for path, context, numbits in cur: + key = (files[path], context) + if key in lines: + numbits = numbits_union(lines[key], numbits) + lines[key] = numbits cur.close() # Get tracer data. diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 54ae4eb42..56e788533 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -219,11 +219,14 @@ def check_coverage( return cov - def make_data_file(self, basename=None, suffix=None, lines=None, file_tracers=None): + def make_data_file(self, basename=None, suffix=None, lines=None, arcs=None, file_tracers=None): """Write some data into a coverage data file.""" data = coverage.CoverageData(basename=basename, suffix=suffix) + assert lines is None or arcs is None if lines: data.add_lines(lines) + if arcs: + data.add_arcs(arcs) if file_tracers: data.add_file_tracers(file_tracers) data.write() diff --git a/tests/test_api.py b/tests/test_api.py index 195452323..4d0a857bd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -23,7 +23,7 @@ from coverage.misc import import_local_file from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin -from tests.helpers import assert_count_equal, assert_coverage_warnings +from tests.helpers import arcz_to_arcs, assert_count_equal, assert_coverage_warnings from tests.helpers import change_dir, nice_file, os_sep BAD_SQLITE_REGEX = r"file( is encrypted or)? is not a database" @@ -1456,3 +1456,103 @@ def test_combine_parallel_data_keep(self): # After combining, the .coverage file & the original combined file should still be there. self.assert_exists(".coverage") self.assert_file_count(".coverage.*", 2) + + +class ReportMapsPathsTest(CoverageTest): + """Check that reporting implicitly maps paths.""" + + def make_files(self, data="lines", settings=False): + """Create the test files we need for line coverage.""" + src = """\ + if VER == 1: + print("line 2") + if VER == 2: + print("line 4") + if VER == 3: + print("line 6") + """ + self.make_file("src/program.py", src) + self.make_file("ver1/program.py", src) + self.make_file("ver2/program.py", src) + + if data == "line": + self.make_data_file( + lines={ + abs_file("ver1/program.py"): [1, 2, 3, 5], + abs_file("ver2/program.py"): [1, 3, 4, 5], + } + ) + else: + self.make_data_file( + arcs={ + abs_file("ver1/program.py"): arcz_to_arcs(".1 12 23 35 5."), + abs_file("ver2/program.py"): arcz_to_arcs(".1 13 34 45 5."), + } + ) + + if settings: + self.make_file(".coveragerc", """\ + [paths] + source = + src + ver1 + ver2 + """) + + def test_map_paths_during_line_report_without_setting(self): + self.make_files(data="line") + cov = coverage.Coverage() + cov.load() + cov.report(show_missing=True) + expected = textwrap.dedent(os_sep("""\ + Name Stmts Miss Cover Missing + ----------------------------------------------- + ver1/program.py 6 2 67% 4, 6 + ver2/program.py 6 2 67% 2, 6 + ----------------------------------------------- + TOTAL 12 4 67% + """)) + assert expected == self.stdout() + + def test_map_paths_during_line_report(self): + self.make_files(data="line", settings=True) + cov = coverage.Coverage() + cov.load() + cov.report(show_missing=True) + expected = textwrap.dedent(os_sep("""\ + Name Stmts Miss Cover Missing + ---------------------------------------------- + src/program.py 6 1 83% 6 + ---------------------------------------------- + TOTAL 6 1 83% + """)) + assert expected == self.stdout() + + def test_map_paths_during_branch_report_without_setting(self): + self.make_files(data="arcs") + cov = coverage.Coverage(branch=True) + cov.load() + cov.report(show_missing=True) + expected = textwrap.dedent(os_sep("""\ + Name Stmts Miss Branch BrPart Cover Missing + ------------------------------------------------------------- + ver1/program.py 6 2 6 3 58% 1->3, 4, 6 + ver2/program.py 6 2 6 3 58% 2, 3->5, 6 + ------------------------------------------------------------- + TOTAL 12 4 12 6 58% + """)) + assert expected == self.stdout() + + def test_map_paths_during_branch_report(self): + self.make_files(data="arcs", settings=True) + cov = coverage.Coverage(branch=True) + cov.load() + cov.report(show_missing=True) + expected = textwrap.dedent(os_sep("""\ + Name Stmts Miss Branch BrPart Cover Missing + ------------------------------------------------------------ + src/program.py 6 1 6 1 83% 6 + ------------------------------------------------------------ + TOTAL 6 1 6 1 83% + """)) + assert expected == self.stdout()