Skip to content

coverage-v8: conditional await produces a negative implicit-else branch count #11690

Closed

Description

@mrbobbytables

Describe the bug

With the V8 coverage provider, a conditionally reached await before an if without an explicit else produces a negative implicit-else branch count. The reduced test exercises both arms and passes, but branch coverage is 75% and LCOV contains BRDA:3,1,1,-2.

Expected: the if branches have counts [4, 1], and all four branches in the file are covered.
Actual: the if branches have counts [4, -2], despite the final assertion proving that the implicit else ran.

This was reduced from cncf/prow-github-actions#511 and reproduced for cncf/prow-github-actions#550. I searched the tracker: #11476 and #11541 concern mixed native/transformed imports, whereas this reproduction has only one source import in one test file. #9447 concerns an actually untested implicit else; this test exercises it.

Reproduction

In an empty directory, run npm init -y, then:

npm install --save-dev vitest@5.0.4 @vitest/coverage-v8@5.0.4 vite@8.3.4
mkdir .repro-550

Create these three files:

.repro-550/gate.ts:

export async function gate(a: boolean, b: boolean): Promise<string> {
  const ok = a || (await Promise.resolve(b))
  if (ok)
    return 'yes'
  return 'no'
}

.repro-550/gate.test.ts:

import { expect, test } from 'vitest'
import { gate } from './gate'

test('both arms execute after short-circuiting and awaiting', async () => {
  for (let i = 0; i < 3; i++)
    expect(await gate(true, false)).toBe('yes')
  expect(await gate(false, true)).toBe('yes')
  expect(await gate(false, false)).toBe('no')
})

.repro-550/vitest.config.mjs:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    include: ['.repro-550/gate.test.ts'],
    coverage: {
      provider: 'v8',
      include: ['.repro-550/gate.ts'],
      reporter: ['text', 'lcovonly', 'json'],
      reportsDirectory: '.repro-550/coverage',
    },
  },
})

Run:

npx vitest run --coverage --config .repro-550/vitest.config.mjs
cat .repro-550/coverage/lcov.info

Observed: one passing test; 100% statements/functions/lines, 75% branches.
Relevant LCOV records:

FNDA:5,gate
DA:2,5
DA:3,2
DA:4,4
DA:5,1
BRDA:2,0,0,5
BRDA:2,0,1,2
BRDA:3,1,0,4
BRDA:3,1,1,-2
BRF:4
BRH:3

The counts suggest that the range enclosing the if counts only the two calls that resumed from the conditional await, while the then-block counts all four executions. Subtracting the then-block count from that enclosing count would yield 2 - 4 = -2. This is a hypothesis about the conversion, not a confirmed V8 root cause.

System Info

OS: Linux x64, kernel 7.2.5-200.fc44.x86_64
Node: v24.22.0
V8: 13.6.233.17-node.57
npm: 11.19.1
vitest: 5.0.4
@vitest/coverage-v8: 5.0.4
vite: 8.3.4
ast-v8-to-istanbul: 1.0.7

Used Package Manager

npm

— hive: backend=copilot model=gpt-6.1-sol


🐝 Hive Agent: contributor | SHA: unknown

Activity

  1. AriPerkkio commented on Oct 21, 2026

    @AriPerkkio
    Member

    This is a V8 bug. Could you file this to them? I've listed couple of more reproduction setups in AriPerkkio/ast-v8-to-istanbul#207.

  2. mrbobbytables commented on Oct 21, 2026

    @mrbobbytables
    Author

    Thanks! Will do

  3. AriPerkkio commented on Oct 21, 2026

    @AriPerkkio
    Member

    Great, thanks! Their issue tracker is at https://crbug.com/v8/new. Please post link to the issue here too if possible. 🙏

  4. mrbobbytables commented on Oct 21, 2026

    @mrbobbytables
    Author
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

    feat: coverageIssues and PRs related to the coverage featureupstream

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions