From 85f61d65369a263a4b145bb6652d556130d01212 Mon Sep 17 00:00:00 2001 From: Pradeep Tammali Date: Mon, 15 Jun 2026 11:29:31 +0200 Subject: [PATCH] refactor: remove BRANCH_COVERAGE config and fold branch gaps into line coverage Incorporate missing branches into line-level coverage automatically and drop separate branch coverage reporting, annotations, and templates. --- README.md | 8 +- codecov/config.py | 6 -- codecov/coverage/pytest.py | 131 +++++++++++++-------------- codecov/diff_grouper.py | 48 ---------- codecov/groups.py | 8 +- codecov/main.py | 15 --- codecov/template_files/macros.md.j2 | 16 ---- codecov/template_files/pr.md.j2 | 59 +----------- codecov/template_files/project.md.j2 | 59 +----------- tests/conftest.py | 51 ++++------- tests/coverage/test_base.py | 98 -------------------- tests/coverage/test_pytest.py | 33 ++----- tests/test_config.py | 5 - tests/test_diff_grouper.py | 67 ++------------ tests/test_main.py | 32 ------- tests/test_template.py | 31 ------- 16 files changed, 106 insertions(+), 561 deletions(-) diff --git a/README.md b/README.md index 65248ec..3993323 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ Note: Either `GITHUB_PR_NUMBER` or `GITHUB_REF` is required. `GITHUB_PR_NUMBER` - `MINIMUM_GREEN`: The minimum coverage percentage for green status. Default is 100. - `MINIMUM_ORANGE`: The minimum coverage percentage for orange status. Default is 70. -- `BRANCH_COVERAGE`: Show branch coverage in the report. Default is False. - `ANNOTATE_MISSING_LINES`: Whether to annotate missing lines in the coverage report. Default is False. - `ANNOTATION_TYPE`: The type of annotation to use for missing lines. 'notice' or 'warning' or 'error'. Default is 'warning'. - `MAX_FILES_IN_COMMENT`: The maximum number of files to include in the coverage report comment. Default is 25. @@ -52,12 +51,9 @@ Note: Either `GITHUB_PR_NUMBER` or `GITHUB_REF` is required. `GITHUB_PR_NUMBER` 1. The coverage report displays only files that have missing coverage. If all files are fully covered, the report will be empty. -2. When branch coverage is enabled, the coverage percentage is calculated based on the uncovered branches in - the affected files. -3. If the complete project report option is enabled, the report is included as-is in the comment, without any +2. If the complete project report option is enabled, the report is included as-is in the comment, without any modifications or recalculations. If you notice discrepancies between the PR coverage and the complete - project coverage, this may be expected. For consistent results, it is recommended to enable branch - coverage when your report includes it. + project coverage, this may be expected. ## Dev Setup diff --git a/codecov/config.py b/codecov/config.py index edc1729..3a181db 100644 --- a/codecov/config.py +++ b/codecov/config.py @@ -51,8 +51,6 @@ class Config: MINIMUM_GREEN: decimal.Decimal = decimal.Decimal('100') MINIMUM_ORANGE: decimal.Decimal = decimal.Decimal('70') TEST_FRAMEWORK: TestFramework = TestFramework.PYTEST - # TODO: Remove branch coverage and just use the report - BRANCH_COVERAGE: bool = False ANNOTATE_MISSING_LINES: bool = False ANNOTATION_TYPE: AnnotationType = AnnotationType.WARNING MAX_FILES_IN_COMMENT: int = 25 @@ -78,10 +76,6 @@ def clean_minimum_orange(cls, value: str) -> decimal.Decimal: def clean_annotate_missing_lines(cls, value: str) -> bool: return str_to_bool(value) - @classmethod - def clean_branch_coverage(cls, value: str) -> bool: - return str_to_bool(value) - @classmethod def clean_complete_project_report(cls, value: str) -> bool: return str_to_bool(value) diff --git a/codecov/coverage/pytest.py b/codecov/coverage/pytest.py index cf35d39..3c3db13 100644 --- a/codecov/coverage/pytest.py +++ b/codecov/coverage/pytest.py @@ -3,21 +3,45 @@ import decimal import pathlib -from codecov import diff_grouper from codecov.config import Config, TestFramework from codecov.coverage.base import BaseCoverage, BaseCoverageHandler, DiffCoverage, FileDiffCoverage +def _branch_missing_lines(missing_branches: list[list[int]] | None) -> list[int]: + if not missing_branches: + return [] + + lines: list[int] = [] + for branch in missing_branches: + from_line = abs(branch[0]) + if from_line > 0: + lines.append(from_line) + to_line = abs(branch[1]) + if to_line > 0 and to_line != from_line: + lines.append(to_line) + return lines + + +def _incorporate_branch_missing( + covered_lines: list[int], + missing_lines: list[int], + missing_branches: list[list[int]] | None, +) -> tuple[list[int], list[int]]: + branch_lines = _branch_missing_lines(missing_branches) + if not branch_lines: + return covered_lines, missing_lines + + missing = sorted(set(missing_lines) | set(branch_lines)) + covered = sorted(set(covered_lines) - set(branch_lines)) + return covered, missing + + @dataclasses.dataclass class PytestCoverageInfo: # pylint: disable=too-many-instance-attributes covered_lines: int num_statements: int missing_lines: int excluded_lines: int - num_branches: int | None - num_partial_branches: int | None # TODO: Removed this - covered_branches: int | None - missing_branches: int | None percent_covered: decimal.Decimal percent_covered_display: str @@ -29,15 +53,12 @@ class PytestFileCoverage: missing_lines: list[int] excluded_lines: list[int] info: PytestCoverageInfo - executed_branches: list[list[int]] | None - missing_branches: list[list[int]] | None @dataclasses.dataclass class PytestCoverageMetadata: version: str timestamp: datetime.datetime - branch_coverage: bool show_contexts: bool @@ -55,11 +76,9 @@ def compute_coverage( self, num_covered: int, num_total: int, - num_branches_covered: int = 0, - num_branches_total: int = 0, ) -> decimal.Decimal: - numerator = decimal.Decimal(num_covered + num_branches_covered) - denominator = decimal.Decimal(num_total + num_branches_total) + numerator = decimal.Decimal(num_covered) + denominator = decimal.Decimal(num_total) if denominator == 0: return decimal.Decimal('1') return numerator / denominator @@ -68,7 +87,6 @@ def extract_meta(self, data: dict) -> PytestCoverageMetadata: return PytestCoverageMetadata( version=data['meta']['version'], timestamp=datetime.datetime.fromisoformat(data['meta']['timestamp']), - branch_coverage=data['meta']['branch_coverage'], show_contexts=data['meta']['show_contexts'], ) @@ -80,21 +98,25 @@ def extract_coverage_info(self, data: dict) -> PytestCoverageInfo: percent_covered_display=data['percent_covered_display'], missing_lines=data['missing_lines'], excluded_lines=data['excluded_lines'], - num_branches=data.get('num_branches'), - num_partial_branches=data.get('num_partial_branches'), - covered_branches=data.get('covered_branches'), - missing_branches=data.get('missing_branches'), ) def extract_file_coverage(self, path: str, file_data: dict) -> PytestFileCoverage: + covered_lines, missing_lines = _incorporate_branch_missing( + covered_lines=file_data['executed_lines'], + missing_lines=file_data['missing_lines'], + missing_branches=file_data.get('missing_branches'), + ) + info = self.extract_coverage_info(file_data['summary']) return PytestFileCoverage( path=pathlib.Path(path), excluded_lines=file_data['excluded_lines'], - missing_lines=file_data['missing_lines'], - covered_lines=file_data['executed_lines'], - executed_branches=file_data.get('executed_branches'), - missing_branches=file_data.get('missing_branches'), - info=self.extract_coverage_info(file_data['summary']), + missing_lines=missing_lines, + covered_lines=covered_lines, + info=dataclasses.replace( + info, + covered_lines=len(covered_lines), + missing_lines=len(missing_lines), + ), ) def extract_info(self, data: dict) -> PytestCoverage: @@ -116,14 +138,9 @@ def extract_info(self, data: dict) -> PytestCoverage: "percent_covered_display": "88", "missing_lines": 4, "excluded_lines": 0, - "num_branches": 22, - "num_partial_branches": 4, - "covered_branches": 18, - "missing_branches": 4 }, "missing_lines": [7], "excluded_lines": [], - "executed_branches": [], "missing_branches": [], } }, @@ -134,20 +151,23 @@ def extract_info(self, data: dict) -> PytestCoverage: "percent_covered_display": "75", "missing_lines": 1, "excluded_lines": 0, - "num_branches": 2, - "num_partial_branches": 1, - "covered_branches": 1, - "missing_branches": 1, }, } """ + files = { + pathlib.Path(path): self.extract_file_coverage(path, file_data) for path, file_data in data['files'].items() + } + total_covered_lines = sum(file.info.covered_lines for file in files.values()) + total_missing_lines = sum(file.info.missing_lines for file in files.values()) + info = self.extract_coverage_info(data['totals']) return PytestCoverage( meta=self.extract_meta(data), - files={ - pathlib.Path(path): self.extract_file_coverage(path, file_data) - for path, file_data in data['files'].items() - }, - info=self.extract_coverage_info(data['totals']), + files=files, + info=dataclasses.replace( + info, + covered_lines=total_covered_lines, + missing_lines=total_missing_lines, + ), ) def get_diff_coverage( # pylint: disable=too-many-locals @@ -159,8 +179,6 @@ def get_diff_coverage( # pylint: disable=too-many-locals files = {} total_num_lines = 0 total_num_violations = 0 - total_num_branches_covered = 0 - total_num_branches = 0 num_changed_lines = 0 for path, added_lines_for_file in added_lines.items(): @@ -184,17 +202,7 @@ def get_diff_coverage( # pylint: disable=too-many-locals total_num_lines += count_total total_num_violations += count_missing - if config.BRANCH_COVERAGE: - total_num_branches_covered += file.info.covered_branches or 0 - total_num_branches += file.info.num_branches or 0 - percent_covered = self.compute_coverage( - num_covered=count_executed, - num_total=count_total, - num_branches_covered=file.info.covered_branches or 0, - num_branches_total=file.info.num_branches or 0, - ) - else: - percent_covered = self.compute_coverage(num_covered=count_executed, num_total=count_total) + percent_covered = self.compute_coverage(num_covered=count_executed, num_total=count_total) files[path] = FileDiffCoverage( path=path, @@ -204,18 +212,11 @@ def get_diff_coverage( # pylint: disable=too-many-locals added_statements=sorted(added), added_lines=added_lines_for_file, ) - if config.BRANCH_COVERAGE: - final_percentage = self.compute_coverage( - num_covered=total_num_lines - total_num_violations, - num_total=total_num_lines, - num_branches_covered=total_num_branches_covered, - num_branches_total=total_num_branches, - ) - else: - final_percentage = self.compute_coverage( - num_covered=total_num_lines - total_num_violations, - num_total=total_num_lines, - ) + + final_percentage = self.compute_coverage( + num_covered=total_num_lines - total_num_violations, + num_total=total_num_lines, + ) return DiffCoverage( total_num_lines=total_num_lines, @@ -224,11 +225,3 @@ def get_diff_coverage( # pylint: disable=too-many-locals num_changed_lines=num_changed_lines, files=files, ) - - def get_coverage(self, config: Config) -> PytestCoverage: - coverage = super().get_coverage(config=config) - - if config.BRANCH_COVERAGE: - coverage = diff_grouper.fill_branch_missing_groups(coverage=coverage) - - return coverage diff --git a/codecov/diff_grouper.py b/codecov/diff_grouper.py index 2c7e53f..c7f9882 100644 --- a/codecov/diff_grouper.py +++ b/codecov/diff_grouper.py @@ -11,20 +11,6 @@ MAX_ANNOTATION_GAP = 3 -def _flatten_branches(branches: list[list[int]] | None) -> list[int]: - flattened_branches: list[int] = [] - if not branches: - return flattened_branches - - for branch in branches: - start, end = abs(branch[0]), abs(branch[1]) - if start == end: - flattened_branches.append(start) - else: - flattened_branches.extend(range(min(start, end), max(start, end) + 1)) - return flattened_branches - - def get_missing_groups( coverage: 'PytestCoverage | JestCoverage', ) -> Iterable[groups.Group]: @@ -77,37 +63,3 @@ def get_diff_missing_groups( line_start=start, line_end=end, ) - - -def fill_branch_missing_groups(coverage: 'PytestCoverage') -> 'PytestCoverage': - for file_coverage in coverage.files.values(): - separators = { - *_flatten_branches(file_coverage.executed_branches), - *file_coverage.excluded_lines, - } - joiners = set(range(1, file_coverage.info.num_statements)) - separators - - file_coverage.missing_branches = [ - [start, end] - for start, end in groups.compute_contiguous_groups( - values=_flatten_branches(branches=file_coverage.missing_branches), - separators=separators, - joiners=joiners, - max_gap=MAX_ANNOTATION_GAP, - ) - ] - return coverage - - -def get_diff_branch_missing_groups( - coverage: 'PytestCoverage', - diff_coverage: 'DiffCoverage', -) -> Iterable[groups.Group]: - for path, _ in diff_coverage.files.items(): - coverage_file = coverage.files[path] - for start, end in coverage_file.missing_branches or []: - yield groups.Group( - file=path, - line_start=start, - line_end=end, - ) diff --git a/codecov/groups.py b/codecov/groups.py index 52d7984..a171bcf 100644 --- a/codecov/groups.py +++ b/codecov/groups.py @@ -41,28 +41,26 @@ def to_dict(self): def create_missing_coverage_annotations( annotation_type: str, annotations: Iterable[Group], - branch: bool = False, ) -> list[Annotation]: """ Create annotations for lines with missing coverage. annotation_type: The type of annotation to create. Can be either "error" or "warning" or "notice". annotations: A list of tuples of the form (file, line_start, line_end) - branch: Whether to create branch coverage annotations or not """ formatted_annotations: list[Annotation] = [] for group in annotations: if group.line_start == group.line_end: - message = f'Missing {"branch " if branch else ""}coverage on line {group.line_start}' + message = f'Missing coverage on line {group.line_start}' else: - message = f'Missing {"branch " if branch else ""}coverage on lines {group.line_start}-{group.line_end}' + message = f'Missing coverage on lines {group.line_start}-{group.line_end}' formatted_annotations.append( Annotation( file=group.file, line_start=group.line_start, line_end=group.line_end, - title=f'Missing {"branch " if branch else ""}coverage', + title='Missing coverage', message_type=annotation_type, message=message, ) diff --git a/codecov/main.py b/codecov/main.py index aa49ee4..9965b5b 100644 --- a/codecov/main.py +++ b/codecov/main.py @@ -1,5 +1,4 @@ import os -from typing import cast from codecov import diff_grouper, groups, template from codecov.config import Config @@ -95,7 +94,6 @@ def _create_comment(self) -> None: self.github.pr_number, self.github.base_ref, self.marker, - branch_coverage=self.config.BRANCH_COVERAGE, complete_project_report=self.config.COMPLETE_PROJECT_REPORT, coverage_report_url=self.config.COVERAGE_REPORT_URL, max_files=self.config.MAX_FILES_IN_COMMENT, @@ -136,19 +134,6 @@ def _generate_annotations(self): annotations=annotations, ) - if self.config.BRANCH_COVERAGE: - branch_annotations = diff_grouper.get_diff_branch_missing_groups( - coverage=cast(PytestCoverage, self.coverage), - diff_coverage=self.diff_coverage, - ) - formatted_annotations.extend( - groups.create_missing_coverage_annotations( - annotation_type=self.config.ANNOTATION_TYPE.value, - annotations=branch_annotations, - branch=True, - ) - ) - if not formatted_annotations: log.info('No annotations to generate. Exiting.') return diff --git a/codecov/template_files/macros.md.j2 b/codecov/template_files/macros.md.j2 index 20122b1..62a95b1 100644 --- a/codecov/template_files/macros.md.j2 +++ b/codecov/template_files/macros.md.j2 @@ -4,22 +4,6 @@ {%- endmacro -%} -{%- macro branches_badge(path, branches_count, base=false) -%} - {% set text = "The " ~ path ~ " contains " ~ branches_count ~ " branch" ~ (branches_count | pluralize(plural='es')) ~"." -%} - {% set color = "008080" -%} - -{%- endmacro -%} - -{%- macro missing_branches_badge(path, missing_branches_count, base=false) -%} - {%- set text = missing_branches_count ~ " branch" ~ (missing_branches_count | pluralize(plural='es')) ~ " missing the coverage in " ~ path ~ "." -%} - {% if missing_branches_count == 0 -%} - {%- set color = "brightgreen" -%} - {% else -%} - {%- set color = "red" -%} - {% endif -%} - -{%- endmacro -%} - {%- macro missing_lines_badge(path, missing_lines_count, base=false) -%} {%- set text = missing_lines_count ~ " statement" ~ (statements_count | pluralize) ~ " missing the coverage in " ~ path ~ "." -%} {% if missing_lines_count == 0 -%} diff --git a/codecov/template_files/pr.md.j2 b/codecov/template_files/pr.md.j2 index b049c0c..2fd5a6e 100644 --- a/codecov/template_files/pr.md.j2 +++ b/codecov/template_files/pr.md.j2 @@ -6,16 +6,15 @@
- {% if branch_coverage %}{% endif %} + - - {% if branch_coverage %}{% endif %} + {%- for parent, files_in_folder in files|groupby(attribute="path.parent") -%} - + {%- for file in files_in_folder -%} @@ -39,24 +38,6 @@ ) -}} {%- endblock missing_lines_badge_cell -%} - {% if branch_coverage %} - {#- Branches cell -#} - {%- block branches_badge_cell scoped -%} - {{- macros.branches_badge( - path=path, - branches_count=file.coverage.info.num_branches, - ) -}} - {%- endblock branches_badge_cell -%} - - {#- Missing cell -#} - {%- block missing_branches_badge_cell scoped -%} - {{- macros.missing_branches_badge( - path=path, - missing_branches_count=file.coverage.info.missing_branches, - ) -}} - {%- endblock missing_branches_badge_cell -%} - {% endif %} - {#- Coverage rate -#} {%- block coverage_rate_badge_cell scoped -%} {{- macros.coverage_rate_badge( @@ -98,19 +79,6 @@ {%- endblock link_to_missing_diff_lines_cell -%} - - {#- Link to branch missing lines -#} - {%- if branch_coverage -%} - {%- block link_to_branches_missing_lines_cell scoped -%} - - {%- endblock link_to_branches_missing_lines_cell -%} - {%- endif -%} {%- endfor -%} @@ -137,24 +105,6 @@ ) -}} {%- endblock missing_lines_badge_total_cell -%} - {% if branch_coverage %} - {#- Branches cell -#} - {%- block branches_badge_total_cell scoped -%} - {{- macros.branches_badge( - path="the whole project", - branches_count=coverage.info.num_branches, - ) -}} - {%- endblock branches_badge_total_cell -%} - - {#- Missing cell -#} - {%- block missing_branches_badge_total_cell scoped -%} - {{- macros.missing_branches_badge( - path="the whole project", - missing_branches_count=coverage.info.missing_branches, - ) -}} - {%- endblock missing_branches_badge_total_cell -%} - {% endif %} - {#- Coverage rate -#} {%- block coverage_rate_badge_total_cell scoped -%} {{- macros.coverage_rate_badge( @@ -177,9 +127,6 @@ {%- endblock diff_coverage_rate_badge_total_cell -%} - {% if branch_coverage %} - - {% endif %}
FileStatementsMissingBranchesMissing
FileStatementsMissing
Coverage         
Coverage         
(new stmts)

Missing stmts              

Missing branches              

Missing stmts              
  {{ parent }}  {{ parent }}
- {%- set comma = joiner() -%} - {%- for branch in file.coverage.missing_branches -%} - {{- comma() -}} - {{- branch[0] | abs -}} -> {{- branch[1] | abs -}} - {%- endfor -%} -
  
diff --git a/codecov/template_files/project.md.j2 b/codecov/template_files/project.md.j2 index 098087b..ddf3c87 100644 --- a/codecov/template_files/project.md.j2 +++ b/codecov/template_files/project.md.j2 @@ -7,10 +7,9 @@ _No additional project files to report the coverage._
- {% if branch_coverage %}{% endif %} + - {% if branch_coverage %}{% endif %} @@ -18,7 +17,7 @@ _No additional project files to report the coverage._ {%- for parent, coverage_files_in_folder in coverage_files|groupby(attribute="path.parent") -%} - + {%- for coverage_file in coverage_files_in_folder -%} @@ -44,26 +43,6 @@ _No additional project files to report the coverage._ ) -}} {%- endblock project_missing_lines_badge_cell -%} -{% if branch_coverage %} -{#- Branches cell -#} -{%- block project_branches_badge_cell scoped -%} -{{- macros.branches_badge( - path=coverage_file_path, - branches_count=coverage_file.coverage.info.num_branches, - base=true, -) -}} -{%- endblock project_branches_badge_cell -%} - -{#- Missing cell -#} -{%- block project_missing_branches_badge_cell scoped -%} -{{- macros.missing_branches_badge( - path=coverage_file_path, - missing_branches_count=coverage_file.coverage.info.missing_branches, - base=true, -) -}} -{%- endblock project_missing_branches_badge_cell -%} -{% endif %} - {#- Coverage cell -#} {%- block project_coverage_rate_badge_cell scoped -%} {{- macros.coverage_rate_badge( @@ -96,19 +75,6 @@ _No additional project files to report the coverage._ {%- endblock project_link_to_missing_lines_cell -%} - -{#- Link to branch missing lines -#} -{%- if branch_coverage -%} -{%- block project_link_to_branches_missing_lines_cell scoped -%} - -{%- endblock project_link_to_branches_missing_lines_cell -%} -{%- endif -%} {%- endfor -%} @@ -135,24 +101,6 @@ _No additional project files to report the coverage._ ) -}} {%- endblock project_missing_lines_badge_total_cell -%} -{% if branch_coverage %} -{#- Branches cell -#} -{%- block project_branches_badge_total_cell scoped -%} -{{- macros.branches_badge( - path="the whole project", - branches_count=coverage.info.num_branches, -) -}} -{%- endblock project_branches_badge_total_cell -%} - -{#- Missing cell -#} -{%- block project_missing_branches_badge_total_cell scoped -%} -{{- macros.missing_branches_badge( - path="the whole project", - missing_branches_count=coverage.info.missing_branches, -) -}} -{%- endblock project_missing_branches_badge_total_cell -%} -{% endif %} - {#- Coverage rate -#} {%- block project_coverage_rate_badge_total_cell scoped -%} {{- macros.coverage_rate_badge( @@ -165,9 +113,6 @@ _No additional project files to report the coverage._ {%- endblock project_coverage_rate_badge_total_cell -%} -{% if branch_coverage %} - -{% endif %}
FileStatementsMissingBranchesMissing
FileStatementsMissing
Coverage         

Missing stmts              

Missing branches              
  {{ parent }}  {{ parent }}
-{%- set comma = joiner() -%} -{%- for branch in coverage_file.coverage.missing_branches -%} -{{- comma() -}} -{{- branch[0] | abs -}} -> {{- branch[1] | abs -}} -{%- endfor -%} -
  
diff --git a/tests/conftest.py b/tests/conftest.py index 3a9e728..5e05ae0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ # mypy: disable-error-code="operator, union-attr" -import dataclasses import datetime import decimal import functools @@ -20,6 +19,7 @@ PytestCoverageInfo, PytestCoverageMetadata, PytestFileCoverage, + _incorporate_branch_missing, ) from codecov.github_client import GitHubClient @@ -39,14 +39,13 @@ def _(**kwargs): @pytest.fixture -def make_coverage() -> Callable[[str, bool], PytestCoverage]: - def _(code: str, has_branches: bool = True) -> PytestCoverage: +def make_coverage() -> Callable[[str], PytestCoverage]: + def _(code: str) -> PytestCoverage: current_file = None coverage_obj = PytestCoverage( meta=PytestCoverageMetadata( version='1.2.3', timestamp=datetime.datetime(2000, 1, 1), - branch_coverage=True, show_contexts=False, ), info=PytestCoverageInfo( @@ -56,10 +55,6 @@ def _(code: str, has_branches: bool = True) -> PytestCoverage: percent_covered_display='100', missing_lines=0, excluded_lines=0, - num_branches=0 if has_branches else None, - num_partial_branches=0 if has_branches else None, - covered_branches=0 if has_branches else None, - missing_branches=0 if has_branches else None, ), files={}, ) @@ -88,13 +83,7 @@ def _(code: str, has_branches: bool = True) -> PytestCoverage: percent_covered_display='100', missing_lines=0, excluded_lines=0, - num_branches=0 if has_branches else None, - num_partial_branches=0 if has_branches else None, - covered_branches=0 if has_branches else None, - missing_branches=0 if has_branches else None, ), - executed_branches=[], - missing_branches=[], ) if any( x in line @@ -122,22 +111,19 @@ def _(code: str, has_branches: bool = True) -> PytestCoverage: coverage_obj.files[current_file].info.excluded_lines += 1 coverage_obj.info.excluded_lines += 1 - if has_branches and 'branch' in line: - coverage_obj.files[current_file].info.num_branches += 1 - coverage_obj.info.num_branches += 1 - coverage_obj.files[current_file].executed_branches.append([line_number, line_number + 1]) - if 'branch partial' in line: - # Even if it's partially covered, it's still considered as a missing branch - coverage_obj.files[current_file].missing_branches.append([line_number, line_number + 1]) - coverage_obj.files[current_file].info.num_partial_branches += 1 - coverage_obj.info.num_partial_branches += 1 - elif 'branch covered' in line: - coverage_obj.files[current_file].info.covered_branches += 1 - coverage_obj.info.covered_branches += 1 - elif 'branch missing' in line: - coverage_obj.files[current_file].missing_branches.append([line_number, line_number + 1]) - coverage_obj.files[current_file].info.missing_branches += 1 - coverage_obj.info.missing_branches += 1 + if 'branch' in line: + branch_missing = None + if 'branch partial' in line or 'branch missing' in line: + branch_missing = [[line_number, line_number + 1]] + covered_lines, missing_lines = _incorporate_branch_missing( + covered_lines=coverage_obj.files[current_file].covered_lines, + missing_lines=coverage_obj.files[current_file].missing_lines, + missing_branches=branch_missing, + ) + coverage_obj.files[current_file].covered_lines = covered_lines + coverage_obj.files[current_file].missing_lines = missing_lines + coverage_obj.files[current_file].info.covered_lines = len(covered_lines) + coverage_obj.files[current_file].info.missing_lines = len(missing_lines) info = coverage_obj.files[current_file].info coverage_obj.files[current_file].info.percent_covered = PytestCoverageHandler().compute_coverage( @@ -163,9 +149,8 @@ def _(code: str, has_branches: bool = True) -> PytestCoverage: def make_diff_coverage(test_config): handler = PytestCoverageHandler() - def _(added_lines, coverage, branch_coverage=False): - config = dataclasses.replace(test_config, BRANCH_COVERAGE=branch_coverage) - return handler.get_diff_coverage(added_lines=added_lines, coverage=coverage, config=config) + def _(added_lines, coverage): + return handler.get_diff_coverage(added_lines=added_lines, coverage=coverage, config=test_config) return _ diff --git a/tests/coverage/test_base.py b/tests/coverage/test_base.py index aad18d2..ca7e107 100644 --- a/tests/coverage/test_base.py +++ b/tests/coverage/test_base.py @@ -1,4 +1,3 @@ -import dataclasses import decimal import json import pathlib @@ -162,100 +161,3 @@ def test_get_diff_coverage_info(self, test_config, make_coverage_obj, added_line config=test_config, ) assert result == expected - - @pytest.mark.parametrize( - 'added_lines, update_obj, expected', - [ - # A similar example to the previous one, but with branch coverage enabled. - # The statements are covered, but the branches are not. - ( - { - pathlib.Path('codebase/code.py'): [4, 5, 6], - pathlib.Path('codebase/other.py'): [10, 13], - }, - {}, - DiffCoverage( - total_num_lines=1, - total_num_violations=1, - total_percent_covered=decimal.Decimal('0.25'), - num_changed_lines=5, - # Percent is due to the fact that the branches are not covered. - files={ - pathlib.Path('codebase/code.py'): FileDiffCoverage( - path=pathlib.Path('codebase/code.py'), - percent_covered=decimal.Decimal('0.25'), - added_statements=[6], - covered_statements=[], - missing_statements=[6], - added_lines=[4, 5, 6], - ), - pathlib.Path('codebase/other.py'): FileDiffCoverage( - path=pathlib.Path('codebase/other.py'), - percent_covered=decimal.Decimal('0.25'), - added_statements=[], - covered_statements=[], - missing_statements=[], - added_lines=[10, 13], - ), - }, - ), - ), - # Files with missing coverage and branches - ( - { - pathlib.Path('codebase/code.py'): [2, 3, 4, 5, 6], - pathlib.Path('codebase/other.py'): [10, 11, 12, 13], - }, - { - 'codebase/code.py': { - 'covered_lines': [1, 2, 3, 5, 6], - 'missing_lines': [4, 5], - }, - 'codebase/other.py': { - 'covered_lines': [10, 11, 12, 13], - 'missing_lines': [10, 13], - }, - }, - DiffCoverage( - total_num_lines=9, - total_num_violations=4, - total_percent_covered=decimal.Decimal('0.4375'), - num_changed_lines=9, - # Percent is due to the fact that the branches are not covered. - files={ - pathlib.Path('codebase/code.py'): FileDiffCoverage( - path=pathlib.Path('codebase/code.py'), - percent_covered=decimal.Decimal('0.625'), - added_statements=[2, 3, 4, 5, 6], - covered_statements=[2, 3, 5, 6], - missing_statements=[4, 5], - added_lines=[2, 3, 4, 5, 6], - ), - pathlib.Path('codebase/other.py'): FileDiffCoverage( - path=pathlib.Path('codebase/other.py'), - percent_covered=decimal.Decimal('0.625'), - added_statements=[10, 11, 12, 13], - covered_statements=[10, 11, 12, 13], - missing_statements=[10, 13], - added_lines=[10, 11, 12, 13], - ), - }, - ), - ), - ], - ) - def test_get_diff_coverage_info_branch_coverage( - self, - test_config, - make_coverage_obj, - added_lines, - update_obj, - expected, - ): - config = dataclasses.replace(test_config, BRANCH_COVERAGE=True) - result = PytestCoverageHandler().get_diff_coverage( - added_lines=added_lines, - coverage=make_coverage_obj(**update_obj), - config=config, - ) - assert result == expected diff --git a/tests/coverage/test_pytest.py b/tests/coverage/test_pytest.py index 96bec92..be25283 100644 --- a/tests/coverage/test_pytest.py +++ b/tests/coverage/test_pytest.py @@ -1,4 +1,3 @@ -import dataclasses import datetime import json import pathlib @@ -19,53 +18,41 @@ def test_extract_info(self, coverage_json): meta=PytestCoverageMetadata( version='1.2.3', timestamp=datetime.datetime.fromisoformat('2000-01-01T00:00:00'), - branch_coverage=True, show_contexts=False, ), files={ pathlib.Path('codebase/code.py'): PytestFileCoverage( path=pathlib.Path('codebase/code.py'), excluded_lines=[], - covered_lines=[1, 2, 3, 5, 13, 14], - missing_lines=[6, 8, 10, 11], + covered_lines=[2, 3, 5, 13, 14], + missing_lines=[1, 6, 8, 10, 11], info=PytestCoverageInfo( - covered_lines=6, + covered_lines=5, num_statements=10, percent_covered=PytestCoverageHandler().convert_to_decimal(60.0), percent_covered_display='60%', - missing_lines=4, + missing_lines=5, excluded_lines=0, - num_branches=3, - num_partial_branches=1, - covered_branches=1, - missing_branches=1, ), - executed_branches=[[1, 0], [2, 1], [3, 0], [5, 1], [13, 0], [14, 0]], - missing_branches=[[6, 0], [8, 1], [10, 0], [11, 0]], ) }, info=PytestCoverageInfo( - covered_lines=6, + covered_lines=5, num_statements=10, percent_covered=PytestCoverageHandler().convert_to_decimal(60.0), percent_covered_display='60%', - missing_lines=4, + missing_lines=5, excluded_lines=0, - num_branches=3, - num_partial_branches=1, - covered_branches=1, - missing_branches=1, ), ) assert PytestCoverageHandler().extract_info(coverage_json) == expected_coverage - def test_get_coverage_with_branch_coverage(self, test_config, coverage_json): - config = dataclasses.replace(test_config, BRANCH_COVERAGE=True) + def test_get_coverage_incorporates_branch_missing(self, test_config, coverage_json): handler = PytestCoverageHandler() with patch('pathlib.Path.open') as mock_open: mock_open.return_value.__enter__.return_value.read.return_value = json.dumps(coverage_json) - coverage = handler.get_coverage(config=config) + coverage = handler.get_coverage(config=test_config) - assert coverage.meta.branch_coverage is True - assert coverage.files[pathlib.Path('codebase/code.py')].missing_branches == [[0, 11]] + assert coverage.files[pathlib.Path('codebase/code.py')].missing_lines == [1, 6, 8, 10, 11] + assert coverage.files[pathlib.Path('codebase/code.py')].covered_lines == [2, 3, 5, 13, 14] diff --git a/tests/test_config.py b/tests/test_config.py index 5af7db5..1e37101 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -125,11 +125,6 @@ def test_config_clean_annotate_missing_lines(): assert value is True -def test_config_clean_branch_coverage(): - value = config.Config.clean_branch_coverage('False') - assert value is False - - def test_config_clean_complete_project_report(): value = config.Config.clean_complete_project_report('True') assert value is True diff --git a/tests/test_diff_grouper.py b/tests/test_diff_grouper.py index c6777a4..e380e2e 100644 --- a/tests/test_diff_grouper.py +++ b/tests/test_diff_grouper.py @@ -7,7 +7,7 @@ def test_get_diff_missing_groups(coverage_obj, diff_coverage_obj): result = diff_grouper.get_diff_missing_groups(coverage=coverage_obj, diff_coverage=diff_coverage_obj) assert list(result) == [ - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=6, line_end=8), + groups.Group(file=pathlib.Path('codebase/code.py'), line_start=5, line_end=8), ] @@ -18,9 +18,8 @@ def test_get_diff_missing_groups_more_files(coverage_obj_more_files, diff_covera ) assert list(result) == [ - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=6, line_end=8), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=2, line_end=2), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=6, line_end=7), + groups.Group(file=pathlib.Path('codebase/code.py'), line_start=5, line_end=8), + groups.Group(file=pathlib.Path('codebase/other.py'), line_start=2, line_end=7), ] @@ -28,7 +27,7 @@ def test_get_missing_groups(coverage_obj): result = diff_grouper.get_missing_groups(coverage=coverage_obj) assert list(result) == [ - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=6, line_end=11), + groups.Group(file=pathlib.Path('codebase/code.py'), line_start=5, line_end=11), ] @@ -36,60 +35,6 @@ def test_get_missing_groups_more_files(coverage_obj_more_files): result = diff_grouper.get_missing_groups(coverage=coverage_obj_more_files) assert list(result) == [ - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=6, line_end=11), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=2, line_end=2), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=6, line_end=9), + groups.Group(file=pathlib.Path('codebase/code.py'), line_start=5, line_end=11), + groups.Group(file=pathlib.Path('codebase/other.py'), line_start=2, line_end=11), ] - - -def test_flatten_branches(): - assert not diff_grouper._flatten_branches(branches=None) - - flattened_branches = diff_grouper._flatten_branches([[1, 2], [3, 4]]) - assert flattened_branches == [1, 2, 3, 4] - - flattened_branches = diff_grouper._flatten_branches([[1, 1]]) - assert flattened_branches == [1] - - flattened_branches = diff_grouper._flatten_branches([[-1, -2], [3, 4]]) - assert flattened_branches == [1, 2, 3, 4] - - flattened_branches = diff_grouper._flatten_branches([[-1, -2], [3, 4], [5, 5]]) - assert flattened_branches == [1, 2, 3, 4, 5] - - -def test_get_branch_missing_groups(coverage_obj, diff_coverage_obj): - result = diff_grouper.get_diff_branch_missing_groups(coverage=coverage_obj, diff_coverage=diff_coverage_obj) - - assert list(result) == [ - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=5, line_end=6), - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=10, line_end=11), - ] - - -def test_get_branch_missing_groups_more_files(coverage_obj_more_files, diff_coverage_obj_more_files): - result = diff_grouper.get_diff_branch_missing_groups( - coverage=coverage_obj_more_files, - diff_coverage=diff_coverage_obj_more_files, - ) - - assert list(result) == [ - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=5, line_end=6), - groups.Group(file=pathlib.Path('codebase/code.py'), line_start=10, line_end=11), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=3, line_end=4), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=5, line_end=6), - groups.Group(file=pathlib.Path('codebase/other.py'), line_start=10, line_end=11), - ] - - -def test_fill_branch_missing_groups(coverage_obj): - result = diff_grouper.fill_branch_missing_groups(coverage=coverage_obj) - - assert result.files[pathlib.Path('codebase/code.py')].missing_branches == [[5, 11]] - - -def test_fill_branch_missing_groups_more_files(coverage_obj_more_files): - result = diff_grouper.fill_branch_missing_groups(coverage=coverage_obj_more_files) - - assert result.files[pathlib.Path('codebase/code.py')].missing_branches == [[5, 11]] - assert result.files[pathlib.Path('codebase/other.py')].missing_branches == [[3, 11]] diff --git a/tests/test_main.py b/tests/test_main.py index a0dac9c..2ff07f9 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -57,20 +57,6 @@ def test_process_coverage(self, test_config, gh, coverage_obj, diff_coverage_obj config=test_config, ) - def test_process_coverage_branch_coverage(self, test_config, gh, coverage_obj, diff_coverage_obj): - with patch.object(Main, '_init_config', return_value=test_config): - test_config.BRANCH_COVERAGE = True - with patch.object(Main, '_init_github', return_value=gh): - main = Main() - main.coverage_module = MagicMock() - main.coverage_module.get_coverage = MagicMock(return_value=coverage_obj) - main.coverage_module.get_diff_coverage = MagicMock(return_value=diff_coverage_obj) - - assert main._process_coverage() is None - - assert main.coverage == coverage_obj - assert main.diff_coverage == diff_coverage_obj - @patch('codecov.main.template.get_comment_markdown') def test_create_comment( self, @@ -145,15 +131,6 @@ def test_generate_annotations_empty( main.diff_coverage = diff_coverage_obj assert main._generate_annotations() is None - with patch.object(Main, '_init_config', return_value=test_config): - with patch.object(Main, '_init_github', return_value=gh): - main = Main() - main.config.ANNOTATE_MISSING_LINES = True - main.config.BRANCH_COVERAGE = True - main.coverage = coverage_obj - main.diff_coverage = diff_coverage_obj - assert main._generate_annotations() is None - def test_generate_annotations(self, test_config, gh, coverage_obj, diff_coverage_obj): with patch.object(Main, '_init_config', return_value=test_config): with patch.object(Main, '_init_github', return_value=gh): @@ -163,15 +140,6 @@ def test_generate_annotations(self, test_config, gh, coverage_obj, diff_coverage main.diff_coverage = diff_coverage_obj assert main._generate_annotations() is None - with patch.object(Main, '_init_config', return_value=test_config): - with patch.object(Main, '_init_github', return_value=gh): - main = Main() - main.config.BRANCH_COVERAGE = True - main.config.ANNOTATE_MISSING_LINES = True - main.coverage = coverage_obj - main.diff_coverage = diff_coverage_obj - assert main._generate_annotations() is None - def test_run(self, test_config, gh): with patch.object(Main, '_init_config', return_value=test_config): with patch.object(Main, '_init_github', return_value=gh): diff --git a/tests/test_template.py b/tests/test_template.py index fac6b21..1221e28 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -166,37 +166,6 @@ def test_comment_template(coverage_obj, diff_coverage_obj): assert '' in result -def test_comment_template_branch_coverage(coverage_obj, diff_coverage_obj): - chaned_files, total = template.select_changed_files( - coverage=coverage_obj, - diff_coverage=diff_coverage_obj, - max_files=25, - skip_covered_files_in_report=True, - ) - result = template.get_comment_markdown( - template.read_template_file('comment.md.j2'), - coverage_obj, - diff_coverage_obj, - decimal.Decimal('100'), - decimal.Decimal('70'), - 'org/repo', - 1, - 'main', - '', - coverage_files=chaned_files, - count_coverage_files=total, - files=chaned_files, - count_files=total, - max_files=25, - branch_coverage=True, - ) - assert result.startswith('## Coverage report') - assert '' in result - assert 'BranchesMissing' in result - assert 'Missing branches' in result - assert 'colspan="9"' in result - - def test_template_no_files(coverage_obj): diff_coverage = DiffCoverage( total_num_lines=0,