[unittest] Add test for synchronizing on instrumentation break

This adds a test for checking if the functionality of
`restoreBreakpointsForScript` in the BreakpointManager. This function
is used in order to synchronize breakpoints on instrumentation
breaks.

Bug: 1229541
Change-Id: I56d33acf329da03948b612c4e003e996302497bf
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3598877
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Commit-Queue: Kim-Anh Tran <kimanh@chromium.org>
This commit is contained in:
Kim-Anh Tran
2022-04-27 10:19:48 +00:00
committed by Devtools-frontend LUCI CQ
parent 8e1a99324b
commit facefdbfe3
4 changed files with 111 additions and 37 deletions
+5
View File
@@ -206,6 +206,11 @@ export class ExperimentsSupport {
this.#enabledTransiently.add(experimentName);
}
disableForTest(experimentName: string): void {
this.checkExperiment(experimentName);
this.#enabledTransiently.delete(experimentName);
}
clearForTest(): void {
this.#experiments = [];
this.#experimentNames.clear();
+1 -1
View File
@@ -652,7 +652,7 @@ export class DebuggerModel extends SDKModel<EventTypes> {
this.evaluateOnCallFrameCallback = callback;
}
setSynchronizeBreakpointsCallback(callback: (script: Script) => Promise<void>): void {
setSynchronizeBreakpointsCallback(callback: ((script: Script) => Promise<void>)|null): void {
this.#synchronizeBreakpointsCallback = callback;
}
+12 -3
View File
@@ -100,11 +100,12 @@ export class BreakpointManager extends Common.ObjectWrapper.ObjectWrapper<EventT
modelAdded(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.INSTRUMENTATION_BREAKPOINTS)) {
debuggerModel.setSynchronizeBreakpointsCallback(this.#restoreBreakpointsForScript.bind(this));
debuggerModel.setSynchronizeBreakpointsCallback(this.restoreBreakpointsForScript.bind(this));
}
}
modelRemoved(): void {
modelRemoved(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
debuggerModel.setSynchronizeBreakpointsCallback(null);
}
async copyBreakpoints(fromURL: Platform.DevToolsPath.UrlString, toSourceCode: Workspace.UISourceCode.UISourceCode):
@@ -117,7 +118,7 @@ export class BreakpointManager extends Common.ObjectWrapper.ObjectWrapper<EventT
// This method explicitly awaits the source map (if necessary) and the uiSourceCodes
// required to set all breakpoints that are related to this script.
async #restoreBreakpointsForScript(script: SDK.Script.Script): Promise<void> {
async restoreBreakpointsForScript(script: SDK.Script.Script): Promise<void> {
if (!Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.INSTRUMENTATION_BREAKPOINTS)) {
return;
}
@@ -416,6 +417,10 @@ export class Breakpoint implements SDK.TargetManager.SDKModelObserver<SDK.Debugg
modelBreakpoint.removeEventListeners();
}
modelBreakpoint(debuggerModel: SDK.DebuggerModel.DebuggerModel): ModelBreakpoint|undefined {
return this.#modelBreakpoints.get(debuggerModel);
}
addUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
if (!this.uiSourceCodes.has(uiSourceCode)) {
this.uiSourceCodes.add(uiSourceCode);
@@ -626,6 +631,10 @@ export class ModelBreakpoint {
}
}
get currentState(): Breakpoint.State|null {
return this.#currentState;
}
resetLocations(): void {
for (const uiLocation of this.#uiLocations.values()) {
this.#breakpoint.uiLocationRemoved(uiLocation);
@@ -3,6 +3,7 @@
// found in the LICENSE file.
const {assert} = chai;
import * as Root from '../../../../../front_end/core/root/root.js';
import * as SDK from '../../../../../front_end/core/sdk/sdk.js';
import * as Workspace from '../../../../../front_end/models/workspace/workspace.js';
import * as Bindings from '../../../../../front_end/models/bindings/bindings.js';
@@ -14,51 +15,110 @@ import {createUISourceCode} from '../../helpers/UISourceCodeHelpers.js';
import {assertNotNullOrUndefined} from '../../../../../front_end/core/platform/platform.js';
describeWithRealConnection('BreakpointManager', () => {
it('allows awaiting on scheduled update in debugger', async () => {
const breakpointManager = Bindings.BreakpointManager.BreakpointManager.instance();
const URL = 'file:///tmp/example.html' as Platform.DevToolsPath.UrlString;
const SCRIPT_ID = 'SCRIPT_ID' as Protocol.Runtime.ScriptId;
const BREAKPOINT_ID = 'BREAKPOINT_ID' as Protocol.Debugger.BreakpointId;
const JS_MIME_TYPE = 'text/javascript';
let target: SDK.Target.Target;
let breakpointManager: Bindings.BreakpointManager.BreakpointManager;
class TestDebuggerModel extends SDK.DebuggerModel.DebuggerModel {
constructor(target: SDK.Target.Target) {
super(target);
}
async setBreakpointByURL(
_url: Platform.DevToolsPath.UrlString, _lineNumber: number, _columnNumber?: number,
_condition?: string): Promise<SDK.DebuggerModel.SetBreakpointResult> {
return Promise.resolve(
{breakpointId: BREAKPOINT_ID, locations: [new SDK.DebuggerModel.Location(this, SCRIPT_ID, 42)]});
}
scriptForId(scriptId: string): SDK.Script.Script|null {
if (scriptId === SCRIPT_ID) {
return new SDK.Script.Script(
this, scriptId as Protocol.Runtime.ScriptId, URL, 0, 0, 0, 0, 0, '', false, false, undefined, false, 0,
null, null, null, null, null, null);
}
return null;
}
}
function createFakeScriptMapping(debuggerModel: TestDebuggerModel, SCRIPT_ID: Protocol.Runtime.ScriptId):
Bindings.DebuggerWorkspaceBinding.DebuggerSourceMapping {
const sdkLocation = new SDK.DebuggerModel.Location(debuggerModel, SCRIPT_ID as Protocol.Runtime.ScriptId, 13);
const mapping = {
rawLocationToUILocation: (_: SDK.DebuggerModel.Location) => null,
uiLocationToRawLocations:
(_uiSourceCode: Workspace.UISourceCode.UISourceCode, _lineNumber: number,
_columnNumber?: number) => [sdkLocation],
};
return mapping;
}
beforeEach(() => {
breakpointManager = Bindings.BreakpointManager.BreakpointManager.instance();
assertNotNullOrUndefined(breakpointManager);
const URL = 'file:///tmp/example.html' as Platform.DevToolsPath.UrlString;
const SCRIPT_ID = 'SCRIPT_ID' as Protocol.Runtime.ScriptId;
const BREAKPOINT_ID = 'BREAKPOINT_ID' as Protocol.Debugger.BreakpointId;
const {uiSourceCode, project} = createUISourceCode({url: URL, mimeType: 'text/javascript'});
const targetManager = SDK.TargetManager.TargetManager.instance();
const target = targetManager.mainTarget();
assertNotNullOrUndefined(target);
const mainTarget = targetManager.mainTarget();
assertNotNullOrUndefined(mainTarget);
target = mainTarget;
});
class TestDebuggerModel extends SDK.DebuggerModel.DebuggerModel {
constructor(target: SDK.Target.Target) {
super(target);
}
it('allows awaiting the restoration of breakpoints', async () => {
Root.Runtime.experiments.enableForTest(Root.Runtime.ExperimentName.INSTRUMENTATION_BREAKPOINTS);
async setBreakpointByURL(
_url: Platform.DevToolsPath.UrlString, _lineNumber: number, _columnNumber?: number,
_condition?: string): Promise<SDK.DebuggerModel.SetBreakpointResult> {
return Promise.resolve(
{breakpointId: BREAKPOINT_ID, locations: [new SDK.DebuggerModel.Location(debuggerModel, SCRIPT_ID, 42)]});
}
const {uiSourceCode} = createUISourceCode({url: URL, mimeType: JS_MIME_TYPE});
const breakpoint = await breakpointManager.setBreakpoint(uiSourceCode, 0, 0, '', true);
scriptForId(scriptId: string): SDK.Script.Script|null {
if (scriptId === SCRIPT_ID) {
return new SDK.Script.Script(
this, scriptId as Protocol.Runtime.ScriptId, URL, 0, 0, 0, 0, 0, '', false, false, undefined, false, 0,
null, null, null, null, null, null);
}
return null;
}
}
// Create a new DebuggerModel and notify the breakpoint engine about it.
const debuggerModel = new TestDebuggerModel(target);
breakpoint.modelAdded(debuggerModel);
// Make sure that we await all updates that are triggered by adding the model.
await breakpoint.updateBreakpoint();
// Retrieve the ModelBreakpoint that is linked to our DebuggerModel.
const modelBreakpoint = breakpoint.modelBreakpoint(debuggerModel);
assertNotNullOrUndefined(modelBreakpoint);
// Make sure that we do not have a linked script yet.
assertNotNullOrUndefined(modelBreakpoint.currentState);
assert.lengthOf(modelBreakpoint.currentState.positions, 1);
assert.isEmpty(modelBreakpoint.currentState.positions[0].scriptId);
// Create a fake mapping that can be used to set a breakpoint.
const mapping = createFakeScriptMapping(debuggerModel, SCRIPT_ID);
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().addSourceMapping(mapping);
// Now await restoring the breakpoint.
// A successful restore should update the ModelBreakpoint of the DebuggerModel
// to reflect a state, in which we have successfully set a breakpoint (i.e. a script id
// is available).
const script = debuggerModel.scriptForId(SCRIPT_ID);
assertNotNullOrUndefined(script);
await breakpointManager.restoreBreakpointsForScript(script);
assertNotNullOrUndefined(modelBreakpoint.currentState);
assert.lengthOf(modelBreakpoint.currentState.positions, 1);
assert.strictEqual(modelBreakpoint.currentState.positions[0].scriptId, SCRIPT_ID);
// Clean up.
breakpointManager.removeBreakpoint(breakpoint, true);
breakpointManager.modelRemoved(debuggerModel);
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().removeSourceMapping(mapping);
Root.Runtime.experiments.disableForTest(Root.Runtime.ExperimentName.INSTRUMENTATION_BREAKPOINTS);
});
it('allows awaiting on scheduled update in debugger', async () => {
const {uiSourceCode, project} = createUISourceCode({url: URL, mimeType: JS_MIME_TYPE});
const debuggerModel = new TestDebuggerModel(target);
const breakpoint = await breakpointManager.setBreakpoint(uiSourceCode, 42, 0, '', true);
const modelBreakpoint = new Bindings.BreakpointManager.ModelBreakpoint(
debuggerModel, breakpoint, breakpointManager.debuggerWorkspaceBinding);
const mapping = {
rawLocationToUILocation: (_: SDK.DebuggerModel.Location) => null,
uiLocationToRawLocations:
(_uiSourceCode: Workspace.UISourceCode.UISourceCode, _lineNumber: number,
_columnNumber?: number) => [new SDK.DebuggerModel.Location(debuggerModel, SCRIPT_ID, 13)],
};
const mapping = createFakeScriptMapping(debuggerModel, SCRIPT_ID);
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().addSourceMapping(mapping);
assert.isNull(breakpoint.currentState);
const update = modelBreakpoint.scheduleUpdateInDebugger();