Browse Source

inspector: rewrite inspector test helper

Helper was rewritten to rely on promises instead of manually written
queue and callbacks. This simplifies the code and makes it easier to
maintain and extend.

PR-URL: https://github.com/nodejs/node/pull/14460
Reviewed-By: James M Snell <jasnell@gmail.com>
v6
Eugene Ostroukhov 8 years ago
parent
commit
2296b677fb
  1. 9
      test/common/README.md
  2. 42
      test/common/index.js
  3. 781
      test/inspector/inspector-helper.js
  4. 68
      test/inspector/test-break-when-eval.js
  5. 41
      test/inspector/test-debug-brk-flag.js
  6. 46
      test/inspector/test-debug-end.js
  7. 45
      test/inspector/test-exception.js
  8. 128
      test/inspector/test-inspector-break-when-eval.js
  9. 59
      test/inspector/test-inspector-debug-brk.js
  10. 64
      test/inspector/test-inspector-exception.js
  11. 51
      test/inspector/test-inspector-ip-detection.js
  12. 21
      test/inspector/test-inspector-stop-profile-after-done.js
  13. 491
      test/inspector/test-inspector.js
  14. 48
      test/inspector/test-ip-detection.js
  15. 28
      test/inspector/test-not-blocked-on-idle.js
  16. 11
      test/inspector/test-off-no-session.js
  17. 24
      test/inspector/test-off-with-session-then-on.js
  18. 0
      test/inspector/test-port-cluster.js
  19. 0
      test/inspector/test-port-zero-cluster.js
  20. 0
      test/inspector/test-port-zero.js
  21. 30
      test/inspector/test-stop-profile-after-done.js
  22. 0
      test/inspector/test-stops-no-file.js

9
test/common/README.md

@ -99,6 +99,15 @@ Tests whether `name` and `expected` are part of a raised warning.
Checks if `pathname` exists
### fires(promise, [error], [timeoutMs])
* promise [&lt;Promise]
* error [&lt;String] default = 'timeout'
* timeoutMs [&lt;Number] default = 100
Returns a new promise that will propagate `promise` resolution or rejection if
that happens within the `timeoutMs` timespan, or rejects with `error` as
a reason otherwise.
### fixturesDir
* return [&lt;String>]

42
test/common/index.js

@ -814,6 +814,32 @@ function restoreWritable(name) {
delete process[name].writeTimes;
}
function onResolvedOrRejected(promise, callback) {
return promise.then((result) => {
callback();
return result;
}, (error) => {
callback();
throw error;
});
}
function timeoutPromise(error, timeoutMs) {
let clearCallback = null;
let done = false;
const promise = onResolvedOrRejected(new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(error), timeoutMs);
clearCallback = () => {
if (done)
return;
clearTimeout(timeout);
resolve();
};
}), () => done = true);
promise.clear = clearCallback;
return promise;
}
exports.hijackStdout = hijackStdWritable.bind(null, 'stdout');
exports.hijackStderr = hijackStdWritable.bind(null, 'stderr');
exports.restoreStdout = restoreWritable.bind(null, 'stdout');
@ -827,3 +853,19 @@ exports.firstInvalidFD = function firstInvalidFD() {
} catch (e) {}
return fd;
};
exports.fires = function fires(promise, error, timeoutMs) {
if (!timeoutMs && util.isNumber(error)) {
timeoutMs = error;
error = null;
}
if (!error)
error = 'timeout';
if (!timeoutMs)
timeoutMs = 100;
const timeout = timeoutPromise(error, timeoutMs);
return Promise.race([
onResolvedOrRejected(promise, () => timeout.clear()),
timeout
]);
};

781
test/inspector/inspector-helper.js

