summaryrefslogtreecommitdiff
path: root/test/parallel/test-worker-debug.js
diff options
context:
space:
mode:
authorEugene Ostroukhov <eostroukhov@google.com>2018-09-08 19:45:10 -0700
committerEugene Ostroukhov <eostroukhov@google.com>2018-09-18 09:01:33 -0700
commitf28c6f7eef58e7c3133bb2cd457d05b986194ba3 (patch)
treeb8e8583ff735a3b0721831af51c4f92d967849f1 /test/parallel/test-worker-debug.js
parentba0b4e43e442926bfb9389a42aa7393f91e6748a (diff)
downloadandroid-node-v8-f28c6f7eef58e7c3133bb2cd457d05b986194ba3.tar.gz
android-node-v8-f28c6f7eef58e7c3133bb2cd457d05b986194ba3.tar.bz2
android-node-v8-f28c6f7eef58e7c3133bb2cd457d05b986194ba3.zip
inspector: workers debugging
Introduce a NodeTarget inspector domain modelled after ChromeDevTools Target domain. It notifies inspector frontend attached to a main V8 isolate when workers are starting and allows passing messages to inspectors on their isolates. All inspector functionality is enabled on worker isolates. PR-URL: https://github.com/nodejs/node/pull/21364 Reviewed-By: Aleksei Koziatinskii <ak239spb@gmail.com> Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'test/parallel/test-worker-debug.js')
-rw-r--r--test/parallel/test-worker-debug.js228
1 files changed, 228 insertions, 0 deletions
diff --git a/test/parallel/test-worker-debug.js b/test/parallel/test-worker-debug.js
new file mode 100644
index 0000000000..8691879f14
--- /dev/null
+++ b/test/parallel/test-worker-debug.js
@@ -0,0 +1,228 @@
+// Flags: --experimental-worker
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const EventEmitter = require('events');
+const { Session } = require('inspector');
+const { pathToFileURL } = require('url');
+const {
+ Worker, isMainThread, parentPort, workerData
+} = require('worker_threads');
+
+
+const workerMessage = 'This is a message from a worker';
+
+function waitForMessage() {
+ return new Promise((resolve) => {
+ parentPort.once('message', resolve);
+ });
+}
+
+// This is at the top so line numbers change less often
+if (!isMainThread) {
+ if (workerData === 1) {
+ console.log(workerMessage);
+ debugger; // eslint-disable-line no-debugger
+ } else if (workerData === 2) {
+ parentPort.postMessage('running');
+ waitForMessage();
+ }
+ return;
+}
+
+function doPost(session, method, params) {
+ return new Promise((resolve, reject) => {
+ session.post(method, params, (error, result) => {
+ if (error)
+ reject(JSON.stringify(error));
+ else
+ resolve(result);
+ });
+ });
+}
+
+function waitForEvent(emitter, event) {
+ return new Promise((resolve) => emitter.once(event, resolve));
+}
+
+function waitForWorkerAttach(session) {
+ return waitForEvent(session, 'NodeWorker.attachedToWorker')
+ .then(({ params }) => params);
+}
+
+async function waitForWorkerDetach(session, id) {
+ let sessionId;
+ do {
+ const { params } =
+ await waitForEvent(session, 'NodeWorker.detachedFromWorker');
+ sessionId = params.sessionId;
+ } while (sessionId !== id);
+}
+
+function runWorker(id, workerCallback = () => {}) {
+ return new Promise((resolve, reject) => {
+ const worker = new Worker(__filename, { workerData: id });
+ workerCallback(worker);
+ worker.on('error', reject);
+ worker.on('exit', resolve);
+ });
+}
+
+class WorkerSession extends EventEmitter {
+ constructor(parentSession, id) {
+ super();
+ this._parentSession = parentSession;
+ this._id = id;
+ this._requestCallbacks = new Map();
+ this._nextCommandId = 1;
+ this._parentSession.on('NodeWorker.receivedMessageFromWorker',
+ ({ params }) => {
+ if (params.sessionId === this._id)
+ this._processMessage(JSON.parse(params.message));
+ });
+ }
+
+ _processMessage(message) {
+ if (message.id === undefined) {
+ // console.log(JSON.stringify(message));
+ this.emit('inspectorNotification', message);
+ this.emit(message.method, message);
+ return;
+ }
+ const callback = this._requestCallbacks.get(message.id);
+ if (callback) {
+ this._requestCallbacks.delete(message.id);
+ if (message.error)
+ callback[1](message.error.message);
+ else
+ callback[0](message.result);
+ }
+ }
+
+ async waitForBreakAfterCommand(command, script, line) {
+ const notificationPromise = waitForEvent(this, 'Debugger.paused');
+ this.post(command);
+ const notification = await notificationPromise;
+ const callFrame = notification.params.callFrames[0];
+ assert.strictEqual(callFrame.url, pathToFileURL(script).toString());
+ assert.strictEqual(callFrame.location.lineNumber, line);
+ }
+
+ post(method, parameters) {
+ const msg = {
+ id: this._nextCommandId++,
+ method
+ };
+ if (parameters)
+ msg.params = parameters;
+
+ return new Promise((resolve, reject) => {
+ this._requestCallbacks.set(msg.id, [resolve, reject]);
+ this._parentSession.post('NodeWorker.sendMessageToWorker', {
+ sessionId: this._id, message: JSON.stringify(msg)
+ });
+ });
+ }
+}
+
+async function testBasicWorkerDebug(session, post) {
+ /*
+ 1. Do 'enble' with waitForDebuggerOnStart = true
+ 2. Run worker. It should break on start.
+ 3. Enable Runtime (to get console message) and Debugger. Resume.
+ 4. Breaks on the 'debugger' statement. Resume.
+ 5. Console message recieved, worker runs to a completion.
+ 6. contextCreated/contextDestroyed had been properly dispatched
+ */
+ console.log('Test basic debug scenario');
+ await post('NodeWorker.enable', { waitForDebuggerOnStart: true });
+ const attached = waitForWorkerAttach(session);
+ const worker = runWorker(1);
+ const { sessionId, waitingForDebugger } = await attached;
+ assert.strictEqual(waitingForDebugger, true);
+ const detached = waitForWorkerDetach(session, sessionId);
+ const workerSession = new WorkerSession(session, sessionId);
+ const contextEvents = Promise.all([
+ waitForEvent(workerSession, 'Runtime.executionContextCreated'),
+ waitForEvent(workerSession, 'Runtime.executionContextDestroyed')
+ ]);
+ const consolePromise = waitForEvent(workerSession, 'Runtime.consoleAPICalled')
+ .then((notification) => notification.params.args[0].value);
+ await workerSession.post('Debugger.enable');
+ await workerSession.post('Runtime.enable');
+ await workerSession.waitForBreakAfterCommand(
+ 'Runtime.runIfWaitingForDebugger', __filename, 2);
+ await workerSession.waitForBreakAfterCommand(
+ 'Debugger.resume', __filename, 27); // V8 line number is zero-based
+ assert.strictEqual(await consolePromise, workerMessage);
+ workerSession.post('Debugger.resume');
+ await Promise.all([worker, detached, contextEvents]);
+}
+
+async function testNoWaitOnStart(session, post) {
+ console.log('Test disabled waitForDebuggerOnStart');
+ await post('NodeWorker.enable', { waitForDebuggerOnStart: false });
+ let worker;
+ const promise = waitForWorkerAttach(session);
+ const exitPromise = runWorker(2, (w) => { worker = w; });
+ const { waitingForDebugger } = await promise;
+ assert.strictEqual(waitingForDebugger, false);
+ worker.postMessage('resume');
+ await exitPromise;
+}
+
+async function testTwoWorkers(session, post) {
+ console.log('Test attach to a running worker and then start a new one');
+ await post('NodeWorker.disable');
+ let okToAttach = false;
+ const worker1attached = waitForWorkerAttach(session).then((notification) => {
+ assert.strictEqual(okToAttach, true);
+ return notification;
+ });
+
+ let worker1Exited;
+ const worker = await new Promise((resolve, reject) => {
+ worker1Exited = runWorker(2, resolve);
+ }).then((worker) => new Promise(
+ (resolve) => worker.once('message', () => resolve(worker))));
+ okToAttach = true;
+ await post('NodeWorker.enable', { waitForDebuggerOnStart: true });
+ const { waitingForDebugger: worker1Waiting } = await worker1attached;
+ assert.strictEqual(worker1Waiting, false);
+
+ const worker2Attached = waitForWorkerAttach(session);
+ let worker2Done = false;
+ const worker2Exited = runWorker(1)
+ .then(() => assert.strictEqual(worker2Done, true));
+ const worker2AttachInfo = await worker2Attached;
+ assert.strictEqual(worker2AttachInfo.waitingForDebugger, true);
+ worker2Done = true;
+
+ const workerSession = new WorkerSession(session, worker2AttachInfo.sessionId);
+ workerSession.post('Runtime.runIfWaitingForDebugger');
+ worker.postMessage('resume');
+ await Promise.all([worker1Exited, worker2Exited]);
+}
+
+async function test() {
+ const session = new Session();
+ session.connect();
+ const post = doPost.bind(null, session);
+
+ await testBasicWorkerDebug(session, post);
+
+ console.log('Test disabling attach to workers');
+ await post('NodeWorker.disable');
+ await runWorker(1);
+
+ await testNoWaitOnStart(session, post);
+ await testTwoWorkers(session, post);
+
+ session.disconnect();
+ console.log('Test done');
+}
+
+test();