Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branch coverage #466

Merged
merged 10 commits into from
Oct 5, 2024
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,16 @@ To run the end-to-end tests, you'll need:
- Please be aware that the tests will launch `gh auth setup-git` which might be
surprising if you use `https` remotes (sadly, setting `GIT_CONFIG_GLOBAL`
seems not to be enough to isolate tests.)

## Coverage labs

### Computing the coverage rate

The coverage rate is `covered_lines / total_lines` (as one would expect).
In case "branch coverage" is enabled, the coverage rate is
`(covered_lines + covered_branches) / (total_lines + total_branches)`.
In order to display coverage rates, we need to round the values. Depending on
the situation, we either round to 0 or 2 decimal places. Rounding rules are:
- We always round down (truncate) the value.
- We don't display the trailing zeros in the decimal part (nor the decimal point
if the decimal part is 0).
55 changes: 47 additions & 8 deletions coverage_comment/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import pathlib
from collections.abc import Sequence
from typing import cast

from coverage_comment import log, subprocess

Expand Down Expand Up @@ -40,6 +41,8 @@ class FileCoverage:
executed_lines: list[int]
missing_lines: list[int]
excluded_lines: list[int]
executed_branches: list[list[int]] | None
missing_branches: list[list[int]] | None
info: CoverageInfo


Expand Down Expand Up @@ -82,10 +85,26 @@ class DiffCoverage:
files: dict[pathlib.Path, FileDiffCoverage]


def compute_coverage(num_covered: int, num_total: int) -> decimal.Decimal:
if num_total == 0:
def compute_coverage(
num_covered: int,
num_total: int,
use_branch_coverage: bool,
num_branches_covered: int | None,
num_branches_total: int | None,
) -> decimal.Decimal:
"""Compute the coverage percentage, with or without branch coverage."""
if use_branch_coverage:
num_branches_covered = cast(int, num_branches_covered)
num_branches_total = cast(int, num_branches_total)
numerator = decimal.Decimal(num_covered + num_branches_covered)
denominator = decimal.Decimal(num_total + num_branches_total)
else:
numerator = decimal.Decimal(num_covered)
denominator = decimal.Decimal(num_total)

if denominator == 0:
return decimal.Decimal("1")
return decimal.Decimal(num_covered) / decimal.Decimal(num_total)
return numerator / denominator


def get_coverage_info(
Expand Down Expand Up @@ -191,12 +210,19 @@ def extract_info(data: dict, coverage_path: pathlib.Path) -> Coverage:
excluded_lines=file_data["excluded_lines"],
executed_lines=file_data["executed_lines"],
missing_lines=file_data["missing_lines"],
executed_branches=file_data.get("executed_branches"),
missing_branches=file_data.get("missing_branches"),
info=CoverageInfo(
covered_lines=file_data["summary"]["covered_lines"],
num_statements=file_data["summary"]["num_statements"],
percent_covered=compute_coverage(
file_data["summary"]["covered_lines"],
file_data["summary"]["num_statements"],
num_covered=file_data["summary"]["covered_lines"],
num_total=file_data["summary"]["num_statements"],
use_branch_coverage=data["meta"]["branch_coverage"],
num_branches_covered=file_data["summary"].get(
"covered_branches"
),
num_branches_total=file_data["summary"].get("num_branches"),
),
missing_lines=file_data["summary"]["missing_lines"],
excluded_lines=file_data["summary"]["excluded_lines"],
Expand All @@ -214,8 +240,11 @@ def extract_info(data: dict, coverage_path: pathlib.Path) -> Coverage:
covered_lines=data["totals"]["covered_lines"],
num_statements=data["totals"]["num_statements"],
percent_covered=compute_coverage(
data["totals"]["covered_lines"],
data["totals"]["num_statements"],
num_covered=data["totals"]["covered_lines"],
num_total=data["totals"]["num_statements"],
use_branch_coverage=data["meta"]["branch_coverage"],
num_branches_covered=data["totals"].get("covered_branches"),
num_branches_total=data["totals"].get("num_branches"),
),
missing_lines=data["totals"]["missing_lines"],
excluded_lines=data["totals"]["excluded_lines"],
Expand Down Expand Up @@ -255,8 +284,18 @@ def get_diff_coverage_info(
total_num_lines += count_total
total_num_violations += count_missing

num_branches_covered = None
num_branches_total = None
if coverage.meta.branch_coverage:
num_branches_covered = 12
num_branches_total = 12
ferdnyc marked this conversation as resolved.
Show resolved Hide resolved

percent_covered = compute_coverage(
num_covered=count_executed, num_total=count_total
num_covered=count_executed,
num_total=count_total,
use_branch_coverage=coverage.meta.branch_coverage,
num_branches_covered=num_branches_covered,
num_branches_total=num_branches_total,
)

files[path] = FileDiffCoverage(
Expand Down
Loading