Browse Source

Revert "inspector: rewrite inspector test helper"

This reverts commit 2296b677fb.

That commit was landed without a green CI and is failing on Windows.

Ref: https://github.com/nodejs/node/pull/14460
PR-URL: https://github.com/nodejs/node/pull/14777
Reviewed-By: Refael Ackermann <refack@gmail.com>
v6
Anna Henningsen 7 years ago
parent
commit
a6973a3811
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  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. 0
      test/inspector/test-inspector-port-cluster.js
  13. 0
      test/inspector/test-inspector-port-zero-cluster.js
  14. 0
      test/inspector/test-inspector-port-zero.js
  15. 21
      test/inspector/test-inspector-stop-profile-after-done.js
  16. 0
      test/inspector/test-inspector-stops-no-file.js
  17. 491
      test/inspector/test-inspector.js
  18. 48
      test/inspector/test-ip-detection.js
  19. 28
      test/inspector/test-not-blocked-on-idle.js
  20. 11
      test/inspector/test-off-no-session.js
  21. 24
      test/inspector/test-off-with-session-then-on.js
  22. 30
      test/inspector/test-stop-profile-after-done.js

9
test/common/README.md

@ -99,15 +99,6 @@ 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,32 +814,6 @@ 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');
@ -853,19 +827,3 @@ 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');
const spawn = require('child_process').spawn;
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 spawnChildProcess(inspectorFlags, scriptContents, scriptFile) {
const args = [].concat(inspectorFlags);
if (scriptContents) {
args.push('-e', scriptContents);
} else {
args.push(scriptFile);
}
const child = spawn(process.execPath, args);
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 handler = tearDown.bind(null, child);
process.on('exit', handler);
process.on('uncaughtException', handler);
process.on('unhandledRejection', handler);
process.on('SIGINT', handler);
const wsHeaderBuf = Buffer.allocUnsafe(16);
wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80;
const bodyLen = messageBuf.length;
return child;
}
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 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);
};
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 tearDown(child, err) {
child.kill();
if (err) {
console.error(err);
process.exit(1);
}
function sendEnd(socket) {
socket.write(Buffer.from([0x88, 0x80, 0x2D, 0x0E, 0x1E, 0xFA]));
}
function parseWSFrame(buffer) {
function parseWSFrame(buffer, handler) {
// Protocol described in https://tools.ietf.org/html/rfc6455#section-5
let message = null;
if (buffer.length < 2)
return { length: 0, message };
return 0;
if (buffer[0] === 0x88 && buffer[1] === 0x00) {
return { length: 2, message, closed: true };
handler(null);
return 2;
}
assert.strictEqual(0x81, buffer[0]);
let dataLen = 0x7F & buffer[1];
@ -74,331 +74,482 @@ function parseWSFrame(buffer) {
bodyOffset = 10;
}
if (buffer.length < bodyOffset + dataLen)
return { length: 0, message };
message = JSON.parse(
buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8'));
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;
}
if (DEBUG)
console.log('[received]', JSON.stringify(message));
return { length: bodyOffset + dataLen, message };
handler(message);
return bodyOffset + dataLen;
}
function formatWSFrame(message) {
const messageBuf = Buffer.from(JSON.stringify(message));
function tearDown(child, err) {
child.kill();
if (err instanceof Error) {
console.error(err.stack);
process.exit(1);
}
}
const wsHeaderBuf = Buffer.allocUnsafe(16);
wsHeaderBuf.writeUInt8(0x81, 0);
let byte2 = 0x80;
const bodyLen = messageBuf.length;
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);
}
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 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);
};
}
for (let i = 0; i < messageBuf.length; i++)
messageBuf[i] = messageBuf[i] ^ (1 << (i % 4));
function timeout(message, multiplicator) {
return setTimeout(common.mustNotCall(message),
TIMEOUT * (multiplicator || 1));
}
return Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]);
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;
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_();
});
}
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);
});
}
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') {
const script = message['params'];
const scriptId = script['scriptId'];
const url = script['url'];
this.scripts_[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;
}
}
};
_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);
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;
} else {
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);
}
id += 100000;
this.lastId_--;
}
send(this.socket_, command, id,
() => this.sendAll_(commands.slice(1), 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.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);
});
});
};
const responsePromise = new Promise((resolve, reject) => {
this._commandResponsePromises.set(msg['id'], { resolve, reject });
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');
}
});
});
};
return new Promise(
(resolve) => this._socket.write(formatWSFrame(msg), resolve))
.then(() => responsePromise);
}
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());
};
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.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();
}
}
};
return this;
};
TestSession.prototype.expectStderrOutput = function(regexp) {
this.harness_.addStderrFilter(
regexp,
this.createCallbackWithTimeout_(`Timed out waiting for ${regexp}`));
return this;
};
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.runNext_ = function() {
if (this.task_) {
setImmediate(() => {
this.task_(() => {
this.task_ = this.task_.next_;
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 {
notification = await new Promise(
(resolve) => this._notificationCallback = resolve);
}
} while (!predicate(notification));
return notification;
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;
}
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;
});
}
_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']);
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_)}`);
};
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);
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;
}
return this;
};
_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;
Harness.prototype.testHttpResponse = function(host, path, check, errorcb) {
return this.enqueue_((doneCallback) => {
function wrap(callback) {
if (callback) {
return function() {
callback(...arguments);
doneCallback();
};
}
}
}
checkHttpResponse(host, this.port, path, wrap(check), wrap(errorcb));
});
};
waitForConsoleOutput(type, values) {
const desc = `Console output matching ${JSON.stringify(values)}`;
return this.waitForNotification(
(notification) => this._matchesConsoleOutputNotification(notification,
type, values),
desc);
}
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'));
};
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;
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);
});
while ((await this._instance.nextStderrString()) !==
'Waiting for the debugger to disconnect...');
await this.disconnect();
}
}
});
};
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;
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();
});
});
}
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 {
this._unprocessedStderrLines.push(line);
assert.strictEqual(errorCode, this.result_.code);
callback();
}
}
}, true);
};
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;
}
});
}
Harness.prototype.kill = function() {
return this.enqueue_((callback) => {
this.process_.kill();
callback();
});
};
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'));
}));
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);
expectShutdown() {
return this._shutdownPromise;
}
const timeoutId = timeout('Child process did not start properly', 4);
nextStderrString() {
if (this._unprocessedStderrLines.length)
return Promise.resolve(this._unprocessedStderrLines.shift());
return new Promise((resolve) => this._stderrLineCallback = resolve);
}
let found = false;
kill() {
this._process.kill();
}
}
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));
});
function readMainScriptSource() {
return fs.readFileSync(_MAINSCRIPT, 'utf8');
}
child.stderr.on('data', dataCallback);
const handler = tearDown.bind(null, child);
process.on('exit', handler);
process.on('uncaughtException', handler);
process.on('SIGINT', handler);
};
exports.mainScriptSource = function() {
return fs.readFileSync(mainScript, 'utf8');
};
module.exports = {
mainScriptPath: _MAINSCRIPT,
readMainScriptSource,
NodeInstance
exports.markMessageNoResponse = function(message) {
message[DONT_EXPECT_RESPONSE_SYMBOL] = true;
};

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

@ -1,68 +0,0 @@
'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

@ -1,41 +0,0 @@
'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

@ -1,46 +0,0 @@
'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

@ -1,45 +0,0 @@
'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

@ -0,0 +1,128 @@
'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

@ -0,0 +1,59 @@
'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

@ -0,0 +1,64 @@
'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

@ -0,0 +1,51 @@
'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');

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

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

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

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

@ -0,0 +1,21 @@
'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;');

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

491
test/inspector/test-inspector.js

@ -4,11 +4,12 @@ const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { mainScriptPath,
readMainScriptSource,
NodeInstance } = require('./inspector-helper.js');
const helper = require('./inspector-helper.js');
function checkListResponse(response) {
let scopeId;
function checkListResponse(err, response) {
assert.ifError(err);
assert.strictEqual(1, response.length);
assert.ok(response[0]['devtoolsFrontendUrl']);
assert.ok(
@ -16,7 +17,8 @@ function checkListResponse(response) {
.test(response[0]['webSocketDebuggerUrl']));
}
function checkVersion(response) {
function checkVersion(err, response) {
assert.ifError(err);
assert.ok(response);
const expected = {
'Browser': `node.js/${process.version}`,
@ -26,10 +28,10 @@ function checkVersion(response) {
JSON.stringify(expected));
}
function checkBadPath(err) {
function checkBadPath(err, response) {
assert(err instanceof SyntaxError);
assert(/Unexpected token/.test(err.message), err.message);
assert(/WebSockets request was expected/.test(err.body), err.body);
assert(/Unexpected token/.test(err.message));
assert(/WebSockets request was expected/.test(err.response));
}
function checkException(message) {
@ -37,26 +39,69 @@ function checkException(message) {
'An exception occurred during execution');
}
function assertNoUrlsWhileConnected(response) {
assert.strictEqual(1, response.length);
assert.ok(!response[0].hasOwnProperty('devtoolsFrontendUrl'));
assert.ok(!response[0].hasOwnProperty('webSocketDebuggerUrl'));
function expectMainScriptSource(result) {
const expected = helper.mainScriptSource();
const source = result['scriptSource'];
assert(source && (source.includes(expected)),
`Script source is wrong: ${source}`);
}
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']);
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;
}
}
if (unmatched.size)
assert.fail(Array.from(unmatched.values()));
};
}
async function testBreakpointOnStart(session) {
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 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 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) {
console.log('[test]',
'Verifying debugger stops on start (--inspect-brk option)');
const commands = [
@ -74,230 +119,262 @@ async function testBreakpointOnStart(session) {
{ 'method': 'Runtime.runIfWaitingForDebugger' }
];
await session.send(commands);
await session.waitForBreakOnLine(0, mainScriptPath);
session
.sendInspectorCommands(commands)
.expectMessages(setupExpectBreakOnLine(0, session.mainScriptPath, session));
}
async function testBreakpoint(session) {
function testSetBreakpointAndResume(session) {
console.log('[test]', 'Setting a breakpoint and verifying it is hit');
const commands = [
{ 'method': 'Debugger.setBreakpointByUrl',
'params': { 'lineNumber': 5,
'url': mainScriptPath,
'url': session.mainScriptPath,
'columnNumber': 0,
'condition': ''
}
},
{ 'method': 'Debugger.resume' },
[ { 'method': 'Debugger.getScriptSource',
'params': { 'scriptId': session.mainScriptId } },
expectMainScriptSource ],
];
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);
session
.sendInspectorCommands(commands)
.expectMessages([
setupExpectConsoleOutput('log', ['A message', 5]),
setupExpectBreakOnLine(5, session.mainScriptPath,
session, (id) => scopeId = id),
]);
}
function testInspectScope(session) {
console.log('[test]', 'Verify we can read current application state');
const response = await session.send({
'method': 'Runtime.getProperties',
'params': {
'objectId': scopeId,
'ownProperties': false,
'accessorPropertiesOnly': false,
'generatePreview': true
}
});
assertScopeValues(response, { t: 1001, k: 1 });
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'])
],
]);
}
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
}
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'));
});
assert.strictEqual(1002, result['value']);
result = (await session.send({
'method': 'Runtime.evaluate', 'params': {
'expression': '5 * 5'
}
})).result;
assert.strictEqual(25, result['value']);
}
async function testI18NCharacters(session) {
function testI18NCharacters(session) {
console.log('[test]', 'Verify sending and receiving UTF8 characters');
const chars = 'טֶ字и';
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
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
}
}
});
await session.waitForConsoleOutput('log', [chars]);
]).expectMessages([
setupExpectConsoleOutput('log', [chars]),
]);
}
async function testCommandLineAPI(session) {
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);
// 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
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);
}
});
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
],
[ // 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);
// `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
],
[ // `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);
// 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
],
[ // 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.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
],
[ // 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.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 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.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
],
[ // 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']), {});
// 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
],
[ // 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']), {
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
],
[ // 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.notStrictEqual(result['result']['value'],
'<inspector console>');
],
]);
}
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);
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);
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);
}
common.crashOnUnhandledRejection();
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);
}
runTest();
helper.startNodeForInspectorTest(runTests);

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

@ -1,48 +0,0 @@
'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 { NodeInstance } = require('./inspector-helper.js');
const helper = require('./inspector-helper.js');
async function runTests() {
const script = 'setInterval(() => {debugger;}, 60000);';
const node = new NodeInstance('--inspect=0', script);
function shouldShutDown(session) {
session
.sendInspectorCommands([
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.pause' },
])
.disconnect(true);
}
function runTests(harness) {
// 1 second wait to make sure the inferior began running the script
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();
setTimeout(() => harness.runFrontendSession([shouldShutDown]).kill(), 1000);
}
common.crashOnUnhandledRejection();
runTests();
const script = 'setInterval(() => {debugger;}, 60000);';
helper.startNodeForInspectorTest(runTests, '--inspect', script);

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

@ -0,0 +1,11 @@
'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

@ -0,0 +1,24 @@
'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);

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

@ -1,30 +0,0 @@
'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();
Loading…
Cancel
Save