@ -4,61 +4,61 @@ const assert = require('assert');
const fs = require('fs');
const http = require('http');
const path = require('path');
const spawn = require('child_process').spawn;
const { spawn } = require('child_process');
const url = require('url');
const _MAINSCRIPT = path.join(common.fixturesDir, 'loop.js');
const DEBUG = false;
const TIMEOUT = 15 * 1000;
const EXPECT_ALIVE_SYMBOL = Symbol('isAlive');
const DONT_EXPECT_RESPONSE_SYMBOL = Symbol('dontExpectResponse');
const mainScript = path.join(common.fixturesDir, 'loop.js');
function send(socket, message, id, callback) {
const msg = JSON.parse(JSON.stringify(message)); // Clone!
msg['id'] = id;
if (DEBUG)
console.log('[sent]', JSON.stringify(msg));
const messageBuf = Buffer.from(JSON.stringify(msg));
const wsHeaderBuf = Buffer.allocUnsafe(16);
wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80;
const bodyLen = messageBuf.length;
let maskOffset = 2;
if (bodyLen < 126) {
byte2 = 0x80 + bodyLen;
} else if (bodyLen < 65536) {
byte2 = 0xFE;
wsHeaderBuf.writeUInt16BE(bodyLen, 2);
maskOffset = 4;
function spawnChildProcess(inspectorFlags, scriptContents, scriptFile) {
const args = [].concat(inspectorFlags);
if (scriptContents) {
args.push('-e', scriptContents);
} else {
byte2 = 0xFF;
wsHeaderBuf.writeUInt32BE(bodyLen, 2);
wsHeaderBuf.writeUInt32BE(0, 6);
maskOffset = 10;
args.push(scriptFile);
}
wsHeaderBuf.writeUInt8(byte2, 1);
wsHeaderBuf.writeUInt32BE(0x01020408, maskOffset);
const child = spawn(process.execPath, args);
for (let i = 0; i < messageBuf.length; i++)
messageBuf[i] = messageBuf[i] ^ (1 << (i % 4));
socket.write(
Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]),
callback);
const handler = tearDown.bind(null, child);
process.on('exit', handler);
process.on('uncaughtException', handler);
process.on('unhandledRejection', handler);
process.on('SIGINT', handler);
return child;
}
function makeBufferingDataCallback(dataCallback) {
let buffer = Buffer.alloc(0);
return (data) => {
const newData = Buffer.concat([buffer, data]);
const str = newData.toString('utf8');
const lines = str.split('\n');
if (str.endsWith('\n'))
buffer = Buffer.alloc(0);
else
buffer = Buffer.from(lines.pop(), 'utf8');
for (const line of lines)
dataCallback(line);
};
}
function sendEnd(socket) {
socket.write(Buffer.from([0x88, 0x80, 0x2D, 0x0E, 0x1E, 0xFA]));
function tearDown(child, err) {
child.kill();
if (err) {
console.error(err);
process.exit(1);
}
}
function parseWSFrame(buffer, handler) {
function parseWSFrame(buffer) {
// Protocol described in https://tools.ietf.org/html/rfc6455#section-5
let message = null;
if (buffer.length < 2)
return 0;
return { length: 0, message };
if (buffer[0] === 0x88 && buffer[1] === 0x00) {
handler(null);
return 2;
return { length: 2, message, closed: true };
}
assert.strictEqual(0x81, buffer[0]);
let dataLen = 0x7F & buffer[1];
@ -74,482 +74,331 @@ function parseWSFrame(buffer, handler) {
bodyOffset = 10;
}
if (buffer.length < bodyOffset + dataLen)
return 0;
const jsonPayload =
buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8');
let message;
try {
message = JSON.parse(jsonPayload);
} catch (e) {
console.error(`JSON.parse() failed for: ${jsonPayload}`);
throw e;
}
return { length: 0, message };
message = JSON.parse(
buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8'));
if (DEBUG)
console.log('[received]', JSON.stringify(message));
handler(message);
return bodyOffset + dataLen;
return { length: bodyOffset + dataLen, message };
}
function tearDown(child, err) {
child.kill();
if (err instanceof Error) {
console.error(err.stack);
process.exit(1);
}
}
function formatWSFrame(message) {
const messageBuf = Buffer.from(JSON.stringify(message));
function checkHttpResponse(host, port, path, callback, errorcb) {
const req = http.get({ host, port, path }, function(res) {
let response = '';
res.setEncoding('utf8');
res
.on('data', (data) => response += data.toString())
.on('end', () => {
let err = null;
let json = undefined;
try {
json = JSON.parse(response);
} catch (e) {
err = e;
err.response = response;
}
callback(err, json);
});
});
if (errorcb)
req.on('error', errorcb);
}
function makeBufferingDataCallback(dataCallback) {
let buffer = Buffer.alloc(0);
return (data) => {
const newData = Buffer.concat([buffer, data]);
const str = newData.toString('utf8');
const lines = str.split('\n');
if (str.endsWith('\n'))
buffer = Buffer.alloc(0);
else
buffer = Buffer.from(lines.pop(), 'utf8');
for (const line of lines)
dataCallback(line);
};
}
const wsHeaderBuf = Buffer.allocUnsafe(16);
wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80;
const bodyLen = messageBuf.length;
function timeout(message, multiplicator) {
return setTimeout(common.mustNotCall(message),
TIMEOUT * (multiplicator || 1));
}
let maskOffset = 2;
if (bodyLen < 126) {
byte2 = 0x80 + bodyLen;
} else if (bodyLen < 65536) {
byte2 = 0xFE;
wsHeaderBuf.writeUInt16BE(bodyLen, 2);
maskOffset = 4;
} else {
byte2 = 0xFF;
wsHeaderBuf.writeUInt32BE(bodyLen, 2);
wsHeaderBuf.writeUInt32BE(0, 6);
maskOffset = 10;
}
wsHeaderBuf.writeUInt8(byte2, 1);
wsHeaderBuf.writeUInt32BE(0x01020408, maskOffset);
function TestSession(socket, harness) {
this.mainScriptPath = harness.mainScriptPath;
this.mainScriptId = null;
this.harness_ = harness;
this.socket_ = socket;
this.expectClose_ = false;
this.scripts_ = {};
this.messagefilter_ = null;
this.responseCheckers_ = {};
this.lastId_ = 0;
this.messages_ = {};
this.expectedId_ = 1;
this.lastMessageResponseCallback_ = null;
this.closeCallback_ = null;
for (let i = 0; i < messageBuf.length; i++)
messageBuf[i] = messageBuf[i] ^ (1 << (i % 4));
let buffer = Buffer.alloc(0);
socket.on('data', (data) => {
buffer = Buffer.concat([buffer, data]);
let consumed;
do {
consumed = parseWSFrame(buffer, this.processMessage_.bind(this));
if (consumed)
buffer = buffer.slice(consumed);
} while (consumed);
}).on('close', () => {
assert(this.expectClose_, 'Socket closed prematurely');
this.closeCallback_ && this.closeCallback_();
});
return Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]);
}
TestSession.prototype.scriptUrlForId = function(id) {
return this.scripts_[id];
};
TestSession.prototype.processMessage_ = function(message) {
if (message === null) {
sendEnd(this.socket_);
return;
class InspectorSession {
constructor(socket, instance) {
this._instance = instance;
this._socket = socket;
this._nextId = 1;
this._commandResponsePromises = new Map();
this._unprocessedNotifications = [];
this._notificationCallback = null;
this._scriptsIdsByUrl = new Map();
let buffer = Buffer.alloc(0);
socket.on('data', (data) => {
buffer = Buffer.concat([buffer, data]);
do {
const { length, message, closed } = parseWSFrame(buffer);
if (!length)
break;
if (closed) {
socket.write(Buffer.from([0x88, 0x00])); // WS close frame
}
buffer = buffer.slice(length);
if (message)
this._onMessage(message);
} while (true);
});
this._terminationPromise = new Promise((resolve) => {
socket.once('close', resolve);
});
}
const method = message['method'];
if (method === 'Debugger.scriptParsed') {
const script = message['params'];
const scriptId = script['scriptId'];
const url = script['url'];
this.scripts_[scriptId] = url;
if (url === mainScript)
this.mainScriptId = scriptId;
waitForServerDisconnect() {
return this._terminationPromise;
}
this.messagefilter_ && this.messagefilter_(message);
const id = message['id'];
if (id) {
this.expectedId_++;
if (this.responseCheckers_[id]) {
const messageJSON = JSON.stringify(message);
const idJSON = JSON.stringify(this.messages_[id]);
assert(message['result'], `${messageJSON} (response to ${idJSON})`);
this.responseCheckers_[id](message['result']);
delete this.responseCheckers_[id];
}
const messageJSON = JSON.stringify(message);
const idJSON = JSON.stringify(this.messages_[id]);
assert(!message['error'], `${messageJSON} (replying to ${idJSON})`);
delete this.messages_[id];
if (id === this.lastId_) {
this.lastMessageResponseCallback_ && this.lastMessageResponseCallback_();
this.lastMessageResponseCallback_ = null;
}
disconnect() {
this._socket.destroy();
}
};
TestSession.prototype.sendAll_ = function(commands, callback) {
if (!commands.length) {
callback();
} else {
let id = ++this.lastId_;
let command = commands[0];
if (command instanceof Array) {
this.responseCheckers_[id] = command[1];
command = command[0];
}
if (command instanceof Function)
command = command();
if (!command[DONT_EXPECT_RESPONSE_SYMBOL]) {
this.messages_[id] = command;
_onMessage(message) {
if (message.id) {
const { resolve, reject } = this._commandResponsePromises.get(message.id);
this._commandResponsePromises.delete(message.id);
if (message.result)
resolve(message.result);
else
reject(message.error);
} else {
id += 100000;
this.lastId_--;
if (message.method === 'Debugger.scriptParsed') {
const script = message['params'];
const scriptId = script['scriptId'];
const url = script['url'];
this._scriptsIdsByUrl.set(scriptId, url);
if (url === _MAINSCRIPT)
this.mainScriptId = scriptId;
}
if (this._notificationCallback) {
// In case callback needs to install another
const callback = this._notificationCallback;
this._notificationCallback = null;
callback(message);
} else {
this._unprocessedNotifications.push(message);
}
}
send(this.socket_, command, id,
() => this.sendAll_(commands.slice(1), callback));
}
};
TestSession.prototype.sendInspectorCommands = function(commands) {
if (!(commands instanceof Array))
commands = [commands];
return this.enqueue((callback) => {
let timeoutId = null;
this.lastMessageResponseCallback_ = () => {
timeoutId && clearTimeout(timeoutId);
callback();
};
this.sendAll_(commands, () => {
timeoutId = setTimeout(() => {
assert.fail(`Messages without response: ${
Object.keys(this.messages_).join(', ')}`);
}, TIMEOUT);
});
});
};
_sendMessage(message) {
const msg = JSON.parse(JSON.stringify(message)); // Clone!
msg['id'] = this._nextId++;
if (DEBUG)
console.log('[sent]', JSON.stringify(msg));
TestSession.prototype.sendCommandsAndExpectClose = function(commands) {
if (!(commands instanceof Array))
commands = [commands];
return this.enqueue((callback) => {
let timeoutId = null;
let done = false;
this.expectClose_ = true;
this.closeCallback_ = function() {
if (timeoutId)
clearTimeout(timeoutId);
done = true;
callback();
};
this.sendAll_(commands, () => {
if (!done) {
timeoutId = timeout('Session still open');
}
const responsePromise = new Promise((resolve, reject) => {
this._commandResponsePromises.set(msg['id'], { resolve, reject });
});
});
};
TestSession.prototype.createCallbackWithTimeout_ = function(message) {
const promise = new Promise((resolve) => {
this.enqueue((callback) => {
const timeoutId = timeout(message);
resolve(() => {
clearTimeout(timeoutId);
callback();
});
});
});
return () => promise.then((callback) => callback());
};
return new Promise(
(resolve) => this._socket.write(formatWSFrame(msg), resolve))
.then(() => responsePromise);
}
TestSession.prototype.expectMessages = function(expects) {
if (!(expects instanceof Array)) expects = [ expects ];
const callback = this.createCallbackWithTimeout_(
`Matching response was not received:\n${expects[0]}`);
this.messagefilter_ = (message) => {
if (expects[0](message))
expects.shift();
if (!expects.length) {
this.messagefilter_ = null;
callback();
send(commands) {
if (Array.isArray(commands)) {
// Multiple commands means the response does not matter. There might even
// never be a response.
return Promise
.all(commands.map((command) => this._sendMessage(command)))
.then(() => {});
} else {
return this._sendMessage(commands);
}
};
return this;
};
TestSession.prototype.expectStderrOutput = function(regexp) {
this.harness_.addStderrFilter(
regexp,
this.createCallbackWithTimeout_(`Timed out waiting for ${regexp}`));
return this;
};
TestSession.prototype.runNext_ = function() {
if (this.task_) {
setImmediate(() => {
this.task_(() => {
this.task_ = this.task_.next_;
this.runNext_();
});
});
}
};
TestSession.prototype.enqueue = function(task) {
if (!this.task_) {
this.task_ = task;
this.runNext_();
} else {
let t = this.task_;
while (t.next_)
t = t.next_;
t.next_ = task;
waitForNotification(methodOrPredicate, description) {
const desc = description || methodOrPredicate;
const message = `Timed out waiting for matching notification (${desc}))`;
return common.fires(
this._asyncWaitForNotification(methodOrPredicate), message, TIMEOUT);
}
return this;
};
TestSession.prototype.disconnect = function(childDone) {
return this.enqueue((callback) => {
this.expectClose_ = true;
this.socket_.destroy();
console.log('[test]', 'Connection terminated');
callback();
}, childDone);
};
TestSession.prototype.expectClose = function() {
return this.enqueue((callback) => {
this.expectClose_ = true;
callback();
});
};
TestSession.prototype.assertClosed = function() {
return this.enqueue((callback) => {
assert.strictEqual(this.closed_, true);
callback();
});
};
TestSession.prototype.testHttpResponse = function(path, check) {
return this.enqueue((callback) =>
checkHttpResponse(null, this.harness_.port, path, (err, response) => {
check.call(this, err, response);
callback();
}));
};
function Harness(port, childProcess) {
this.port = port;
this.mainScriptPath = mainScript;
this.stderrFilters_ = [];
this.process_ = childProcess;
this.result_ = {};
this.running_ = true;
childProcess.stdout.on('data', makeBufferingDataCallback(
(line) => console.log('[out]', line)));
childProcess.stderr.on('data', makeBufferingDataCallback((message) => {
const pending = [];
console.log('[err]', message);
for (const filter of this.stderrFilters_)
if (!filter(message)) pending.push(filter);
this.stderrFilters_ = pending;
}));
childProcess.on('exit', (code, signal) => {
this.result_ = { code, signal };
this.running_ = false;
});
}
async _asyncWaitForNotification(methodOrPredicate) {
function matchMethod(notification) {
return notification.method === methodOrPredicate;
}
const predicate =
typeof methodOrPredicate === 'string' ? matchMethod : methodOrPredicate;
let notification = null;
do {
if (this._unprocessedNotifications.length) {
notification = this._unprocessedNotifications.shift();
} else {
notification = await new Promise(
(resolve) => this._notificationCallback = resolve);
}
} while (!predicate(notification));
return notification;
}
Harness.prototype.addStderrFilter = function(regexp, callback) {
this.stderrFilters_.push((message) => {
if (message.match(regexp)) {
callback();
_isBreakOnLineNotification(message, line, url) {
if ('Debugger.paused' === message['method']) {
const callFrame = message['params']['callFrames'][0];
const location = callFrame['location'];
assert.strictEqual(url, this._scriptsIdsByUrl.get(location['scriptId']));
assert.strictEqual(line, location['lineNumber']);
return true;
}
});
};
Harness.prototype.assertStillAlive = function() {
assert.strictEqual(this.running_, true,
`Child died: ${JSON.stringify(this.result_)}`);
};
Harness.prototype.run_ = function() {
setImmediate(() => {
if (!this.task_[EXPECT_ALIVE_SYMBOL])
this.assertStillAlive();
this.task_(() => {
this.task_ = this.task_.next_;
if (this.task_)
this.run_();
});
});
};
}
Harness.prototype.enqueue_ = function(task, expectAlive) {
task[EXPECT_ALIVE_SYMBOL] = !!expectAlive;
if (!this.task_) {
this.task_ = task;
this.run_();
} else {
let chain = this.task_;
while (chain.next_)
chain = chain.next_;
chain.next_ = task;
waitForBreakOnLine(line, url) {
return this
.waitForNotification(
(notification) =>
this._isBreakOnLineNotification(notification, line, url),
`break on ${url}:${line}`)
.then((notification) =>
notification.params.callFrames[0].scopeChain[0].object.objectId);
}
return this;
};
Harness.prototype.testHttpResponse = function(host, path, check, errorcb) {
return this.enqueue_((doneCallback) => {
function wrap(callback) {
if (callback) {
return function() {
callback(...arguments);
doneCallback();
};
_matchesConsoleOutputNotification(notification, type, values) {
if (!Array.isArray(values))
values = [ values ];
if ('Runtime.consoleAPICalled' === notification['method']) {
const params = notification['params'];
if (params['type'] === type) {
let i = 0;
for (const value of params['args']) {
if (value['value'] !== values[i++])
return false;
}
return i === values.length;
}
}
checkHttpResponse(host, this.port, path, wrap(check), wrap(errorcb));
});
};
}
Harness.prototype.wsHandshake = function(devtoolsUrl, tests, readyCallback) {
http.get({
port: this.port,
path: url.parse(devtoolsUrl).path,
headers: {
'Connection': 'Upgrade',
'Upgrade': 'websocket',
'Sec-WebSocket-Version': 13,
'Sec-WebSocket-Key': 'key=='
}
}).on('upgrade', (message, socket) => {
const session = new TestSession(socket, this);
if (!(tests instanceof Array))
tests = [tests];
function enqueue(tests) {
session.enqueue((sessionCb) => {
if (tests.length) {
tests[0](session);
session.enqueue((cb2) => {
enqueue(tests.slice(1));
cb2();
});
} else {
readyCallback();
}
sessionCb();
});
}
enqueue(tests);
}).on('response', common.mustNotCall('Upgrade was not received'));
};
waitForConsoleOutput(type, values) {
const desc = `Console output matching ${JSON.stringify(values)}`;
return this.waitForNotification(
(notification) => this._matchesConsoleOutputNotification(notification,
type, values),
desc);
}
Harness.prototype.runFrontendSession = function(tests) {
return this.enqueue_((callback) => {
checkHttpResponse(null, this.port, '/json/list', (err, response) => {
assert.ifError(err);
this.wsHandshake(response[0]['webSocketDebuggerUrl'], tests, callback);
async runToCompletion() {
console.log('[test]', 'Verify node waits for the frontend to disconnect');
await this.send({ 'method': 'Debugger.resume' });
await this.waitForNotification((notification) => {
return notification.method === 'Runtime.executionContextDestroyed' &&
notification.params.executionContextId === 1;
});
});
};
while ((await this._instance.nextStderrString()) !==
'Waiting for the debugger to disconnect...');
await this.disconnect();
}
}
Harness.prototype.expectShutDown = function(errorCode) {
this.enqueue_((callback) => {
if (this.running_) {
const timeoutId = timeout('Have not terminated');
this.process_.on('exit', (code, signal) => {
clearTimeout(timeoutId);
assert.strictEqual(errorCode, code, JSON.stringify({ code, signal }));
callback();
class NodeInstance {
constructor(inspectorFlags = ['--inspect-brk=0'],
scriptContents = '',
scriptFile = _MAINSCRIPT) {
this._portCallback = null;
this.portPromise = new Promise((resolve) => this._portCallback = resolve);
this._process = spawnChildProcess(inspectorFlags, scriptContents,
scriptFile);
this._running = true;
this._stderrLineCallback = null;
this._unprocessedStderrLines = [];
this._process.stdout.on('data', makeBufferingDataCallback(
(line) => console.log('[out]', line)));
this._process.stderr.on('data', makeBufferingDataCallback(
(message) => this.onStderrLine(message)));
this._shutdownPromise = new Promise((resolve) => {
this._process.once('exit', (exitCode, signal) => {
resolve({ exitCode, signal });
this._running = false;
});
});
}
onStderrLine(line) {
console.log('[err]', line);
if (this._portCallback) {
const matches = line.match(/Debugger listening on ws:\/\/.+:(\d+)\/.+/);
if (matches)
this._portCallback(matches[1]);
this._portCallback = null;
}
if (this._stderrLineCallback) {
this._stderrLineCallback(line);
this._stderrLineCallback = null;
} else {
assert.strictEqual(errorCode, this.result_.code);
callback();
this._unprocessedStderrLines.push(line);
}
}, true);
};
Harness.prototype.kill = function() {
return this.enqueue_((callback) => {
this.process_.kill();
callback();
});
};
exports.startNodeForInspectorTest = function(callback,
inspectorFlags = ['--inspect-brk'],
scriptContents = '',
scriptFile = mainScript) {
const args = [].concat(inspectorFlags);
if (scriptContents) {
args.push('-e', scriptContents);
} else {
args.push(scriptFile);
}
const child = spawn(process.execPath, args);
const timeoutId = timeout('Child process did not start properly', 4);
httpGet(host, path) {
console.log('[test]', `Testing ${path}`);
return this.portPromise.then((port) => new Promise((resolve, reject) => {
const req = http.get({ host, port, path }, (res) => {
let response = '';
res.setEncoding('utf8');
res
.on('data', (data) => response += data.toString())
.on('end', () => {
resolve(response);
});
});
req.on('error', reject);
})).then((response) => {
try {
return JSON.parse(response);
} catch (e) {
e.body = response;
throw e;
}
});
}
let found = false;
wsHandshake(devtoolsUrl) {
return this.portPromise.then((port) => new Promise((resolve) => {
http.get({
port,
path: url.parse(devtoolsUrl).path,
headers: {
'Connection': 'Upgrade',
'Upgrade': 'websocket',
'Sec-WebSocket-Version': 13,
'Sec-WebSocket-Key': 'key=='
}
}).on('upgrade', (message, socket) => {
resolve(new InspectorSession(socket, this));
}).on('response', common.mustNotCall('Upgrade was not received'));
}));
}
const dataCallback = makeBufferingDataCallback((text) => {
clearTimeout(timeoutId);
console.log('[err]', text);
if (found) return;
const match = text.match(/Debugger listening on ws:\/\/.+:(\d+)\/.+/);
found = true;
child.stderr.removeListener('data', dataCallback);
assert.ok(match, text);
callback(new Harness(match[1], child));
});
async connectInspectorSession() {
console.log('[test]', 'Connecting to a child Node process');
const response = await this.httpGet(null, '/json/list');
const url = response[0]['webSocketDebuggerUrl'];
return await this.wsHandshake(url);
}
child.stderr.on('data', dataCallback);
expectShutdown() {
return this._shutdownPromise;
}
const handler = tearDown.bind(null, child);
nextStderrString() {
if (this._unprocessedStderrLines.length)
return Promise.resolve(this._unprocessedStderrLines.shift());
return new Promise((resolve) => this._stderrLineCallback = resolve);
}
process.on('exit', handler);
process.on('uncaughtException', handler);
process.on('SIGINT', handler);
};
kill() {
this._process.kill();
}
}
exports.mainScriptSource = function() {
return fs.readFileSync(mainScript, 'utf8');
};
function readMainScriptSource() {
return fs.readFileSync(_MAINSCRIPT, 'utf8');
}
exports.markMessageNoResponse = function(message) {
message[DONT_EXPECT_RESPONSE_SYMBOL] = true;
module.exports = {
mainScriptPath: _MAINSCRIPT,
readMainScriptSource,
NodeInstance
};

68
test/inspector/test-break-when-eval.js

@ -0,0 +1,68 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { NodeInstance } = require('./inspector-helper.js');
const path = require('path');
const script = path.join(path.dirname(module.filename), 'global-function.js');
async function setupDebugger(session) {
console.log('[test]', 'Setting up a debugger');
const commands = [
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 0 } },
{ 'method': 'Runtime.runIfWaitingForDebugger' },
];
session.send(commands);
await session.waitForNotification('Runtime.consoleAPICalled');
}
async function breakOnLine(session) {
console.log('[test]', 'Breaking in the code');
const commands = [
{ 'method': 'Debugger.setBreakpointByUrl',
'params': { 'lineNumber': 9,
'url': script,
'columnNumber': 0,
'condition': ''
}
},
{ 'method': 'Runtime.evaluate',
'params': { 'expression': 'sum()',
'objectGroup': 'console',
'includeCommandLineAPI': true,
'silent': false,
'contextId': 1,
'returnByValue': false,
'generatePreview': true,
'userGesture': true,
'awaitPromise': false
}
}
];
session.send(commands);
await session.waitForBreakOnLine(9, script);
}
async function stepOverConsoleStatement(session) {
console.log('[test]', 'Step over console statement and test output');
session.send({ 'method': 'Debugger.stepOver' });
await session.waitForConsoleOutput('log', [0, 3]);
await session.waitForNotification('Debugger.paused');
}
async function runTests() {
const child = new NodeInstance(['--inspect=0'], undefined, script);
const session = await child.connectInspectorSession();
await setupDebugger(session);
await breakOnLine(session);
await stepOverConsoleStatement(session);
await session.runToCompletion();
assert.strictEqual(0, (await child.expectShutdown()).exitCode);
}
common.crashOnUnhandledRejection();
runTests();

41
test/inspector/test-debug-brk-flag.js

@ -0,0 +1,41 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { mainScriptPath,
NodeInstance } = require('./inspector-helper.js');
async function testBreakpointOnStart(session) {
const commands = [
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setPauseOnExceptions',
'params': { 'state': 'none' } },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 0 } },
{ 'method': 'Profiler.enable' },
{ 'method': 'Profiler.setSamplingInterval',
'params': { 'interval': 100 } },
{ 'method': 'Debugger.setBlackboxPatterns',
'params': { 'patterns': [] } },
{ 'method': 'Runtime.runIfWaitingForDebugger' }
];
session.send(commands);
await session.waitForBreakOnLine(0, mainScriptPath);
}
async function runTests() {
const child = new NodeInstance(['--inspect', '--debug-brk']);
const session = await child.connectInspectorSession();
await testBreakpointOnStart(session);
await session.runToCompletion();
assert.strictEqual(55, (await child.expectShutdown()).exitCode);
}
common.crashOnUnhandledRejection();
runTests();

46
test/inspector/test-debug-end.js

@ -0,0 +1,46 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const { strictEqual } = require('assert');
const { NodeInstance } = require('./inspector-helper.js');
async function testNoServerNoCrash() {
console.log('Test there\'s no crash stopping server that was not started');
const instance = new NodeInstance([],
`process._debugEnd();
process.exit(42);`);
strictEqual(42, (await instance.expectShutdown()).exitCode);
}
async function testNoSessionNoCrash() {
console.log('Test there\'s no crash stopping server without connecting');
const instance = new NodeInstance('--inspect=0',
'process._debugEnd();process.exit(42);');
strictEqual(42, (await instance.expectShutdown()).exitCode);
}
async function testSessionNoCrash() {
console.log('Test there\'s no crash stopping server after connecting');
const script = `process._debugEnd();
process._debugProcess(process.pid);
setTimeout(() => {
console.log("Done");
process.exit(42);
});`;
const instance = new NodeInstance('--inspect-brk=0', script);
const session = await instance.connectInspectorSession();
await session.send({ 'method': 'Runtime.runIfWaitingForDebugger' });
await session.waitForServerDisconnect();
strictEqual(42, (await instance.expectShutdown()).exitCode);
}
async function runTest() {
await testNoServerNoCrash();
await testNoSessionNoCrash();
await testSessionNoCrash();
}
common.crashOnUnhandledRejection();
runTest();

45
test/inspector/test-exception.js

@ -0,0 +1,45 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { NodeInstance } = require('./inspector-helper.js');
const path = require('path');
const script = path.join(common.fixturesDir, 'throws_error.js');
async function testBreakpointOnStart(session) {
console.log('[test]',
'Verifying debugger stops on start (--inspect-brk option)');
const commands = [
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setPauseOnExceptions',
'params': { 'state': 'none' } },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 0 } },
{ 'method': 'Profiler.enable' },
{ 'method': 'Profiler.setSamplingInterval',
'params': { 'interval': 100 } },
{ 'method': 'Debugger.setBlackboxPatterns',
'params': { 'patterns': [] } },
{ 'method': 'Runtime.runIfWaitingForDebugger' }
];
await session.send(commands);
await session.waitForBreakOnLine(0, script);
}
async function runTest() {
const child = new NodeInstance(undefined, undefined, script);
const session = await child.connectInspectorSession();
await testBreakpointOnStart(session);
await session.runToCompletion();
assert.strictEqual(1, (await child.expectShutdown()).exitCode);
}
common.crashOnUnhandledRejection();
runTest();

128
test/inspector/test-inspector-break-when-eval.js

@ -1,128 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const helper = require('./inspector-helper.js');
const path = require('path');
const script = path.join(path.dirname(module.filename), 'global-function.js');
function setupExpectBreakOnLine(line, url, session) {
return function(message) {
if ('Debugger.paused' === message['method']) {
const callFrame = message['params']['callFrames'][0];
const location = callFrame['location'];
assert.strictEqual(url, session.scriptUrlForId(location['scriptId']));
assert.strictEqual(line, location['lineNumber']);
return true;
}
};
}
function setupExpectConsoleOutputAndBreak(type, values) {
if (!(values instanceof Array))
values = [ values ];
let consoleLog = false;
function matchConsoleLog(message) {
if ('Runtime.consoleAPICalled' === message['method']) {
const params = message['params'];
if (params['type'] === type) {
let i = 0;
for (const value of params['args']) {
if (value['value'] !== values[i++])
return false;
}
return i === values.length;
}
}
}
return function(message) {
if (consoleLog)
return message['method'] === 'Debugger.paused';
consoleLog = matchConsoleLog(message);
return false;
};
}
function setupExpectContextDestroyed(id) {
return function(message) {
if ('Runtime.executionContextDestroyed' === message['method'])
return message['params']['executionContextId'] === id;
};
}
function setupDebugger(session) {
console.log('[test]', 'Setting up a debugger');
const commands = [
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 0 } },
{ 'method': 'Runtime.runIfWaitingForDebugger' },
];
session
.sendInspectorCommands(commands)
.expectMessages((message) => 'Runtime.consoleAPICalled' === message.method);
}
function breakOnLine(session) {
console.log('[test]', 'Breaking in the code');
const commands = [
{ 'method': 'Debugger.setBreakpointByUrl',
'params': { 'lineNumber': 9,
'url': script,
'columnNumber': 0,
'condition': ''
}
},
{ 'method': 'Runtime.evaluate',
'params': { 'expression': 'sum()',
'objectGroup': 'console',
'includeCommandLineAPI': true,
'silent': false,
'contextId': 1,
'returnByValue': false,
'generatePreview': true,
'userGesture': true,
'awaitPromise': false
}
}
];
helper.markMessageNoResponse(commands[1]);
session
.sendInspectorCommands(commands)
.expectMessages(setupExpectBreakOnLine(9, script, session));
}
function stepOverConsoleStatement(session) {
console.log('[test]', 'Step over console statement and test output');
session
.sendInspectorCommands({ 'method': 'Debugger.stepOver' })
.expectMessages(setupExpectConsoleOutputAndBreak('log', [0, 3]));
}
function testWaitsForFrontendDisconnect(session, harness) {
console.log('[test]', 'Verify node waits for the frontend to disconnect');
session.sendInspectorCommands({ 'method': 'Debugger.resume' })
.expectMessages(setupExpectContextDestroyed(1))
.expectStderrOutput('Waiting for the debugger to disconnect...')
.disconnect(true);
}
function runTests(harness) {
harness
.runFrontendSession([
setupDebugger,
breakOnLine,
stepOverConsoleStatement,
testWaitsForFrontendDisconnect
]).expectShutDown(0);
}
helper.startNodeForInspectorTest(runTests,
['--inspect'],
undefined,
script);

59
test/inspector/test-inspector-debug-brk.js

@ -1,59 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const helper = require('./inspector-helper.js');
function setupExpectBreakOnLine(line, url, session, scopeIdCallback) {
return function(message) {
if ('Debugger.paused' === message['method']) {
const callFrame = message['params']['callFrames'][0];
const location = callFrame['location'];
assert.strictEqual(url, session.scriptUrlForId(location['scriptId']));
assert.strictEqual(line, location['lineNumber']);
scopeIdCallback &&
scopeIdCallback(callFrame['scopeChain'][0]['object']['objectId']);
return true;
}
};
}
function testBreakpointOnStart(session) {
const commands = [
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setPauseOnExceptions',
'params': { 'state': 'none' } },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 0 } },
{ 'method': 'Profiler.enable' },
{ 'method': 'Profiler.setSamplingInterval',
'params': { 'interval': 100 } },
{ 'method': 'Debugger.setBlackboxPatterns',
'params': { 'patterns': [] } },
{ 'method': 'Runtime.runIfWaitingForDebugger' }
];
session
.sendInspectorCommands(commands)
.expectMessages(setupExpectBreakOnLine(0, session.mainScriptPath, session));
}
function testWaitsForFrontendDisconnect(session, harness) {
console.log('[test]', 'Verify node waits for the frontend to disconnect');
session.sendInspectorCommands({ 'method': 'Debugger.resume' })
.expectStderrOutput('Waiting for the debugger to disconnect...')
.disconnect(true);
}
function runTests(harness) {
harness
.runFrontendSession([
testBreakpointOnStart,
testWaitsForFrontendDisconnect
]).expectShutDown(55);
}
helper.startNodeForInspectorTest(runTests, ['--inspect', '--debug-brk']);

64
test/inspector/test-inspector-exception.js

@ -1,64 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const helper = require('./inspector-helper.js');
const path = require('path');
const script = path.join(common.fixturesDir, 'throws_error.js');
function setupExpectBreakOnLine(line, url, session) {
return function(message) {
if ('Debugger.paused' === message['method']) {
const callFrame = message['params']['callFrames'][0];
const location = callFrame['location'];
assert.strictEqual(url, session.scriptUrlForId(location['scriptId']));
assert.strictEqual(line, location['lineNumber']);
return true;
}
};
}
function testBreakpointOnStart(session) {
const commands = [
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setPauseOnExceptions',
'params': { 'state': 'none' } },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 0 } },
{ 'method': 'Profiler.enable' },
{ 'method': 'Profiler.setSamplingInterval',
'params': { 'interval': 100 } },
{ 'method': 'Debugger.setBlackboxPatterns',
'params': { 'patterns': [] } },
{ 'method': 'Runtime.runIfWaitingForDebugger' }
];
session
.sendInspectorCommands(commands)
.expectMessages(setupExpectBreakOnLine(0, script, session));
}
function testWaitsForFrontendDisconnect(session, harness) {
console.log('[test]', 'Verify node waits for the frontend to disconnect');
session.sendInspectorCommands({ 'method': 'Debugger.resume' })
.expectStderrOutput('Waiting for the debugger to disconnect...')
.disconnect(true);
}
function runTests(harness) {
harness
.runFrontendSession([
testBreakpointOnStart,
testWaitsForFrontendDisconnect
]).expectShutDown(1);
}
helper.startNodeForInspectorTest(runTests,
undefined,
undefined,
script);

51
test/inspector/test-inspector-ip-detection.js

@ -1,51 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const helper = require('./inspector-helper.js');
const os = require('os');
const ip = pickIPv4Address();
if (!ip)
common.skip('No IP address found');
function checkListResponse(instance, err, response) {
assert.ifError(err);
const res = response[0];
const wsUrl = res['webSocketDebuggerUrl'];
assert.ok(wsUrl);
const match = wsUrl.match(/^ws:\/\/(.*):9229\/(.*)/);
assert.strictEqual(ip, match[1]);
assert.strictEqual(res['id'], match[2]);
assert.strictEqual(ip, res['devtoolsFrontendUrl'].match(/.*ws=(.*):9229/)[1]);
instance.childInstanceDone = true;
}
function checkError(instance, error) {
// Some OSes will not allow us to connect
if (error.code === 'EHOSTUNREACH') {
common.printSkipMessage('Unable to connect to self');
} else {
throw error;
}
instance.childInstanceDone = true;
}
function runTests(instance) {
instance
.testHttpResponse(ip, '/json/list', checkListResponse.bind(null, instance),
checkError.bind(null, instance))
.kill();
}
function pickIPv4Address() {
for (const i of [].concat(...Object.values(os.networkInterfaces()))) {
if (i.family === 'IPv4' && i.address !== '127.0.0.1')
return i.address;
}
}
helper.startNodeForInspectorTest(runTests, '--inspect-brk=0.0.0.0');

21
test/inspector/test-inspector-stop-profile-after-done.js

@ -1,21 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const helper = require('./inspector-helper.js');
function test(session) {
session.sendInspectorCommands([
{ 'method': 'Runtime.runIfWaitingForDebugger' },
{ 'method': 'Profiler.setSamplingInterval', 'params': { 'interval': 100 } },
{ 'method': 'Profiler.enable' },
{ 'method': 'Profiler.start' }]);
session.expectStderrOutput('Waiting for the debugger to disconnect...');
session.sendInspectorCommands({ 'method': 'Profiler.stop' });
session.disconnect(true);
}
function runTests(harness) {
harness.runFrontendSession([test]).expectShutDown(0);
}
helper.startNodeForInspectorTest(runTests, ['--inspect-brk'], 'let a = 2;');

491
test/inspector/test-inspector.js

@ -4,12 +4,11 @@ const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const helper = require('./inspector-helper.js');
const { mainScriptPath,
readMainScriptSource,
NodeInstance } = require('./inspector-helper.js');
let scopeId;
function checkListResponse(err, response) {
assert.ifError(err);
function checkListResponse(response) {
assert.strictEqual(1, response.length);
assert.ok(response[0]['devtoolsFrontendUrl']);
assert.ok(
@ -17,8 +16,7 @@ function checkListResponse(err, response) {
.test(response[0]['webSocketDebuggerUrl']));
}
function checkVersion(err, response) {
assert.ifError(err);
function checkVersion(response) {
assert.ok(response);
const expected = {
'Browser': `node.js/${process.version}`,
@ -28,10 +26,10 @@ function checkVersion(err, response) {
JSON.stringify(expected));
}
function checkBadPath(err, response) {
function checkBadPath(err) {
assert(err instanceof SyntaxError);
assert(/Unexpected token/.test(err.message));
assert(/WebSockets request was expected/.test(err.response));
assert(/Unexpected token/.test(err.message), err.message);
assert(/WebSockets request was expected/.test(err.body), err.body);
}
function checkException(message) {
@ -39,69 +37,26 @@ function checkException(message) {
'An exception occurred during execution');
}
function expectMainScriptSource(result) {
const expected = helper.mainScriptSource();
const source = result['scriptSource'];
assert(source && (source.includes(expected)),
`Script source is wrong: ${source}`);
}
function setupExpectBreakOnLine(line, url, session, scopeIdCallback) {
return function(message) {
if ('Debugger.paused' === message['method']) {
const callFrame = message['params']['callFrames'][0];
const location = callFrame['location'];
assert.strictEqual(url, session.scriptUrlForId(location['scriptId']));
assert.strictEqual(line, location['lineNumber']);
scopeIdCallback &&
scopeIdCallback(callFrame['scopeChain'][0]['object']['objectId']);
return true;
}
};
}
function setupExpectConsoleOutput(type, values) {
if (!(values instanceof Array))
values = [ values ];
return function(message) {
if ('Runtime.consoleAPICalled' === message['method']) {
const params = message['params'];
if (params['type'] === type) {
let i = 0;
for (const value of params['args']) {
if (value['value'] !== values[i++])
return false;
}
return i === values.length;
}
}
};
function assertNoUrlsWhileConnected(response) {
assert.strictEqual(1, response.length);
assert.ok(!response[0].hasOwnProperty('devtoolsFrontendUrl'));
assert.ok(!response[0].hasOwnProperty('webSocketDebuggerUrl'));
}
function setupExpectScopeValues(expected) {
return function(result) {
for (const actual of result['result']) {
const value = expected[actual['name']];
if (value)
assert.strictEqual(value, actual['value']['value']);
function assertScopeValues({ result }, expected) {
const unmatched = new Set(Object.keys(expected));
for (const actual of result) {
const value = expected[actual['name']];
if (value) {
assert.strictEqual(value, actual['value']['value']);
unmatched.delete(actual['name']);
}
};
}
if (unmatched.size)
assert.fail(Array.from(unmatched.values()));
}
function setupExpectValue(value) {
return function(result) {
assert.strictEqual(value, result['result']['value']);
};
}
function setupExpectContextDestroyed(id) {
return function(message) {
if ('Runtime.executionContextDestroyed' === message['method'])
return message['params']['executionContextId'] === id;
};
}
function testBreakpointOnStart(session) {
async function testBreakpointOnStart(session) {
console.log('[test]',
'Verifying debugger stops on start (--inspect-brk option)');
const commands = [
@ -119,262 +74,230 @@ function testBreakpointOnStart(session) {
{ 'method': 'Runtime.runIfWaitingForDebugger' }
];
session
.sendInspectorCommands(commands)
.expectMessages(setupExpectBreakOnLine(0, session.mainScriptPath, session));
await session.send(commands);
await session.waitForBreakOnLine(0, mainScriptPath);
}
function testSetBreakpointAndResume(session) {
async function testBreakpoint(session) {
console.log('[test]', 'Setting a breakpoint and verifying it is hit');
const commands = [
{ 'method': 'Debugger.setBreakpointByUrl',
'params': { 'lineNumber': 5,
'url': session.mainScriptPath,
'url': mainScriptPath,
'columnNumber': 0,
'condition': ''
}
},
{ 'method': 'Debugger.resume' },
[ { 'method': 'Debugger.getScriptSource',
'params': { 'scriptId': session.mainScriptId } },
expectMainScriptSource ],
];
session
.sendInspectorCommands(commands)
.expectMessages([
setupExpectConsoleOutput('log', ['A message', 5]),
setupExpectBreakOnLine(5, session.mainScriptPath,
session, (id) => scopeId = id),
]);
}
await session.send(commands);
const { scriptSource } = await session.send({
'method': 'Debugger.getScriptSource',
'params': { 'scriptId': session.mainScriptId } });
assert(scriptSource && (scriptSource.includes(readMainScriptSource())),
`Script source is wrong: ${scriptSource}`);
await session.waitForConsoleOutput('log', ['A message', 5]);
const scopeId = await session.waitForBreakOnLine(5, mainScriptPath);
function testInspectScope(session) {
console.log('[test]', 'Verify we can read current application state');
session.sendInspectorCommands([
[
{
'method': 'Runtime.getProperties',
'params': {
'objectId': scopeId,
'ownProperties': false,
'accessorPropertiesOnly': false,
'generatePreview': true
}
}, setupExpectScopeValues({ t: 1001, k: 1 })
],
[
{
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': 'k + t',
'objectGroup': 'console',
'includeCommandLineAPI': true,
'silent': false,
'returnByValue': false,
'generatePreview': true
}
}, setupExpectValue(1002)
],
[
{
'method': 'Runtime.evaluate', 'params': {
'expression': '5 * 5'
}
}, (message) => assert.strictEqual(25, message['result']['value'])
],
]);
}
const response = await session.send({
'method': 'Runtime.getProperties',
'params': {
'objectId': scopeId,
'ownProperties': false,
'accessorPropertiesOnly': false,
'generatePreview': true
}
});
assertScopeValues(response, { t: 1001, k: 1 });
function testNoUrlsWhenConnected(session) {
session.testHttpResponse('/json/list', (err, response) => {
assert.ifError(err);
assert.strictEqual(1, response.length);
assert.ok(!response[0].hasOwnProperty('devtoolsFrontendUrl'));
assert.ok(!response[0].hasOwnProperty('webSocketDebuggerUrl'));
let { result } = await session.send({
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': 'k + t',
'objectGroup': 'console',
'includeCommandLineAPI': true,
'silent': false,
'returnByValue': false,
'generatePreview': true
}
});
assert.strictEqual(1002, result['value']);
result = (await session.send({
'method': 'Runtime.evaluate', 'params': {
'expression': '5 * 5'
}
})).result;
assert.strictEqual(25, result['value']);
}
function testI18NCharacters(session) {
async function testI18NCharacters(session) {
console.log('[test]', 'Verify sending and receiving UTF8 characters');
const chars = 'טֶ字и';
session.sendInspectorCommands([
{
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': `console.log("${chars}")`,
'objectGroup': 'console',
'includeCommandLineAPI': true,
'silent': false,
'returnByValue': false,
'generatePreview': true
}
session.send({
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': `console.log("${chars}")`,
'objectGroup': 'console',
'includeCommandLineAPI': true,
'silent': false,
'returnByValue': false,
'generatePreview': true
}
]).expectMessages([
setupExpectConsoleOutput('log', [chars]),
]);
});
await session.waitForConsoleOutput('log', [chars]);
}
function testCommandLineAPI(session) {
async function testCommandLineAPI(session) {
const testModulePath = require.resolve('../fixtures/empty.js');
const testModuleStr = JSON.stringify(testModulePath);
const printAModulePath = require.resolve('../fixtures/printA.js');
const printAModuleStr = JSON.stringify(printAModulePath);
const printBModulePath = require.resolve('../fixtures/printB.js');
const printBModuleStr = JSON.stringify(printBModulePath);
session.sendInspectorCommands([
[ // we can use `require` outside of a callframe with require in scope
{
'method': 'Runtime.evaluate', 'params': {
'expression': 'typeof require("fs").readFile === "function"',
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
// we can use `require` outside of a callframe with require in scope
let result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': 'typeof require("fs").readFile === "function"',
'includeCommandLineAPI': true
}
],
[ // the global require has the same properties as a normal `require`
{
'method': 'Runtime.evaluate', 'params': {
'expression': [
'typeof require.resolve === "function"',
'typeof require.extensions === "object"',
'typeof require.cache === "object"'
].join(' && '),
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
});
checkException(result);
assert.strictEqual(result['result']['value'], true);
// the global require has the same properties as a normal `require`
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': [
'typeof require.resolve === "function"',
'typeof require.extensions === "object"',
'typeof require.cache === "object"'
].join(' && '),
'includeCommandLineAPI': true
}
],
[ // `require` twice returns the same value
{
'method': 'Runtime.evaluate', 'params': {
// 1. We require the same module twice
// 2. We mutate the exports so we can compare it later on
'expression': `
Object.assign(
require(${testModuleStr}),
{ old: 'yes' }
) === require(${testModuleStr})`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
});
checkException(result);
assert.strictEqual(result['result']['value'], true);
// `require` twice returns the same value
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
// 1. We require the same module twice
// 2. We mutate the exports so we can compare it later on
'expression': `
Object.assign(
require(${testModuleStr}),
{ old: 'yes' }
) === require(${testModuleStr})`,
'includeCommandLineAPI': true
}
],
[ // after require the module appears in require.cache
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify(
require.cache[${testModuleStr}].exports
)`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']),
{ old: 'yes' });
});
checkException(result);
assert.strictEqual(result['result']['value'], true);
// after require the module appears in require.cache
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify(
require.cache[${testModuleStr}].exports
)`,
'includeCommandLineAPI': true
}
],
[ // remove module from require.cache
{
'method': 'Runtime.evaluate', 'params': {
'expression': `delete require.cache[${testModuleStr}]`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
});
checkException(result);
assert.deepStrictEqual(JSON.parse(result['result']['value']),
{ old: 'yes' });
// remove module from require.cache
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': `delete require.cache[${testModuleStr}]`,
'includeCommandLineAPI': true
}
],
[ // require again, should get fresh (empty) exports
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify(require(${testModuleStr}))`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']), {});
});
checkException(result);
assert.strictEqual(result['result']['value'], true);
// require again, should get fresh (empty) exports
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify(require(${testModuleStr}))`,
'includeCommandLineAPI': true
}
],
[ // require 2nd module, exports an empty object
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify(require(${printAModuleStr}))`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']), {});
});
checkException(result);
assert.deepStrictEqual(JSON.parse(result['result']['value']), {});
// require 2nd module, exports an empty object
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify(require(${printAModuleStr}))`,
'includeCommandLineAPI': true
}
],
[ // both modules end up with the same module.parent
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify({
parentsEqual:
require.cache[${testModuleStr}].parent ===
require.cache[${printAModuleStr}].parent,
parentId: require.cache[${testModuleStr}].parent.id,
})`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']), {
parentsEqual: true,
parentId: '<inspector console>'
});
});
checkException(result);
assert.deepStrictEqual(JSON.parse(result['result']['value']), {});
// both modules end up with the same module.parent
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': `JSON.stringify({
parentsEqual:
require.cache[${testModuleStr}].parent ===
require.cache[${printAModuleStr}].parent,
parentId: require.cache[${testModuleStr}].parent.id,
})`,
'includeCommandLineAPI': true
}
],
[ // the `require` in the module shadows the command line API's `require`
{
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': `(
require(${printBModuleStr}),
require.cache[${printBModuleStr}].parent.id
)`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.notStrictEqual(message['result']['value'],
'<inspector console>');
});
checkException(result);
assert.deepStrictEqual(JSON.parse(result['result']['value']), {
parentsEqual: true,
parentId: '<inspector console>'
});
// the `require` in the module shadows the command line API's `require`
result = await session.send(
{
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': `(
require(${printBModuleStr}),
require.cache[${printBModuleStr}].parent.id
)`,
'includeCommandLineAPI': true
}
],
]);
});
checkException(result);
assert.notStrictEqual(result['result']['value'],
'<inspector console>');
}
function testWaitsForFrontendDisconnect(session, harness) {
console.log('[test]', 'Verify node waits for the frontend to disconnect');
session.sendInspectorCommands({ 'method': 'Debugger.resume' })
.expectMessages(setupExpectContextDestroyed(1))
.expectStderrOutput('Waiting for the debugger to disconnect...')
.disconnect(true);
}
async function runTest() {
const child = new NodeInstance();
checkListResponse(await child.httpGet(null, '/json'));
checkListResponse(await child.httpGet(null, '/json/list'));
checkVersion(await child.httpGet(null, '/json/version'));
await child.httpGet(null, '/json/activate').catch(checkBadPath);
await child.httpGet(null, '/json/activate/boom').catch(checkBadPath);
await child.httpGet(null, '/json/badpath').catch(checkBadPath);
function runTests(harness) {
harness
.testHttpResponse(null, '/json', checkListResponse)
.testHttpResponse(null, '/json/list', checkListResponse)
.testHttpResponse(null, '/json/version', checkVersion)
.testHttpResponse(null, '/json/activate', checkBadPath)
.testHttpResponse(null, '/json/activate/boom', checkBadPath)
.testHttpResponse(null, '/json/badpath', checkBadPath)
.runFrontendSession([
testNoUrlsWhenConnected,
testBreakpointOnStart,
testSetBreakpointAndResume,
testInspectScope,
testI18NCharacters,
testCommandLineAPI,
testWaitsForFrontendDisconnect
]).expectShutDown(55);
const session = await child.connectInspectorSession();
assertNoUrlsWhileConnected(await child.httpGet(null, '/json/list'));
await testBreakpointOnStart(session);
await testBreakpoint(session);
await testI18NCharacters(session);
await testCommandLineAPI(session);
await session.runToCompletion();
assert.strictEqual(55, (await child.expectShutdown()).exitCode);
}
helper.startNodeForInspectorTest(runTests);
common.crashOnUnhandledRejection();
runTest();

48
test/inspector/test-ip-detection.js

@ -0,0 +1,48 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { NodeInstance } = require('./inspector-helper.js');
const os = require('os');
const ip = pickIPv4Address();
if (!ip)
common.skip('No IP address found');
function checkIpAddress(ip, response) {
const res = response[0];
const wsUrl = res['webSocketDebuggerUrl'];
assert.ok(wsUrl);
const match = wsUrl.match(/^ws:\/\/(.*):\d+\/(.*)/);
assert.strictEqual(ip, match[1]);
assert.strictEqual(res['id'], match[2]);
assert.strictEqual(ip, res['devtoolsFrontendUrl'].match(/.*ws=(.*):\d+/)[1]);
}
function pickIPv4Address() {
for (const i of [].concat(...Object.values(os.networkInterfaces()))) {
if (i.family === 'IPv4' && i.address !== '127.0.0.1')
return i.address;
}
}
async function test() {
const instance = new NodeInstance('--inspect-brk=0.0.0.0:0');
try {
checkIpAddress(ip, await instance.httpGet(ip, '/json/list'));
} catch (error) {
if (error.code === 'EHOSTUNREACH') {
common.printSkipMessage('Unable to connect to self');
} else {
throw error;
}
}
instance.kill();
}
common.crashOnUnhandledRejection();
test();

28
test/inspector/test-not-blocked-on-idle.js

@ -1,21 +1,21 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const helper = require('./inspector-helper.js');
const { NodeInstance } = require('./inspector-helper.js');
function shouldShutDown(session) {
session
.sendInspectorCommands([
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.pause' },
])
.disconnect(true);
}
function runTests(harness) {
async function runTests() {
const script = 'setInterval(() => {debugger;}, 60000);';
const node = new NodeInstance('--inspect=0', script);
// 1 second wait to make sure the inferior began running the script
setTimeout(() => harness.runFrontendSession([shouldShutDown]).kill(), 1000);
await new Promise((resolve) => setTimeout(() => resolve(), 1000));
const session = await node.connectInspectorSession();
await session.send([
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.pause' }
]);
session.disconnect();
node.kill();
}
const script = 'setInterval(() => {debugger;}, 60000);';
helper.startNodeForInspectorTest(runTests, '--inspect', script);
common.crashOnUnhandledRejection();
runTests();

11
test/inspector/test-off-no-session.js

@ -1,11 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const helper = require('./inspector-helper.js');
function testStop(harness) {
harness.expectShutDown(42);
}
helper.startNodeForInspectorTest(testStop, '--inspect',
'process._debugEnd();process.exit(42);');

24
test/inspector/test-off-with-session-then-on.js

@ -1,24 +0,0 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const helper = require('./inspector-helper.js');
function testResume(session) {
session.sendCommandsAndExpectClose([
{ 'method': 'Runtime.runIfWaitingForDebugger' }
]);
}
function testDisconnectSession(harness) {
harness
.runFrontendSession([
testResume,
]).expectShutDown(42);
}
const script = 'process._debugEnd();' +
'process._debugProcess(process.pid);' +
'setTimeout(() => {console.log("Done");process.exit(42)});';
helper.startNodeForInspectorTest(testDisconnectSession, '--inspect-brk',
script);

0
test/inspector/test-inspector-port-cluster.js → test/inspector/test-port-cluster.js

0
test/inspector/test-inspector-port-zero-cluster.js → test/inspector/test-port-zero-cluster.js

0
test/inspector/test-inspector-port-zero.js → test/inspector/test-port-zero.js

30
test/inspector/test-stop-profile-after-done.js

@ -0,0 +1,30 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { NodeInstance } = require('./inspector-helper.js');
async function runTests() {
const child = new NodeInstance(['--inspect=0'],
`let c = 0;
const interval = setInterval(() => {
console.log(new Object());
if (c++ === 10)
clearInterval(interval);
}, 10);`);
const session = await child.connectInspectorSession();
session.send([
{ 'method': 'Profiler.setSamplingInterval', 'params': { 'interval': 100 } },
{ 'method': 'Profiler.enable' },
{ 'method': 'Runtime.runIfWaitingForDebugger' },
{ 'method': 'Profiler.start' }]);
while (await child.nextStderrString() !==
'Waiting for the debugger to disconnect...');
await session.send({ 'method': 'Profiler.stop' });
session.disconnect();
assert.strictEqual(0, (await child.expectShutdown()).exitCode);
}
common.crashOnUnhandledRejection();
runTests();

0
test/inspector/test-inspector-stops-no-file.js → test/inspector/test-stops-no-file.js

Loading…
Cancel
Save