summaryrefslogtreecommitdiff
path: root/test/sequential
diff options
context:
space:
mode:
authorRich Trott <rtrott@gmail.com>2017-10-17 21:50:25 -0700
committerRich Trott <rtrott@gmail.com>2017-10-17 23:10:20 -0700
commit9be3d99b2b94d8a08b5a36efb06f3a6fd196805a (patch)
tree77a6cd98b9fc4e72134ddde0fbfd2606af39f91c /test/sequential
parent978629ca1240b9f2038390c7e960f3d226daa4e8 (diff)
downloadandroid-node-v8-9be3d99b2b94d8a08b5a36efb06f3a6fd196805a.tar.gz
android-node-v8-9be3d99b2b94d8a08b5a36efb06f3a6fd196805a.tar.bz2
android-node-v8-9be3d99b2b94d8a08b5a36efb06f3a6fd196805a.zip
test: fix inspector tests
The inspector tests should not be in the parallel directory as they likely all (or certainly almost all) use static ports, so port collisions will happen. This moves them all to sequential. We can move them back on a case-by-case basis. They were run sequentially when they were in the inspector directory which they were only moved from very recently. PR-URL: https://github.com/nodejs/node/pull/16281 Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com> Reviewed-By: Yuta Hiroto <hello@about-hiroppy.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Bryan English <bryan@bryanenglish.com>
Diffstat (limited to 'test/sequential')
-rw-r--r--test/sequential/sequential.status4
-rw-r--r--test/sequential/test-inspector-async-hook-setup-at-inspect-brk.js50
-rw-r--r--test/sequential/test-inspector-async-hook-setup-at-signal.js81
-rw-r--r--test/sequential/test-inspector-async-hook-teardown-at-debug-end.js33
-rw-r--r--test/sequential/test-inspector-async-stack-traces-promise-then.js72
-rw-r--r--test/sequential/test-inspector-async-stack-traces-set-interval.js48
-rw-r--r--test/sequential/test-inspector-bindings.js140
-rw-r--r--test/sequential/test-inspector-break-e.js22
-rw-r--r--test/sequential/test-inspector-break-when-eval.js68
-rw-r--r--test/sequential/test-inspector-contexts.js63
-rw-r--r--test/sequential/test-inspector-debug-brk-flag.js41
-rw-r--r--test/sequential/test-inspector-debug-end.js46
-rw-r--r--test/sequential/test-inspector-exception.js45
-rw-r--r--test/sequential/test-inspector-invalid-args.js27
-rw-r--r--test/sequential/test-inspector-ip-detection.js48
-rw-r--r--test/sequential/test-inspector-module.js62
-rw-r--r--test/sequential/test-inspector-not-blocked-on-idle.js21
-rw-r--r--test/sequential/test-inspector-open.js103
-rw-r--r--test/sequential/test-inspector-port-zero-cluster.js51
-rw-r--r--test/sequential/test-inspector-port-zero.js53
-rw-r--r--test/sequential/test-inspector-scriptparsed-context.js107
-rw-r--r--test/sequential/test-inspector-stop-profile-after-done.js30
-rw-r--r--test/sequential/test-inspector-stops-no-file.js16
-rw-r--r--test/sequential/test-inspector.js304
24 files changed, 1535 insertions, 0 deletions
diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status
index 8467864395..86095d16c3 100644
--- a/test/sequential/sequential.status
+++ b/test/sequential/sequential.status
@@ -7,6 +7,9 @@ prefix sequential
[true] # This section applies to all platforms
[$system==win32]
+test-inspector-bindings : PASS, FLAKY
+test-inspector-debug-end : PASS, FLAKY
+test-inspector-stop-profile-after-done: PASS, FLAKY
[$system==linux]
@@ -17,3 +20,4 @@ prefix sequential
[$system==freebsd]
[$system==aix]
+test-inspector-stop-profile-after-done: PASS, FLAKY
diff --git a/test/sequential/test-inspector-async-hook-setup-at-inspect-brk.js b/test/sequential/test-inspector-async-hook-setup-at-inspect-brk.js
new file mode 100644
index 0000000000..980e9e4d46
--- /dev/null
+++ b/test/sequential/test-inspector-async-hook-setup-at-inspect-brk.js
@@ -0,0 +1,50 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+common.skipIf32Bits();
+common.crashOnUnhandledRejection();
+const { NodeInstance } = require('../common/inspector-helper.js');
+const assert = require('assert');
+
+const script = `
+setTimeout(() => {
+ debugger;
+ process.exitCode = 55;
+}, 50);
+`;
+
+async function skipBreakpointAtStart(session) {
+ await session.waitForBreakOnLine(0, '[eval]');
+ await session.send({ 'method': 'Debugger.resume' });
+}
+
+async function checkAsyncStackTrace(session) {
+ console.error('[test]', 'Verify basic properties of asyncStackTrace');
+ const paused = await session.waitForBreakOnLine(4, '[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 === 'Module._compile'));
+}
+
+async function runTests() {
+ const instance = new NodeInstance(undefined, 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': [] } },
+ { 'method': 'Runtime.runIfWaitingForDebugger' }
+ ]);
+ await skipBreakpointAtStart(session);
+ await checkAsyncStackTrace(session);
+
+ await session.runToCompletion();
+ assert.strictEqual(55, (await instance.expectShutdown()).exitCode);
+}
+
+runTests();
diff --git a/test/sequential/test-inspector-async-hook-setup-at-signal.js b/test/sequential/test-inspector-async-hook-setup-at-signal.js
new file mode 100644
index 0000000000..96e8b28a7a
--- /dev/null
+++ b/test/sequential/test-inspector-async-hook-setup-at-signal.js
@@ -0,0 +1,81 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+common.skipIf32Bits();
+common.crashOnUnhandledRejection();
+const { NodeInstance } = require('../common/inspector-helper.js');
+const assert = require('assert');
+
+const script = `
+process._rawDebug('Waiting until a signal enables the inspector...');
+let waiting = setInterval(waitUntilDebugged, 50);
+
+function waitUntilDebugged() {
+ if (!process.binding('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 unnecesarily 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(15, '[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(22, '[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': [] } },
+ { 'method': 'Runtime.runIfWaitingForDebugger' }
+ ]);
+
+ await waitForInitialSetup(session);
+ await setupTimeoutForStackTrace(session);
+ await checkAsyncStackTrace(session);
+
+ console.error('[test]', 'Stopping child instance');
+ session.disconnect();
+ instance.kill();
+}
+
+runTests();
diff --git a/test/sequential/test-inspector-async-hook-teardown-at-debug-end.js b/test/sequential/test-inspector-async-hook-teardown-at-debug-end.js
new file mode 100644
index 0000000000..9084efdd41
--- /dev/null
+++ b/test/sequential/test-inspector-async-hook-teardown-at-debug-end.js
@@ -0,0 +1,33 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+common.skipIf32Bits();
+
+const spawn = require('child_process').spawn;
+
+const script = `
+const assert = require('assert');
+
+// Verify that inspector-async-hook is registered
+// by checking that emitInit with invalid arguments
+// throw an error.
+// See test/async-hooks/test-emit-init.js
+assert.throws(
+ () => async_hooks.emitInit(),
+ 'inspector async hook should have been enabled initially');
+
+process._debugEnd();
+
+// Verify that inspector-async-hook is no longer registered,
+// thus emitInit() ignores invalid arguments
+// See test/async-hooks/test-emit-init.js
+assert.doesNotThrow(
+ () => async_hooks.emitInit(),
+ 'inspector async hook should have beend disabled by _debugEnd()');
+`;
+
+const args = ['--inspect', '-e', script];
+const child = spawn(process.execPath, args, { stdio: 'inherit' });
+child.on('exit', (code, signal) => {
+ process.exit(code || signal);
+});
diff --git a/test/sequential/test-inspector-async-stack-traces-promise-then.js b/test/sequential/test-inspector-async-stack-traces-promise-then.js
new file mode 100644
index 0000000000..a321855b5e
--- /dev/null
+++ b/test/sequential/test-inspector-async-stack-traces-promise-then.js
@@ -0,0 +1,72 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+common.skipIf32Bits();
+common.crashOnUnhandledRejection();
+const { NodeInstance } = require('../common/inspector-helper');
+const assert = require('assert');
+
+const script = `runTest();
+function runTest() {
+ const p = Promise.resolve();
+ p.then(function break1() { // lineNumber 3
+ debugger;
+ });
+ p.then(function break2() { // lineNumber 6
+ debugger;
+ });
+}
+`;
+
+async function runTests() {
+ const instance = new NodeInstance(undefined, 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': [] } },
+ { 'method': 'Runtime.runIfWaitingForDebugger' }
+ ]);
+
+ await session.waitForBreakOnLine(0, '[eval]');
+ await session.send({ 'method': 'Debugger.resume' });
+
+ console.error('[test] Waiting for break1');
+ debuggerPausedAt(await session.waitForBreakOnLine(6, '[eval]'),
+ 'break1', 'runTest:5');
+
+ await session.send({ 'method': 'Debugger.resume' });
+
+ console.error('[test] Waiting for break2');
+ debuggerPausedAt(await session.waitForBreakOnLine(9, '[eval]'),
+ 'break2', 'runTest:8');
+
+ await session.runToCompletion();
+ assert.strictEqual(0, (await instance.expectShutdown()).exitCode);
+}
+
+function debuggerPausedAt(msg, functionName, previousTickLocation) {
+ assert(
+ !!msg.params.asyncStackTrace,
+ `${Object.keys(msg.params)} contains "asyncStackTrace" property`);
+
+ assert.strictEqual(msg.params.callFrames[0].functionName, functionName);
+ assert.strictEqual(msg.params.asyncStackTrace.description, 'PROMISE');
+
+ const frameLocations = msg.params.asyncStackTrace.callFrames.map(
+ (frame) => `${frame.functionName}:${frame.lineNumber}`);
+ assertArrayIncludes(frameLocations, previousTickLocation);
+}
+
+function assertArrayIncludes(actual, expected) {
+ const expectedString = JSON.stringify(expected);
+ const actualString = JSON.stringify(actual);
+ assert(
+ actual.includes(expected),
+ `Expected ${actualString} to contain ${expectedString}.`);
+}
+
+runTests();
diff --git a/test/sequential/test-inspector-async-stack-traces-set-interval.js b/test/sequential/test-inspector-async-stack-traces-set-interval.js
new file mode 100644
index 0000000000..e778bfc802
--- /dev/null
+++ b/test/sequential/test-inspector-async-stack-traces-set-interval.js
@@ -0,0 +1,48 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+common.skipIf32Bits();
+common.crashOnUnhandledRejection();
+const { NodeInstance } = require('../common/inspector-helper');
+const assert = require('assert');
+
+const script = 'setInterval(() => { debugger; }, 50);';
+
+async function skipFirstBreakpoint(session) {
+ console.log('[test]', 'Skipping the first breakpoint in the eval script');
+ await session.waitForBreakOnLine(0, '[eval]');
+ await session.send({ 'method': 'Debugger.resume' });
+}
+
+async function checkAsyncStackTrace(session) {
+ console.error('[test]', 'Verify basic properties of asyncStackTrace');
+ const paused = await session.waitForBreakOnLine(2, '[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 === 'Module._compile'));
+}
+
+async function runTests() {
+ const instance = new NodeInstance(undefined, 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': [] } },
+ { 'method': 'Runtime.runIfWaitingForDebugger' }
+ ]);
+
+ await skipFirstBreakpoint(session);
+ await checkAsyncStackTrace(session);
+
+ console.error('[test]', 'Stopping child instance');
+ session.disconnect();
+ instance.kill();
+}
+
+runTests();
diff --git a/test/sequential/test-inspector-bindings.js b/test/sequential/test-inspector-bindings.js
new file mode 100644
index 0000000000..b2140c11a3
--- /dev/null
+++ b/test/sequential/test-inspector-bindings.js
@@ -0,0 +1,140 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+const assert = require('assert');
+const inspector = require('inspector');
+const path = require('path');
+
+// This test case will set a breakpoint 4 lines below
+function debuggedFunction() {
+ let i;
+ let accum = 0;
+ for (i = 0; i < 5; i++) {
+ accum += i;
+ }
+ return accum;
+}
+
+let scopeCallback = null;
+
+function checkScope(session, scopeId) {
+ session.post('Runtime.getProperties', {
+ 'objectId': scopeId,
+ 'ownProperties': false,
+ 'accessorPropertiesOnly': false,
+ 'generatePreview': true
+ }, scopeCallback);
+}
+
+function debuggerPausedCallback(session, notification) {
+ const params = notification['params'];
+ const callFrame = params['callFrames'][0];
+ const scopeId = callFrame['scopeChain'][0]['object']['objectId'];
+ checkScope(session, scopeId);
+}
+
+function waitForWarningSkipAsyncStackTraces(resolve) {
+ process.once('warning', function(warning) {
+ if (warning.code === 'INSPECTOR_ASYNC_STACK_TRACES_NOT_AVAILABLE') {
+ waitForWarningSkipAsyncStackTraces(resolve);
+ } else {
+ resolve(warning);
+ }
+ });
+}
+
+async function testNoCrashWithExceptionInCallback() {
+ // There is a deliberate exception in the callback
+ const session = new inspector.Session();
+ session.connect();
+ const error = new Error('We expect this');
+ console.log('Expecting warning to be emitted');
+ const promise = new Promise(waitForWarningSkipAsyncStackTraces);
+ session.post('Console.enable', () => { throw error; });
+ assert.strictEqual(await promise, error);
+ session.disconnect();
+}
+
+function testSampleDebugSession() {
+ let cur = 0;
+ const failures = [];
+ const expects = {
+ i: [0, 1, 2, 3, 4],
+ accum: [0, 0, 1, 3, 6]
+ };
+ scopeCallback = function(error, result) {
+ const i = cur++;
+ let v, actual, expected;
+ for (v of result['result']) {
+ actual = v['value']['value'];
+ expected = expects[v['name']][i];
+ if (actual !== expected) {
+ failures.push(`Iteration ${i} variable: ${v['name']} ` +
+ `expected: ${expected} actual: ${actual}`);
+ }
+ }
+ };
+ const session = new inspector.Session();
+ session.connect();
+ let secondSessionOpened = false;
+ const secondSession = new inspector.Session();
+ try {
+ secondSession.connect();
+ secondSessionOpened = true;
+ } catch (error) {
+ // expected as the session already exists
+ }
+ assert.strictEqual(secondSessionOpened, false);
+ session.on('Debugger.paused',
+ (notification) => debuggerPausedCallback(session, notification));
+ let cbAsSecondArgCalled = false;
+ assert.throws(() => {
+ session.post('Debugger.enable', function() {}, function() {});
+ }, TypeError);
+ session.post('Debugger.enable', () => cbAsSecondArgCalled = true);
+ session.post('Debugger.setBreakpointByUrl', {
+ 'lineNumber': 12,
+ 'url': path.resolve(__dirname, __filename),
+ 'columnNumber': 0,
+ 'condition': ''
+ });
+
+ debuggedFunction();
+ assert.deepStrictEqual(cbAsSecondArgCalled, true);
+ assert.deepStrictEqual(failures, []);
+ assert.strictEqual(cur, 5);
+ scopeCallback = null;
+ session.disconnect();
+ assert.throws(() => session.post('Debugger.enable'), (e) => !!e);
+}
+
+async function testNoCrashConsoleLogBeforeThrow() {
+ const session = new inspector.Session();
+ session.connect();
+ let attempt = 1;
+ process.on('warning', common.mustCall(3));
+ session.on('inspectorNotification', () => {
+ if (attempt++ > 3)
+ return;
+ console.log('console.log in handler');
+ throw new Error('Exception in handler');
+ });
+ session.post('Runtime.enable');
+ console.log('Did not crash');
+ session.disconnect();
+}
+
+common.crashOnUnhandledRejection();
+
+async function doTests() {
+ await testNoCrashWithExceptionInCallback();
+ testSampleDebugSession();
+ let breakpointHit = false;
+ scopeCallback = () => (breakpointHit = true);
+ debuggedFunction();
+ assert.strictEqual(breakpointHit, false);
+ testSampleDebugSession();
+ await testNoCrashConsoleLogBeforeThrow();
+}
+
+doTests();
diff --git a/test/sequential/test-inspector-break-e.js b/test/sequential/test-inspector-break-e.js
new file mode 100644
index 0000000000..1a8f8ca50d
--- /dev/null
+++ b/test/sequential/test-inspector-break-e.js
@@ -0,0 +1,22 @@
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { NodeInstance } = require('../common/inspector-helper.js');
+
+async function runTests() {
+ const instance = new NodeInstance(undefined, 'console.log(10)');
+ const session = await instance.connectInspectorSession();
+ await session.send([
+ { 'method': 'Runtime.enable' },
+ { 'method': 'Debugger.enable' },
+ { 'method': 'Runtime.runIfWaitingForDebugger' }
+ ]);
+ await session.waitForBreakOnLine(0, '[eval]');
+ await session.runToCompletion();
+ assert.strictEqual(0, (await instance.expectShutdown()).exitCode);
+}
+
+runTests();
diff --git a/test/sequential/test-inspector-break-when-eval.js b/test/sequential/test-inspector-break-when-eval.js
new file mode 100644
index 0000000000..e5d01cb189
--- /dev/null
+++ b/test/sequential/test-inspector-break-when-eval.js
@@ -0,0 +1,68 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+const assert = require('assert');
+const { NodeInstance } = require('../common/inspector-helper.js');
+const fixtures = require('../common/fixtures');
+
+const script = fixtures.path('inspector-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();
diff --git a/test/sequential/test-inspector-contexts.js b/test/sequential/test-inspector-contexts.js
new file mode 100644
index 0000000000..54acfab0d5
--- /dev/null
+++ b/test/sequential/test-inspector-contexts.js
@@ -0,0 +1,63 @@
+'use strict';
+
+// Flags: --expose-gc
+
+const common = require('../common');
+common.skipIfInspectorDisabled();
+
+const { strictEqual } = require('assert');
+const { runInNewContext } = require('vm');
+const { Session } = require('inspector');
+
+const session = new Session();
+session.connect();
+
+function notificationPromise(method) {
+ return new Promise((resolve) => session.once(method, resolve));
+}
+
+async function testContextCreatedAndDestroyed() {
+ console.log('Testing context created/destroyed notifications');
+ const mainContextPromise =
+ notificationPromise('Runtime.executionContextCreated');
+
+ session.post('Runtime.enable');
+ let contextCreated = await mainContextPromise;
+ strictEqual('Node.js Main Context',
+ contextCreated.params.context.name,
+ JSON.stringify(contextCreated));
+
+ const secondContextCreatedPromise =
+ notificationPromise('Runtime.executionContextCreated');
+
+ let contextDestroyed = null;
+ session.once('Runtime.executionContextDestroyed',
+ (notification) => contextDestroyed = notification);
+
+ runInNewContext('1 + 1', {});
+
+ contextCreated = await secondContextCreatedPromise;
+ strictEqual('VM Context 1',
+ contextCreated.params.context.name,
+ JSON.stringify(contextCreated));
+
+ // GC is unpredictable...
+ while (!contextDestroyed)
+ global.gc();
+
+ strictEqual(contextCreated.params.context.id,
+ contextDestroyed.params.executionContextId,
+ JSON.stringify(contextDestroyed));
+}
+
+async function testBreakpointHit() {
+ console.log('Testing breakpoint is hit in a new context');
+ session.post('Debugger.enable');
+
+ const pausedPromise = notificationPromise('Debugger.paused');
+ runInNewContext('debugger', {});
+ await pausedPromise;
+}
+
+common.crashOnUnhandledRejection();
+testContextCreatedAndDestroyed().then(testBreakpointHit);
diff --git a/test/sequential/test-inspector-debug-brk-flag.js b/test/sequential/test-inspector-debug-brk-flag.js
new file mode 100644
index 0000000000..235e7043d8
--- /dev/null
+++ b/test/sequential/test-inspector-debug-brk-flag.js
@@ -0,0 +1,41 @@
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { mainScriptPath,
+ NodeInstance } = require('../common/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();
diff --git a/test/sequential/test-inspector-debug-end.js b/test/sequential/test-inspector-debug-end.js
new file mode 100644
index 0000000000..effef9f233
--- /dev/null
+++ b/test/sequential/test-inspector-debug-end.js
@@ -0,0 +1,46 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+const { strictEqual } = require('assert');
+const { NodeInstance } = require('../common/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();
diff --git a/test/sequential/test-inspector-exception.js b/test/sequential/test-inspector-exception.js
new file mode 100644
index 0000000000..743742f50f
--- /dev/null
+++ b/test/sequential/test-inspector-exception.js
@@ -0,0 +1,45 @@
+'use strict';
+const common = require('../common');
+const fixtures = require('../common/fixtures');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { NodeInstance } = require('../common/inspector-helper.js');
+
+const script = fixtures.path('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();
diff --git a/test/sequential/test-inspector-invalid-args.js b/test/sequential/test-inspector-invalid-args.js
new file mode 100644
index 0000000000..ae051b92ce
--- /dev/null
+++ b/test/sequential/test-inspector-invalid-args.js
@@ -0,0 +1,27 @@
+'use strict';
+const common = require('../common');
+const fixtures = require('../common/fixtures');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const execFile = require('child_process').execFile;
+
+const mainScript = fixtures.path('loop.js');
+const expected =
+ '`node --debug` and `node --debug-brk` are invalid. ' +
+ 'Please use `node --inspect` or `node --inspect-brk` instead.';
+for (const invalidArg of ['--debug-brk', '--debug']) {
+ execFile(
+ process.execPath,
+ [invalidArg, mainScript],
+ common.mustCall((error, stdout, stderr) => {
+ assert.strictEqual(error.code, 9, `node ${invalidArg} should exit 9`);
+ assert.strictEqual(
+ stderr.includes(expected),
+ true,
+ `${stderr} should include '${expected}'`
+ );
+ })
+ );
+}
diff --git a/test/sequential/test-inspector-ip-detection.js b/test/sequential/test-inspector-ip-detection.js
new file mode 100644
index 0000000000..f7dee4494d
--- /dev/null
+++ b/test/sequential/test-inspector-ip-detection.js
@@ -0,0 +1,48 @@
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { NodeInstance } = require('../common/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();
diff --git a/test/sequential/test-inspector-module.js b/test/sequential/test-inspector-module.js
new file mode 100644
index 0000000000..c0dc71efbf
--- /dev/null
+++ b/test/sequential/test-inspector-module.js
@@ -0,0 +1,62 @@
+'use strict';
+
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { Session } = require('inspector');
+
+const session = new Session();
+
+common.expectsError(
+ () => session.post('Runtime.evaluate', { expression: '2 + 2' }),
+ {
+ code: 'ERR_INSPECTOR_NOT_CONNECTED',
+ type: Error,
+ message: 'Session is not connected'
+ }
+);
+
+assert.doesNotThrow(() => session.connect());
+
+assert.doesNotThrow(
+ () => session.post('Runtime.evaluate', { expression: '2 + 2' }));
+
+[1, {}, [], true, Infinity, undefined].forEach((i) => {
+ common.expectsError(
+ () => session.post(i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message:
+ 'The "method" argument must be of type string. ' +
+ `Received type ${typeof i}`
+ }
+ );
+});
+
+[1, true, Infinity].forEach((i) => {
+ common.expectsError(
+ () => session.post('test', i),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message:
+ 'The "params" argument must be of type object. ' +
+ `Received type ${typeof i}`
+ }
+ );
+});
+
+common.expectsError(
+ () => session.connect(),
+ {
+ code: 'ERR_INSPECTOR_ALREADY_CONNECTED',
+ type: Error,
+ message: 'The inspector is already connected'
+ }
+);
+
+assert.doesNotThrow(() => session.disconnect());
+assert.doesNotThrow(() => session.disconnect());
diff --git a/test/sequential/test-inspector-not-blocked-on-idle.js b/test/sequential/test-inspector-not-blocked-on-idle.js
new file mode 100644
index 0000000000..68e4eaaa57
--- /dev/null
+++ b/test/sequential/test-inspector-not-blocked-on-idle.js
@@ -0,0 +1,21 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+const { NodeInstance } = require('../common/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();
+}
+
+common.crashOnUnhandledRejection();
+runTests();
diff --git a/test/sequential/test-inspector-open.js b/test/sequential/test-inspector-open.js
new file mode 100644
index 0000000000..a0d2eaf1f3
--- /dev/null
+++ b/test/sequential/test-inspector-open.js
@@ -0,0 +1,103 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+
+// Test inspector open()/close()/url() API. It uses ephemeral ports so can be
+// run safely in parallel.
+
+const assert = require('assert');
+const fork = require('child_process').fork;
+const net = require('net');
+const url = require('url');
+
+if (process.env.BE_CHILD)
+ return beChild();
+
+const child = fork(__filename,
+ { env: Object.assign({}, process.env, { BE_CHILD: 1 }) });
+
+child.once('message', common.mustCall((msg) => {
+ assert.strictEqual(msg.cmd, 'started');
+
+ child.send({ cmd: 'open', args: [0] });
+ child.once('message', common.mustCall(firstOpen));
+}));
+
+let firstPort;
+
+function firstOpen(msg) {
+ assert.strictEqual(msg.cmd, 'url');
+ const port = url.parse(msg.url).port;
+ ping(port, (err) => {
+ assert.ifError(err);
+ // Inspector is already open, and won't be reopened, so args don't matter.
+ child.send({ cmd: 'open', args: [] });
+ child.once('message', common.mustCall(tryToOpenWhenOpen));
+ firstPort = port;
+ });
+}
+
+function tryToOpenWhenOpen(msg) {
+ assert.strictEqual(msg.cmd, 'url');
+ const port = url.parse(msg.url).port;
+ // Reopen didn't do anything, the port was already open, and has not changed.
+ assert.strictEqual(port, firstPort);
+ ping(port, (err) => {
+ assert.ifError(err);
+ child.send({ cmd: 'close' });
+ child.once('message', common.mustCall(closeWhenOpen));
+ });
+}
+
+function closeWhenOpen(msg) {
+ assert.strictEqual(msg.cmd, 'url');
+ assert.strictEqual(msg.url, undefined);
+ ping(firstPort, (err) => {
+ assert(err);
+ child.send({ cmd: 'close' });
+ child.once('message', common.mustCall(tryToCloseWhenClosed));
+ });
+}
+
+function tryToCloseWhenClosed(msg) {
+ assert.strictEqual(msg.cmd, 'url');
+ assert.strictEqual(msg.url, undefined);
+ child.send({ cmd: 'open', args: [] });
+ child.once('message', common.mustCall(reopenAfterClose));
+}
+
+function reopenAfterClose(msg) {
+ assert.strictEqual(msg.cmd, 'url');
+ const port = url.parse(msg.url).port;
+ ping(port, (err) => {
+ assert.ifError(err);
+ process.exit();
+ });
+}
+
+function ping(port, callback) {
+ net.connect(port)
+ .on('connect', function() { close(this); })
+ .on('error', function(err) { close(this, err); });
+
+ function close(self, err) {
+ self.end();
+ self.on('close', () => callback(err));
+ }
+}
+
+function beChild() {
+ const inspector = require('inspector');
+
+ process.send({ cmd: 'started' });
+
+ process.on('message', (msg) => {
+ if (msg.cmd === 'open') {
+ inspector.open(...msg.args);
+ }
+ if (msg.cmd === 'close') {
+ inspector.close();
+ }
+ process.send({ cmd: 'url', url: inspector.url() });
+ });
+}
diff --git a/test/sequential/test-inspector-port-zero-cluster.js b/test/sequential/test-inspector-port-zero-cluster.js
new file mode 100644
index 0000000000..f64e05f314
--- /dev/null
+++ b/test/sequential/test-inspector-port-zero-cluster.js
@@ -0,0 +1,51 @@
+// Flags: --inspect=0
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+// Assert that even when started with `--inspect=0` workers are assigned
+// consecutive (i.e. deterministically predictable) debug ports
+
+const assert = require('assert');
+const cluster = require('cluster');
+
+function serialFork() {
+ return new Promise((res) => {
+ const worker = cluster.fork();
+ worker.on('exit', common.mustCall((code, signal) => {
+ // code 0 is normal
+ // code 12 can happen if inspector could not bind because of a port clash
+ if (code !== 0 && code !== 12)
+ assert.fail(`code: ${code}, signal: ${signal}`);
+ const port = worker.process.spawnargs
+ .map((a) => (/=(?:.*:)?(\d{2,5})$/.exec(a) || [])[1])
+ .filter((p) => p)
+ .pop();
+ res(Number(port));
+ }));
+ });
+}
+
+if (cluster.isMaster) {
+ Promise.all([serialFork(), serialFork(), serialFork()])
+ .then(common.mustCall((ports) => {
+ ports.push(process.debugPort);
+ ports.sort();
+ // 4 = [master, worker1, worker2, worker3].length()
+ assert.strictEqual(ports.length, 4);
+ assert(ports.every((port) => port > 0));
+ assert(ports.every((port) => port < 65536));
+ // Ports should be consecutive.
+ assert.strictEqual(ports[0] + 1, ports[1]);
+ assert.strictEqual(ports[1] + 1, ports[2]);
+ assert.strictEqual(ports[2] + 1, ports[3]);
+ }))
+ .catch(
+ (err) => {
+ console.error(err);
+ process.exit(1);
+ });
+} else {
+ process.exit(0);
+}
diff --git a/test/sequential/test-inspector-port-zero.js b/test/sequential/test-inspector-port-zero.js
new file mode 100644
index 0000000000..a3eb08e5fb
--- /dev/null
+++ b/test/sequential/test-inspector-port-zero.js
@@ -0,0 +1,53 @@
+'use strict';
+const { mustCall, skipIfInspectorDisabled } = require('../common');
+
+skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { URL } = require('url');
+const { spawn } = require('child_process');
+
+function test(arg, port = '') {
+ const args = [arg, '-p', 'process.debugPort'];
+ const proc = spawn(process.execPath, args);
+ proc.stdout.setEncoding('utf8');
+ proc.stderr.setEncoding('utf8');
+ let stdout = '';
+ let stderr = '';
+ proc.stdout.on('data', (data) => stdout += data);
+ proc.stderr.on('data', (data) => stderr += data);
+ proc.stdout.on('close', assert.ifError);
+ proc.stderr.on('close', assert.ifError);
+ proc.stderr.on('data', () => {
+ if (!stderr.includes('\n')) return;
+ assert(/Debugger listening on (.+)/.test(stderr));
+ port = new URL(RegExp.$1).port;
+ assert(+port > 0);
+ });
+ if (/inspect-brk/.test(arg)) {
+ proc.stderr.on('data', () => {
+ if (stderr.includes('\n') && !proc.killed) proc.kill();
+ });
+ } else {
+ let onclose = () => {
+ onclose = () => assert.strictEqual(port, stdout.trim());
+ };
+ proc.stdout.on('close', mustCall(() => onclose()));
+ proc.stderr.on('close', mustCall(() => onclose()));
+ proc.on('exit', mustCall((exitCode) => assert.strictEqual(exitCode, 0)));
+ }
+}
+
+test('--inspect=0');
+test('--inspect=127.0.0.1:0');
+test('--inspect=localhost:0');
+
+test('--inspect-brk=0');
+test('--inspect-brk=127.0.0.1:0');
+test('--inspect-brk=localhost:0');
+
+// In these cases, the inspector doesn't listen, so an ephemeral port is not
+// allocated and the expected value of `process.debugPort` is `0`.
+test('--inspect-port=0', '0');
+test('--inspect-port=127.0.0.1:0', '0');
+test('--inspect-port=localhost:0', '0');
diff --git a/test/sequential/test-inspector-scriptparsed-context.js b/test/sequential/test-inspector-scriptparsed-context.js
new file mode 100644
index 0000000000..f295d737da
--- /dev/null
+++ b/test/sequential/test-inspector-scriptparsed-context.js
@@ -0,0 +1,107 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+common.crashOnUnhandledRejection();
+const { NodeInstance } = require('../common/inspector-helper.js');
+const assert = require('assert');
+
+const script = `
+ 'use strict';
+ const assert = require('assert');
+ const vm = require('vm');
+ const { kParsingContext } = process.binding('contextify');
+ global.outer = true;
+ global.inner = false;
+ const context = vm.createContext({
+ outer: false,
+ inner: true
+ });
+ debugger;
+
+ const scriptMain = new vm.Script("outer");
+ debugger;
+
+ const scriptContext = new vm.Script("inner", {
+ [kParsingContext]: context
+ });
+ debugger;
+
+ assert.strictEqual(scriptMain.runInThisContext(), true);
+ assert.strictEqual(scriptMain.runInContext(context), false);
+ assert.strictEqual(scriptContext.runInThisContext(), false);
+ assert.strictEqual(scriptContext.runInContext(context), true);
+ debugger;
+
+ vm.runInContext('inner', context);
+ debugger;
+
+ vm.runInNewContext('Array', {});
+ debugger;
+
+ vm.runInNewContext('debugger', {});
+`;
+
+async function getContext(session) {
+ const created =
+ await session.waitForNotification('Runtime.executionContextCreated');
+ return created.params.context;
+}
+
+async function checkScriptContext(session, context) {
+ const scriptParsed =
+ await session.waitForNotification('Debugger.scriptParsed');
+ assert.strictEqual(scriptParsed.params.executionContextId, context.id);
+}
+
+async function runTests() {
+ const instance = new NodeInstance(['--inspect-brk=0', '--expose-internals'],
+ script);
+ const session = await instance.connectInspectorSession();
+ await session.send([
+ { 'method': 'Debugger.enable' },
+ { 'method': 'Runtime.runIfWaitingForDebugger' }
+ ]);
+ await session.waitForBreakOnLine(0, '[eval]');
+
+ await session.send({ 'method': 'Runtime.enable' });
+ const topContext = await getContext(session);
+ await session.send({ 'method': 'Debugger.resume' });
+ const childContext = await getContext(session);
+ await session.waitForBreakOnLine(13, '[eval]');
+
+ console.error('[test]', 'Script associated with current context by default');
+ await session.send({ 'method': 'Debugger.resume' });
+ await checkScriptContext(session, topContext);
+ await session.waitForBreakOnLine(16, '[eval]');
+
+ console.error('[test]', 'Script associated with selected context');
+ await session.send({ 'method': 'Debugger.resume' });
+ await checkScriptContext(session, childContext);
+ await session.waitForBreakOnLine(21, '[eval]');
+
+ console.error('[test]', 'Script is unbound');
+ await session.send({ 'method': 'Debugger.resume' });
+ await session.waitForBreakOnLine(27, '[eval]');
+
+ console.error('[test]', 'vm.runInContext associates script with context');
+ await session.send({ 'method': 'Debugger.resume' });
+ await checkScriptContext(session, childContext);
+ await session.waitForBreakOnLine(30, '[eval]');
+
+ console.error('[test]', 'vm.runInNewContext associates script with context');
+ await session.send({ 'method': 'Debugger.resume' });
+ const thirdContext = await getContext(session);
+ await checkScriptContext(session, thirdContext);
+ await session.waitForBreakOnLine(33, '[eval]');
+
+ console.error('[test]', 'vm.runInNewContext can contain debugger statements');
+ await session.send({ 'method': 'Debugger.resume' });
+ const fourthContext = await getContext(session);
+ await checkScriptContext(session, fourthContext);
+ await session.waitForBreakOnLine(0, 'evalmachine.<anonymous>');
+
+ await session.runToCompletion();
+ assert.strictEqual(0, (await instance.expectShutdown()).exitCode);
+}
+
+runTests();
diff --git a/test/sequential/test-inspector-stop-profile-after-done.js b/test/sequential/test-inspector-stop-profile-after-done.js
new file mode 100644
index 0000000000..4762bc5239
--- /dev/null
+++ b/test/sequential/test-inspector-stop-profile-after-done.js
@@ -0,0 +1,30 @@
+'use strict';
+const common = require('../common');
+common.skipIfInspectorDisabled();
+const assert = require('assert');
+const { NodeInstance } = require('../common/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();
diff --git a/test/sequential/test-inspector-stops-no-file.js b/test/sequential/test-inspector-stops-no-file.js
new file mode 100644
index 0000000000..772063b279
--- /dev/null
+++ b/test/sequential/test-inspector-stops-no-file.js
@@ -0,0 +1,16 @@
+'use strict';
+require('../common');
+
+const spawn = require('child_process').spawn;
+
+const child = spawn(process.execPath,
+ [ '--inspect', 'no-such-script.js' ],
+ { 'stdio': 'inherit' });
+
+function signalHandler(value) {
+ child.kill();
+ process.exit(1);
+}
+
+process.on('SIGINT', signalHandler);
+process.on('SIGTERM', signalHandler);
diff --git a/test/sequential/test-inspector.js b/test/sequential/test-inspector.js
new file mode 100644
index 0000000000..992a12e902
--- /dev/null
+++ b/test/sequential/test-inspector.js
@@ -0,0 +1,304 @@
+'use strict';
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { mainScriptPath,
+ readMainScriptSource,
+ NodeInstance } = require('../common/inspector-helper.js');
+
+function checkListResponse(response) {
+ assert.strictEqual(1, response.length);
+ assert.ok(response[0]['devtoolsFrontendUrl']);
+ assert.ok(
+ /ws:\/\/127\.0\.0\.1:\d+\/[0-9A-Fa-f]{8}-/
+ .test(response[0]['webSocketDebuggerUrl']));
+}
+
+function checkVersion(response) {
+ assert.ok(response);
+ const expected = {
+ 'Browser': `node.js/${process.version}`,
+ 'Protocol-Version': '1.1',
+ };
+ assert.strictEqual(JSON.stringify(response),
+ JSON.stringify(expected));
+}
+
+function checkBadPath(err) {
+ assert(err instanceof SyntaxError);
+ assert(/Unexpected token/.test(err.message), err.message);
+ assert(/WebSockets request was expected/.test(err.body), err.body);
+}
+
+function checkException(message) {
+ assert.strictEqual(message['exceptionDetails'], undefined,
+ '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 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']);
+ }
+ }
+ if (unmatched.size)
+ assert.fail(Array.from(unmatched.values()));
+}
+
+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, mainScriptPath);
+}
+
+async function testBreakpoint(session) {
+ console.log('[test]', 'Setting a breakpoint and verifying it is hit');
+ const commands = [
+ { 'method': 'Debugger.setBreakpointByUrl',
+ 'params': { 'lineNumber': 5,
+ 'url': mainScriptPath,
+ 'columnNumber': 0,
+ 'condition': ''
+ }
+ },
+ { 'method': 'Debugger.resume' },
+ ];
+ 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 paused = await session.waitForBreakOnLine(5, mainScriptPath);
+ const scopeId = paused.params.callFrames[0].scopeChain[0].object.objectId;
+
+ 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']);
+}
+
+async 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
+ }
+ });
+ await session.waitForConsoleOutput('log', [chars]);
+}
+
+async 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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
+ }
+ });
+ 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);
+}
+
+common.crashOnUnhandledRejection();
+
+runTest();