Skip to content
This repository has been archived by the owner on Feb 1, 2022. It is now read-only.

Commit

Permalink
feat: Support for debugging a pid
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Krems committed Mar 16, 2017
1 parent 759f187 commit b64abe8
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 53 deletions.
122 changes: 69 additions & 53 deletions lib/_inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,53 +53,6 @@ function getDefaultPort() {
return 9229;
}

function runScript(script, scriptArgs, inspectPort, childPrint) {
return new Promise((resolve) => {
const args = [
'--inspect',
`--debug-brk=${inspectPort}`,
].concat([script], scriptArgs);
const child = spawn(process.execPath, args);
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', childPrint);
child.stderr.on('data', childPrint);

let output = '';
function waitForListenHint(text) {
output += text;
if (/chrome-devtools:\/\//.test(output)) {
child.stderr.removeListener('data', waitForListenHint);
resolve(child);
}
}

child.stderr.on('data', waitForListenHint);
});
}

function createAgentProxy(domain, client) {
const agent = new EventEmitter();
agent.then = (...args) => {
// TODO: potentially fetch the protocol and pretty-print it here.
const descriptor = {
[util.inspect.custom](depth, { stylize }) {
return stylize(`[Agent ${domain}]`, 'special');
},
};
return Promise.resolve(descriptor).then(...args);
};

return new Proxy(agent, {
get(target, name) {
if (name in target) return target[name];
return function callVirtualMethod(params) {
return client.callMethod(`${domain}.${name}`, params);
};
},
});
}

function portIsFree(host, port, timeout = 2000) {
const retryDelay = 150;
let didTimeOut = false;
Expand Down Expand Up @@ -139,6 +92,56 @@ function portIsFree(host, port, timeout = 2000) {
});
}

function runScript(script, scriptArgs, inspectHost, inspectPort, childPrint) {
return portIsFree(inspectHost, inspectPort)
.then(() => {
return new Promise((resolve) => {
const args = [
'--inspect',
`--debug-brk=${inspectPort}`,
].concat([script], scriptArgs);
const child = spawn(process.execPath, args);
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', childPrint);
child.stderr.on('data', childPrint);

let output = '';
function waitForListenHint(text) {
output += text;
if (/chrome-devtools:\/\//.test(output)) {
child.stderr.removeListener('data', waitForListenHint);
resolve(child);
}
}

child.stderr.on('data', waitForListenHint);
});
});
}

function createAgentProxy(domain, client) {
const agent = new EventEmitter();
agent.then = (...args) => {
// TODO: potentially fetch the protocol and pretty-print it here.
const descriptor = {
[util.inspect.custom](depth, { stylize }) {
return stylize(`[Agent ${domain}]`, 'special');
},
};
return Promise.resolve(descriptor).then(...args);
};

return new Proxy(agent, {
get(target, name) {
if (name in target) return target[name];
return function callVirtualMethod(params) {
return client.callMethod(`${domain}.${name}`, params);
};
},
});
}

class NodeInspector {
constructor(options, stdin, stdout) {
this.options = options;
Expand All @@ -152,6 +155,7 @@ class NodeInspector {
this._runScript = runScript.bind(null,
options.script,
options.scriptArgs,
options.host,
options.port,
this.childPrint.bind(this));
} else {
Expand Down Expand Up @@ -220,12 +224,7 @@ class NodeInspector {
this.killChild();
const { host, port } = this.options;

const runOncePortIsFree = () => {
return portIsFree(host, port)
.then(() => this._runScript());
};

return runOncePortIsFree().then((child) => {
return this._runScript().then((child) => {
this.child = child;

let connectionAttempts = 0;
Expand Down Expand Up @@ -308,6 +307,22 @@ function parseArgv([target, ...args]) {
port = parseInt(portMatch[1], 10);
script = args[0];
scriptArgs = args.slice(1);
} else if (args.length === 1 && /^\d+$/.test(args[0]) && target === '-p') {
// Start debugger against a given pid
const pid = parseInt(args[0], 10);
try {
process._debugProcess(pid);
} catch (e) {
if (e.code === 'ESRCH') {
/* eslint-disable no-console */
console.error(`Target process: ${pid} doesn't exist.`);
/* eslint-enable no-console */
process.exit(1);
}
throw e;
}
script = null;
isRemote = true;
}

return {
Expand All @@ -327,6 +342,7 @@ function startInspect(argv = process.argv.slice(2),

console.error(`Usage: ${invokedAs} script.js`);
console.error(` ${invokedAs} <host>:<port>`);
console.error(` ${invokedAs} -p <pid>`);
process.exit(1);
}

Expand Down
52 changes: 52 additions & 0 deletions test/cli/pid.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';
const { spawn } = require('child_process');
const Path = require('path');

const { test } = require('tap');

const startCLI = require('./start-cli');

function launchTarget(...args) {
const childProc = spawn(process.execPath, args);
return Promise.resolve(childProc);
}

// process.debugPort is our proxy for "the version of node used to run this
// test suite doesn't support SIGUSR1 for enabling --inspect for a process".
const defaultsToOldProtocol = process.debugPort === 5858;

test('examples/alive.js', { skip: defaultsToOldProtocol }, (t) => {
const script = Path.join('examples', 'alive.js');
let cli = null;
let target = null;

function cleanup(error) {
if (cli) {
cli.quit();
cli = null;
}
if (target) {
target.kill();
target = null;
}
if (error) throw error;
}

return launchTarget(script)
.then((childProc) => {
target = childProc;
cli = startCLI(['-p', `${target.pid}`]);
return cli.waitForPrompt();
})
.then(() => cli.command('sb("alive.js", 3)'))
.then(() => cli.waitFor(/break/))
.then(() => cli.waitForPrompt())
.then(() => {
t.match(
cli.output,
'> 3 ++x;',
'marks the 3rd line');
})
.then(() => cleanup())
.then(null, cleanup);
});

0 comments on commit b64abe8

Please sign in to comment.