• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3
4common.skipIfInspectorDisabled();
5
6const assert = require('assert');
7const { NodeInstance } = require('../common/inspector-helper.js');
8
9function checkListResponse(response) {
10  const expectedLength = 1;
11  assert.strictEqual(
12    response.length,
13    expectedLength,
14    `Expected response length ${response.length} to be ${expectedLength}.`
15  );
16  assert.ok(response[0].devtoolsFrontendUrl);
17  assert.ok(
18    /ws:\/\/localhost:\d+\/[0-9A-Fa-f]{8}-/
19      .test(response[0].webSocketDebuggerUrl),
20    response[0].webSocketDebuggerUrl);
21}
22
23function checkVersion(response) {
24  assert.ok(response);
25  const expected = {
26    'Browser': `node.js/${process.version}`,
27    'Protocol-Version': '1.1',
28  };
29  assert.strictEqual(JSON.stringify(response),
30                     JSON.stringify(expected));
31}
32
33function checkBadPath(err) {
34  assert(err instanceof SyntaxError);
35  assert(/Unexpected token/.test(err.message), err.message);
36  assert(/WebSockets request was expected/.test(err.body), err.body);
37}
38
39function checkException(message) {
40  assert.strictEqual(message.exceptionDetails, undefined);
41}
42
43function assertScopeValues({ result }, expected) {
44  const unmatched = new Set(Object.keys(expected));
45  for (const actual of result) {
46    const value = expected[actual.name];
47    if (value) {
48      assert.strictEqual(
49        actual.value.value,
50        value,
51        `Expected scope values to be ${actual.value.value} instead of ${value}.`
52      );
53      unmatched.delete(actual.name);
54    }
55  }
56  if (unmatched.size)
57    assert.fail(Array.from(unmatched.values()));
58}
59
60async function testBreakpointOnStart(session) {
61  console.log('[test]',
62              'Verifying debugger stops on start (--inspect-brk option)');
63  const commands = [
64    { 'method': 'Runtime.enable' },
65    { 'method': 'Debugger.enable' },
66    { 'method': 'Debugger.setPauseOnExceptions',
67      'params': { 'state': 'none' } },
68    { 'method': 'Debugger.setAsyncCallStackDepth',
69      'params': { 'maxDepth': 0 } },
70    { 'method': 'Profiler.enable' },
71    { 'method': 'Profiler.setSamplingInterval',
72      'params': { 'interval': 100 } },
73    { 'method': 'Debugger.setBlackboxPatterns',
74      'params': { 'patterns': [] } },
75    { 'method': 'Runtime.runIfWaitingForDebugger' },
76  ];
77
78  await session.send(commands);
79  await session.waitForBreakOnLine(0, session.scriptURL());
80}
81
82async function testBreakpoint(session) {
83  console.log('[test]', 'Setting a breakpoint and verifying it is hit');
84  const commands = [
85    { 'method': 'Debugger.setBreakpointByUrl',
86      'params': { 'lineNumber': 5,
87                  'url': session.scriptURL(),
88                  'columnNumber': 0,
89                  'condition': '' } },
90    { 'method': 'Debugger.resume' },
91  ];
92  await session.send(commands);
93  const { scriptSource } = await session.send({
94    'method': 'Debugger.getScriptSource',
95    'params': { 'scriptId': session.mainScriptId },
96  });
97  assert(scriptSource && (scriptSource.includes(session.script())),
98         `Script source is wrong: ${scriptSource}`);
99
100  await session.waitForConsoleOutput('log', ['A message', 5]);
101  const paused = await session.waitForBreakOnLine(5, session.scriptURL());
102  const scopeId = paused.params.callFrames[0].scopeChain[0].object.objectId;
103
104  console.log('[test]', 'Verify we can read current application state');
105  const response = await session.send({
106    'method': 'Runtime.getProperties',
107    'params': {
108      'objectId': scopeId,
109      'ownProperties': false,
110      'accessorPropertiesOnly': false,
111      'generatePreview': true
112    }
113  });
114  assertScopeValues(response, { t: 1001, k: 1 });
115
116  let { result } = await session.send({
117    'method': 'Debugger.evaluateOnCallFrame', 'params': {
118      'callFrameId': session.pausedDetails().callFrames[0].callFrameId,
119      'expression': 'k + t',
120      'objectGroup': 'console',
121      'includeCommandLineAPI': true,
122      'silent': false,
123      'returnByValue': false,
124      'generatePreview': true
125    }
126  });
127  const expectedEvaluation = 1002;
128  assert.strictEqual(
129    result.value,
130    expectedEvaluation,
131    `Expected evaluation to be ${expectedEvaluation}, got ${result.value}.`
132  );
133
134  result = (await session.send({
135    'method': 'Runtime.evaluate', 'params': {
136      'expression': '5 * 5'
137    }
138  })).result;
139  const expectedResult = 25;
140  assert.strictEqual(
141    result.value,
142    expectedResult,
143    `Expected Runtime.evaluate to be ${expectedResult}, got ${result.value}.`
144  );
145}
146
147async function testI18NCharacters(session) {
148  console.log('[test]', 'Verify sending and receiving UTF8 characters');
149  const chars = 'טֶ字и';
150  session.send({
151    'method': 'Debugger.evaluateOnCallFrame', 'params': {
152      'callFrameId': session.pausedDetails().callFrames[0].callFrameId,
153      'expression': `console.log("${chars}")`,
154      'objectGroup': 'console',
155      'includeCommandLineAPI': true,
156      'silent': false,
157      'returnByValue': false,
158      'generatePreview': true
159    }
160  });
161  await session.waitForConsoleOutput('log', [chars]);
162}
163
164async function testCommandLineAPI(session) {
165  const testModulePath = require.resolve('../fixtures/empty.js');
166  const testModuleStr = JSON.stringify(testModulePath);
167  const printAModulePath = require.resolve('../fixtures/printA.js');
168  const printAModuleStr = JSON.stringify(printAModulePath);
169  const printBModulePath = require.resolve('../fixtures/printB.js');
170  const printBModuleStr = JSON.stringify(printBModulePath);
171
172  // We can use `require` outside of a callframe with require in scope
173  let result = await session.send(
174    {
175      'method': 'Runtime.evaluate', 'params': {
176        'expression': 'typeof require("fs").readFile === "function"',
177        'includeCommandLineAPI': true
178      }
179    });
180  checkException(result);
181  assert.strictEqual(result.result.value, true);
182
183  // The global require has the same properties as a normal `require`
184  result = await session.send(
185    {
186      'method': 'Runtime.evaluate', 'params': {
187        'expression': [
188          'typeof require.resolve === "function"',
189          'typeof require.extensions === "object"',
190          'typeof require.cache === "object"',
191        ].join(' && '),
192        'includeCommandLineAPI': true
193      }
194    });
195  checkException(result);
196  assert.strictEqual(result.result.value, true);
197  // `require` twice returns the same value
198  result = await session.send(
199    {
200      'method': 'Runtime.evaluate', 'params': {
201        // 1. We require the same module twice
202        // 2. We mutate the exports so we can compare it later on
203        'expression': `
204          Object.assign(
205            require(${testModuleStr}),
206            { old: 'yes' }
207          ) === require(${testModuleStr})`,
208        'includeCommandLineAPI': true
209      }
210    });
211  checkException(result);
212  assert.strictEqual(result.result.value, true);
213  // After require the module appears in require.cache
214  result = await session.send(
215    {
216      'method': 'Runtime.evaluate', 'params': {
217        'expression': `JSON.stringify(
218          require.cache[${testModuleStr}].exports
219        )`,
220        'includeCommandLineAPI': true
221      }
222    });
223  checkException(result);
224  assert.deepStrictEqual(JSON.parse(result.result.value),
225                         { old: 'yes' });
226  // Remove module from require.cache
227  result = await session.send(
228    {
229      'method': 'Runtime.evaluate', 'params': {
230        'expression': `delete require.cache[${testModuleStr}]`,
231        'includeCommandLineAPI': true
232      }
233    });
234  checkException(result);
235  assert.strictEqual(result.result.value, true);
236  // Require again, should get fresh (empty) exports
237  result = await session.send(
238    {
239      'method': 'Runtime.evaluate', 'params': {
240        'expression': `JSON.stringify(require(${testModuleStr}))`,
241        'includeCommandLineAPI': true
242      }
243    });
244  checkException(result);
245  assert.deepStrictEqual(JSON.parse(result.result.value), {});
246  // require 2nd module, exports an empty object
247  result = await session.send(
248    {
249      'method': 'Runtime.evaluate', 'params': {
250        'expression': `JSON.stringify(require(${printAModuleStr}))`,
251        'includeCommandLineAPI': true
252      }
253    });
254  checkException(result);
255  assert.deepStrictEqual(JSON.parse(result.result.value), {});
256  // Both modules end up with the same module.parent
257  result = await session.send(
258    {
259      'method': 'Runtime.evaluate', 'params': {
260        'expression': `JSON.stringify({
261          parentsEqual:
262            require.cache[${testModuleStr}].parent ===
263            require.cache[${printAModuleStr}].parent,
264          parentId: require.cache[${testModuleStr}].parent.id,
265        })`,
266        'includeCommandLineAPI': true
267      }
268    });
269  checkException(result);
270  assert.deepStrictEqual(JSON.parse(result.result.value), {
271    parentsEqual: true,
272    parentId: '<inspector console>'
273  });
274  // The `require` in the module shadows the command line API's `require`
275  result = await session.send(
276    {
277      'method': 'Debugger.evaluateOnCallFrame', 'params': {
278        'callFrameId': session.pausedDetails().callFrames[0].callFrameId,
279        'expression': `(
280          require(${printBModuleStr}),
281          require.cache[${printBModuleStr}].parent.id
282        )`,
283        'includeCommandLineAPI': true
284      }
285    });
286  checkException(result);
287  assert.notStrictEqual(result.result.value,
288                        '<inspector console>');
289}
290
291async function runTest() {
292  const child = new NodeInstance();
293  checkListResponse(await child.httpGet(null, '/json'));
294  checkListResponse(await child.httpGet(null, '/json/list'));
295  checkVersion(await child.httpGet(null, '/json/version'));
296
297  await child.httpGet(null, '/json/activate').catch(checkBadPath);
298  await child.httpGet(null, '/json/activate/boom').catch(checkBadPath);
299  await child.httpGet(null, '/json/badpath').catch(checkBadPath);
300
301  const session = await child.connectInspectorSession();
302  await testBreakpointOnStart(session);
303  await testBreakpoint(session);
304  await testI18NCharacters(session);
305  await testCommandLineAPI(session);
306  await session.runToCompletion();
307  const expectedExitCode = 55;
308  const { exitCode } = await child.expectShutdown();
309  assert.strictEqual(
310    exitCode,
311    expectedExitCode,
312    `Expected exit code to be ${expectedExitCode} but got ${expectedExitCode}.`
313  );
314}
315
316runTest().then(common.mustCall());
317