Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Comment on lines +91 to +93

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than conditionally including the branch numbers, you could just arrange to have them default to 0 if there's no branch data. If they're both 0 they won't affect the math, so they can just be added in unconditionally.

) -> 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)
Comment on lines +96 to +100

@ferdnyc ferdnyc Aug 30, 2024

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This math exactly checks out, with the raw_data.totals numbers and raw_data.totals.percent_covered produced by pytest-cov.

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

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