提交 9af920e1 编写于 作者: E Ed Munoz

Fixes #3650: Support stop-all-threads mode debugging for multi-threaded programs

Add a new flag to the StoppedEvent that will trigger the debug adapter to be
queried for callstacks of the expanded threads in the callstack viewlet.
上级 bbe56857
...@@ -134,18 +134,18 @@ export class DebugEditorModelManager implements IWorkbenchContribution { ...@@ -134,18 +134,18 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
const result: editorcommon.IModelDeltaDecoration[] = []; const result: editorcommon.IModelDeltaDecoration[] = [];
const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame(); const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
const allThreads = this.debugService.getModel().getThreads(); const allThreads = this.debugService.getModel().getThreads();
if (!focusedStackFrame || !allThreads[focusedStackFrame.threadId] || !allThreads[focusedStackFrame.threadId].callStack) { if (!focusedStackFrame || !allThreads[focusedStackFrame.threadId] || !allThreads[focusedStackFrame.threadId].getCachedCallStack()) {
return result; return result;
} }
// only show decorations for the currently focussed thread. // only show decorations for the currently focussed thread.
const thread = allThreads[focusedStackFrame.threadId]; const thread = allThreads[focusedStackFrame.threadId];
thread.callStack.filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => { thread.getCachedCallStack().filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => {
const wholeLineRange = createRange(sf.lineNumber, sf.column, sf.lineNumber, Number.MAX_VALUE); const wholeLineRange = createRange(sf.lineNumber, sf.column, sf.lineNumber, Number.MAX_VALUE);
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focussed stack frame, // compute how to decorate the editor. Different decorations are used if this is a top stack frame, focussed stack frame,
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line). // an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
if (sf === thread.callStack[0]) { if (sf === thread.getCachedCallStack()[0]) {
result.push({ result.push({
options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN, options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1) range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1)
......
...@@ -189,6 +189,10 @@ export class BaseDebugController extends treedefaults.DefaultController { ...@@ -189,6 +189,10 @@ export class BaseDebugController extends treedefaults.DefaultController {
export class CallStackDataSource implements tree.IDataSource { export class CallStackDataSource implements tree.IDataSource {
constructor(@debug.IDebugService private debugService: debug.IDebugService) {
// noop
}
public getId(tree: tree.ITree, element: any): string { public getId(tree: tree.ITree, element: any): string {
return element.getId(); return element.getId();
} }
...@@ -199,7 +203,7 @@ export class CallStackDataSource implements tree.IDataSource { ...@@ -199,7 +203,7 @@ export class CallStackDataSource implements tree.IDataSource {
public getChildren(tree: tree.ITree, element: any): TPromise<any> { public getChildren(tree: tree.ITree, element: any): TPromise<any> {
if (element instanceof model.Thread) { if (element instanceof model.Thread) {
return TPromise.as((<model.Thread> element).callStack); return (<model.Thread> element).getCallStack(this.debugService);
} }
const threads = (<model.Model> element).getThreads(); const threads = (<model.Model> element).getThreads();
...@@ -209,7 +213,7 @@ export class CallStackDataSource implements tree.IDataSource { ...@@ -209,7 +213,7 @@ export class CallStackDataSource implements tree.IDataSource {
}); });
if (threadsArray.length === 1) { if (threadsArray.length === 1) {
return TPromise.as(threadsArray[0].callStack); return threadsArray[0].getCallStack(this.debugService);
} else { } else {
return TPromise.as(threadsArray); return TPromise.as(threadsArray);
} }
......
...@@ -226,7 +226,7 @@ class CallStackView extends viewlet.CollapsibleViewletView { ...@@ -226,7 +226,7 @@ class CallStackView extends viewlet.CollapsibleViewletView {
this.treeContainer = renderViewTree(container); this.treeContainer = renderViewTree(container);
this.tree = new treeimpl.Tree(this.treeContainer, { this.tree = new treeimpl.Tree(this.treeContainer, {
dataSource: new viewer.CallStackDataSource(), dataSource: this.instantiationService.createInstance(viewer.CallStackDataSource),
renderer: this.instantiationService.createInstance(viewer.CallStackRenderer), renderer: this.instantiationService.createInstance(viewer.CallStackRenderer),
accessibilityProvider: this.instantiationService.createInstance(viewer.CallstackAccessibilityProvider) accessibilityProvider: this.instantiationService.createInstance(viewer.CallstackAccessibilityProvider)
}, debugTreeOptions(nls.localize('callStackAriaLabel', "Debug Call Stack"))); }, debugTreeOptions(nls.localize('callStackAriaLabel', "Debug Call Stack")));
...@@ -275,7 +275,7 @@ class CallStackView extends viewlet.CollapsibleViewletView { ...@@ -275,7 +275,7 @@ class CallStackView extends viewlet.CollapsibleViewletView {
this.toDispose.push(this.debugService.getViewModel().addListener2(debug.ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED, () => { this.toDispose.push(this.debugService.getViewModel().addListener2(debug.ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED, () => {
const focussedThread = this.debugService.getModel().getThreads()[this.debugService.getViewModel().getFocusedThreadId()]; const focussedThread = this.debugService.getModel().getThreads()[this.debugService.getViewModel().getFocusedThreadId()];
if (focussedThread && focussedThread.stoppedDetails && focussedThread.stoppedDetails.reason !== 'step') { if (focussedThread && focussedThread.stoppedDetails && focussedThread.stoppedDetails.reason && focussedThread.stoppedDetails.reason !== 'step') {
this.pauseMessageLabel.text(nls.localize('debugStopped', "Paused on {0}", focussedThread.stoppedDetails.reason)); this.pauseMessageLabel.text(nls.localize('debugStopped', "Paused on {0}", focussedThread.stoppedDetails.reason));
if (focussedThread.stoppedDetails.text) { if (focussedThread.stoppedDetails.text) {
this.pauseMessageLabel.title(focussedThread.stoppedDetails.text); this.pauseMessageLabel.title(focussedThread.stoppedDetails.text);
...@@ -292,7 +292,11 @@ class CallStackView extends viewlet.CollapsibleViewletView { ...@@ -292,7 +292,11 @@ class CallStackView extends viewlet.CollapsibleViewletView {
if (focused) { if (focused) {
const threads = this.debugService.getModel().getThreads(); const threads = this.debugService.getModel().getThreads();
for (let ref in threads) { for (let ref in threads) {
if (threads[ref].callStack.some(sf => sf === focused)) { // Only query for threads whose callstacks are already available
// so that we don't perform unnecessary queries to the
// debug adapter. If it's a thread we need to expand, its
// callstack would have already been populated already
if (threads[ref].getCachedCallStack() && threads[ref].getCachedCallStack().some(sf => sf === focused)) {
this.tree.expand(threads[ref]); this.tree.expand(threads[ref]);
} }
} }
......
...@@ -25,6 +25,7 @@ export interface IRawModelUpdate { ...@@ -25,6 +25,7 @@ export interface IRawModelUpdate {
thread?: DebugProtocol.Thread; thread?: DebugProtocol.Thread;
callStack?: DebugProtocol.StackFrame[]; callStack?: DebugProtocol.StackFrame[];
stoppedDetails?: IRawStoppedDetails; stoppedDetails?: IRawStoppedDetails;
allThreadsStopped?: boolean;
} }
export interface IRawStoppedDetails { export interface IRawStoppedDetails {
...@@ -53,8 +54,31 @@ export interface IExpression extends ITreeElement, IExpressionContainer { ...@@ -53,8 +54,31 @@ export interface IExpression extends ITreeElement, IExpressionContainer {
export interface IThread extends ITreeElement { export interface IThread extends ITreeElement {
threadId: number; threadId: number;
name: string; name: string;
callStack: IStackFrame[];
stoppedDetails: IRawStoppedDetails; stoppedDetails: IRawStoppedDetails;
/**
* Queries the debug adapter for the callstack and returns a promise with
* the stack frames of the callstack.
* If the thread is not stopped, it returns a promise to an empty array.
*/
getCallStack(debugService: IDebugService): TPromise<IStackFrame[]>;
/**
* Gets the callstack if it has already been received from the debug
* adapter, otherwise it returns undefined.
*/
getCachedCallStack(): IStackFrame[];
/**
* Invalidates the callstack cache
*/
clearCallStack(): void;
/**
* Indicates whether this thread is stopped. The callstack for stopped
* threads can be retrieved from the debug adapter.
*/
stopped: boolean;
} }
export interface IScope extends IExpressionContainer { export interface IScope extends IExpressionContainer {
...@@ -229,6 +253,7 @@ export interface IRawDebugSession extends ee.EventEmitter { ...@@ -229,6 +253,7 @@ export interface IRawDebugSession extends ee.EventEmitter {
continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse>; continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse>;
pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse>; pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse>;
stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse>;
scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse>; scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse>;
variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse>; variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse>;
evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse>; evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse>;
......
...@@ -13,6 +13,7 @@ import severity from 'vs/base/common/severity'; ...@@ -13,6 +13,7 @@ import severity from 'vs/base/common/severity';
import types = require('vs/base/common/types'); import types = require('vs/base/common/types');
import arrays = require('vs/base/common/arrays'); import arrays = require('vs/base/common/arrays');
import debug = require('vs/workbench/parts/debug/common/debug'); import debug = require('vs/workbench/parts/debug/common/debug');
import errors = require('vs/base/common/errors');
import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { Source } from 'vs/workbench/parts/debug/common/debugSource';
const MAX_REPL_LENGTH = 10000; const MAX_REPL_LENGTH = 10000;
...@@ -92,16 +93,57 @@ export function getFullExpressionName(expression: debug.IExpression, sessionType ...@@ -92,16 +93,57 @@ export function getFullExpressionName(expression: debug.IExpression, sessionType
} }
export class Thread implements debug.IThread { export class Thread implements debug.IThread {
private promisedCallStack: TPromise<debug.IStackFrame[]>;
private cachedCallStack: debug.IStackFrame[];
public stoppedDetails: debug.IRawStoppedDetails; public stoppedDetails: debug.IRawStoppedDetails;
public stopped: boolean;
constructor(public name: string, public threadId, public callStack: debug.IStackFrame[]) { constructor(public name: string, public threadId) {
this.promisedCallStack = undefined;
this.stoppedDetails = undefined; this.stoppedDetails = undefined;
this.cachedCallStack = undefined;
this.stopped = false;
} }
public getId(): string { public getId(): string {
return `thread:${ this.name }:${ this.threadId }`; return `thread:${ this.name }:${ this.threadId }`;
} }
public clearCallStack(): void {
this.promisedCallStack = undefined;
this.cachedCallStack = undefined;
}
public getCachedCallStack(): debug.IStackFrame[] {
return this.cachedCallStack;
}
public getCallStack(debugService: debug.IDebugService): TPromise<debug.IStackFrame[]> {
if (!this.stopped) {
return TPromise.as([]);
}
if (!this.promisedCallStack) {
this.promisedCallStack = this.getCallStackImpl(debugService);
this.promisedCallStack.then(result => {
this.cachedCallStack = result;
}, errors.onUnexpectedError);
}
return this.promisedCallStack;
}
private getCallStackImpl(debugService: debug.IDebugService): TPromise<debug.IStackFrame[]> {
let session = debugService.getActiveSession();
return session.stackTrace({ threadId: this.threadId, levels: 20 }).then(response => {
return response.body.stackFrames.map((rsf, level) => {
if (!rsf) {
return new StackFrame(this.threadId, 0, new Source({ name: 'unknown' }), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
}
return new StackFrame(this.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: 'unknown' }), rsf.name, rsf.line, rsf.column);
});
});
}
} }
export class OutputElement implements debug.ITreeElement { export class OutputElement implements debug.ITreeElement {
...@@ -360,7 +402,7 @@ export class Model extends ee.EventEmitter implements debug.IModel { ...@@ -360,7 +402,7 @@ export class Model extends ee.EventEmitter implements debug.IModel {
if (removeThreads) { if (removeThreads) {
delete this.threads[reference]; delete this.threads[reference];
} else { } else {
this.threads[reference].callStack = []; this.threads[reference].clearCallStack();
this.threads[reference].stoppedDetails = undefined; this.threads[reference].stoppedDetails = undefined;
} }
} else { } else {
...@@ -370,7 +412,7 @@ export class Model extends ee.EventEmitter implements debug.IModel { ...@@ -370,7 +412,7 @@ export class Model extends ee.EventEmitter implements debug.IModel {
} else { } else {
for (let ref in this.threads) { for (let ref in this.threads) {
if (this.threads.hasOwnProperty(ref)) { if (this.threads.hasOwnProperty(ref)) {
this.threads[ref].callStack = []; this.threads[ref].clearCallStack();
this.threads[ref].stoppedDetails = undefined; this.threads[ref].stoppedDetails = undefined;
} }
} }
...@@ -380,6 +422,16 @@ export class Model extends ee.EventEmitter implements debug.IModel { ...@@ -380,6 +422,16 @@ export class Model extends ee.EventEmitter implements debug.IModel {
this.emit(debug.ModelEvents.CALLSTACK_UPDATED); this.emit(debug.ModelEvents.CALLSTACK_UPDATED);
} }
public continueThreads(): void {
for (let ref in this.threads) {
if (this.threads.hasOwnProperty(ref)) {
this.threads[ref].stopped = false;
}
}
this.clearThreads(false);
}
public getBreakpoints(): debug.IBreakpoint[] { public getBreakpoints(): debug.IBreakpoint[] {
return this.breakpoints; return this.breakpoints;
} }
...@@ -624,11 +676,13 @@ export class Model extends ee.EventEmitter implements debug.IModel { ...@@ -624,11 +676,13 @@ export class Model extends ee.EventEmitter implements debug.IModel {
public sourceIsUnavailable(source: Source): void { public sourceIsUnavailable(source: Source): void {
Object.keys(this.threads).forEach(key => { Object.keys(this.threads).forEach(key => {
this.threads[key].callStack.forEach(stackFrame => { if (this.threads[key].getCachedCallStack()) {
this.threads[key].getCachedCallStack().forEach(stackFrame => {
if (stackFrame.source.uri.toString() === source.uri.toString()) { if (stackFrame.source.uri.toString() === source.uri.toString()) {
stackFrame.source.available = false; stackFrame.source.available = false;
} }
}); });
}
}); });
this.emit(debug.ModelEvents.CALLSTACK_UPDATED); this.emit(debug.ModelEvents.CALLSTACK_UPDATED);
...@@ -636,21 +690,28 @@ export class Model extends ee.EventEmitter implements debug.IModel { ...@@ -636,21 +690,28 @@ export class Model extends ee.EventEmitter implements debug.IModel {
public rawUpdate(data: debug.IRawModelUpdate): void { public rawUpdate(data: debug.IRawModelUpdate): void {
if (data.thread) { if (data.thread) {
this.threads[data.threadId] = new Thread(data.thread.name, data.thread.id, []); this.threads[data.threadId] = new Thread(data.thread.name, data.thread.id);
} }
if (data.callStack) { if (data.stoppedDetails) {
// convert raw call stack into proper modelled call stack // Set the availability of the threads' callstacks depending on
this.threads[data.threadId].callStack = data.callStack.map( // whether the thread is stopped or not
(rsf, level) => { for (let ref in this.threads) {
if (!rsf) { if (this.threads.hasOwnProperty(ref)) {
return new StackFrame(data.threadId, 0, new Source({ name: 'unknown' }), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined); if (data.allThreadsStopped) {
// Only update the details if all the threads are stopped
// because we don't want to overwrite the details of other
// threads that have stopped for a different reason
this.threads[ref].stoppedDetails = data.stoppedDetails;
} }
return new StackFrame(data.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: 'unknown' }), rsf.name, rsf.line, rsf.column); this.threads[ref].stopped = data.allThreadsStopped;
}); this.threads[ref].clearCallStack();
}
}
this.threads[data.threadId].stoppedDetails = data.stoppedDetails; this.threads[data.threadId].stoppedDetails = data.stoppedDetails;
this.threads[data.threadId].stopped = true;
} }
this.emit(debug.ModelEvents.CALLSTACK_UPDATED); this.emit(debug.ModelEvents.CALLSTACK_UPDATED);
......
...@@ -40,8 +40,8 @@ export class Source { ...@@ -40,8 +40,8 @@ export class Source {
// first try to find the raw source amongst the stack frames - since that represenation has more data (source reference), // first try to find the raw source amongst the stack frames - since that represenation has more data (source reference),
const threads = model.getThreads(); const threads = model.getThreads();
for (let threadId in threads) { for (let threadId in threads) {
if (threads.hasOwnProperty(threadId) && threads[threadId].callStack) { if (threads.hasOwnProperty(threadId) && threads[threadId].getCachedCallStack()) {
const found = threads[threadId].callStack.filter(sf => sf.source.uri.toString() === uri.toString()).pop(); const found = threads[threadId].getCachedCallStack().filter(sf => sf.source.uri.toString() === uri.toString()).pop();
if (found) { if (found) {
return found.source.raw; return found.source.raw;
} }
......
...@@ -240,13 +240,17 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService ...@@ -240,13 +240,17 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService
this.setStateAndEmit(debug.State.Stopped); this.setStateAndEmit(debug.State.Stopped);
const threadId = event.body.threadId; const threadId = event.body.threadId;
this.getThreadData(threadId).then(() => { this.getThreadData(threadId).done(() => {
this.session.stackTrace({ threadId: threadId, levels: 20 }).done((result) => { let thread = this.model.getThreads()[threadId];
this.model.rawUpdate({ threadId: threadId, callStack: result.body.stackFrames, stoppedDetails: event.body }); this.model.rawUpdate({
this.windowService.getWindow().focus(); threadId: threadId,
const callStack = this.model.getThreads()[threadId].callStack; stoppedDetails: event.body,
allThreadsStopped: event.body.allThreadsStopped
});
thread.getCallStack(this).then(callStack => {
this.windowService.getWindow().focus();
if (callStack.length > 0) { if (callStack.length > 0) {
// focus first stack frame from top that has source location // focus first stack frame from top that has source location
const stackFrameToFocus = arrays.first(callStack, sf => !!sf.source, callStack[0]); const stackFrameToFocus = arrays.first(callStack, sf => !!sf.source, callStack[0]);
...@@ -263,7 +267,7 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService ...@@ -263,7 +267,7 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService
this.toDisposeOnSessionEnd.push(this.session.addListener2(debug.SessionEvents.CONTINUED, () => { this.toDisposeOnSessionEnd.push(this.session.addListener2(debug.SessionEvents.CONTINUED, () => {
aria.status(nls.localize('debuggingContinued', "Debugging continued.")); aria.status(nls.localize('debuggingContinued', "Debugging continued."));
this.model.clearThreads(false); this.model.continueThreads();
this.setFocusedStackFrameAndEvaluate(null); this.setFocusedStackFrameAndEvaluate(null);
this.setStateAndEmit(this.configurationManager.getConfiguration().noDebug ? debug.State.RunningNoDebug : debug.State.Running); this.setStateAndEmit(this.configurationManager.getConfiguration().noDebug ? debug.State.RunningNoDebug : debug.State.Running);
})); }));
......
...@@ -7,6 +7,11 @@ import assert = require('assert'); ...@@ -7,6 +7,11 @@ import assert = require('assert');
import uri from 'vs/base/common/uri'; import uri from 'vs/base/common/uri';
import severity from 'vs/base/common/severity'; import severity from 'vs/base/common/severity';
import debugmodel = require('vs/workbench/parts/debug/common/debugModel'); import debugmodel = require('vs/workbench/parts/debug/common/debugModel');
import debug = require('vs/workbench/parts/debug/common/debug');
import { TPromise } from 'vs/base/common/winjs.base';
import { DebugService } from 'vs/workbench/parts/debug/electron-browser/debugService';
import * as sinon from 'sinon';
import { MockDebugService } from 'vs/workbench/parts/debug/test/common/mockDebugService';
suite('Debug - Model', () => { suite('Debug - Model', () => {
var model: debugmodel.Model; var model: debugmodel.Model;
...@@ -91,6 +96,183 @@ suite('Debug - Model', () => { ...@@ -91,6 +96,183 @@ suite('Debug - Model', () => {
assert.equal(model.getThreads[threadId], null); assert.equal(model.getThreads[threadId], null);
}); });
test('threads multiple wtih allThreadsStopped', () => {
const mockDebugService = new MockDebugService();
const sessionStub = sinon.spy(mockDebugService.getActiveSession(), 'stackTrace');
const threadId1 = 1;
const threadName1 = "firstThread";
const threadId2 = 2;
const threadName2 = "secondThread";
const stoppedReason = "breakpoint";
// Add the threads
model.rawUpdate({
threadId: threadId1,
thread: {
id: threadId1,
name: threadName1
}
});
model.rawUpdate({
threadId: threadId2,
thread: {
id: threadId2,
name: threadName2
}
});
// Stopped event with all threads stopped
model.rawUpdate({
threadId: threadId1,
stoppedDetails: {
reason: stoppedReason,
threadId: 1
},
allThreadsStopped: true
});
const thread1 = model.getThreads()[threadId1];
const thread2 = model.getThreads()[threadId2];
// at the beginning, callstacks are obtainable but not available
assert.equal(thread1.name, threadName1);
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCachedCallStack(), undefined);
assert.equal(thread1.stoppedDetails.reason, stoppedReason);
assert.equal(thread2.name, threadName2);
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCachedCallStack(), undefined);
assert.equal(thread2.stoppedDetails.reason, stoppedReason);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.getCallStack(mockDebugService).then(() => {
assert.notEqual(thread1.getCachedCallStack(), undefined);
assert.equal(thread2.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 1);
});
thread2.getCallStack(mockDebugService).then(() => {
assert.notEqual(thread1.getCachedCallStack(), undefined);
assert.notEqual(thread2.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 2);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.getCallStack(mockDebugService).then(() => {
return thread2.getCallStack(mockDebugService);
}).then(() => {
assert.equal(sessionStub.callCount, 2);
});
// clearing the callstack results in the callstack not being available
thread1.clearCallStack();
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCachedCallStack(), undefined);
thread2.clearCallStack();
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCachedCallStack(), undefined);
model.continueThreads();
assert.equal(thread1.stopped, false);
assert.equal(thread2.stopped, false);
model.clearThreads(true);
assert.equal(model.getThreads[threadId1], null);
assert.equal(model.getThreads[threadId2], null);
});
test('threads mutltiple without allThreadsStopped', () => {
const mockDebugService = new MockDebugService();
const sessionStub = sinon.spy(mockDebugService.getActiveSession(), 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = "stoppedThread";
const runningThreadId = 2;
const runningThreadName = "runningThread";
const stoppedReason = "breakpoint";
// Add the threads
model.rawUpdate({
threadId: stoppedThreadId,
thread: {
id: stoppedThreadId,
name: stoppedThreadName
}
});
model.rawUpdate({
threadId: runningThreadId,
thread: {
id: runningThreadId,
name: runningThreadName
}
});
// Stopped event with only one thread stopped
model.rawUpdate({
threadId: stoppedThreadId,
stoppedDetails: {
reason: stoppedReason,
threadId: 1
},
allThreadsStopped: false
});
const stoppedThread = model.getThreads()[stoppedThreadId];
const runningThread = model.getThreads()[runningThreadId];
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
assert.equal(stoppedThread.name, stoppedThreadName);
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCachedCallStack(), undefined);
assert.equal(stoppedThread.stoppedDetails.reason, stoppedReason);
assert.equal(runningThread.name, runningThreadName);
assert.equal(runningThread.stopped, false);
assert.equal(runningThread.getCachedCallStack(), undefined);
assert.equal(runningThread.stoppedDetails, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.getCallStack(mockDebugService).then(() => {
assert.notEqual(stoppedThread.getCachedCallStack(), undefined);
assert.equal(runningThread.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 1);
});
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.getCallStack(mockDebugService).then(callStack => {
assert.deepEqual(callStack, []);
assert.equal(sessionStub.callCount, 1);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
stoppedThread.getCallStack(mockDebugService).then(() => {
assert.equal(sessionStub.callCount, 1);
});
// clearing the callstack results in the callstack not being available
stoppedThread.clearCallStack();
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCachedCallStack(), undefined);
model.continueThreads();
assert.equal(runningThread.stopped, false);
assert.equal(stoppedThread.stopped, false);
model.clearThreads(true);
assert.equal(model.getThreads[stoppedThreadId], null);
assert.equal(model.getThreads[runningThreadId], null);
});
// Expressions // Expressions
function assertWatchExpressions(watchExpressions: debugmodel.Expression[], expectedName: string) { function assertWatchExpressions(watchExpressions: debugmodel.Expression[], expectedName: string) {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import debug = require('vs/workbench/parts/debug/common/debug');
import editor = require('vs/editor/common/editorCommon');
import ee = require('vs/base/common/eventEmitter');
import uri from 'vs/base/common/uri';
import editorbrowser = require('vs/editor/browser/editorBrowser');
import severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
export class MockDebugService extends ee.EventEmitter implements debug.IDebugService {
private session: MockRawSession;
public serviceId = debug.IDebugService;
constructor() {
super();
this.session = new MockRawSession();
}
public getState(): debug.State {
return null;
}
public canSetBreakpointsIn(model: editor.IModel): boolean {
return false;
}
public getConfigurationName(): string {
return null;
}
public setConfiguration(name: string): TPromise<void> {
return TPromise.as(null);
}
public openConfigFile(sideBySide: boolean): TPromise<boolean> {
return TPromise.as(false);
}
public loadLaunchConfig(): TPromise<debug.IGlobalConfig> {
return TPromise.as(null);
}
public setFocusedStackFrameAndEvaluate(focusedStackFrame: debug.IStackFrame): void {}
public setBreakpointsForModel(modelUri: uri, rawData: debug.IRawBreakpoint[]): void {}
public toggleBreakpoint(IRawBreakpoint): TPromise<void> {
return TPromise.as(null);
}
public enableOrDisableAllBreakpoints(enabled: boolean): TPromise<void> {
return TPromise.as(null);
}
public toggleEnablement(element: debug.IEnablement): TPromise<void> {
return TPromise.as(null);
}
public toggleBreakpointsActivated(): TPromise<void> {
return TPromise.as(null);
}
public removeAllBreakpoints(): TPromise<any> {
return TPromise.as(null);
}
public sendAllBreakpoints(): TPromise<any> {
return TPromise.as(null);
}
public editBreakpoint(editor: editorbrowser.ICodeEditor, lineNumber: number): TPromise<void> {
return TPromise.as(null);
}
public addFunctionBreakpoint(): void {}
public renameFunctionBreakpoint(id: string, newFunctionName: string): TPromise<void> {
return TPromise.as(null);
}
public removeFunctionBreakpoints(id?: string): TPromise<void> {
return TPromise.as(null);
}
public addReplExpression(name: string): TPromise<void> {
return TPromise.as(null);
}
public clearReplExpressions(): void {}
public logToRepl(value: string, severity?: severity): void;
public logToRepl(value: { [key: string]: any }, severity?: severity): void;
public logToRepl(value: any, severity?: severity): void {}
public appendReplOutput(value: string, severity?: severity): void {}
public addWatchExpression(name?: string): TPromise<void> {
return TPromise.as(null);
}
public renameWatchExpression(id: string, newName: string): TPromise<void> {
return TPromise.as(null);
}
public clearWatchExpressions(id?: string): void {}
public createSession(noDebug: boolean): TPromise<any> {
return TPromise.as(null);
}
public restartSession(): TPromise<any> {
return TPromise.as(null);
}
public getActiveSession(): debug.IRawDebugSession {
return this.session;
}
public getModel(): debug.IModel {
return null;
}
public getViewModel(): debug.IViewModel {
return null
}
public openOrRevealEditor(source: Source, lineNumber: number, preserveFocus: boolean, sideBySide: boolean): TPromise<any> {
return TPromise.as(null);
}
public revealRepl(focus?: boolean): TPromise<void> {
return TPromise.as(null);
}
}
class MockRawSession extends ee.EventEmitter implements debug.IRawDebugSession {
public isAttach: boolean = false;
public capabilities: DebugProtocol.Capabilites;
public getType(): string {
return null;
}
public disconnect(restart?: boolean, force?: boolean): TPromise<DebugProtocol.DisconnectResponse> {
return TPromise.as(null);
}
public next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse> {
return TPromise.as(null);
}
public stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse> {
return TPromise.as(null);
}
public stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse> {
return TPromise.as(null);
}
public continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse> {
return TPromise.as(null);
}
public pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse> {
return TPromise.as(null);
}
public stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse> {
return TPromise.as({
body: {
stackFrames: []
}
});
}
public scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse> {
return TPromise.as(null);
}
public variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse> {
return TPromise.as(null);
}
evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse> {
return TPromise.as(null);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册