Skip to content

all: true is ignored — coverage report only includes imported files; CLI c8 --all works on the same data #88

Description

@emersonjds

Summary

When configuring @pokujs/c8 with all: true and src: ['src'], the final coverage report only contains files that were imported by tests. Files inside src/ that were never required at runtime are silently omitted, producing a misleading high-coverage number.

Running the standalone c8 CLI on the very same NODE_V8_COVERAGE directory does include the uncovered files (and reports them as 0%), so the V8 coverage data is fine — the issue seems to be in how the plugin propagates all / src to c8's Report (or in how globs are resolved).

Environment

Node v24.14.0
poku 4.3.0
@pokujs/c8 1.2.0
c8 10.1.3
OS macOS Darwin 25.5.0 (arm64)

Minimal reproduction

repro/
├── package.json
├── poku.config.mjs
└── src/
    ├── covered.ts                        # imported by test
    ├── never-imported.ts                 # NOT imported anywhere
    └── __tests__/covered.test.ts

package.json:

{
  "type": "module",
  "scripts": { "test": "poku --coverage" },
  "devDependencies": {
    "poku": "4.3.0",
    "@pokujs/c8": "1.2.0",
    "c8": "10.1.3",
    "tsx": "^4.21.0"
  }
}

poku.config.mjs:

import { defineConfig } from 'poku';
import { coverage } from '@pokujs/c8';

export default defineConfig({
  parallel: true,
  concurrency: 4,
  include: ['src/**/__tests__'],
  plugins: [
    coverage({
      requireFlag: true,
      all: true,
      src: ['src'],
      include: ['src/**/*.ts', 'src/**/*.tsx'],
      exclude: ['src/**/__tests__/**', 'src/**/*.d.ts'],
      extension: ['.ts', '.tsx'],
      reporter: ['text'],
    }),
  ],
});

src/covered.ts:

export const sum = (a: number, b: number) => a + b;

src/never-imported.ts:

export const noop = () => null;
export const greet = (name: string) => `hi ${name}`;

src/__tests__/covered.test.ts:

import { test, assert } from 'poku';
import { sum } from '../covered';

await test('sum', () => assert.strictEqual(sum(2, 3), 5));

Steps

npx poku --coverage

Actual output (incorrect)

File         | % Stmts | % Branch | % Funcs | % Lines
covered.ts   |  100    |  100     |  100    |  100

never-imported.ts is missing from the report despite all: true and src: ['src'].

Expected output

never-imported.ts should appear with 0% coverage (this is the documented behavior of c8 --all).

Workaround proving the V8 data is correct

Capture NODE_V8_COVERAGE to a fixed dir and run the c8 CLI on the same data:

rm -rf /tmp/cov && mkdir /tmp/cov
NODE_V8_COVERAGE=/tmp/cov npx poku
npx c8 report \
  --reporter=text-summary \
  --include='src/**/*.ts' --include='src/**/*.tsx' \
  --exclude='src/**/__tests__/**' \
  --extension=.ts --extension=.tsx \
  --all --src=src \
  --temp-directory=/tmp/cov

Output (correct, includes uncovered files):

=============================== Coverage summary ===============================
Statements   : 33.33% (3/9)
Branches     : 100% (0/0)
Functions    : 33.33% (1/3)
Lines        : 33.33% (3/9)
================================================================================

Possible cause

Looking at node_modules/@pokujs/c8/lib/index.js, the constructor call to c8's Report propagates all and src:

const report = Report({
  tempDirectory: tempDir,
  reportsDirectory,
  reporter,
  include: options.include ?? [],
  exclude: options.exclude,
  extension: options.extension,
  all: options.all ?? false,
  src: options.src,
  resolve: '',
  // ...
});

So all: true should reach Report. Inside c8, _includeUncoveredFiles (node_modules/c8/lib/report.js:400) does this.exclude.globSync(workingDir).forEach(...) and filters by extension.includes(ext). The fact that the standalone CLI works on identical data points to one of:

  1. The include glob format passed by the plugin (['src/**/*.ts', 'src/**/*.tsx']) is interpreted differently than the equivalent --include flags by test-exclude once it goes through Report constructed programmatically.
  2. The extension array is being passed in a form the plugin path doesn't honor (e.g., the CLI normalizes --extension=.ts differently than passing ['.ts'] programmatically).
  3. The tempDirectory ends up split across multiple per-process dirs and only the main process's dir is read by the plugin's Report, while c8 report --temp-directory=/tmp/cov reads everything.

I'd start by reproducing with DEBUG=c8 npx poku --coverage and logging tempDir, options.all, options.src, and the result of the internal Exclude.globSync(workingDir) call.

Impact

Coverage thresholds via the plugin are unreliable: a project with 5% real coverage shows 98%+ because only loaded modules count. CI gates can't depend on the plugin output today; users have to run c8 report --all ... as a post-step (which defeats the purpose of having a plugin).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions