Skip to content

fix: correct coverage percentage when branch coverage is enabled#285

Merged
MishaKav merged 4 commits into
mainfrom
fix-branch-coverage-percentage
Jul 4, 2026
Merged

fix: correct coverage percentage when branch coverage is enabled#285
MishaKav merged 4 commits into
mainfrom
fix-branch-coverage-percentage

Conversation

@MishaKav

@MishaKav MishaKav commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Continues #284 by @mschoettle (which landed on fix-branch-coverage-percentage), with hardening added on top.

What

  • combine statement + branch coverage and round the same way coverage report does (never rounding up to 100% or down to 0%)
  • reject malformed coverage XML instead of falling back to 100%
  • make the text-badge fraction branch-aware (e.g. 10/12, not 8/8)
  • bump to 1.9.0 + changelog entry

Credit

Original branch-coverage fix by @mschoettle in #284. This PR carries that work plus the follow-up hardening to main.

Test

npm run all green (95 tests). Added coverage for the partial-branch 83% case and the branch-aware text badge.

🤖 Generated with Claude Code

mschoettle and others added 2 commits July 3, 2026 13:06
- combine statement + branch coverage and round like `coverage report`
  (never rounding up to 100% or down to 0%)
- reject malformed coverage XML instead of falling back to 100%
- make the text-badge fraction branch-aware (e.g. 10/12, not 8/8)
- bump version to 1.9.0 and add changelog entry

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 12:33
@codeant-ai

codeant-ai Bot commented Jul 4, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@codeant-ai

codeant-ai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report
FileStmtsMissCoverMissing
functions/example_completed
   example_completed.py641970%33, 39–45, 48–51, 55–58, 65–70, 91–92
functions/example_manager
   example_manager.py441175%31–33, 49–55, 67–69
   example_static.py40295%60–61
functions/my_exampels
   example.py20200%1–31
functions/resources
   resources.py26260%1–37
TOTAL105573930% 

Tests Skipped Failures Errors Time
109 2 💤 1 ❌ 0 🔥 0.583s ⏱️

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jul 4, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix branch-aware coverage percentage rounding and harden Cobertura XML parsing

🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Combine statement + branch coverage to match coverage report percentage semantics.
• Prevent malformed coverage XML from silently reporting 100% coverage.
• Make text-badge fractions branch-aware and add regression tests.
Diagram

graph TD
  A[("Cobertura XML")]
  B["src/parseXml.ts"]
  C["compute/format %"]
  D["src/parse.ts"]
  E["HTML/Badge output"]
  F["__tests__/parseXml.test.ts"]
  A --> B --> C --> D --> E
  F --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Continue using Cobertura `line-rate` / `branch-rate` fields
  • ➕ Minimal computation; relies on XML-provided percentages.
  • ➖ Incorrect when line-rate is 1.0 but branches are partially covered (root cause).
  • ➖ Hard to match coverage report semantics and rounding edge-cases.
2. Parse `coverage report` textual output instead of Cobertura XML
  • ➕ Directly matches the authoritative output/rounding rules.
  • ➖ Adds dependency on running coverage.py output generation in CI.
  • ➖ More brittle across coverage.py versions/locales and harder to link to per-file XML details.
3. Use a dedicated Cobertura parser library for validation + typing
  • ➕ Could reduce custom XML edge-case handling and improve schema validation.
  • ➖ Extra dependency surface area for a small, well-scoped parsing need.
  • ➖ Doesn’t inherently solve the combined-percent semantics.

Recommendation: The chosen approach (compute a combined statement+branch percentage from the XML counts, then apply coverage.py-like rounding clamps) is the best fit: it preserves XML as the source of truth, fixes the semantic bug, and improves safety by failing closed on malformed totals. The added tests lock in the regression cases that previously produced misleading 100% output.

Files changed (6) +160 / -18

Bug fix (3) +128 / -17
index.jsUpdate compiled output to use branch-aware percent and badge fraction +50/-8

Update compiled output to use branch-aware percent and badge fraction

• Mirrors source changes in the built artifact: computes combined statement+branch coverage percent with coverage.py-like rounding, hardens XML-total validation, and updates text-badge fraction to include branches.

dist/index.js

parse.tsMake text-badge fraction branch-aware +10/-4

Make text-badge fraction branch-aware

• Changes text badge formatting to include covered branches and total branches so the fraction matches the branch-aware percentage rather than statements-only counts.

src/parse.ts

parseXml.tsCompute combined statement+branch coverage percent with safer rounding/validation +68/-5

Compute combined statement+branch coverage percent with safer rounding/validation

• Introduces helpers to compute a single coverage percent from statement+branch counts and round like coverage.py (clamping 99.x→99 and 0.x→1). Rejects malformed total coverage attributes instead of silently producing misleading results and updates file-level coverage to use the combined percent.

