Skip to content

Commit

Permalink
chore: add test cases for wasm dwarf debugging
Browse files Browse the repository at this point in the history
Closes #1789
  • Loading branch information
connor4312 committed Sep 20, 2023
1 parent 613b9ea commit ae8c0fa
Show file tree
Hide file tree
Showing 24 changed files with 6,642 additions and 27 deletions.
26 changes: 20 additions & 6 deletions src/adapter/breakpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,16 +487,18 @@ export class BreakpointManager {
const perScriptSm =
(this.launchConfig as IChromiumBaseConfiguration).perScriptSourcemaps === 'yes';

let entryBpSet: Promise<boolean>;
if (perScriptSm) {
return Promise.all([
entryBpSet = Promise.all([
this.updateEntryBreakpointMode(thread, EntryBreakpointMode.Greedy),
thread.setScriptSourceMapHandler(false, this._scriptSourceMapHandler),
]).then(() => true);
} else if (this._breakpointsPredictor && !this.launchConfig.pauseForSourceMap) {
return thread.setScriptSourceMapHandler(false, this._scriptSourceMapHandler);
entryBpSet = thread.setScriptSourceMapHandler(false, this._scriptSourceMapHandler);
} else {
return thread.setScriptSourceMapHandler(true, this._scriptSourceMapHandler);
entryBpSet = thread.setScriptSourceMapHandler(true, this._scriptSourceMapHandler);
}
this._sourceMapHandlerInstalled = { entryBpSet };
}

private async _uninstallSourceMapHandler(thread: Thread) {
Expand All @@ -517,7 +519,7 @@ export class BreakpointManager {
ids: number[],
): Promise<Dap.SetBreakpointsResult> {
if (!this._sourceMapHandlerInstalled && this._thread && params.breakpoints?.length) {
this._sourceMapHandlerInstalled = { entryBpSet: this._installSourceMapHandler(this._thread) };
this._installSourceMapHandler(this._thread);
}

const wasEntryBpSet = await this._sourceMapHandlerInstalled?.entryBpSet;
Expand Down Expand Up @@ -604,6 +606,14 @@ export class BreakpointManager {
return { breakpoints: [] };
}

// Ignore no-op breakpoint sets. These can come in from VS Code at the start
// of the session (if a file only has disabled breakpoints) and make it look
// like the user had removed all breakpoints they previously set, causing
// us to uninstall/re-install the SM handler repeatedly.
if (result.unbound.length === 0 && result.new.length === 0) {
return { breakpoints: [] };
}

// Cleanup existing breakpoints before setting new ones.
this._totalBreakpointsCount -= result.unbound.length;
await Promise.all(
Expand All @@ -615,8 +625,12 @@ export class BreakpointManager {

this._totalBreakpointsCount += result.new.length;

if (this._thread && this._totalBreakpointsCount === 0 && this._sourceMapHandlerInstalled) {
this._uninstallSourceMapHandler(this._thread);
if (this._thread) {
if (this._totalBreakpointsCount === 0 && this._sourceMapHandlerInstalled) {
this._uninstallSourceMapHandler(this._thread);
} else if (this._totalBreakpointsCount > 0 && !this._sourceMapHandlerInstalled) {
this._installSourceMapHandler(this._thread);
}
}

if (thread && result.new.length) {
Expand Down
26 changes: 16 additions & 10 deletions src/adapter/stackTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export class StackTrace {
continue;
}

const newFrames: InlinedFrame[] = [];
for (let i = 0; i < stack.length; i++) {
const inlinedFrame = new InlinedFrame({
source,
Expand All @@ -170,9 +171,12 @@ export class StackTrace {
name: stack[i].name,
root: frame,
});

this._appendFrame(inlinedFrame, last++);
this._frameById.set(inlinedFrame.frameId, inlinedFrame);
newFrames.push(inlinedFrame);
}

this._spliceFrames(last, 1, ...newFrames);
last += stack.length - 1;
}

return last;
Expand Down Expand Up @@ -206,17 +210,19 @@ export class StackTrace {
}
}

private _appendFrame(frame: FrameElement, index?: number) {
if (index !== undefined) {
this.frames.splice(index, 0, frame);
} else {
this.frames.push(frame);
}
if (!(frame instanceof AsyncSeparator)) {
this._frameById.set(frame.frameId, frame);
private _spliceFrames(index: number, deleteCount: number, ...frames: FrameElement[]) {
this.frames.splice(index, deleteCount, ...frames);
for (const frame of frames) {
if (!(frame instanceof AsyncSeparator)) {
this._frameById.set(frame.frameId, frame);
}
}
}

private _appendFrame(frame: FrameElement) {
this._spliceFrames(this.frames.length, 0, frame);
}

public async formatAsNative(): Promise<string> {
return await this.formatWithMapper(frame => frame.formatAsNative());
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export class Logger {
return variable;
}

async logStackTrace(threadId: number, withScopes?: boolean) {
async logStackTrace(threadId: number, withScopes = 0) {
const initial = await this._dap.stackTrace({ threadId });
const stack = initial.stackFrames;
let totalFrames = initial.totalFrames || stack.length;
Expand All @@ -161,7 +161,7 @@ export class Logger {
stack.push(...response.stackFrames);
if (response.totalFrames) totalFrames = Math.min(totalFrames, response.totalFrames);
}
let emptyLine = !!withScopes;
let emptyLine = withScopes > 0;
for (const frame of stack) {
if (emptyLine) this._log('');
if (frame.presentationHint === 'label') {
Expand All @@ -178,7 +178,7 @@ export class Logger {
frame.column
}${origin}`,
);
if (!withScopes) continue;
if (withScopes-- <= 0) continue;
const scopes = await this._dap.scopes({ frameId: frame.id });
if (typeof scopes === 'string') {
this._log(` scope error: ${scopes}`);
Expand Down
4 changes: 2 additions & 2 deletions src/test/node/node-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('node runtime', () => {
handle.load();
const stoppedParams = await handle.dap.once('stopped');
await delay(200); // need to pause test to let debouncer update scripts
await handle.logger.logStackTrace(stoppedParams.threadId!, false);
await handle.logger.logStackTrace(stoppedParams.threadId!);
handle.assertLog({ customAssert: assertSkipFiles });
});

Expand All @@ -89,7 +89,7 @@ describe('node runtime', () => {

handle.dap.on('output', o => handle.logger.logOutput(o));
handle.dap.on('stopped', async o => {
await handle.logger.logStackTrace(o.threadId!, false);
await handle.logger.logStackTrace(o.threadId!);
await handle.dap.continue({ threadId: o.threadId! });
});

Expand Down
10 changes: 5 additions & 5 deletions src/test/stacks/stacksTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { itIntegrates, waitForPause } from '../testIntegrationUtils';
describe('stacks', () => {
async function dumpStackAndContinue(p: TestP, scopes: boolean) {
const event = await p.dap.once('stopped');
await p.logger.logStackTrace(event.threadId!, scopes);
await p.logger.logStackTrace(event.threadId!, scopes ? Infinity : 0);
await p.dap.continue({ threadId: event.threadId! });
}

Expand Down Expand Up @@ -250,7 +250,7 @@ describe('stacks', () => {
async function waitForPausedThenDelayStackTrace(p: TestP, scopes: boolean) {
const event = await p.dap.once('stopped');
await delay(200); // need to pause test to let debouncer update scripts
await p.logger.logStackTrace(event.threadId!, scopes);
await p.logger.logStackTrace(event.threadId!, scopes ? Infinity : 0);
return event;
}

Expand Down Expand Up @@ -313,15 +313,15 @@ describe('stacks', () => {

const event = await p.dap.once('stopped');
await delay(500); // need to pause test to let debouncer update scripts
await p.logger.logStackTrace(event.threadId!, false);
await p.logger.logStackTrace(event.threadId!);

p.log('----send toggle skipfile status request----');
await p.dap.toggleSkipFileStatus({ resource: path });
await p.logger.logStackTrace(event.threadId!, false);
await p.logger.logStackTrace(event.threadId!);

p.log('----send (un)toggle skipfile status request----');
await p.dap.toggleSkipFileStatus({ resource: path });
await p.logger.logStackTrace(event.threadId!, false);
await p.logger.logStackTrace(event.threadId!);

p.assertLog();
});
Expand Down
2 changes: 1 addition & 1 deletion src/test/variables/variablesTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ describe('variables', () => {
const p = await r.launchUrlAndLoad('minified/index.html');
p.cdp.Runtime.evaluate({ expression: `test()` });
const event = await p.dap.once('stopped');
const stacks = await p.logger.logStackTrace(event.threadId!, true);
const stacks = await p.logger.logStackTrace(event.threadId!);

p.log('\nPreserves eval sourceURL (#1259):'); // https://github.com/microsoft/vscode-js-debug/issues/1259#issuecomment-1442584596
p.log(
Expand Down
149 changes: 149 additions & 0 deletions src/test/wasm/wasm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import Dap from '../../dap/api';
import { TestRoot } from '../test';
import { itIntegrates } from '../testIntegrationUtils';

describe('webassembly', () => {
Expand Down Expand Up @@ -47,4 +49,151 @@ describe('webassembly', () => {

p.assertLog();
});

describe('dwarf', () => {
const prepare = async (
r: TestRoot,
context: Mocha.Context,
file: string,
bp: Dap.SetBreakpointsParams,
) => {
// starting the dwarf debugger can be pretty slow, I observed up to 40
// seconds in one case :(
// context.timeout(120_000);

const p = await r.launchUrlAndLoad(`dwarf/${file}.html`);
bp.source.path = p.workspacePath(bp.source.path!);
await p.dap.setBreakpoints(bp);

await p.dap.once('breakpoint', bp => bp.breakpoint.verified);
await p.cdp.Page.reload({});
return p;
};

itIntegrates('scopes and variables', async ({ r, context }) => {
const p = await prepare(r, context, 'fibonacci', {
source: { path: 'web/dwarf/fibonacci.c' },
breakpoints: [{ line: 6 }],
});

const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId, 2);

r.assertLog();
});

itIntegrates('basic stepping', async ({ r, context }) => {
const p = await prepare(r, context, 'fibonacci', {
source: { path: 'web/dwarf/fibonacci.c' },
breakpoints: [{ line: 6 }],
});

{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.dap.setBreakpoints({
source: { path: p.workspacePath('web/dwarf/fibonacci.c') },
breakpoints: [],
});

p.dap.next({ threadId });
}

{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);
p.dap.stepOut({ threadId });
}

{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);
}

r.assertLog();
});

itIntegrates('inline breakpoints set at all call sites', async ({ r, context }) => {
const p = await prepare(r, context, 'diverse-inlining', {
source: {
path: 'web/dwarf/diverse-inlining.h',
},
breakpoints: [{ line: 2 }],
});

{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);
p.dap.continue({ threadId });
}

{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId, 2);
p.dap.continue({ threadId });
}

r.assertLog();
});

itIntegrates('inline function stepping 1', async ({ r, context }) => {
const p = await prepare(r, context, 'diverse-inlining', {
source: {
path: 'web/dwarf/diverse-inlining-main.c',
},
breakpoints: [{ line: 7 }],
});

const steps = [
// stopped at `argc = foo(argc);`
'stepIn',
// stopped at `INLINE static int`
'stepOut',
// stopped at `argc = foo(argc);`,
'next',

// stopped at `argc = bar(argc);`
'stepIn',
// stopped at `int bar(int x) {`
'stepIn',
// stopped at `x = x + 1;`
'stepIn',
] as const;

for (const step of steps) {
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);
p.dap[step]({ threadId });
p.log(`---- ${step} ----`);
}

const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);

r.assertLog();
});

itIntegrates('inline function stepping 2', async ({ r, context }) => {
const p = await prepare(r, context, 'diverse-inlining', {
source: {
path: 'web/dwarf/diverse-inlining-extern.c',
},
breakpoints: [{ line: 5 }],
});

// stopped at return foo()
{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);
p.dap.next({ threadId });
}

// should be back in main, stepped over inline range
{
const { threadId } = p.log(await p.dap.once('stopped'));
await p.logger.logStackTrace(threadId);
}

r.assertLog();
});
});
});
Loading

0 comments on commit ae8c0fa

Please sign in to comment.