lib: make coverage work for Node.js
PR-URL: https://github.com/nodejs/node/pull/23941 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yang Guo <yangguo@chromium.org>
This commit is contained in:
parent
2ea70eae73
commit
616fac9169
@ -362,6 +362,12 @@
|
|||||||
NativeModule._cache[this.id] = this;
|
NativeModule._cache[this.id] = this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// coverage must be turned on early, so that we can collect
|
||||||
|
// it for Node.js' own internal libraries.
|
||||||
|
if (process.env.NODE_V8_COVERAGE) {
|
||||||
|
NativeModule.require('internal/process/coverage').setup();
|
||||||
|
}
|
||||||
|
|
||||||
// This will be passed to the bootstrapNodeJSCore function in
|
// This will be passed to the bootstrapNodeJSCore function in
|
||||||
// bootstrap/node.js.
|
// bootstrap/node.js.
|
||||||
return loaderExports;
|
return loaderExports;
|
||||||
|
@ -103,9 +103,7 @@
|
|||||||
NativeModule.require('internal/process/write-coverage').setup();
|
NativeModule.require('internal/process/write-coverage').setup();
|
||||||
|
|
||||||
if (process.env.NODE_V8_COVERAGE) {
|
if (process.env.NODE_V8_COVERAGE) {
|
||||||
const { resolve } = NativeModule.require('path');
|
NativeModule.require('internal/process/coverage').setupExitHooks();
|
||||||
process.env.NODE_V8_COVERAGE = resolve(process.env.NODE_V8_COVERAGE);
|
|
||||||
NativeModule.require('internal/process/coverage').setup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.config.variables.v8_enable_inspector) {
|
if (process.config.variables.v8_enable_inspector) {
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const path = require('path');
|
let coverageConnection = null;
|
||||||
const { mkdirSync, writeFileSync } = require('fs');
|
let coverageDirectory;
|
||||||
const hasInspector = process.config.variables.v8_enable_inspector === 1;
|
|
||||||
let inspector = null;
|
|
||||||
if (hasInspector) inspector = require('inspector');
|
|
||||||
|
|
||||||
let session;
|
|
||||||
|
|
||||||
function writeCoverage() {
|
function writeCoverage() {
|
||||||
if (!session) {
|
if (!coverageConnection && coverageDirectory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { join } = require('path');
|
||||||
|
const { mkdirSync, writeFileSync } = require('fs');
|
||||||
const { threadId } = require('internal/worker');
|
const { threadId } = require('internal/worker');
|
||||||
|
|
||||||
const filename = `coverage-${process.pid}-${Date.now()}-${threadId}.json`;
|
const filename = `coverage-${process.pid}-${Date.now()}-${threadId}.json`;
|
||||||
try {
|
try {
|
||||||
// TODO(bcoe): switch to mkdirp once #22302 is addressed.
|
mkdirSync(coverageDirectory, { recursive: true });
|
||||||
mkdirSync(process.env.NODE_V8_COVERAGE);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code !== 'EEXIST') {
|
if (err.code !== 'EEXIST') {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -25,41 +21,73 @@ function writeCoverage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = path.join(process.env.NODE_V8_COVERAGE, filename);
|
const target = join(coverageDirectory, filename);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.post('Profiler.takePreciseCoverage', (err, coverageInfo) => {
|
disableAllAsyncHooks();
|
||||||
if (err) return console.error(err);
|
let msg;
|
||||||
try {
|
coverageConnection._coverageCallback = function(_msg) {
|
||||||
writeFileSync(target, JSON.stringify(coverageInfo));
|
msg = _msg;
|
||||||
} catch (err) {
|
};
|
||||||
console.error(err);
|
coverageConnection.dispatch(JSON.stringify({
|
||||||
}
|
id: 3,
|
||||||
});
|
method: 'Profiler.takePreciseCoverage'
|
||||||
|
}));
|
||||||
|
const coverageInfo = JSON.parse(msg).result;
|
||||||
|
writeFileSync(target, JSON.stringify(coverageInfo));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
} finally {
|
} finally {
|
||||||
session.disconnect();
|
coverageConnection.disconnect();
|
||||||
session = null;
|
coverageConnection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function disableAllAsyncHooks() {
|
||||||
|
const { getHookArrays } = require('internal/async_hooks');
|
||||||
|
const [hooks_array] = getHookArrays();
|
||||||
|
hooks_array.forEach((hook) => { hook.disable(); });
|
||||||
|
}
|
||||||
|
|
||||||
exports.writeCoverage = writeCoverage;
|
exports.writeCoverage = writeCoverage;
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
if (!hasInspector) {
|
const { Connection } = process.binding('inspector');
|
||||||
console.warn('coverage currently only supported in main thread');
|
if (!Connection) {
|
||||||
|
console.warn('inspector not enabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session = new inspector.Session();
|
coverageConnection = new Connection((res) => {
|
||||||
session.connect();
|
if (coverageConnection._coverageCallback) {
|
||||||
session.post('Profiler.enable');
|
coverageConnection._coverageCallback(res);
|
||||||
session.post('Profiler.startPreciseCoverage', { callCount: true,
|
}
|
||||||
detailed: true });
|
});
|
||||||
|
coverageConnection.dispatch(JSON.stringify({
|
||||||
|
id: 1,
|
||||||
|
method: 'Profiler.enable'
|
||||||
|
}));
|
||||||
|
coverageConnection.dispatch(JSON.stringify({
|
||||||
|
id: 2,
|
||||||
|
method: 'Profiler.startPreciseCoverage',
|
||||||
|
params: {
|
||||||
|
callCount: true,
|
||||||
|
detailed: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { resolve } = require('path');
|
||||||
|
coverageDirectory = process.env.NODE_V8_COVERAGE =
|
||||||
|
resolve(process.env.NODE_V8_COVERAGE);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.setup = setup;
|
||||||
|
|
||||||
|
function setupExitHooks() {
|
||||||
const reallyReallyExit = process.reallyExit;
|
const reallyReallyExit = process.reallyExit;
|
||||||
|
|
||||||
process.reallyExit = function(code) {
|
process.reallyExit = function(code) {
|
||||||
writeCoverage();
|
writeCoverage();
|
||||||
reallyReallyExit(code);
|
reallyReallyExit(code);
|
||||||
@ -68,4 +96,4 @@ function setup() {
|
|||||||
process.on('exit', writeCoverage);
|
process.on('exit', writeCoverage);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.setup = setup;
|
exports.setupExitHooks = setupExitHooks;
|
||||||
|
11
test/fixtures/v8-coverage/async-hooks.js
vendored
Normal file
11
test/fixtures/v8-coverage/async-hooks.js
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const async_hooks = require('async_hooks');
|
||||||
|
const common = require('../../common');
|
||||||
|
|
||||||
|
const hook = async_hooks.createHook({
|
||||||
|
init: common.mustNotCall(),
|
||||||
|
before: common.mustNotCall(),
|
||||||
|
after: common.mustNotCall(),
|
||||||
|
destroy: common.mustNotCall()
|
||||||
|
});
|
||||||
|
|
||||||
|
hook.enable();
|
@ -7,17 +7,38 @@ common.skipIfInspectorDisabled();
|
|||||||
const { validateSnapshotNodes } = require('../common/heap');
|
const { validateSnapshotNodes } = require('../common/heap');
|
||||||
const inspector = require('inspector');
|
const inspector = require('inspector');
|
||||||
|
|
||||||
const session = new inspector.Session();
|
const snapshotNode = {
|
||||||
validateSnapshotNodes('Node / JSBindingsConnection', []);
|
children: [
|
||||||
session.connect();
|
{ node_name: 'Node / InspectorSession', edge_name: 'session' }
|
||||||
validateSnapshotNodes('Node / JSBindingsConnection', [
|
]
|
||||||
{
|
};
|
||||||
children: [
|
|
||||||
{ node_name: 'Node / InspectorSession', edge_name: 'session' },
|
// starts with no JSBindingsConnection (or 1 if coverage enabled).
|
||||||
{ node_name: 'Connection', edge_name: 'wrapped' },
|
{
|
||||||
(edge) => edge.name === 'callback' &&
|
const expected = [];
|
||||||
(edge.to.type === undefined || // embedded graph
|
if (process.env.NODE_V8_COVERAGE) {
|
||||||
edge.to.type === 'closure') // snapshot
|
expected.push(snapshotNode);
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]);
|
validateSnapshotNodes('Node / JSBindingsConnection', expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSBindingsConnection should be added.
|
||||||
|
{
|
||||||
|
const session = new inspector.Session();
|
||||||
|
session.connect();
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{ node_name: 'Node / InspectorSession', edge_name: 'session' },
|
||||||
|
{ node_name: 'Connection', edge_name: 'wrapped' },
|
||||||
|
(edge) => edge.name === 'callback' &&
|
||||||
|
(edge.to.type === undefined || // embedded graph
|
||||||
|
edge.to.type === 'closure') // snapshot
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (process.env.NODE_V8_COVERAGE) {
|
||||||
|
expected.push(snapshotNode);
|
||||||
|
}
|
||||||
|
validateSnapshotNodes('Node / JSBindingsConnection', expected);
|
||||||
|
}
|
||||||
|
@ -108,6 +108,20 @@ function nextdir() {
|
|||||||
assert.strictEqual(fixtureCoverage, undefined);
|
assert.strictEqual(fixtureCoverage, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disables async hooks before writing coverage.
|
||||||
|
{
|
||||||
|
const coverageDirectory = path.join(tmpdir.path, nextdir());
|
||||||
|
const output = spawnSync(process.execPath, [
|
||||||
|
require.resolve('../fixtures/v8-coverage/async-hooks')
|
||||||
|
], { env: { ...process.env, NODE_V8_COVERAGE: coverageDirectory } });
|
||||||
|
assert.strictEqual(output.status, 0);
|
||||||
|
const fixtureCoverage = getFixtureCoverage('async-hooks.js',
|
||||||
|
coverageDirectory);
|
||||||
|
assert.ok(fixtureCoverage);
|
||||||
|
// first branch executed.
|
||||||
|
assert.strictEqual(fixtureCoverage.functions[1].ranges[0].count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// extracts the coverage object for a given fixture name.
|
// extracts the coverage object for a given fixture name.
|
||||||
function getFixtureCoverage(fixtureFile, coverageDirectory) {
|
function getFixtureCoverage(fixtureFile, coverageDirectory) {
|
||||||
const coverageFiles = fs.readdirSync(coverageDirectory);
|
const coverageFiles = fs.readdirSync(coverageDirectory);
|
||||||
|
@ -20,7 +20,10 @@ assert(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const args = ['--inspect', '-e', script];
|
const args = ['--inspect', '-e', script];
|
||||||
const child = spawn(process.execPath, args, { stdio: 'inherit' });
|
const child = spawn(process.execPath, args, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: { ...process.env, NODE_V8_COVERAGE: '' }
|
||||||
|
});
|
||||||
child.on('exit', (code, signal) => {
|
child.on('exit', (code, signal) => {
|
||||||
process.exit(code || signal);
|
process.exit(code || signal);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user