src/parseXml.ts

Tests (1) +23 / -0
parseXml.test.tsAdd regression tests for partial-branch percent and branch-aware text badge +23/-0

Add regression tests for partial-branch percent and branch-aware text badge

• Adds coverage XML regression tests ensuring partially covered branches reduce the reported percent (e.g., 83% vs 100%). Verifies text-badge fractions include both statements and branches (e.g., 10/12).

tests/parseXml.test.ts

Documentation (1) +8 / -0
CHANGELOG.mdAdd 1.9.0 changelog entry for branch-aware coverage fix +8/-0

Add 1.9.0 changelog entry for branch-aware coverage fix

• Documents the 1.9.0 release and describes the corrected coverage percentage behavior when branch coverage is enabled, including attribution and issue reference.

CHANGELOG.md

Other (1) +1 / -1
package.jsonBump package version to 1.9.0 +1/-1

Bump package version to 1.9.0

• Updates the published package version to 1.9.0 to reflect the branch-coverage percentage fix release.

package.json

Comment thread src/parse.ts Outdated
@codeant-ai

codeant-ai Bot commented Jul 4, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 4, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Text badge fraction wrong ✓ Resolved 🐞 Bug ≡ Correctness
Description
In toHtml(), the text badge fraction now assumes brpart is the number of missing branches and
computes covered branches as (branch - brpart), which is false for coverage text report input
(where BrPart is not total missing branches). This can make the fraction contradict total.cover
(e.g., showing 42% worth of covered items while the badge says 22%), misleading users.
Code

src/parse.ts[R267-276]

+  const stmts =
+    typeof total.stmts === 'number' ? total.stmts : parseInt(total.stmts);
+  const miss =
+    typeof total.miss === 'number' ? total.miss : parseInt(total.miss);
+  // include branches so the fraction matches the branch-aware %
+  const branch = total.branch ? parseInt(total.branch) : 0;
+  const brpart = total.brpart ? parseInt(total.brpart) : 0;
+  const covered = stmts - miss + (branch - brpart);
+  const totalCount = stmts + branch;
+  const textBadge = `${total.cover} (${covered}/${totalCount})`;
Evidence
The code now computes the fraction assuming brpart is missing branches, but for text coverage
parsing brpart is read from the BrPart column, which in fixtures does not match missing-branches
totals, producing a fraction inconsistent with the reported percentage.

src/parse.ts[167-193]
src/parse.ts[239-276]
data/pytest-coverage_6.txt[3-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`toHtml()` now builds the text badge fraction using `covered = stmts - miss + (branch - brpart)`. This only makes sense when `brpart` is the *count of missing branch arcs* (as in the XML parsing path). For coverage **text report** parsing (`parseTotalLine()`), `BrPart` is not the total missing branches, so the fraction becomes incorrect and can contradict the displayed percentage.
### Issue Context
- For XML input, `src/parseXml.ts` sets `TotalLine.brpart = branchesValid - branchesCovered`, which *is* missing branches.
- For text report input, `src/parse.ts` assigns `TotalLine.brpart` directly from the coverage report's `BrPart` column, which does not represent total missing branches.
### Fix Focus Areas
- src/parse.ts[254-277]
### Suggested fix approach
- Only use the `(branch - brpart)` branch-coverage fraction math when the data source guarantees `brpart` means "missing branches" (e.g., when `dataFromXml` is non-null).
- For non-XML (text report) input, either:
1) keep the old statement-only fraction `(stmts - miss) / stmts`, or
2) compute the numerator from the displayed percent to keep the fraction consistent (e.g., `covered = Math.round((parseFloat(total.cover) / 100) * (stmts + branch))`), and clamp to `[0, totalCount]`.
- Add a unit test covering `textInsteadBadge: true` with a text coverage fixture containing branches (e.g. `data/pytest-coverage_6.txt`) to ensure the fraction does not contradict `total.cover`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Branch attrs coerced to 0 🐞 Bug ☼ Reliability
Description
getTotalCoverage() still coerces branches-valid/branches-covered with parseInt(...) || 0, so
malformed or partially-missing branch attributes can slip past the new Number.isFinite(cover)
validation. This can also produce impossible results (e.g., >100%) instead of rejecting malformed
coverage XML as intended.
Code

src/parseXml.ts[R72-89]

