node/test/parallel/test-inspector-async-hook-setup-at-signal.js
mary marchini 0161ad0baf
inspector: add NodeRuntime.waitingForDebugger event
`NodeRuntime.waitingForDebugger` is a new Inspector Protocol event that
will fire when the process being inspected is waiting for the debugger
(for example, when `inspector.waitForDebugger()` is called). This allows
inspecting processes to know when the inspected process is waiting for a
`Runtime.runIfWaitingForDebugger` message to resume execution. It allows
tooling to resume execution of the inspected process as soon as it deems
necessary, without having to guess if the inspected process is waiting
or not, making the workflow more deterministic. With a more
deterministic workflow, it is possible to update Node.js core tests to
avoid race conditions that can cause flakiness. Therefore, tests were
also changed as following:

  * Remove no-op Runtime.runIfWaitingForDebugger from tests that don't
    need it
  * Use NodeRuntime.waitingForDebugger in all tests that need
    Runtime.runIfWaitingForDebugger, to ensure order of operations is
    predictable and correct
  * Simplify test-inspector-multisession-ws

There might be value in adding `NodeWorker.waitingForDebugger` in a
future patch, but as of right now, no Node.js core inspector tests using
worker threads are not failing due to race conditions.

Fixes: https://github.com/nodejs/node/issues/34730
PR-URL: https://github.com/nodejs/node/pull/51560
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
2024-02-23 22:46:29 +00:00

84 lines
3.0 KiB
JavaScript

// Flags: --expose-internals
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
common.skipIf32Bits();
const { NodeInstance } = require('../common/inspector-helper.js');
const assert = require('assert');
common.skipIfWorker(); // Signal starts a server for a main thread inspector
const script = `
process._rawDebug('Waiting until a signal enables the inspector...');
let waiting = setInterval(waitUntilDebugged, 50);
function waitUntilDebugged() {
const { internalBinding } = require('internal/test/binding');
if (!internalBinding('inspector').isEnabled()) return;
clearInterval(waiting);
// At this point, even though the Inspector is enabled, the default async
// call stack depth is 0. We need a chance to call
// Debugger.setAsyncCallStackDepth *before* activating the actual timer for
// async stack traces to work. Directly using a debugger statement would be
// too brittle, and using a longer timeout would unnecessarily slow down the
// test on most machines. Triggering a debugger break through an interval is
// a faster and more reliable way.
process._rawDebug('Signal received, waiting for debugger setup');
waiting = setInterval(() => { debugger; }, 50);
}
// This function is called by the inspector client (session)
function setupTimeoutWithBreak() {
clearInterval(waiting);
process._rawDebug('Debugger ready, setting up timeout with a break');
setTimeout(() => { debugger; }, 50);
}
`;
async function waitForInitialSetup(session) {
console.error('[test]', 'Waiting for initial setup');
await session.waitForBreakOnLine(16, '[eval]');
}
async function setupTimeoutForStackTrace(session) {
console.error('[test]', 'Setting up timeout for async stack trace');
await session.send([
{ 'method': 'Runtime.evaluate',
'params': { expression: 'setupTimeoutWithBreak()' } },
{ 'method': 'Debugger.resume' },
]);
}
async function checkAsyncStackTrace(session) {
console.error('[test]', 'Verify basic properties of asyncStackTrace');
const paused = await session.waitForBreakOnLine(23, '[eval]');
assert(paused.params.asyncStackTrace,
`${Object.keys(paused.params)} contains "asyncStackTrace" property`);
assert(paused.params.asyncStackTrace.description, 'Timeout');
assert(paused.params.asyncStackTrace.callFrames
.some((frame) => frame.functionName === 'setupTimeoutWithBreak'));
}
async function runTests() {
const instance = await NodeInstance.startViaSignal(script);
const session = await instance.connectInspectorSession();
await session.send([
{ 'method': 'Runtime.enable' },
{ 'method': 'Debugger.enable' },
{ 'method': 'Debugger.setAsyncCallStackDepth',
'params': { 'maxDepth': 10 } },
{ 'method': 'Debugger.setBlackboxPatterns',
'params': { 'patterns': [] } },
]);
await waitForInitialSetup(session);
await setupTimeoutForStackTrace(session);
await checkAsyncStackTrace(session);
console.error('[test]', 'Stopping child instance');
session.disconnect();
instance.kill();
}
runTests().then(common.mustCall());