blob: 4faca7929b71c4cd4b2650b180d9cb1619696aa7 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import {
addBreakpointForLine,
captureAddedSourceFiles,
checkBreakpointDidNotActivate,
DEBUGGER_PAUSED_EVENT,
getBreakpointDecorators,
getCallFrameLocations,
getCallFrameNames,
getNonBreakableLines,
getValuesForScope,
isBreakpointSet,
openSourceCodeEditorForFile,
openSourcesPanel,
PAUSE_BUTTON,
reloadPageAndWaitForSourceFile,
removeBreakpointForLine,
RESUME_BUTTON,
retrieveTopCallFrameScriptLocation,
retrieveTopCallFrameWithoutResuming,
SELECTED_THREAD_SELECTOR,
stepThroughTheCode,
switchToCallFrame,
THREADS_SELECTOR,
waitForStackTopMatch,
} from '../helpers/sources-helpers.js';
describe('Sources Tab', function() {
it('shows the correct wasm source on load and reload', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
await openSourcesPanel(devToolsPage);
{
const capturedFileNames = await captureAddedSourceFiles(
2, () => inspectedPage.goToResource('sources/wasm/call-to-add-wasm.html'), devToolsPage);
assert.deepEqual(capturedFileNames, [
'/test/e2e/resources/sources/wasm/call-to-add-wasm.html',
'/test/e2e/resources/sources/wasm/add.wasm',
]);
}
{
const capturedFileNames = await captureAddedSourceFiles(2, async () => {
await inspectedPage.reload();
}, devToolsPage);
assert.deepEqual(capturedFileNames, [
'/test/e2e/resources/sources/wasm/call-to-add-wasm.html',
'/test/e2e/resources/sources/wasm/add.wasm',
]);
}
});
it('can add a breakpoint in raw wasm', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
await openSourceCodeEditorForFile('add.wasm', 'wasm/call-to-add-wasm.html', devToolsPage, inspectedPage);
await addBreakpointForLine('0x023', devToolsPage);
const scriptLocation = await retrieveTopCallFrameScriptLocation('main();', inspectedPage, devToolsPage);
assert.deepEqual(scriptLocation, 'add.wasm:0x23');
});
it('hits two breakpoints that are set and activated separately', async function({devToolsPage, inspectedPage}) {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
const fileName = 'add.wasm';
await openSourceCodeEditorForFile(fileName, 'wasm/call-to-add-wasm.html', devToolsPage, inspectedPage);
await addBreakpointForLine('0x027', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x027', devToolsPage));
let scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'add.wasm:0x27');
await removeBreakpointForLine('0x027', devToolsPage);
await devToolsPage.page.keyboard.press('F8');
await devToolsPage.waitFor(PAUSE_BUTTON);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => !(await isBreakpointSet('0x027', devToolsPage)));
await checkBreakpointDidNotActivate(devToolsPage);
await addBreakpointForLine('0x028', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x028', devToolsPage));
scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'add.wasm:0x28');
});
it('shows variable value in popover', async function({devToolsPage, inspectedPage}) {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
const fileName = 'add.wasm';
await openSourceCodeEditorForFile('add.wasm', 'wasm/call-to-add-wasm.html', devToolsPage, inspectedPage);
await addBreakpointForLine('0x023', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
const pausedPosition = await devToolsPage.waitForFunction(async () => {
const element = await devToolsPage.$('.cm-executionLine .token-variable');
if (element && await element.evaluate(e => e.isConnected)) {
return element;
}
return undefined;
});
await pausedPosition.hover();
const popover = await devToolsPage.waitFor('[data-stable-name-for-test="object-popover-content"]');
const value =
await devToolsPage.waitFor('.object-value-number', popover).then(e => e.evaluate(node => node.textContent));
assert.strictEqual(value, '0');
});
it('cannot set a breakpoint on non-breakable line in raw wasm', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
await openSourceCodeEditorForFile('add.wasm', 'wasm/call-to-add-wasm.html', devToolsPage, inspectedPage);
assert.deepEqual(await getNonBreakableLines(devToolsPage), [
0x000,
0x022,
0x04b,
]);
assert.deepEqual(await getBreakpointDecorators(undefined, undefined, devToolsPage), []);
// Line 3 is breakable.
await addBreakpointForLine('0x023', devToolsPage);
assert.deepEqual(await getBreakpointDecorators(undefined, undefined, devToolsPage), [0x023]);
});
it('is able to step with state', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
const fileName = 'stepping-with-state.wasm';
await openSourceCodeEditorForFile(
'stepping-with-state.wasm', 'wasm/stepping-with-state.html', devToolsPage, inspectedPage);
await addBreakpointForLine('0x060', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x060', devToolsPage));
let scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'stepping-with-state.wasm:0x60');
await stepThroughTheCode(devToolsPage);
await stepThroughTheCode(devToolsPage);
let localScopeValues = await getValuesForScope('Local', 0, 3, devToolsPage);
assert.deepEqual(localScopeValues, [
'$var0: i32 {value: 42}',
'$var1: i32 {value: 8}',
'$var2: i32 {value: 5}',
]);
await devToolsPage.page.keyboard.press('F8');
await devToolsPage.waitFor(PAUSE_BUTTON);
await removeBreakpointForLine('0x060', devToolsPage);
await addBreakpointForLine('0x048', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x048', devToolsPage));
scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'stepping-with-state.wasm:0x48');
await stepThroughTheCode(devToolsPage);
await stepThroughTheCode(devToolsPage);
localScopeValues = await getValuesForScope('Local', 0, 2, devToolsPage);
assert.deepEqual(localScopeValues, [
'$var0: i32 {value: 50}',
'$var1: i32 {value: 5}',
]);
await devToolsPage.page.keyboard.press('F8');
await devToolsPage.waitFor(PAUSE_BUTTON);
await checkBreakpointDidNotActivate(devToolsPage);
});
describe('with instrumentation breackpoints', () => {
setup({enabledDevToolsExperiments: ['instrumentation-breakpoints']});
it('is able to step with state in multi-threaded code in main thread', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
const fileName = 'stepping-with-state.wasm';
await openSourceCodeEditorForFile(
'stepping-with-state.wasm', 'wasm/stepping-with-state-and-threads.html', devToolsPage, inspectedPage);
await Promise.all([
devToolsPage.click(THREADS_SELECTOR),
devToolsPage.waitFor(THREADS_SELECTOR + '[aria-expanded="true"]'),
]);
let selectedThreadElement = await devToolsPage.waitFor(SELECTED_THREAD_SELECTOR);
let selectedThreadName = await selectedThreadElement.evaluate(element => {
return element.innerText;
});
assert.strictEqual(selectedThreadName, 'Main', 'the Main thread is not active');
assert.deepEqual(await getNonBreakableLines(devToolsPage), [
0x000,
0x03f,
0x047,
0x04f,
0x057,
0x05f,
0x06c,
0x0c1,
]);
await addBreakpointForLine('0x060', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x060', devToolsPage));
let scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'stepping-with-state.wasm:0x60');
await stepThroughTheCode(devToolsPage);
await stepThroughTheCode(devToolsPage);
let localScopeValues = await getValuesForScope('Local', 0, 3, devToolsPage);
assert.deepEqual(localScopeValues, [
'$var0: i32 {value: 42}',
'$var1: i32 {value: 8}',
'$var2: i32 {value: 5}',
]);
await devToolsPage.page.keyboard.press('F8');
await devToolsPage.waitFor(PAUSE_BUTTON);
await removeBreakpointForLine('0x060', devToolsPage);
await addBreakpointForLine('0x048', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x048', devToolsPage));
scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'stepping-with-state.wasm:0x48');
selectedThreadElement = await devToolsPage.waitFor(SELECTED_THREAD_SELECTOR);
selectedThreadName = await selectedThreadElement.evaluate(element => {
return element.innerText;
});
assert.strictEqual(selectedThreadName, 'Main', 'the Main thread is not active');
await stepThroughTheCode(devToolsPage);
await stepThroughTheCode(devToolsPage);
localScopeValues = await getValuesForScope('Local', 0, 2, devToolsPage);
assert.deepEqual(localScopeValues, [
'$var0: i32 {value: 50}',
'$var1: i32 {value: 5}',
]);
await removeBreakpointForLine('0x048', devToolsPage);
await devToolsPage.page.keyboard.press('F8');
await devToolsPage.waitFor(PAUSE_BUTTON);
await checkBreakpointDidNotActivate(devToolsPage);
});
it('is able to step with state in multi-threaded code in worker thread', async ({devToolsPage, inspectedPage}) => {
await devToolsPage.installEventListener(DEBUGGER_PAUSED_EVENT);
const fileName = 'stepping-with-state.wasm';
await openSourceCodeEditorForFile(
fileName, 'wasm/stepping-with-state-and-threads.html', devToolsPage, inspectedPage);
await Promise.all([
devToolsPage.click(THREADS_SELECTOR),
devToolsPage.waitFor(THREADS_SELECTOR + '[aria-expanded="true"]'),
]);
let selectedThreadElement = await devToolsPage.waitFor(SELECTED_THREAD_SELECTOR);
let selectedThreadName = await selectedThreadElement.evaluate(element => {
return element.innerText;
});
assert.strictEqual(selectedThreadName, 'Main', 'the Main thread is not active');
await addBreakpointForLine('0x06d', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x06d', devToolsPage));
const scriptLocation = await retrieveTopCallFrameWithoutResuming(devToolsPage);
assert.deepEqual(scriptLocation, 'stepping-with-state.wasm:0x6d');
selectedThreadElement = await devToolsPage.waitFor(SELECTED_THREAD_SELECTOR);
selectedThreadName = await selectedThreadElement.evaluate(element => {
return element.innerText;
});
assert.strictEqual(
selectedThreadName, 'worker-stepping-with-state-and-threads.js', 'the worker thread is not active');
await stepThroughTheCode(devToolsPage);
await stepThroughTheCode(devToolsPage);
let localScopeValues = await getValuesForScope('Local', 0, 1, devToolsPage);
assert.deepEqual(localScopeValues, [
'$var0: i32 {value: 42}',
'$var1: i32 {value: 6}',
'$var2: i32 {value: 5}',
]);
await removeBreakpointForLine('0x06d', devToolsPage);
await addBreakpointForLine('0x050', devToolsPage);
await reloadPageAndWaitForSourceFile(fileName, devToolsPage, inspectedPage);
await devToolsPage.waitForFunction(async () => await isBreakpointSet('0x050', devToolsPage));
// Due to the reload we may catch the last
// step though, so make sure DevTools updates.
await waitForStackTopMatch(/stepping-with-state.wasm:0x50/, devToolsPage);
selectedThreadElement = await devToolsPage.waitFor(SELECTED_THREAD_SELECTOR);
selectedThreadName = await selectedThreadElement.evaluate(element => {
return element.innerText;
});
assert.strictEqual(
selectedThreadName, 'worker-stepping-with-state-and-threads.js', 'the worker thread is not active');
await stepThroughTheCode(devToolsPage);
await stepThroughTheCode(devToolsPage);
localScopeValues = await getValuesForScope('Local', 0, 1, devToolsPage);
assert.deepEqual(localScopeValues, [
'$var0: i32 {value: 42}',
'$var1: i32 {value: 6}',
]);
await devToolsPage.page.keyboard.press('F8');
await devToolsPage.waitFor(PAUSE_BUTTON);
await checkBreakpointDidNotActivate(devToolsPage);
});
});
});
describe('Raw-Wasm', () => {
it('displays correct location in Wasm source', async ({devToolsPage, inspectedPage}) => {
// Have the target load the page.
await openSourceCodeEditorForFile(
'callstack-wasm-to-js.wasm', 'wasm/callstack-wasm-to-js.html', devToolsPage, inspectedPage);
// Go
const fooPromise = inspectedPage.evaluate('foo();'); // Don't await this, the target hits a debugger statement.
// This page automatically enters debugging.
const messageElement = await devToolsPage.waitFor('.paused-message');
const statusMain = await devToolsPage.waitFor('.status-main', messageElement);
assert.isOk(statusMain, 'Unable to find .status-main element');
const pauseMessage = await statusMain.evaluate(n => n.textContent);
assert.strictEqual(pauseMessage, 'Debugger paused');
// Find second frame of call stack
const titles = await getCallFrameNames(devToolsPage);
const locations = await getCallFrameLocations(devToolsPage);
assert.isAbove(titles.length, 1);
assert.isAbove(locations.length, 1);
assert.strictEqual(titles[1], '$foo');
assert.strictEqual(locations[1], 'callstack-wasm-to-js.wasm:0x32');
// Select second call frame.
await switchToCallFrame(2, devToolsPage);
// Wasm code for function call should be highlighted
const codeLine = await devToolsPage.waitFor('.cm-executionLine');
const codeText = await codeLine.evaluate(n => n.textContent);
assert.strictEqual(codeText, ' call $bar');
// Resume the evaluation
await devToolsPage.click(RESUME_BUTTON);
await fooPromise;
});
});