Skip to content

[quality] the bundle e2e suite's coverage of src/ is never captured — runBundle.ts drops NODE_V8_COVERAGE and dist/ has no source map #235

Closed

Description

@hivecommons-hive

Finding

__tests__/bundle/bundle.test.ts is the repository's end-to-end suite: it starts a fake GitHub API (fakeGithub.ts) and runs the committed dist/index.js against it in a child process. It is the only place the shipped bundle is exercised, yet none of its coverage reaches the coverage report:

  • __tests__/bundle/runBundle.ts builds the child's environment from scratch (PATH, HOME, RUNNER_TEMP, GITHUB_*, inputs). NODE_V8_COVERAGE, which @vitest/coverage-v8 sets on the worker, is not propagated, so the child writes no coverage.
  • Even if it were propagated, npm run pack (ncc build) emits no source map, so V8 hits on dist/index.js could not be mapped back to src/**/*.ts.

Consequence: npx vitest run --coverage on main @ c48bd6d reports 99.83% lines / 98.35% branches, but that is unit-only evidence. Whether a path is exercised end-to-end is unknowable from the report, and unit and e2e coverage cannot be combined at line granularity. Every coverage-gap finding on this repository must therefore be stated as unit-only, and the e2e suite's regressions in coverage are invisible.

Recommendation

  • runBundle.ts: forward NODE_V8_COVERAGE (and VITEST*-independent) into the child env when set, so the child emits V8 coverage into the same directory vitest collects
  • package.json pack: build with ncc build --source-map (or -s) so v8 coverage on dist/index.js maps to src/; decide whether the map is committed alongside dist/index.js or built only for the coverage run
  • vitest.config.mjs coverage.include continues to be src/**/*.ts; confirm dist/index.js hits are remapped rather than reported as an untracked file

If the maintainers prefer not to change the pack step, the alternative is to record in vitest.config.mjs / docs/ that the coverage figure is unit-only and that __tests__/bundle is excluded, so readers do not mistake 99.8% for end-to-end coverage.

Priority

  • Impact: medium — the only e2e suite has no measurable coverage; unit and e2e evidence cannot be merged
  • Effort: medium

Filed by quality agent (hold-gated mode)


🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 924fa8d

— hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

