Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/internal/test_runner/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ function setupCoverage(options) {
return null;
}

internalBinding('profiler').startCoverage();

// Ensure that NODE_V8_COVERAGE is set so that coverage can propagate to
// child processes.
process.env.NODE_V8_COVERAGE = coverageDirectory;
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,8 @@ function run(options = kEmptyObject) {
coverageExcludeGlobs = [coverageExcludeGlobs];
}
validateStringArray(coverageExcludeGlobs, 'options.coverageExcludeGlobs');
} else if (coverage) {
coverageExcludeGlobs = [kDefaultPattern];
}
if (coverageIncludeGlobs != null) {
if (!ArrayIsArray(coverageIncludeGlobs)) {
Expand Down
26 changes: 26 additions & 0 deletions src/inspector_profiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,30 @@ static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
env->set_source_map_cache_getter(args[0].As<Function>());
}

static void StartCoverage(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Debug(env,
DebugCategory::INSPECTOR_PROFILER,
"StartCoverage, connection %s nullptr\n",
env->coverage_connection() == nullptr ? "==" : "!=");

if (env->coverage_connection() != nullptr) {
return;
}

// The parent of `--test --test-isolation=process` intentionally has no
// inspector (see Environment::should_create_inspector); workers handle
// coverage themselves. Without an inspector, V8CoverageConnection would
// get a null session and crash on the first DispatchMessage.
if (!env->should_create_inspector()) {
return;
}

env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
env->coverage_connection()->Start();
}

static void TakeCoverage(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
V8CoverageConnection* connection = env->coverage_connection();
Expand Down Expand Up @@ -601,6 +625,7 @@ static void Initialize(Local<Object> target,
SetMethod(context, target, "setCoverageDirectory", SetCoverageDirectory);
SetMethod(
context, target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
SetMethod(context, target, "startCoverage", StartCoverage);
SetMethod(context, target, "takeCoverage", TakeCoverage);
SetMethod(context, target, "stopCoverage", StopCoverage);
SetMethod(context, target, "endCoverage", EndCoverage);
Expand All @@ -609,6 +634,7 @@ static void Initialize(Local<Object> target,
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(SetCoverageDirectory);
registry->Register(SetSourceMapCacheGetter);
registry->Register(StartCoverage);
registry->Register(TakeCoverage);
registry->Register(StopCoverage);
registry->Register(EndCoverage);
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/test-runner/coverage-isolation-none/src/foo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function add(a, b) {
return a + b;
}

export function sub(a, b) {
return a - b;
}

export function unused() {
return 'unused';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import test from 'node:test';
import assert from 'node:assert';
import { add, sub } from '../src/foo.mjs';

test('add', () => {
assert.strictEqual(add(2, 3), 5);
});

test('sub', () => {
assert.strictEqual(sub(5, 3), 2);
});
92 changes: 92 additions & 0 deletions test/parallel/test-runner-coverage-isolation-none-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as common from '../common/index.mjs';
import { before, describe, it, run } from 'node:test';
import assert from 'node:assert';
import { spawnSync } from 'node:child_process';
import { cp, writeFile } from 'node:fs/promises';
import { join, sep } from 'node:path';
import tmpdir from '../common/tmpdir.js';
import fixtures from '../common/fixtures.js';

const skipIfNoInspector = {
skip: !process.features.inspector ? 'inspector disabled' : false,
};

tmpdir.refresh();

async function setupFixtures() {
const fixtureDir = fixtures.path('test-runner', 'coverage-isolation-none');
await cp(fixtureDir, tmpdir.path, { recursive: true });
}

describe('run() coverage with isolation: none', skipIfNoInspector, () => {
before(async () => {
await setupFixtures();
});

for (const isolation of ['none', 'process']) {
it(`reports src coverage and excludes test files by default (isolation=${isolation})`, async () => {
const stream = run({
files: [join(tmpdir.path, 'tests', 'foo.test.mjs')],
coverage: true,
isolation,
cwd: tmpdir.path,
});
stream.on('test:fail', common.mustNotCall());

let summary;
stream.on('test:coverage', common.mustCall(({ summary: s }) => {
summary = s;
}));
// eslint-disable-next-line no-unused-vars
for await (const _ of stream);

assert.ok(summary, 'test:coverage event must fire');
const paths = summary.files.map((f) => f.path);
assert.ok(
paths.length > 0,
`coverage files must be reported (isolation=${isolation}); got ${JSON.stringify(paths)}`,
);
assert.ok(
paths.some((p) => p.endsWith(`src${sep}foo.mjs`)),
`expected src/foo.mjs to be present (isolation=${isolation}); got ${JSON.stringify(paths)}`,
);
assert.ok(
paths.every((p) => !p.endsWith('foo.test.mjs')),
`expected foo.test.mjs to be excluded by default (isolation=${isolation}); got ${JSON.stringify(paths)}`,
);
});
}

it('is idempotent when --experimental-test-coverage is also passed', async () => {
const runnerPath = join(tmpdir.path, 'runner.mjs');
await writeFile(runnerPath, `\
import { run } from 'node:test';
import { join } from 'node:path';

const stream = run({
files: [join(import.meta.dirname, 'tests', 'foo.test.mjs')],
coverage: true,
isolation: 'none',
cwd: import.meta.dirname,
});
stream.on('test:fail', () => process.exit(10));
let summary;
stream.on('test:coverage', (event) => { summary = event.summary; });
for await (const _ of stream);
if (!summary || summary.files.length === 0) process.exit(11);
const hasSrc = summary.files.some((f) => f.path.endsWith('foo.mjs') && !f.path.endsWith('foo.test.mjs'));
const hasTest = summary.files.some((f) => f.path.endsWith('foo.test.mjs'));
if (!hasSrc) process.exit(12);
if (hasTest) process.exit(13);
`);
const result = spawnSync(process.execPath, [
'--experimental-test-coverage',
runnerPath,
], { cwd: tmpdir.path });
assert.strictEqual(
result.status,
0,
`exited with ${result.status}\nstderr: ${result.stderr}\nstdout: ${result.stdout}`,
);
});
});
10 changes: 9 additions & 1 deletion test/parallel/test-runner-run-coverage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ describe('require(\'node:test\').run coverage settings', { concurrency: true },
const stream = run({
files,
coverage: true,
coverageExcludeGlobs: '!test/**',
coverageIncludeGlobs: ['test/fixtures/test-runner/coverage.js', 'test/*/v8-coverage/throw.js'],
});
stream.on('test:fail', common.mustNotCall());
Expand Down Expand Up @@ -157,7 +158,14 @@ describe('require(\'node:test\').run coverage settings', { concurrency: true },
const thresholdErrors = [];
const originalExitCode = process.exitCode;
assert.notStrictEqual(originalExitCode, 1);
const stream = run({ files, coverage: true, lineCoverage: 99, branchCoverage: 99, functionCoverage: 99 });
const stream = run({
files,
coverage: true,
coverageExcludeGlobs: '!test/**',
lineCoverage: 99,
branchCoverage: 99,
functionCoverage: 99,
});
stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(1));
stream.on('test:diagnostic', ({ message }) => {
Expand Down
Loading