| 'use strict'; |
| const common = require('../common'); |
| |
| common.skipIfInspectorDisabled(); |
| |
| const assert = require('assert'); |
| const { Session } = require('inspector'); |
| const path = require('path'); |
| const { pathToFileURL } = require('url'); |
| const { isMainThread, parentPort, Worker, workerData } = |
| require('worker_threads'); |
| |
| if (!workerData) { |
| common.skipIfWorker(); |
| } |
| |
| function toDebug() { |
| let a = 1; |
| a = a + 1; |
| return a * 200; |
| } |
| |
| const messagesSent = []; |
| |
| async function post(session, method, params) { |
| return new Promise((resolve, reject) => { |
| session.post(method, params, (error, success) => { |
| messagesSent.push(method); |
| if (error) { |
| process._rawDebug(`Message ${method} produced an error`); |
| reject(error); |
| } else { |
| process._rawDebug(`Message ${method} was sent`); |
| resolve(success); |
| } |
| }); |
| }); |
| } |
| |
| async function waitForNotification(session, notification) { |
| return new Promise((resolve) => session.once(notification, resolve)); |
| } |
| |
| function startWorker(skipChild, sharedBuffer) { |
| return new Promise((resolve) => { |
| const worker = new Worker(__filename, { |
| workerData: { skipChild, sharedBuffer } |
| }); |
| worker.on('error', (e) => { |
| console.error(e); |
| throw e; |
| }); |
| // Add 2 promises to the worker, one resolved when a message with a |
| // .doConsoleLog property is received and one resolved when a .messagesSent |
| // property is received. |
| let resolveConsoleRequest; |
| let resolveMessagesSent; |
| worker.onConsoleRequest = |
| new Promise((resolve) => resolveConsoleRequest = resolve); |
| worker.onMessagesSent = |
| new Promise((resolve) => resolveMessagesSent = resolve); |
| worker.on('message', (m) => { |
| resolve(worker); |
| if (m.doConsoleLog) resolveConsoleRequest(); |
| if (m.messagesSent) resolveMessagesSent(m.messagesSent); |
| }); |
| }); |
| } |
| |
| function doConsoleLog(arrayBuffer) { |
| console.log('Message for a test'); |
| arrayBuffer[0] = 128; |
| } |
| |
| // This tests that inspector callbacks are called in a microtask |
| // and do not interrupt the main code. Interrupting the code flow |
| // can lead to unexpected behaviors. |
| async function ensureListenerDoesNotInterrupt(session) { |
| // Make sure that the following code is not affected by the fact that it may |
| // run inside an inspector message callback, during which other inspector |
| // message callbacks (such as the one triggered by doConsoleLog()) would |
| // not be processed. |
| await new Promise(setImmediate); |
| |
| const currentTime = Date.now(); |
| let consoleLogHappened = false; |
| session.once('Runtime.consoleAPICalled', |
| () => { consoleLogHappened = true; }); |
| const buf = new Uint8Array(workerData.sharedBuffer); |
| parentPort.postMessage({ doConsoleLog: true }); |
| while (buf[0] === 1) { |
| // Making sure the console.log was executed |
| } |
| while ((Date.now() - currentTime) < 50) { |
| // Spin wait for 50ms, assume that was enough to get inspector message |
| } |
| assert.strictEqual(consoleLogHappened, false); |
| await new Promise(queueMicrotask); |
| assert.strictEqual(consoleLogHappened, true); |
| } |
| |
| async function main() { |
| const sharedBuffer = new SharedArrayBuffer(1); |
| const arrayBuffer = new Uint8Array(sharedBuffer); |
| arrayBuffer[0] = 1; |
| const worker = await startWorker(false, sharedBuffer); |
| worker.onConsoleRequest.then(doConsoleLog.bind(null, arrayBuffer)); |
| assert.strictEqual(toDebug(), 400); |
| assert.deepStrictEqual(await worker.onMessagesSent, [ |
| 'Debugger.enable', |
| 'Runtime.enable', |
| 'Debugger.setBreakpointByUrl', |
| 'Debugger.evaluateOnCallFrame', |
| 'Debugger.resume' |
| ]); |
| } |
| |
| async function childMain() { |
| // Ensures the worker does not terminate too soon |
| parentPort.on('message', () => { }); |
| await (await startWorker(true)).onMessagesSent; |
| const session = new Session(); |
| session.connectToMainThread(); |
| await post(session, 'Debugger.enable'); |
| await post(session, 'Runtime.enable'); |
| await post(session, 'Debugger.setBreakpointByUrl', { |
| 'lineNumber': 18, |
| 'url': pathToFileURL(path.resolve(__dirname, __filename)).toString(), |
| 'columnNumber': 0, |
| 'condition': '' |
| }); |
| const pausedPromise = waitForNotification(session, 'Debugger.paused'); |
| parentPort.postMessage('Ready'); |
| const callFrameId = (await pausedPromise).params.callFrames[0].callFrameId; |
| |
| // Delay to ensure main thread is truly suspended |
| await new Promise((resolve) => setTimeout(resolve, 50)); |
| |
| const { result: { value } } = |
| await post(session, |
| 'Debugger.evaluateOnCallFrame', |
| { callFrameId, expression: 'a * 100' }); |
| assert.strictEqual(value, 100); |
| await post(session, 'Debugger.resume'); |
| await ensureListenerDoesNotInterrupt(session); |
| parentPort.postMessage({ messagesSent }); |
| parentPort.close(); |
| console.log('Worker is done'); |
| } |
| |
| async function skipChildMain() { |
| // Ensures the worker does not terminate too soon |
| parentPort.on('message', () => { }); |
| |
| const session = new Session(); |
| session.connectToMainThread(); |
| const notifications = []; |
| session.on('NodeWorker.attachedToWorker', (n) => notifications.push(n)); |
| await post(session, 'NodeWorker.enable', { waitForDebuggerOnStart: false }); |
| // 2 notifications mean there are 2 workers so we are connected to a main |
| // thread |
| assert.strictEqual(notifications.length, 2); |
| parentPort.postMessage('Ready'); |
| parentPort.postMessage({ messagesSent }); |
| parentPort.close(); |
| console.log('Skip child is done'); |
| } |
| |
| if (isMainThread) { |
| main().then(common.mustCall()); |
| } else if (workerData.skipChild) { |
| skipChildMain().then(common.mustCall()); |
| } else { |
| childMain().then(common.mustCall()); |
| } |