Revert "inspector: rewrite inspector test helper"
This reverts commit 2296b677fb1e2ea71e86a899b028ba9e817e86ad. 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>
This commit is contained in:
parent
f422e38bca
commit
a6973a3811
@ -99,15 +99,6 @@ Tests whether `name` and `expected` are part of a raised warning.
|
||||
|
||||
Checks if `pathname` exists
|
||||
|
||||
### fires(promise, [error], [timeoutMs])
|
||||
* promise [<Promise]
|
||||
* error [<String] default = 'timeout'
|
||||
* timeoutMs [<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 [<String>]
|
||||
|
||||
|
@ -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
|
||||
]);
|
||||
};
|
||||
|
@ -4,86 +4,21 @@ 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);
|
||||
|
||||
const handler = tearDown.bind(null, child);
|
||||
process.on('exit', handler);
|
||||
process.on('uncaughtException', handler);
|
||||
process.on('unhandledRejection', handler);
|
||||
process.on('SIGINT', handler);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function makeBufferingDataCallback(dataCallback) {
|
||||
let buffer = Buffer.alloc(0);
|
||||
return (data) => {
|
||||
const newData = Buffer.concat([buffer, data]);
|
||||
const str = newData.toString('utf8');
|
||||
const lines = str.split('\n');
|
||||
if (str.endsWith('\n'))
|
||||
buffer = Buffer.alloc(0);
|
||||
else
|
||||
buffer = Buffer.from(lines.pop(), 'utf8');
|
||||
for (const line of lines)
|
||||
dataCallback(line);
|
||||
};
|
||||
}
|
||||
|
||||
function tearDown(child, err) {
|
||||
child.kill();
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function parseWSFrame(buffer) {
|
||||
// Protocol described in https://tools.ietf.org/html/rfc6455#section-5
|
||||
let message = null;
|
||||
if (buffer.length < 2)
|
||||
return { length: 0, message };
|
||||
if (buffer[0] === 0x88 && buffer[1] === 0x00) {
|
||||
return { length: 2, message, closed: true };
|
||||
}
|
||||
assert.strictEqual(0x81, buffer[0]);
|
||||
let dataLen = 0x7F & buffer[1];
|
||||
let bodyOffset = 2;
|
||||
if (buffer.length < bodyOffset + dataLen)
|
||||
return 0;
|
||||
if (dataLen === 126) {
|
||||
dataLen = buffer.readUInt16BE(2);
|
||||
bodyOffset = 4;
|
||||
} else if (dataLen === 127) {
|
||||
assert(buffer[2] === 0 && buffer[3] === 0, 'Inspector message too big');
|
||||
dataLen = buffer.readUIntBE(4, 6);
|
||||
bodyOffset = 10;
|
||||
}
|
||||
if (buffer.length < bodyOffset + dataLen)
|
||||
return { length: 0, message };
|
||||
message = JSON.parse(
|
||||
buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8'));
|
||||
function send(socket, message, id, callback) {
|
||||
const msg = JSON.parse(JSON.stringify(message)); // Clone!
|
||||
msg['id'] = id;
|
||||
if (DEBUG)
|
||||
console.log('[received]', JSON.stringify(message));
|
||||
return { length: bodyOffset + dataLen, message };
|
||||
}
|
||||
|
||||
function formatWSFrame(message) {
|
||||
const messageBuf = Buffer.from(JSON.stringify(message));
|
||||
console.log('[sent]', JSON.stringify(msg));
|
||||
const messageBuf = Buffer.from(JSON.stringify(msg));
|
||||
|
||||
const wsHeaderBuf = Buffer.allocUnsafe(16);
|
||||
wsHeaderBuf.writeUInt8(0x81, 0);
|
||||
@ -108,297 +43,513 @@ function formatWSFrame(message) {
|
||||
|
||||
for (let i = 0; i < messageBuf.length; i++)
|
||||
messageBuf[i] = messageBuf[i] ^ (1 << (i % 4));
|
||||
|
||||
return Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]);
|
||||
socket.write(
|
||||
Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]),
|
||||
callback);
|
||||
}
|
||||
|
||||
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();
|
||||
function sendEnd(socket) {
|
||||
socket.write(Buffer.from([0x88, 0x80, 0x2D, 0x0E, 0x1E, 0xFA]));
|
||||
}
|
||||
|
||||
let buffer = Buffer.alloc(0);
|
||||
socket.on('data', (data) => {
|
||||
buffer = Buffer.concat([buffer, data]);
|
||||
do {
|
||||
const { length, message, closed } = parseWSFrame(buffer);
|
||||
if (!length)
|
||||
break;
|
||||
function parseWSFrame(buffer, handler) {
|
||||
// Protocol described in https://tools.ietf.org/html/rfc6455#section-5
|
||||
if (buffer.length < 2)
|
||||
return 0;
|
||||
if (buffer[0] === 0x88 && buffer[1] === 0x00) {
|
||||
handler(null);
|
||||
return 2;
|
||||
}
|
||||
assert.strictEqual(0x81, buffer[0]);
|
||||
let dataLen = 0x7F & buffer[1];
|
||||
let bodyOffset = 2;
|
||||
if (buffer.length < bodyOffset + dataLen)
|
||||
return 0;
|
||||
if (dataLen === 126) {
|
||||
dataLen = buffer.readUInt16BE(2);
|
||||
bodyOffset = 4;
|
||||
} else if (dataLen === 127) {
|
||||
assert(buffer[2] === 0 && buffer[3] === 0, 'Inspector message too big');
|
||||
dataLen = buffer.readUIntBE(4, 6);
|
||||
bodyOffset = 10;
|
||||
}
|
||||
if (buffer.length < bodyOffset + dataLen)
|
||||
return 0;
|
||||
const jsonPayload =
|
||||
buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8');
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(jsonPayload);
|
||||
} catch (e) {
|
||||
console.error(`JSON.parse() failed for: ${jsonPayload}`);
|
||||
throw e;
|
||||
}
|
||||
if (DEBUG)
|
||||
console.log('[received]', JSON.stringify(message));
|
||||
handler(message);
|
||||
return bodyOffset + dataLen;
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
socket.write(Buffer.from([0x88, 0x00])); // WS close frame
|
||||
function tearDown(child, err) {
|
||||
child.kill();
|
||||
if (err instanceof Error) {
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
buffer = buffer.slice(length);
|
||||
if (message)
|
||||
this._onMessage(message);
|
||||
} while (true);
|
||||
});
|
||||
this._terminationPromise = new Promise((resolve) => {
|
||||
socket.once('close', resolve);
|
||||
});
|
||||
}
|
||||
callback(err, json);
|
||||
});
|
||||
});
|
||||
if (errorcb)
|
||||
req.on('error', errorcb);
|
||||
}
|
||||
|
||||
waitForServerDisconnect() {
|
||||
return this._terminationPromise;
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this._socket.destroy();
|
||||
}
|
||||
function timeout(message, multiplicator) {
|
||||
return setTimeout(common.mustNotCall(message),
|
||||
TIMEOUT * (multiplicator || 1));
|
||||
}
|
||||
|
||||
_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._scriptsIdsByUrl.set(scriptId, url);
|
||||
if (url === _MAINSCRIPT)
|
||||
this.mainScriptId = scriptId;
|
||||
}
|
||||
function TestSession(socket, harness) {
|
||||
this.mainScriptPath = harness.mainScriptPath;
|
||||
this.mainScriptId = null;
|
||||
|
||||
if (this._notificationCallback) {
|
||||
// In case callback needs to install another
|
||||
const callback = this._notificationCallback;
|
||||
this._notificationCallback = null;
|
||||
callback(message);
|
||||
} else {
|
||||
this._unprocessedNotifications.push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
_sendMessage(message) {
|
||||
const msg = JSON.parse(JSON.stringify(message)); // Clone!
|
||||
msg['id'] = this._nextId++;
|
||||
if (DEBUG)
|
||||
console.log('[sent]', JSON.stringify(msg));
|
||||
|
||||
const responsePromise = new Promise((resolve, reject) => {
|
||||
this._commandResponsePromises.set(msg['id'], { resolve, reject });
|
||||
});
|
||||
|
||||
return new Promise(
|
||||
(resolve) => this._socket.write(formatWSFrame(msg), resolve))
|
||||
.then(() => responsePromise);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
waitForNotification(methodOrPredicate, description) {
|
||||
const desc = description || methodOrPredicate;
|
||||
const message = `Timed out waiting for matching notification (${desc}))`;
|
||||
return common.fires(
|
||||
this._asyncWaitForNotification(methodOrPredicate), message, TIMEOUT);
|
||||
}
|
||||
|
||||
async _asyncWaitForNotification(methodOrPredicate) {
|
||||
function matchMethod(notification) {
|
||||
return notification.method === methodOrPredicate;
|
||||
}
|
||||
const predicate =
|
||||
typeof methodOrPredicate === 'string' ? matchMethod : methodOrPredicate;
|
||||
let notification = null;
|
||||
let buffer = Buffer.alloc(0);
|
||||
socket.on('data', (data) => {
|
||||
buffer = Buffer.concat([buffer, data]);
|
||||
let consumed;
|
||||
do {
|
||||
if (this._unprocessedNotifications.length) {
|
||||
notification = this._unprocessedNotifications.shift();
|
||||
} else {
|
||||
notification = await new Promise(
|
||||
(resolve) => this._notificationCallback = resolve);
|
||||
}
|
||||
} while (!predicate(notification));
|
||||
return notification;
|
||||
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_();
|
||||
});
|
||||
}
|
||||
|
||||
TestSession.prototype.scriptUrlForId = function(id) {
|
||||
return this.scripts_[id];
|
||||
};
|
||||
|
||||
TestSession.prototype.processMessage_ = function(message) {
|
||||
if (message === null) {
|
||||
sendEnd(this.socket_);
|
||||
return;
|
||||
}
|
||||
|
||||
_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']);
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
id += 100000;
|
||||
this.lastId_--;
|
||||
}
|
||||
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());
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
TestSession.prototype.runNext_ = function() {
|
||||
if (this.task_) {
|
||||
setImmediate(() => {
|
||||
this.task_(() => {
|
||||
this.task_ = this.task_.next_;
|
||||
this.runNext_();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TestSession.prototype.enqueue = function(task) {
|
||||
if (!this.task_) {
|
||||
this.task_ = task;
|
||||
this.runNext_();
|
||||
} else {
|
||||
let t = this.task_;
|
||||
while (t.next_)
|
||||
t = t.next_;
|
||||
t.next_ = task;
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
Harness.prototype.addStderrFilter = function(regexp, callback) {
|
||||
this.stderrFilters_.push((message) => {
|
||||
if (message.match(regexp)) {
|
||||
callback();
|
||||
return 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);
|
||||
}
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
class NodeInstance {
|
||||
constructor(inspectorFlags = ['--inspect-brk=0'],
|
||||
scriptContents = '',
|
||||
scriptFile = _MAINSCRIPT) {
|
||||
this._portCallback = null;
|
||||
this.portPromise = new Promise((resolve) => this._portCallback = resolve);
|
||||
this._process = spawnChildProcess(inspectorFlags, scriptContents,
|
||||
scriptFile);
|
||||
this._running = true;
|
||||
this._stderrLineCallback = null;
|
||||
this._unprocessedStderrLines = [];
|
||||
|
||||
this._process.stdout.on('data', makeBufferingDataCallback(
|
||||
(line) => console.log('[out]', line)));
|
||||
|
||||
this._process.stderr.on('data', makeBufferingDataCallback(
|
||||
(message) => this.onStderrLine(message)));
|
||||
|
||||
this._shutdownPromise = new Promise((resolve) => {
|
||||
this._process.once('exit', (exitCode, signal) => {
|
||||
resolve({ exitCode, signal });
|
||||
this._running = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onStderrLine(line) {
|
||||
console.log('[err]', line);
|
||||
if (this._portCallback) {
|
||||
const matches = line.match(/Debugger listening on ws:\/\/.+:(\d+)\/.+/);
|
||||
if (matches)
|
||||
this._portCallback(matches[1]);
|
||||
this._portCallback = null;
|
||||
}
|
||||
if (this._stderrLineCallback) {
|
||||
this._stderrLineCallback(line);
|
||||
this._stderrLineCallback = null;
|
||||
} else {
|
||||
this._unprocessedStderrLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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'));
|
||||
}));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
expectShutdown() {
|
||||
return this._shutdownPromise;
|
||||
}
|
||||
|
||||
nextStderrString() {
|
||||
if (this._unprocessedStderrLines.length)
|
||||
return Promise.resolve(this._unprocessedStderrLines.shift());
|
||||
return new Promise((resolve) => this._stderrLineCallback = resolve);
|
||||
}
|
||||
|
||||
kill() {
|
||||
this._process.kill();
|
||||
}
|
||||
}
|
||||
|
||||
function readMainScriptSource() {
|
||||
return fs.readFileSync(_MAINSCRIPT, 'utf8');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mainScriptPath: _MAINSCRIPT,
|
||||
readMainScriptSource,
|
||||
NodeInstance
|
||||
});
|
||||
};
|
||||
|
||||
Harness.prototype.assertStillAlive = function() {
|
||||
assert.strictEqual(this.running_, true,
|
||||
`Child died: ${JSON.stringify(this.result_)}`);
|
||||
};
|
||||
|
||||
Harness.prototype.run_ = function() {
|
||||
setImmediate(() => {
|
||||
if (!this.task_[EXPECT_ALIVE_SYMBOL])
|
||||
this.assertStillAlive();
|
||||
this.task_(() => {
|
||||
this.task_ = this.task_.next_;
|
||||
if (this.task_)
|
||||
this.run_();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Harness.prototype.enqueue_ = function(task, expectAlive) {
|
||||
task[EXPECT_ALIVE_SYMBOL] = !!expectAlive;
|
||||
if (!this.task_) {
|
||||
this.task_ = task;
|
||||
this.run_();
|
||||
} else {
|
||||
let chain = this.task_;
|
||||
while (chain.next_)
|
||||
chain = chain.next_;
|
||||
chain.next_ = task;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
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));
|
||||
});
|
||||
};
|
||||
|
||||
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'));
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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');
|
||||
};
|
||||
|
||||
exports.markMessageNoResponse = function(message) {
|
||||
message[DONT_EXPECT_RESPONSE_SYMBOL] = true;
|
||||
};
|
||||
|
@ -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();
|
@ -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();
|
@ -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();
|
@ -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
Normal file
128
test/inspector/test-inspector-break-when-eval.js
Normal file
@ -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
Normal file
59
test/inspector/test-inspector-debug-brk.js
Normal file
@ -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
Normal file
64
test/inspector/test-inspector-exception.js
Normal file
@ -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
Normal file
51
test/inspector/test-inspector-ip-detection.js
Normal file
@ -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');
|
21
test/inspector/test-inspector-stop-profile-after-done.js
Normal file
21
test/inspector/test-inspector-stop-profile-after-done.js
Normal file
@ -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;');
|
@ -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);
|
||||
|
||||
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 });
|
||||
|
||||
let { result } = await session.send({
|
||||
'method': 'Debugger.evaluateOnCallFrame', 'params': {
|
||||
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
|
||||
'expression': 'k + t',
|
||||
'objectGroup': 'console',
|
||||
'includeCommandLineAPI': true,
|
||||
'silent': false,
|
||||
'returnByValue': false,
|
||||
'generatePreview': true
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(1002, result['value']);
|
||||
|
||||
result = (await session.send({
|
||||
'method': 'Runtime.evaluate', 'params': {
|
||||
'expression': '5 * 5'
|
||||
}
|
||||
})).result;
|
||||
assert.strictEqual(25, result['value']);
|
||||
session
|
||||
.sendInspectorCommands(commands)
|
||||
.expectMessages([
|
||||
setupExpectConsoleOutput('log', ['A message', 5]),
|
||||
setupExpectBreakOnLine(5, session.mainScriptPath,
|
||||
session, (id) => scopeId = id),
|
||||
]);
|
||||
}
|
||||
|
||||
async function testI18NCharacters(session) {
|
||||
function testInspectScope(session) {
|
||||
console.log('[test]', 'Verify we can read current application state');
|
||||
session.sendInspectorCommands([
|
||||
[
|
||||
{
|
||||
'method': 'Runtime.getProperties',
|
||||
'params': {
|
||||
'objectId': scopeId,
|
||||
'ownProperties': false,
|
||||
'accessorPropertiesOnly': false,
|
||||
'generatePreview': true
|
||||
}
|
||||
}, setupExpectScopeValues({ t: 1001, k: 1 })
|
||||
],
|
||||
[
|
||||
{
|
||||
'method': 'Debugger.evaluateOnCallFrame', 'params': {
|
||||
'callFrameId': '{"ordinal":0,"injectedScriptId":1}',
|
||||
'expression': 'k + t',
|
||||
'objectGroup': 'console',
|
||||
'includeCommandLineAPI': true,
|
||||
'silent': false,
|
||||
'returnByValue': false,
|
||||
'generatePreview': true
|
||||
}
|
||||
}, setupExpectValue(1002)
|
||||
],
|
||||
[
|
||||
{
|
||||
'method': 'Runtime.evaluate', 'params': {
|
||||
'expression': '5 * 5'
|
||||
}
|
||||
}, (message) => assert.strictEqual(25, message['result']['value'])
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
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'));
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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();
|
@ -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);
|
||||
// 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();
|
||||
function shouldShutDown(session) {
|
||||
session
|
||||
.sendInspectorCommands([
|
||||
{ 'method': 'Debugger.enable' },
|
||||
{ 'method': 'Debugger.pause' },
|
||||
])
|
||||
.disconnect(true);
|
||||
}
|
||||
|
||||
common.crashOnUnhandledRejection();
|
||||
runTests();
|
||||
function runTests(harness) {
|
||||
// 1 second wait to make sure the inferior began running the script
|
||||
setTimeout(() => harness.runFrontendSession([shouldShutDown]).kill(), 1000);
|
||||
}
|
||||
|
||||
const script = 'setInterval(() => {debugger;}, 60000);';
|
||||
helper.startNodeForInspectorTest(runTests, '--inspect', script);
|
||||
|
11
test/inspector/test-off-no-session.js
Normal file
11
test/inspector/test-off-no-session.js
Normal file
@ -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
Normal file
24
test/inspector/test-off-with-session-then-on.js
Normal file
@ -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);
|
@ -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…
x
Reference in New Issue
Block a user