const linesValid = parseInt(coverage['lines-valid']);
const linesCovered = parseInt(coverage['lines-covered']);
const branchesValid = parseInt(coverage['branches-valid']) || 0;
const branchesCovered = parseInt(coverage['branches-covered']) || 0;
+  const cover = formatCoverPercent(
+    computeCoverPercent(
+      linesCovered,
+      linesValid,
+      branchesCovered,
+      branchesValid,
+    ),
+  );
+
+  if (!Number.isFinite(cover)) {
+    // prettier-ignore
+    core.warning(`Coverage xml file is missing valid total coverage attributes`);
+    return null;
+  }
Evidence
The current implementation explicitly uses || 0 for branch attributes and only rejects non-finite
computed cover, so missing/invalid branch attribute values can be silently treated as zero and pass
validation.

src/parseXml.ts[71-89]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new combined coverage calculation rejects `NaN` counts, but `branches-valid` and `branches-covered` are still parsed with `parseInt(...) || 0`. This converts malformed/missing values to `0` (finite), so malformed XML may not be rejected and can yield incorrect coverage (including >100%) instead of returning `null`.
### Issue Context
This PR explicitly aims to reject malformed XML rather than falling back to a seemingly-valid percentage; the current fallback-to-0 undermines that.
### Fix Focus Areas
- src/parseXml.ts[71-101]
### Suggested fix approach
- Parse branch counts without `|| 0` and validate consistency:
- If **both** branch attributes are missing, treat branch coverage as disabled (use `0,0`).
- If **one** is present and the other is missing/NaN, treat as malformed and return `null` (with a warning).
- If both present, validate `0 <= covered <= valid`.
- Consider also validating `0 <= linesCovered <= linesValid` for symmetry.
- Add a unit test with malformed XML attributes to confirm the function returns `null` and logs a warning.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/parse.ts
Comment thread src/parseXml.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes incorrect coverage reporting when Cobertura XML includes branch coverage by computing a combined statement+branch percentage and matching coverage report rounding behavior, while also hardening XML parsing and updating the release metadata.

Changes:

  • Compute coverage as (covered statements + covered branches) / (total statements + total branches) and round like coverage report (avoiding false 100%/0%).
  • Reject malformed coverage XML totals instead of producing misleading results.
  • Make the text badge fraction branch-aware for XML-based reports; bump version to 1.9.0 and add a changelog entry.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/parseXml.ts Adds combined statement+branch coverage computation, rounding, and stricter XML validation paths.
src/parse.ts Updates text badge fraction logic to incorporate branch counts.
tests/parseXml.test.ts Adds tests for partial-branch 83% case and branch-aware text badge in XML mode.
CHANGELOG.md Adds 1.9.0 release notes for the branch-coverage fix.
package.json Bumps package version to 1.9.0.
package-lock.json Updates lockfile version metadata to 1.9.0.
dist/index.js Rebuild output reflecting source changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/parse.ts
Comment thread src/parseXml.ts
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@MishaKav, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a10657d-cf3a-4d7d-ae4d-36ab2177cb7b

📥 Commits

Reviewing files that changed from the base of the PR and between 0f65f1a and 864fa63.

📒 Files selected for processing (1)
  • src/types.d.ts

Walkthrough

This PR updates coverage handling to include branch coverage in total and per-file percentage calculations. src/parseXml.ts now computes coverage from statement and branch counts with new rounding helpers, and only generates report output when coverage is present. src/parse.ts updates the text badge math to include branch data when XML coverage is available. Tests, a changelog entry, and a version bump to 1.9.0 are included.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: size:XXL

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change and testing, but it misses the required Related Issue and checklist sections from the template. Add the template sections for Related Issue and Checklist, including the issue number and completed test/doc checkboxes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change to branch-coverage percentage calculation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7df8264f-95a4-4826-9a65-8ca0a4dc20bb

📥 Commits

Reviewing files that changed from the base of the PR and between a15368f and e6b549a.

⛔ Files ignored due to path filters (2)
  • dist/index.js is excluded by !**/dist/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • CHANGELOG.md
  • __tests__/parseXml.test.ts
  • package.json
  • src/parse.ts
  • src/parseXml.ts

Comment thread CHANGELOG.md

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

3 issues found across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/parse.ts
Comment thread src/parseXml.ts
Comment thread src/parseXml.ts
The branch-aware fraction assumed `brpart` is the count of missing
branches, which is only true for XML totals. Text reports expose
coverage.py's `BrPart` (partial branches), so gate the branch math on
the XML path to avoid overstating covered branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 3 files (changes from recent commits).

Re-trigger cubic

Pre-existing formatting drift (prettier bump in #275) that broke the
format-check CI step once this branch touched src files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MishaKav MishaKav merged commit 4a4889e into main Jul 4, 2026
7 checks passed
@MishaKav MishaKav deleted the fix-branch-coverage-percentage branch July 4, 2026 13:07
@mschoettle

Copy link
Copy Markdown
Contributor

@MishaKav Thanks a lot for finishing this up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants