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/14797
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
v6
Eugene Ostroukhov 8 years ago
parent
commit
5f31d54720
  1. 9
      test/common/README.md
  2. 42
      test/common/index.js
  3. 717
      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. 311
      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
]);
};

717
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));
function spawnChildProcess(inspectorFlags, scriptContents, scriptFile) {
const args = [].concat(inspectorFlags);
if (scriptContents) {
args.push('-e', scriptContents);
} else {
args.push(scriptFile);
}
const child = spawn(process.execPath, args);
const wsHeaderBuf = Buffer.allocUnsafe(16);
wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80;
const bodyLen = messageBuf.length;
const handler = tearDown.bind(null, child);
process.on('exit', handler);
process.on('uncaughtException', handler);
process.on('unhandledRejection', handler);
process.on('SIGINT', handler);
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;
return child;
}
wsHeaderBuf.writeUInt8(byte2, 1);
wsHeaderBuf.writeUInt32BE(0x01020408, maskOffset);
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);
function makeBufferingDataCallback(dataCallback) {
let buffer = Buffer.alloc(0);
return (data) => {
const newData = Buffer.concat([buffer, data]);
const str = newData.toString('utf8');
const lines = str.replace(/\r/g, '').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,10 +74,9 @@ function parseWSFrame(buffer, handler) {
bodyOffset = 10;
}
if (buffer.length < bodyOffset + dataLen)
return 0;
return { length: 0, message };
const jsonPayload =
buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8');
let message;
try {
message = JSON.parse(jsonPayload);
} catch (e) {
@ -86,368 +85,285 @@ function parseWSFrame(buffer, handler) {
}
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);
}
const wsHeaderBuf = Buffer.allocUnsafe(16);
wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80;
const bodyLen = messageBuf.length;
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);
};
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 timeout(message, multiplicator) {
return setTimeout(common.mustNotCall(message),
TIMEOUT * (multiplicator || 1));
}
for (let i = 0; i < messageBuf.length; i++)
messageBuf[i] = messageBuf[i] ^ (1 << (i % 4));
function TestSession(socket, harness) {
this.mainScriptPath = harness.mainScriptPath;
this.mainScriptId = null;
return Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]);
}
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;
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]);
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_();
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);
});
}
TestSession.prototype.scriptUrlForId = function(id) {
return this.scripts_[id];
};
waitForServerDisconnect() {
return this._terminationPromise;
}
TestSession.prototype.processMessage_ = function(message) {
if (message === null) {
sendEnd(this.socket_);
return;
disconnect() {
this._socket.destroy();
}
const method = message['method'];
if (method === 'Debugger.scriptParsed') {
_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 {
if (message.method === 'Debugger.scriptParsed') {
const script = message['params'];
const scriptId = script['scriptId'];
const url = script['url'];
this.scripts_[scriptId] = url;
if (url === mainScript)
this._scriptsIdsByUrl.set(scriptId, url);
if (url === _MAINSCRIPT)
this.mainScriptId = scriptId;
}
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;
}
}
};
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;
if (this._notificationCallback) {
// In case callback needs to install another
const callback = this._notificationCallback;
this._notificationCallback = null;
callback(message);
} else {
id += 100000;
this.lastId_--;
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);
});
});
};
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');
}
});
});
};
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());
};
_sendMessage(message) {
const msg = JSON.parse(JSON.stringify(message)); // Clone!
msg['id'] = this._nextId++;
if (DEBUG)
console.log('[sent]', JSON.stringify(msg));
TestSession.prototype.expectMessages = function(expects) {
if (!(expects instanceof Array)) expects = [ expects ];
const responsePromise = new Promise((resolve, reject) => {
this._commandResponsePromises.set(msg['id'], { resolve, reject });
});
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();
return new Promise(
(resolve) => this._socket.write(formatWSFrame(msg), resolve))
.then(() => responsePromise);
}
};
return this;
};
TestSession.prototype.expectStderrOutput = function(regexp) {
this.harness_.addStderrFilter(
regexp,
this.createCallbackWithTimeout_(`Timed out waiting for ${regexp}`));
return this;
};
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);
}
}
TestSession.prototype.runNext_ = function() {
if (this.task_) {
setImmediate(() => {
this.task_(() => {
this.task_ = this.task_.next_;
this.runNext_();
});
});
waitForNotification(methodOrPredicate, description) {
const desc = description || methodOrPredicate;
const message = `Timed out waiting for matching notification (${desc}))`;
return common.fires(
this._asyncWaitForNotification(methodOrPredicate), message, TIMEOUT);
}
};
TestSession.prototype.enqueue = function(task) {
if (!this.task_) {
this.task_ = task;
this.runNext_();
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 {
let t = this.task_;
while (t.next_)
t = t.next_;
t.next_ = task;
notification = await new Promise(
(resolve) => this._notificationCallback = resolve);
}
} while (!predicate(notification));
return notification;
}
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();
}));
};
_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;
}
}
function Harness(port, childProcess) {
this.port = port;
this.mainScriptPath = mainScript;
this.stderrFilters_ = [];
this.process_ = childProcess;
this.result_ = {};
this.running_ = true;
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);
}
childProcess.stdout.on('data', makeBufferingDataCallback(
(line) => console.log('[out]', line)));
_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;
}
}
}
waitForConsoleOutput(type, values) {
const desc = `Console output matching ${JSON.stringify(values)}`;
return this.waitForNotification(
(notification) => this._matchesConsoleOutputNotification(notification,
type, values),
desc);
}
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 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.addStderrFilter = function(regexp, callback) {
this.stderrFilters_.push((message) => {
if (message.match(regexp)) {
callback();
return true;
}
});
};
Harness.prototype.assertStillAlive = function() {
assert.strictEqual(this.running_, true,
`Child died: ${JSON.stringify(this.result_)}`);
};
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)));
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_();
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;
});
});
};
}
Harness.prototype.enqueue_ = function(task, expectAlive) {
task[EXPECT_ALIVE_SYMBOL] = !!expectAlive;
if (!this.task_) {
this.task_ = task;
this.run_();
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 {
let chain = this.task_;
while (chain.next_)
chain = chain.next_;
chain.next_ = task;
this._unprocessedStderrLines.push(line);
}
return this;
};
Harness.prototype.testHttpResponse = function(host, path, check, errorcb) {
return this.enqueue_((doneCallback) => {
function wrap(callback) {
if (callback) {
return function() {
callback(...arguments);
doneCallback();
};
}
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;
}
checkHttpResponse(host, this.port, path, wrap(check), wrap(errorcb));
});
};
}
Harness.prototype.wsHandshake = function(devtoolsUrl, tests, readyCallback) {
wsHandshake(devtoolsUrl) {
return this.portPromise.then((port) => new Promise((resolve) => {
http.get({
port: this.port,
port,
path: url.parse(devtoolsUrl).path,
headers: {
'Connection': 'Upgrade',
@ -456,100 +372,39 @@ Harness.prototype.wsHandshake = function(devtoolsUrl, tests, readyCallback) {
'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);
resolve(new InspectorSession(socket, this));
}).on('response', common.mustNotCall('Upgrade was not received'));
};
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);
});
});
};
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();
});
} else {
assert.strictEqual(errorCode, this.result_.code);
callback();
}));
}
}, 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);
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);
}
const child = spawn(process.execPath, args);
const timeoutId = timeout('Child process did not start properly', 4);
let found = false;
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));
});
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;');

311
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']) {
function assertScopeValues({ result }, expected) {
const unmatched = new Set(Object.keys(expected));
for (const actual of result) {
const value = expected[actual['name']];
if (value)
if (value) {
assert.strictEqual(value, actual['value']['value']);
unmatched.delete(actual['name']);
}
};
}
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;
};
if (unmatched.size)
assert.fail(Array.from(unmatched.values()));
}
function testBreakpointOnStart(session) {
async function testBreakpointOnStart(session) {
console.log('[test]',
'Verifying debugger stops on start (--inspect-brk option)');
const commands = [
@ -119,40 +74,34 @@ 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([
[
{
const response = await session.send({
'method': 'Runtime.getProperties',
'params': {
'objectId': scopeId,
@ -160,10 +109,10 @@ function testInspectScope(session) {
'accessorPropertiesOnly': false,
'generatePreview': true
}
}, setupExpectScopeValues({ t: 1001, k: 1 })
],
[
{
});
assertScopeValues(response, { t: 1001, k: 1 });
let { result } = await session.send({
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': 'k + t',
@ -173,32 +122,22 @@ function testInspectScope(session) {
'returnByValue': false,
'generatePreview': true
}
}, setupExpectValue(1002)
],
[
{
});
assert.strictEqual(1002, result['value']);
result = (await session.send({
'method': 'Runtime.evaluate', 'params': {
'expression': '5 * 5'
}
}, (message) => assert.strictEqual(25, message['result']['value'])
],
]);
}
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'));
});
})).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([
{
session.send({
'method': 'Debugger.evaluateOnCallFrame', 'params': {
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
'expression': `console.log("${chars}")`,
@ -208,32 +147,31 @@ function testI18NCharacters(session) {
'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
// 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
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
}
],
[ // the global require has the same properties as a normal `require`
});
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': [
@ -243,12 +181,11 @@ function testCommandLineAPI(session) {
].join(' && '),
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
}
],
[ // `require` twice returns the same value
});
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
@ -260,12 +197,11 @@ function testCommandLineAPI(session) {
) === require(${testModuleStr})`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
}
],
[ // after require the module appears in require.cache
});
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(
@ -273,46 +209,42 @@ function testCommandLineAPI(session) {
)`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']),
});
checkException(result);
assert.deepStrictEqual(JSON.parse(result['result']['value']),
{ old: 'yes' });
}
],
[ // remove module from require.cache
// remove module from require.cache
result = await session.send(
{
'method': 'Runtime.evaluate', 'params': {
'expression': `delete require.cache[${testModuleStr}]`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.strictEqual(message['result']['value'], true);
}
],
[ // require again, should get fresh (empty) exports
});
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
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']), {});
}
],
[ // require 2nd module, exports an empty object
});
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
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']), {});
}
],
[ // both modules end up with the same module.parent
});
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({
@ -323,15 +255,14 @@ function testCommandLineAPI(session) {
})`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.deepStrictEqual(JSON.parse(message['result']['value']), {
});
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`
// 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}',
@ -341,40 +272,32 @@ function testCommandLineAPI(session) {
)`,
'includeCommandLineAPI': true
}
}, (message) => {
checkException(message);
assert.notStrictEqual(message['result']['value'],
});
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