diff --git a/diff_cover/diff_cover_tool.py b/diff_cover/diff_cover_tool.py index 5e04bcd5..5a401ae4 100644 --- a/diff_cover/diff_cover_tool.py +++ b/diff_cover/diff_cover_tool.py @@ -41,6 +41,9 @@ ) QUIET_HELP = "Only print errors and failures" SHOW_UNCOVERED = "Show uncovered lines on the console" +EXPAND_COVERAGE_REPORT = ( + "Append missing lines in coverage reports based on the hits of the previous line." +) INCLUDE_UNTRACKED_HELP = "Include untracked files" CONFIG_FILE_HELP = "The configuration file to use" DIFF_FILE_HELP = "The diff file to use" @@ -93,6 +96,13 @@ def parse_coverage_args(argv): "--show-uncovered", action="store_true", default=None, help=SHOW_UNCOVERED ) + parser.add_argument( + "--expand_coverage_report", + action="store_true", + default=None, + help=EXPAND_COVERAGE_REPORT, + ) + parser.add_argument( "--external-css-file", metavar="FILENAME", @@ -183,6 +193,7 @@ def parse_coverage_args(argv): "ignore_whitespace": False, "diff_range_notation": "...", "quiet": False, + "expand_coverage_report": False, } return get_config(parser=parser, argv=argv, defaults=defaults, tool=Tool.DIFF_COVER) @@ -204,6 +215,7 @@ def generate_coverage_report( src_roots=None, quiet=False, show_uncovered=False, + expand_coverage_report=False, ): """ Generate the diff coverage report, using kwargs from `parse_args()`. @@ -231,7 +243,7 @@ def generate_coverage_report( if len(xml_roots) > 0 and len(lcov_roots) > 0: raise ValueError(f"Mixing LCov and XML reports is not supported yet") elif len(xml_roots) > 0: - coverage = XmlCoverageReporter(xml_roots, src_roots) + coverage = XmlCoverageReporter(xml_roots, src_roots, expand_coverage_report) else: coverage = LcovCoverageReporter(lcov_roots, src_roots) @@ -308,6 +320,7 @@ def main(argv=None, directory=None): src_roots=arg_dict["src_roots"], quiet=quiet, show_uncovered=arg_dict["show_uncovered"], + expand_coverage_report=arg_dict["expand_coverage_report"], ) if percent_covered >= fail_under: diff --git a/diff_cover/violationsreporters/violations_reporter.py b/diff_cover/violationsreporters/violations_reporter.py index a4f4f382..350b2b95 100644 --- a/diff_cover/violationsreporters/violations_reporter.py +++ b/diff_cover/violationsreporters/violations_reporter.py @@ -24,7 +24,7 @@ class XmlCoverageReporter(BaseViolationReporter): Query information from a Cobertura|Clover|JaCoCo XML coverage report. """ - def __init__(self, xml_roots, src_roots=None): + def __init__(self, xml_roots, src_roots=None, expand_coverage_report=False): """ Load the XML coverage report represented by the cElementTree with root element `xml_root`. @@ -41,6 +41,7 @@ def __init__(self, xml_roots, src_roots=None): self._xml_cache = [{} for i in range(len(xml_roots))] self._src_roots = src_roots or [""] + self._expand_coverage_report = expand_coverage_report def _get_xml_classes(self, xml_document): """ @@ -216,6 +217,25 @@ def _cache_file(self, src_path): if line_nodes is None: continue + # Expand coverage report with not reported lines + if self._expand_coverage_report: + reported_line_hits = {} + for line in line_nodes: + reported_line_hits[int(line.get(_number))] = int( + line.get(_hits, 0) + ) + last_hit_number = 0 + for line_number in range( + min(reported_line_hits.keys()), max(reported_line_hits.keys()) + ): + if line_number in reported_line_hits: + last_hit_number = reported_line_hits[line_number] + else: + # This is an unreported line. We add it with the previous line hit score + line_nodes.append( + {_hits: last_hit_number, _number: line_number} + ) + # First case, need to define violations initially if violations is None: violations = {