diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/pause/tests/pause.spec.js')
-rw-r--r-- | devtools/client/debugger/src/actions/pause/tests/pause.spec.js | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/pause/tests/pause.spec.js b/devtools/client/debugger/src/actions/pause/tests/pause.spec.js new file mode 100644 index 0000000000..3a562ccfdd --- /dev/null +++ b/devtools/client/debugger/src/actions/pause/tests/pause.spec.js @@ -0,0 +1,413 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +import { + actions, + selectors, + createStore, + createSourceObject, + waitForState, + makeSource, + makeOriginalSource, + makeFrame, +} from "../../../utils/test-head"; + +import { makeWhyNormal } from "../../../utils/test-mockup"; +import { createLocation } from "../../../utils/location"; + +const { isStepping } = selectors; + +let stepInResolve = null; +const mockCommandClient = { + stepIn: () => + new Promise(_resolve => { + stepInResolve = _resolve; + }), + stepOver: () => new Promise(_resolve => _resolve), + evaluate: async () => {}, + evaluateExpressions: async () => [], + resume: async () => {}, + getFrameScopes: async frame => frame.scope, + getFrames: async () => [], + setBreakpoint: () => new Promise(_resolve => {}), + sourceContents: ({ source }) => { + return new Promise((resolve, reject) => { + switch (source) { + case "foo1": + return resolve({ + source: "function foo1() {\n return 5;\n}", + contentType: "text/javascript", + }); + case "await": + return resolve({ + source: "async function aWait() {\n await foo(); return 5;\n}", + contentType: "text/javascript", + }); + + case "foo": + return resolve({ + source: "function foo() {\n return -5;\n}", + contentType: "text/javascript", + }); + case "foo-original": + return resolve({ + source: "\n\nfunction fooOriginal() {\n return -5;\n}", + contentType: "text/javascript", + }); + case "foo-wasm": + return resolve({ + source: { binary: new ArrayBuffer(0) }, + contentType: "application/wasm", + }); + case "foo-wasm/originalSource": + return resolve({ + source: "fn fooBar() {}\nfn barZoo() { fooBar() }", + contentType: "text/rust", + }); + } + + return resolve(); + }); + }, + getSourceActorBreakpointPositions: async () => ({}), + getSourceActorBreakableLines: async () => [], + actorID: "threadActorID", +}; + +const mockFrameId = "1"; + +function createPauseInfo( + frameLocation = createLocation({ + source: createSourceObject("foo1"), + line: 2, + }), + frameOpts = {} +) { + const frames = [ + makeFrame( + { id: mockFrameId, sourceId: frameLocation.sourceId }, + { + location: frameLocation, + generatedLocation: frameLocation, + ...frameOpts, + } + ), + ]; + return { + thread: "FakeThread", + frame: frames[0], + frames, + loadedObjects: [], + why: makeWhyNormal(), + }; +} + +describe("pause", () => { + describe("stepping", () => { + it("should set and clear the command", async () => { + const { dispatch, getState } = createStore(mockCommandClient); + const mockPauseInfo = createPauseInfo(); + + await dispatch(actions.newGeneratedSource(makeSource("foo1"))); + await dispatch(actions.paused(mockPauseInfo)); + const cx = selectors.getThreadContext(getState()); + const stepped = dispatch(actions.stepIn(cx)); + expect(isStepping(getState(), "FakeThread")).toBeTruthy(); + if (!stepInResolve) { + throw new Error("no stepInResolve"); + } + await stepInResolve(); + await stepped; + expect(isStepping(getState(), "FakeThread")).toBeFalsy(); + }); + + it("should only step when paused", async () => { + const client = { stepIn: jest.fn() }; + const { dispatch, cx } = createStore(client); + + dispatch(actions.stepIn(cx)); + expect(client.stepIn.mock.calls).toHaveLength(0); + }); + + it("should step when paused", async () => { + const { dispatch, getState } = createStore(mockCommandClient); + const mockPauseInfo = createPauseInfo(); + + await dispatch(actions.newGeneratedSource(makeSource("foo1"))); + await dispatch(actions.paused(mockPauseInfo)); + const cx = selectors.getThreadContext(getState()); + dispatch(actions.stepIn(cx)); + expect(isStepping(getState(), "FakeThread")).toBeTruthy(); + }); + + it("getting frame scopes with bindings", async () => { + const client = { ...mockCommandClient }; + const store = createStore(client, {}); + const { dispatch, getState } = store; + + const source = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + const generatedLocation = createLocation({ + source, + line: 1, + column: 0, + sourceActor: selectors.getFirstSourceActorForGeneratedSource( + getState(), + source.id + ), + }); + const mockPauseInfo = createPauseInfo(generatedLocation, { + scope: { + bindings: { + variables: { b: { value: {} } }, + arguments: [{ a: { value: {} } }], + }, + }, + }); + + const { frames } = mockPauseInfo; + client.getFrames = async () => frames; + await dispatch(actions.newOriginalSources([makeOriginalSource(source)])); + + await dispatch(actions.paused(mockPauseInfo)); + expect(selectors.getFrames(getState(), "FakeThread")).toEqual([ + { + id: mockFrameId, + generatedLocation, + location: generatedLocation, + originalDisplayName: "foo", + scope: { + bindings: { + arguments: [{ a: { value: {} } }], + variables: { b: { value: {} } }, + }, + }, + thread: "FakeThread", + }, + ]); + + expect(selectors.getFrameScopes(getState(), "FakeThread")).toEqual({ + generated: { + 1: { + pending: false, + scope: { + bindings: { + arguments: [{ a: { value: {} } }], + variables: { b: { value: {} } }, + }, + }, + }, + }, + mappings: { 1: undefined }, + original: { 1: { pending: false, scope: undefined } }, + }); + + expect( + selectors.getSelectedFrameBindings(getState(), "FakeThread") + ).toEqual(["b", "a"]); + }); + + it("maps frame locations and names to original source", async () => { + const sourceMapLoaderMock = { + getOriginalLocation: () => Promise.resolve(originalLocation), + getOriginalLocations: async items => items, + getOriginalSourceText: async () => ({ + text: "\n\nfunction fooOriginal() {\n return -5;\n}", + contentType: "text/javascript", + }), + getGeneratedLocation: async location => location, + }; + + const client = { ...mockCommandClient }; + const store = createStore(client, {}, sourceMapLoaderMock); + const { dispatch, getState } = store; + + const originalSource = await dispatch( + actions.newGeneratedSource(makeSource("foo-original")) + ); + + const originalLocation = createLocation({ + source: originalSource, + line: 3, + column: 0, + sourceActor: selectors.getFirstSourceActorForGeneratedSource( + getState(), + originalSource.id + ), + }); + + const generatedSource = await dispatch( + actions.newGeneratedSource(makeSource("foo")) + ); + const generatedLocation = createLocation({ + source: generatedSource, + line: 1, + column: 0, + sourceActor: selectors.getFirstSourceActorForGeneratedSource( + getState(), + generatedSource.id + ), + }); + const mockPauseInfo = createPauseInfo(generatedLocation); + + const { frames } = mockPauseInfo; + client.getFrames = async () => frames; + + await dispatch(actions.paused(mockPauseInfo)); + expect(selectors.getFrames(getState(), "FakeThread")).toEqual([ + { + id: mockFrameId, + generatedLocation, + location: originalLocation, + originalDisplayName: "fooOriginal", + scope: { bindings: { arguments: [], variables: {} } }, + thread: "FakeThread", + }, + ]); + }); + + it("maps frame to original frames", async () => { + const sourceMapLoaderMock = { + getOriginalStackFrames: loc => Promise.resolve(originStackFrames), + getOriginalLocation: () => Promise.resolve(originalLocation), + getOriginalLocations: async items => items, + getOriginalSourceText: async () => ({ + text: "fn fooBar() {}\nfn barZoo() { fooBar() }", + contentType: "text/rust", + }), + getGeneratedRangesForOriginal: async () => [], + }; + + const client = { ...mockCommandClient }; + const store = createStore(client, {}, sourceMapLoaderMock); + const { dispatch, getState } = store; + + const generatedSource = await dispatch( + actions.newGeneratedSource( + makeSource("foo-wasm", { introductionType: "wasm" }) + ) + ); + + const generatedLocation = createLocation({ + source: generatedSource, + line: 1, + column: 0, + sourceActor: selectors.getFirstSourceActorForGeneratedSource( + getState(), + generatedSource.id + ), + }); + const mockPauseInfo = createPauseInfo(generatedLocation); + const { frames } = mockPauseInfo; + client.getFrames = async () => frames; + + const [originalSource] = await dispatch( + actions.newOriginalSources([makeOriginalSource(generatedSource)]) + ); + + const originalLocation = createLocation({ + source: originalSource, + line: 1, + column: 1, + sourceActor: selectors.getFirstSourceActorForGeneratedSource( + getState(), + originalSource.id + ), + }); + const originalLocation2 = createLocation({ + source: originalSource, + line: 2, + column: 14, + sourceActor: selectors.getFirstSourceActorForGeneratedSource( + getState(), + originalSource.id + ), + }); + + const originStackFrames = [ + { + displayName: "fooBar", + thread: "FakeThread", + }, + { + displayName: "barZoo", + location: originalLocation2, + thread: "FakeThread", + }, + ]; + + await dispatch(actions.paused(mockPauseInfo)); + expect(selectors.getFrames(getState(), "FakeThread")).toEqual([ + { + asyncCause: undefined, + displayName: "fooBar", + generatedLocation, + id: "1", + index: undefined, + isOriginal: true, + location: originalLocation, + originalDisplayName: "fooBar", + originalVariables: undefined, + scope: { bindings: { arguments: [], variables: {} } }, + source: null, + state: undefined, + this: undefined, + thread: "FakeThread", + }, + { + asyncCause: undefined, + displayName: "barZoo", + generatedLocation, + id: "1-originalFrame1", + index: undefined, + isOriginal: true, + location: originalLocation2, + originalDisplayName: "barZoo", + originalVariables: undefined, + scope: { bindings: { arguments: [], variables: {} } }, + source: null, + state: undefined, + this: undefined, + thread: "FakeThread", + }, + ]); + }); + }); + + describe("resumed", () => { + it("should not evaluate expression while stepping", async () => { + const client = { ...mockCommandClient, evaluateExpressions: jest.fn() }; + const { dispatch, getState } = createStore(client); + const mockPauseInfo = createPauseInfo(); + + await dispatch(actions.newGeneratedSource(makeSource("foo1"))); + await dispatch(actions.paused(mockPauseInfo)); + + const cx = selectors.getThreadContext(getState()); + dispatch(actions.stepIn(cx)); + await dispatch(actions.resumed(mockCommandClient.actorID)); + expect(client.evaluateExpressions.mock.calls).toHaveLength(1); + }); + + it("resuming - will re-evaluate watch expressions", async () => { + const client = { ...mockCommandClient, evaluateExpressions: jest.fn() }; + const store = createStore(client); + const { dispatch, getState, cx } = store; + const mockPauseInfo = createPauseInfo(); + + await dispatch(actions.newGeneratedSource(makeSource("foo1"))); + await dispatch(actions.newGeneratedSource(makeSource("foo"))); + await dispatch(actions.addExpression(cx, "foo")); + await waitForState(store, state => selectors.getExpression(state, "foo")); + + client.evaluateExpressions.mockReturnValue(Promise.resolve(["YAY"])); + await dispatch(actions.paused(mockPauseInfo)); + + await dispatch(actions.resumed(mockCommandClient.actorID)); + const expression = selectors.getExpression(getState(), "foo"); + expect(expression && expression.value).toEqual("YAY"); + }); + }); +}); |