From f023b656f0cb35920f07537d232a1b7a5bb7cbc4 Mon Sep 17 00:00:00 2001 From: Eljees <3.14hell@gmail.com> Date: Thu, 27 Aug 2026 19:42:47 +0000 Subject: [PATCH] Apply --ignore-regex to the TOTAL row of diff reports --ignore-regex removes files from the body of a diff report, but the TOTAL row is still computed over every file, so the totals do not match the rows above them. With --ignore-regex '.*' the table is empty and TOTAL still reads -1 -4 +31.06%. DeltaReporter.get_summary_lines() calls diff_total_statements(), diff_total_misses() and diff_line_rate() without passing self.ignore_regex, and those three plus _diff_attr had no such parameter to pass it to. The underlying Cobertura.line_rate(), total_statements(), total_misses() and total_hits() already accept it. This threads ignore_regex through, which fixes the TOTAL row for every delta format (text, csv, markdown, json, yaml, html), since they all share get_summary_lines(). The show command was given the same treatment in #176. Adds one CLI test and two reporter tests. --- pycobertura/cobertura.py | 22 ++++++++++++---------- pycobertura/reporters.py | 12 +++++++++--- tests/test_cli.py | 23 +++++++++++++++++++++++ tests/test_reporters.py | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/pycobertura/cobertura.py b/pycobertura/cobertura.py index d328fce1..ca3815b8 100644 --- a/pycobertura/cobertura.py +++ b/pycobertura/cobertura.py @@ -363,7 +363,7 @@ def has_all_changes_covered(self): return False # line not covered return True - def _diff_attr(self, attr_name, filename): + def _diff_attr(self, attr_name, filename, ignore_regex=None): """ Return the difference between `self.cobertura2.(filename)` and @@ -375,7 +375,7 @@ def _diff_attr(self, attr_name, filename): The returned count may be a float. """ - files = [filename] if filename else self.files() + files = [filename] if filename else self.files(ignore_regex) total_count = 0.0 for filename in files: @@ -390,19 +390,21 @@ def _diff_attr(self, attr_name, filename): return total_count - def diff_total_statements(self, filename=None): - return int(self._diff_attr("total_statements", filename)) + def diff_total_statements(self, filename=None, ignore_regex=None): + return int(self._diff_attr("total_statements", filename, ignore_regex)) - def diff_total_misses(self, filename=None): - return int(self._diff_attr("total_misses", filename)) + def diff_total_misses(self, filename=None, ignore_regex=None): + return int(self._diff_attr("total_misses", filename, ignore_regex)) - def diff_total_hits(self, filename=None): - return int(self._diff_attr("total_hits", filename)) + def diff_total_hits(self, filename=None, ignore_regex=None): + return int(self._diff_attr("total_hits", filename, ignore_regex)) - def diff_line_rate(self, filename=None): + def diff_line_rate(self, filename=None, ignore_regex=None): if filename is not None: return self._diff_attr("line_rate", filename) - return self.cobertura2.line_rate() - self.cobertura1.line_rate() + return self.cobertura2.line_rate( + ignore_regex=ignore_regex + ) - self.cobertura1.line_rate(ignore_regex=ignore_regex) def diff_missed_lines( self, filename: str diff --git a/pycobertura/reporters.py b/pycobertura/reporters.py index 29a8c05d..549c9b47 100644 --- a/pycobertura/reporters.py +++ b/pycobertura/reporters.py @@ -336,13 +336,19 @@ def get_summary_lines(self): summary_lines["Filename"].append("TOTAL") summary_lines["Stmts"].append( - self.format_total_statements(self.differ.diff_total_statements()) + self.format_total_statements( + self.differ.diff_total_statements(ignore_regex=self.ignore_regex) + ) ) summary_lines["Miss"].append( - self.format_total_misses(self.differ.diff_total_misses()) + self.format_total_misses( + self.differ.diff_total_misses(ignore_regex=self.ignore_regex) + ) ) summary_lines["Cover"].append( - self.format_line_rate(self.differ.diff_line_rate()) + self.format_line_rate( + self.differ.diff_line_rate(ignore_regex=self.ignore_regex) + ) ) if self.show_source: diff --git a/tests/test_cli.py b/tests/test_cli.py index e699e82c..3eca9a00 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1255,3 +1255,26 @@ def test_diff__format_yaml__with_ignore_regex(): # Verify total is present assert 'Filename: TOTAL' in result.output + + +def test_diff__ignore_regex_applies_to_total(): + """--ignore-regex must exclude the ignored files from the TOTAL row too.""" + from pycobertura.cli import diff + + runner = CliRunner() + result = runner.invoke( + diff, + [ + "tests/dummy.source1/coverage.xml", + "tests/dummy.source2/coverage.xml", + "--no-source", + "--format", "json", + "--no-color", + "--ignore-regex", ".*", + ], + catch_exceptions=False, + ) + payload = json.loads(result.output) + assert payload["files"] == [] + assert payload["total"]["Stmts"] == "0" + assert payload["total"]["Miss"] == "0" diff --git a/tests/test_reporters.py b/tests/test_reporters.py index 355bbca9..e78e68bc 100644 --- a/tests/test_reporters.py +++ b/tests/test_reporters.py @@ -987,3 +987,41 @@ def test_delta_reporter__single_file_coverage_changed(): "Cover": "+16.67%" } } + + +def test_text_report_delta__ignore_regex_applies_to_total(): + from pycobertura.reporters import TextReporterDelta + + cobertura1 = make_cobertura("tests/dummy.source1/coverage.xml") + cobertura2 = make_cobertura("tests/dummy.source2/coverage.xml") + + # every file is ignored, so the TOTAL row must not report any change + report_delta = TextReporterDelta( + cobertura1, cobertura2, ignore_regex=".*", show_source=False + ) + + assert report_delta.generate() == """\ +Filename Stmts Miss Cover +---------- ------- ------ -------- +TOTAL 0 0 +100.00%""" + + +def test_text_report_delta__ignore_regex_excludes_file_from_total(): + from pycobertura.reporters import TextReporterDelta + + cobertura1 = make_cobertura("tests/dummy.source1/coverage.xml") + cobertura2 = make_cobertura("tests/dummy.source2/coverage.xml") + + unfiltered = TextReporterDelta(cobertura1, cobertura2, show_source=False) + filtered = TextReporterDelta( + cobertura1, cobertura2, ignore_regex="^dummy/dummy3.*", show_source=False + ) + + # dummy/dummy3.py adds 2 statements, 2 of them missed; ignoring the file + # has to remove its contribution from the TOTAL row as well + unfiltered_total = unfiltered.get_summary_lines() + filtered_total = filtered.get_summary_lines() + + assert unfiltered_total["Stmts"][-1] == "-1" + assert filtered_total["Stmts"][-1] == "-3" + assert filtered_total["Miss"][-1] != unfiltered_total["Miss"][-1]