Activity

  1. hivecommons-hive commented on Sep 30, 2026

    @hivecommons-hive
    ContributorAuthor

    Re-verified on main @ c48bd6d with Node v26.10.0 and @vitest/coverage-v8 5.0.1. Two corrections to the finding and a concrete recipe.

    Correction 1 — runBundle.ts does not drop NODE_V8_COVERAGE. Node's child_process re-injects the parent's NODE_V8_COVERAGE into any child whose env object lacks that key, so the from-scratch env in runBundle.ts already propagates it. Verified: parent with NODE_V8_COVERAGE=/tmp/cov spawning node -p process.env.NODE_V8_COVERAGE with env: { PATH } prints /tmp/cov. No change to runBundle.ts is needed; strike that box.

    Correction 2 — the variable is never set on the worker in the first place. vitest 5's v8 provider collects the worker's own coverage through the inspector session, not through NODE_V8_COVERAGE. It only exports the variable (into coverage/tmp/<uuid>) and reads child-process output when coverage.autoAttachSubprocess: true is set. That option is the actual missing switch.

    Proof that the switch works: npx vitest run __tests__/bundle --coverage --coverage.autoAttachSubprocess --coverage.include='dist/**' (thresholds zeroed) reports dist/index.js | 25.59 | 15.62 | 27.81 | 25.87 — the child's hits do reach the report. Without the flag, dist/index.js is absent.

    Side effect the switch has today: with autoAttachSubprocess on, bundle.test.ts › is a syntactically valid bundle fails, because node --check dist/index.js inherits the variable and aborts on Node 26 (TypeError: WeakRef: invalid target in SourceMapCacheMap.set). Fixed by passing an explicit empty value in that one spawn — that is the PR linked from this issue (__tests__/bundle/bundle.test.ts only). With it, the full suite passes both with and without the flag (69 files / 1447 tests, 99.83 | 98.35 | 100 | 99.82).

    Remapping to src/ is harder than ncc -s. npm run pack bundles lib/main.js (tsc output), and tsconfig does not emit source maps, so ncc build -s produces a map whose first-party sources are lib/**/*.js, not src/**/*.ts. Adding --sourceMap to tsc alone does not close the gap either: ncc/webpack does not consume lib/*.js.map without a source-map loader, so the bundle's map still points at lib/. Measured: with tsc --sourceMap + ncc build -s --no-source-map-register, src/ coverage from the bundle suite stays at 1.74% (only the uncovered-file rows). Note also that plain ncc -s prepends require('./sourcemap-register.js') to dist/index.js, which changes the shipped bundle's runtime behaviour; --no-source-map-register avoids that.

    Revised checklist:

    • runBundle.ts forwards NODE_V8_COVERAGE — not needed, Node propagates it
    • __tests__/bundle/bundle.test.ts: node --check spawn sets NODE_V8_COVERAGE: '' (PR open, hold-gated)
    • vitest.config.mjs: coverage.autoAttachSubprocess: true — one line inside the coverage block; deferred until chore(coverage): emit lcovonly and json-summary reporters from vitest.config.mjs #230 (which edits the same block) lands, to avoid two PRs over the same lines
    • build chain that yields a dist/index.js.map whose sources are src/**/*.ts (e.g. inlineSourceMap in tsc plus a source-map-consuming pack step, or bundling src/main.ts directly with a TS-aware ncc invocation) — build-script change, needs a maintainer decision on whether the map is committed next to dist/index.js (the dist drift gate would then cover it) or only produced for the coverage run
    • confirm remapped hits land under the existing coverage.include: ['src/**/*.ts'] rather than as an untracked dist/index.js row

    Until the last two land, every coverage figure from npm run test:coverage is unit-only, as stated in the finding.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: e40ba37

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  2. hivecommons-hive commented on Sep 30, 2026

    @hivecommons-hive
    ContributorAuthor

    /kind cleanup


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: unknown

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  3. added
    kind/cleanupCategorizes issue or PR as related to cleaning up code, process, or technical debt.
    and removed on Sep 30, 2026
  4. added
    🐝 covered by PRHive verified that an open PR references or claims this issue; still actionable until confirmed
    hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmation
    and removed
    🐝 covered by PRHive verified that an open PR references or claims this issue; still actionable until confirmed
    on Sep 30, 2026
  5. hivecommons-hive commented on Oct 2, 2026

    @hivecommons-hive
    ContributorAuthor

    task-list sweep: 0 of 3 items ticked. Not closing yet — outstanding boxes remain.

    Outstanding items:

    • 🔲 runBundle.ts: forward NODE_V8_COVERAGE (and VITEST*-independent) into the child env when set, so the child emits V8 coverage into the same directory vitest collects
    • 🔲 package.json pack: build with ncc build --source-map (or -s) so v8 coverage on dist/index.js maps to src/; decide whether the map is committed alongside dist/index.js or built only for the coverage run
    • 🔲 vitest.config.mjs coverage.include continues to be src/**/*.ts; confirm dist/index.js hits are remapped rather than reported as an untracked file

    Merged PRs referencing this issue so far:

    This comment is edited in place by the task-list sweep on every cycle; it is not duplicated.

  6. hivecommons-hive commented on Oct 3, 2026

    @hivecommons-hive
    ContributorAuthor

    Verified still open on main @ 61577d5. #236 only set NODE_V8_COVERAGE='' on the node --check spawn so the suite survives coverage.autoAttachSubprocess; it does not address any box above: runBundle.ts still builds the child env from scratch without NODE_V8_COVERAGE, pack is still ncc build with no source map, and dist/ contains only index.js. Coverage remains unit-only.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 61577d5

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  7. hivecommons-hive commented on Oct 3, 2026

    @hivecommons-hive
    ContributorAuthor

    Verified recipe (main @ 61577d5, Node v26.10.0, vitest 5.0.1, @vitest/coverage-v8 5.0.1, ncc 0.45.0)

    Correcting the first box of the recommendation: runBundle.ts does not need to forward NODE_V8_COVERAGE. Node's child_process re-injects the parent's NODE_V8_COVERAGE into any child env that lacks the key (this is exactly why #236 had to set it to '' on the node --check spawn). What is missing is on the vitest and pack side only. Three experiments, each measured on the current __tests__/bundle suites:

    config result
    coverage.autoAttachSubprocess: true, include: ['src/**/*.ts', 'dist/**/*.js'], committed dist/index.js (no map) child hits are collected but reported against dist/index.js as one 16 135-line file (25.87 % lines); nothing reaches src/
    same + tsc --sourceMap + ncc build --source-map (current lib/main.js entry) hits remap, but only to lib/**/*.js: ncc does not chain tsc's input maps, so src/ still shows unit-only numbers
    same + ncc build src/main.ts --source-map --no-source-map-register + excludeAfterRemap: true works — every src/**/*.ts file gets e2e line/branch data in the same istanbul map as the unit run

    With the third row, the four __tests__/bundle/*.test.ts files alone (125 tests) cover src/ at 65.39 % lines / 51.11 % branches / 68.65 % functions (1846/2823 lines). Lowest e2e-covered files: issueComment/meow.ts 4.68 %, utils/mergeQueue.ts 30.37 %, labels/fixed.ts 33.33 %, utils/comments.ts 35 %, utils/owners.ts 45.26 %, utils/labeling.ts 49.47 %, utils/config.ts 49.85 %, plugins/tide.ts 55.01 %. These are the first numbers the repository has had for what the shipped bundle actually exercises, and they can be merged with the unit data at line granularity because both land in one coverage map.

    Why this is not a PR from this lane

    The working recipe changes two things that are not test code:

    1. package.json pack must become ncc build src/main.ts --source-map --no-source-map-register (ncc compiles the TypeScript itself; the tsc → lib/ step stays for type-checking only). That rewrites the committed dist/index.js (different emit path plus a trailing //# sourceMappingURL=index.js.map) and introduces a 1.9 MB dist/index.js.map that must be either committed next to the bundle or .gitignored and rebuilt by test:coverage — a release-artifact decision for maintainers.
    2. vitest.config.mjs needs coverage.autoAttachSubprocess: true and coverage.excludeAfterRemap: true. That file is occupied by the held chore(coverage): emit lcovonly and json-summary reporters from vitest.config.mjs #230, and the review there asks that coverage-config changes land together with the consumer that reads them ([quality] test.yml runs vitest coverage but never publishes the report — add lcov/json-summary artifact and job summary #209).

    So this needs a decision before any PR: (a) accept the pack change and the map handling, or (b) keep the shipped bundle as-is and have test:coverage build a coverage-only bundle into an ignored directory with runBundle.ts honouring an override path — at the cost of the e2e suite exercising a non-committed artifact under coverage. If (a) or (b) is chosen, the test-side half (vitest.config.mjs once #230 lands, runBundle.ts) can follow as a hold-gated PR.

    Reproduce row 3: npx tsc && npx ncc build src/main.ts -o dist --source-map --no-source-map-register, then npx vitest run --coverage with the two coverage options above and include: ['src/**/*.ts', 'dist/**/*.js'].


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 61577d5

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  8. added and removed
    hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmation
    on Oct 3, 2026
  9. hivecommons-hive commented on Oct 3, 2026

    @hivecommons-hive
    ContributorAuthor

    Test-side half is up as #274 (hold-gated): npm run test:coverage:e2e builds a source-mapped bundle into the ignored .coverage-bundle/, runBundle.ts honours PROW_BUNDLE_PATH, and a separate vitest.e2e-coverage.config.mjs turns on autoAttachSubprocess + excludeAfterRemap — dist/, pack and vitest.config.mjs untouched. Measured on main @ a270568: src/ at 65.39 % lines (1846/2823) from __tests__/bundle alone, matching the recipe above.

    One finding that changes the last box: the e2e figure cannot be line-merged with the unit run. Running the full suite under the same config gives 93.05 % over 3816 lines — lower than unit-only 100 %/2296 — because vite's transform of src/ and the tsc+webpack map produce different statement maps, so the merge unions them and marks bundle-only statements uncovered. #274 therefore reports e2e separately and documents the limitation in docs/contributing.md.

    Still open here and needing a maintainer: whether the shipped dist/index.js should be built with a map (pack change), and the vitest.config.mjs line once #230 lands.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 0061f4e

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  10. hivecommons-hive commented on Oct 3, 2026

    @hivecommons-hive
    ContributorAuthor

    Correction to the recipe above and to the 65.39 % figure: the source map it produced was wrong.

    ncc build src/main.ts --source-map takes ts-loader's compilerOptions from the nearest tsconfig.json above the entry — the root one, which has no sourceMap. ts-loader then returns no map, and webpack emits an identity line map onto tsc's transpiled JS while attaching the TS file as sourcesContent. Every hit is attributed to the TS line with the same number as the JS line: in tide.ts, the JSDoc block at 443–446 scored 38 hits and tideOnCheckSuite (510) scored 0; source-map-js resolves the bundle's tideOnCheckSuite to tide.ts:395. The 2823-line denominator (vs. 2296 in the unit run) was the same artefact.

    Fixed in #274 @ f62ba00: the coverage bundle is built from __tests__/bundle/coverageEntry/main.ts, whose sibling tsconfig extends the root one with sourceMap: true (root tsconfig, npm run build, npm run pack and dist/ unchanged — setting sourceMap in the root tsconfig would drift dist/index.js with sourceMappingURL comments, which is why it is not done there). Corrected end-to-end figure on main @ a270568: src/ at 75.08 % lines (1724/2296) / 67.52 % branches / 84.16 % functions; the line denominator now matches the unit run, so per-line unit-vs-e2e comparison is possible.

    This also bears on box 2 (pack with --source-map): on its own that flag would produce the same broken map for the shipped bundle. If maintainers ever want a source-mapped dist/, sourceMap: true has to reach ts-loader as well.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: f62ba00

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  11. hivecommons-hive commented on Oct 4, 2026

    @hivecommons-hive
    ContributorAuthor

    Status after #274 merged (2026-10-03): npm run test:coverage:e2e is on main and is now the e2e evidence source for this lane's coverage-gap findings (first use: #297 / #298, ownersLabel.ts 80 → 100 % e2e lines).

    What remains in the task list, and why none of it is a PR from this lane:

    So the remainder needs a human (or an ISSUES_PRS_MERGE agent): decide box 2, tick or strike box 1, and land #230/#209 together for box 3. If the decision is to keep dist/ map-free, the separate test:coverage:e2e run from #274 already covers the measurement need and this issue can close as-is.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: 3fc21f2

    — hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88

  12. mrbobbytables commented on Oct 8, 2026

    @mrbobbytables
    Member

    Resolved on main via the documented-alternative route this issue proposed:

    • test(coverage): add test:coverage:e2e to report the bundle suite's coverage of src/ #274 (32e57b2) added npm run test:coverage:e2e: pack:coverage builds a source-mapped bundle from __tests__/bundle/coverageEntry/ into .coverage-bundle/ (ncc build --source-map), and the bundle suite runs against it via PROW_BUNDLE_PATH with a dedicated vitest.e2e-coverage.config.mjs, so e2e hits are reported against src/.
    • docs/contributing.md documents that test:coverage is unit-only and why the two figures are deliberately reported separately (different transforms, maps don't combine).

    Completion criteria map: coverage propagation + source mapping are handled by the coverage bundle instead of instrumenting the committed dist/index.js, which stays map-free. Closing as completed.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityCreated by Hive for agent-filed issue provenancehive/verified-openkind/cleanupCategorizes issue or PR as related to cleaning up code, process, or technical debt.qualityCreated by Hive for agent-filed issue provenancetestingCreated by Hive for agent-filed issue provenance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions