Skip to content

ci(covr): install xml2 from r-lib/xml2#479 to fix multi-hour coverage runs#757

Merged
krlmlr merged 1 commit into
mainfrom
claude/festive-babbage-ma9xpq
Jun 20, 2026
Merged

ci(covr): install xml2 from r-lib/xml2#479 to fix multi-hour coverage runs#757
krlmlr merged 1 commit into
mainfrom
claude/festive-babbage-ma9xpq

Conversation

@krlmlr

@krlmlr krlmlr commented Jun 20, 2026

Copy link
Copy Markdown
Member

Problem

The coverage job on GitHub Actions takes ~5 hours. The time is spent entirely after the last Moving gcov outputs to covr directory. message — i.e. in covr::to_cobertura(cov), which the covr action runs after package_coverage().

to_cobertura() builds the Cobertura report by appending one <line> XML node per covered source line. With released xml2, xml_add_child() defaults to .where = length(xml_children(.x)), which re-enumerates all existing siblings on every append → O(n²) per file. For RSQLite's bundled sqlite3.c (~67k covered lines under a single <lines> node) that is ~4.5 billion operations.

Evidence

Profiling the real coverage object (74,017 entries; sqlite3.c = 67,619) showed everything up to and including package_coverage() finishing in ~1.2 min, while to_cobertura() dominated, with Rprof attributing 94% of time to xml_add_childxml_children/xml_nodeset.

Fix

The root cause is in xml2 and is fixed upstream by r-lib/xml2#479, which makes the default append O(1).

This PR installs xml2 from that PR at the start of the covr action, so the fast path is used only for coverage runs:

- name: Install xml2 with O(1) xml_add_child() (r-lib/xml2#479)
  run: pak::pak("r-lib/xml2#479")
  shell: Rscript {0}

pak is already provisioned by setup-r-dependencies in this job, so no extra setup is required.

Verified

With xml2 installed from the PR, the unmodified, released to_cobertura() processes the full RSQLite coverage data in ~25 seconds (down from ~5 hours).

Note

This pins coverage to a PR build of xml2. Once r-lib/xml2#479 is merged and released, this step can be dropped (or switched to r-lib/xml2 / the released version).

🤖 Generated with Claude Code


Generated by Claude Code

covr::to_cobertura() appends one XML node per covered source line. With
released xml2, xml_add_child() re-scans all existing siblings on every
append, which is O(n^2) per file. For RSQLite's bundled sqlite3.c
(~67k covered lines) this made the coverage job on GitHub Actions exceed
5 hours.

r-lib/xml2#479 changes the default append to O(1). Install xml2 from that
PR at the start of the covr action so the fix applies only to coverage
runs; report generation drops from hours to ~25 seconds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dh6ePmbJHgSzUoVMWk7q1s
@krlmlr krlmlr enabled auto-merge (squash) June 20, 2026 22:13
@krlmlr krlmlr merged commit a751349 into main Jun 20, 2026
5 of 6 checks passed
@krlmlr krlmlr deleted the claude/festive-babbage-ma9xpq branch June 20, 2026 22:19
krlmlr added a commit that referenced this pull request Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants