11import { mkdirSync , readdirSync , readFileSync , writeFileSync } from 'node:fs'
22import { join } from 'node:path'
33
4- import istanbulLibCoverage from 'istanbul-lib-coverage'
5-
6- // Merges one workflow run's downloaded per-cell `coverage-*` artifacts into a single lcov file and a
7- // single istanbul JSON file under `coverage-upload/<run-id>/`, scoped to that run alone. All Green
8- // calls this as soon as a sibling workflow finishes, instead of waiting for every workflow to
9- // complete before merging and uploading anything — the goal is for each workflow's coverage to reach
10- // Datadog and Codecov shortly after that workflow finishes, in parallel with the rest still running.
4+ // Merges one workflow run's downloaded per-cell `coverage-*` artifacts into a single lcov file under
5+ // `coverage-upload/<run-id>/`, scoped to that run alone. All Green calls this as soon as a sibling
6+ // workflow finishes, instead of waiting for every workflow to complete before merging and uploading
7+ // anything — the goal is for each workflow's coverage to reach Datadog and Codecov shortly after that
8+ // workflow finishes, in parallel with the rest still running.
119//
12- // Codecov reads branch/function coverage from istanbul's JSON; Datadog only ingests the lcov — see
13- // `upload- coverage.mjs`. Both formats need a real per-file merge, not concatenation: every matrix
14- // cell in a workflow run (each Node.js version, each plugin partition) writes its own complete
15- // report, so a shared source file's coverage shows up once per cell. Concatenating lcov's `SF:`
16- // blocks produces a report with duplicate `SF:` sections per file, which downstream lcov consumers
17- // resolve by keeping only the last block for that file rather than summing across blocks — silently
18- // discarding most of the branch/function data every earlier cell had recorded. A naive object merge
19- // of istanbul's JSON has the same failure mode. Uploading either format unmerged across sessions has
20- // the same problem one level up: Codecov's own cross-session merge has been observed overwriting
21- // rather than summing a shared file's coverage when more than one session reports it, so every
22- // format is merged down to one report per run before upload instead of relying on the backend to
23- // reconcile per- session duplicates. `mergeLcov` sums `DA:`/`FNDA:`/`BRDA:` hit counts per file across
24- // cells ( the way `lcov --add-tracefile` does), and `mergeCoverageJson` uses
25- // `istanbul-lib-coverage`'s `merge` to do the same for the JSON report .
10+ // Both Datadog and Codecov ingest lcov only — istanbul JSON support (which Codecov used to read
11+ // branch/function coverage from) was dropped: it doubled the merge cost on runs with many cells
12+ // (`istanbul-lib-coverage`'s merge is far slower than `mergeLcov`) for coverage the lcov format
13+ // doesn't carry (branch/function hit counts) — an acceptable trade-off. Merging is still required,
14+ // not concatenation: every matrix cell in a workflow run (each Node.js version, each plugin
15+ // partition) writes its own complete report, so a shared source file's coverage shows up once per
16+ // cell. Concatenating lcov's `SF:` blocks produces a report with duplicate `SF:` sections per file,
17+ // which downstream lcov consumers resolve by keeping only the last block for that file rather than
18+ // summing across blocks — silently discarding most of the coverage every earlier cell had recorded.
19+ // Uploading unmerged across sessions has the same problem one level up: Codecov's own cross-session
20+ // merge has been observed overwriting rather than summing a shared file's coverage when more than one
21+ // session reports it, so lcov is merged down to one report per run before upload instead of relying
22+ // on the backend to reconcile per-session duplicates. `mergeLcov` sums `DA:`/`FNDA:`/`BRDA:` hit
23+ // counts per file across cells, the way `lcov --add-tracefile` does .
2624//
2725// Per-integration/per-area flags were dropped: `.codecov.yml` only gates the separate
2826// `master-coverage` flag (attached to every upload regardless of grouping), so a finer-grained flag
@@ -35,7 +33,6 @@ const ARTIFACT_PREFIX = 'coverage-'
3533
3634const REPORTS = new Map ( [
3735 [ 'lcov.info' , 'lcov' ] ,
38- [ 'coverage-final.json' , 'json' ] ,
3936] )
4037
4138/**
@@ -231,44 +228,21 @@ function mergeLcov (reportPaths) {
231228}
232229
233230/**
234- * Sum per-statement/branch/function hit counts across every cell's istanbul JSON report, so a
235- * source file exercised by more than one cell keeps every cell's coverage instead of only the last
236- * report merged for that file.
237- *
238- * @param {string[] } reportPaths
239- * @returns {object }
240- */
241- function mergeCoverageJson ( reportPaths ) {
242- const map = istanbulLibCoverage . createCoverageMap ( { } )
243- for ( const reportPath of reportPaths ) {
244- map . merge ( JSON . parse ( readFileSync ( reportPath , 'utf8' ) ) )
245- }
246- return map . toJSON ( )
247- }
248-
249- /**
250- * Merge a single workflow run's downloaded coverage reports into one lcov file and one istanbul
251- * JSON file for upload.
231+ * Merge a single workflow run's downloaded coverage reports into one lcov file for upload.
252232 *
253233 * @param {string|number } runId
254234 * @param {string } [inputDir]
255235 * @param {string } [outputDir]
256- * @param {boolean } [skipJson] Skip merging the istanbul JSON reports — only Codecov reads them
257- * (see `upload-coverage.mjs`), and `istanbul-lib-coverage`'s merge is far slower than `mergeLcov`
258- * on a run with many cells, so a run whose Codecov upload already succeeded in a previous job
259- * attempt can skip this merge entirely instead of paying for it only to discard the result.
260- * @returns {{ lcovDir: string|null, jsonDir: string|null } } Directories containing the merged
261- * `lcov.info` and `coverage-final.json`, each null if the run produced no report in that format
262- * (or, for `jsonDir`, if `skipJson` was set).
236+ * @returns {{ lcovDir: string|null } } Directory containing the merged `lcov.info`, null if the run
237+ * produced no coverage report.
263238 */
264- function mergeRunCoverage ( runId , inputDir = INPUT_DIR , outputDir = OUTPUT_DIR , skipJson = false ) {
239+ function mergeRunCoverage ( runId , inputDir = INPUT_DIR , outputDir = OUTPUT_DIR ) {
265240 const files = collectCoverageFiles ( join ( inputDir , String ( runId ) ) , [ ] , { runId : String ( runId ) } )
266- if ( files . length === 0 ) return { lcovDir : null , jsonDir : null }
241+ if ( files . length === 0 ) return { lcovDir : null }
267242
268243 const { reportsByArtifact, artifacts } = planCoverageGroups ( files )
269244 const reports = artifacts . flatMap ( artifact => reportsByArtifact . get ( artifact ) )
270245 const lcovReportPaths = reports . filter ( r => r . format === 'lcov' ) . map ( r => r . reportPath )
271- const jsonReportPaths = skipJson ? [ ] : reports . filter ( r => r . format === 'json' ) . map ( r => r . reportPath )
272246
273247 let lcovDir = null
274248 if ( lcovReportPaths . length > 0 ) {
@@ -277,14 +251,7 @@ function mergeRunCoverage (runId, inputDir = INPUT_DIR, outputDir = OUTPUT_DIR,
277251 writeFileSync ( join ( lcovDir , 'lcov.info' ) , mergeLcov ( lcovReportPaths ) )
278252 }
279253
280- let jsonDir = null
281- if ( jsonReportPaths . length > 0 ) {
282- jsonDir = join ( outputDir , String ( runId ) , 'json' )
283- mkdirSync ( jsonDir , { recursive : true } )
284- writeFileSync ( join ( jsonDir , 'coverage-final.json' ) , JSON . stringify ( mergeCoverageJson ( jsonReportPaths ) ) )
285- }
286-
287- return { lcovDir, jsonDir }
254+ return { lcovDir }
288255}
289256
290- export { OUTPUT_DIR , mergeCoverageJson , mergeLcov , mergeRunCoverage , planCoverageGroups }
257+ export { OUTPUT_DIR , mergeLcov , mergeRunCoverage , planCoverageGroups }
0 commit comments