From 9af920e19af0c90085d108b92b4915b5cc7ddde2 Mon Sep 17 00:00:00 2001 From: Ed Munoz Date: Thu, 10 Mar 2016 20:04:13 -0800 Subject: [PATCH] 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. --- .../debug/browser/debugEditorModelManager.ts | 6 +- .../parts/debug/browser/debugViewer.ts | 8 +- .../parts/debug/browser/debugViewlet.ts | 12 +- src/vs/workbench/parts/debug/common/debug.ts | 27 ++- .../parts/debug/common/debugModel.ts | 97 +++++++-- .../parts/debug/common/debugSource.ts | 4 +- .../debug/electron-browser/debugService.ts | 16 +- .../debug/test/common/debugModel.test.ts | 182 +++++++++++++++++ .../debug/test/common/mockDebugService.ts | 193 ++++++++++++++++++ 9 files changed, 509 insertions(+), 36 deletions(-) create mode 100644 src/vs/workbench/parts/debug/test/common/mockDebugService.ts diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index a51fc716661fa..6c65329b4fb03 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -134,18 +134,18 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const result: editorcommon.IModelDeltaDecoration[] = []; const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame(); 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; } // only show decorations for the currently focussed thread. 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); // 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). - if (sf === thread.callStack[0]) { + if (sf === thread.getCachedCallStack()[0]) { result.push({ options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN, range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1) diff --git a/src/vs/workbench/parts/debug/browser/debugViewer.ts b/src/vs/workbench/parts/debug/browser/debugViewer.ts index d61081827eba3..018093f10adc3 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewer.ts @@ -189,6 +189,10 @@ export class BaseDebugController extends treedefaults.DefaultController { export class CallStackDataSource implements tree.IDataSource { + constructor(@debug.IDebugService private debugService: debug.IDebugService) { + // noop + } + public getId(tree: tree.ITree, element: any): string { return element.getId(); } @@ -199,7 +203,7 @@ export class CallStackDataSource implements tree.IDataSource { public getChildren(tree: tree.ITree, element: any): TPromise { if (element instanceof model.Thread) { - return TPromise.as(( element).callStack); + return ( element).getCallStack(this.debugService); } const threads = ( element).getThreads(); @@ -209,7 +213,7 @@ export class CallStackDataSource implements tree.IDataSource { }); if (threadsArray.length === 1) { - return TPromise.as(threadsArray[0].callStack); + return threadsArray[0].getCallStack(this.debugService); } else { return TPromise.as(threadsArray); } diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index 39776b9ec79d3..6260ff622a116 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -93,7 +93,7 @@ class VariablesView extends viewlet.CollapsibleViewletView { this.toDispose.push(this.debugService.addListener2(debug.ServiceEvents.STATE_CHANGED, () => { collapseAction.enabled = this.debugService.getState() === debug.State.Running || this.debugService.getState() === debug.State.Stopped; })); - + this.toDispose.push(this.tree.addListener2(events.EventType.FOCUS, (e: tree.IFocusEvent) => { const isMouseClick = (e.payload && e.payload.origin === 'mouse'); const isVariableType = (e.focus instanceof model.Variable); @@ -226,7 +226,7 @@ class CallStackView extends viewlet.CollapsibleViewletView { this.treeContainer = renderViewTree(container); this.tree = new treeimpl.Tree(this.treeContainer, { - dataSource: new viewer.CallStackDataSource(), + dataSource: this.instantiationService.createInstance(viewer.CallStackDataSource), renderer: this.instantiationService.createInstance(viewer.CallStackRenderer), accessibilityProvider: this.instantiationService.createInstance(viewer.CallstackAccessibilityProvider) }, debugTreeOptions(nls.localize('callStackAriaLabel', "Debug Call Stack"))); @@ -275,7 +275,7 @@ class CallStackView extends viewlet.CollapsibleViewletView { this.toDispose.push(this.debugService.getViewModel().addListener2(debug.ViewModelEvents.FOCUSED_STACK_FRAME_UPDATED, () => { 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)); if (focussedThread.stoppedDetails.text) { this.pauseMessageLabel.title(focussedThread.stoppedDetails.text); @@ -292,7 +292,11 @@ class CallStackView extends viewlet.CollapsibleViewletView { if (focused) { const threads = this.debugService.getModel().getThreads(); 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]); } } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 4c6fd25b8b105..612814d5997a9 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -25,6 +25,7 @@ export interface IRawModelUpdate { thread?: DebugProtocol.Thread; callStack?: DebugProtocol.StackFrame[]; stoppedDetails?: IRawStoppedDetails; + allThreadsStopped?: boolean; } export interface IRawStoppedDetails { @@ -53,8 +54,31 @@ export interface IExpression extends ITreeElement, IExpressionContainer { export interface IThread extends ITreeElement { threadId: number; name: string; - callStack: IStackFrame[]; 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; + + /** + * 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 { @@ -229,6 +253,7 @@ export interface IRawDebugSession extends ee.EventEmitter { continue(args: DebugProtocol.ContinueArguments): TPromise; pause(args: DebugProtocol.PauseArguments): TPromise; + stackTrace(args: DebugProtocol.StackTraceArguments): TPromise; scopes(args: DebugProtocol.ScopesArguments): TPromise; variables(args: DebugProtocol.VariablesArguments): TPromise; evaluate(args: DebugProtocol.EvaluateArguments): TPromise; diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 4c264873e1030..99aea6c66f6eb 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -13,6 +13,7 @@ import severity from 'vs/base/common/severity'; import types = require('vs/base/common/types'); import arrays = require('vs/base/common/arrays'); 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'; const MAX_REPL_LENGTH = 10000; @@ -92,16 +93,57 @@ export function getFullExpressionName(expression: debug.IExpression, sessionType } export class Thread implements debug.IThread { - + private promisedCallStack: TPromise; + private cachedCallStack: debug.IStackFrame[]; 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.cachedCallStack = undefined; + this.stopped = false; } public getId(): string { 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 { + 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 { + 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 { @@ -360,7 +402,7 @@ export class Model extends ee.EventEmitter implements debug.IModel { if (removeThreads) { delete this.threads[reference]; } else { - this.threads[reference].callStack = []; + this.threads[reference].clearCallStack(); this.threads[reference].stoppedDetails = undefined; } } else { @@ -370,7 +412,7 @@ export class Model extends ee.EventEmitter implements debug.IModel { } else { for (let ref in this.threads) { if (this.threads.hasOwnProperty(ref)) { - this.threads[ref].callStack = []; + this.threads[ref].clearCallStack(); this.threads[ref].stoppedDetails = undefined; } } @@ -380,6 +422,16 @@ export class Model extends ee.EventEmitter implements debug.IModel { 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[] { return this.breakpoints; } @@ -624,11 +676,13 @@ export class Model extends ee.EventEmitter implements debug.IModel { public sourceIsUnavailable(source: Source): void { Object.keys(this.threads).forEach(key => { - this.threads[key].callStack.forEach(stackFrame => { - if (stackFrame.source.uri.toString() === source.uri.toString()) { - stackFrame.source.available = false; - } - }); + if (this.threads[key].getCachedCallStack()) { + this.threads[key].getCachedCallStack().forEach(stackFrame => { + if (stackFrame.source.uri.toString() === source.uri.toString()) { + stackFrame.source.available = false; + } + }); + } }); this.emit(debug.ModelEvents.CALLSTACK_UPDATED); @@ -636,21 +690,28 @@ export class Model extends ee.EventEmitter implements debug.IModel { public rawUpdate(data: debug.IRawModelUpdate): void { 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) { - // convert raw call stack into proper modelled call stack - this.threads[data.threadId].callStack = data.callStack.map( - (rsf, level) => { - if (!rsf) { - return new StackFrame(data.threadId, 0, new Source({ name: 'unknown' }), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined); + if (data.stoppedDetails) { + // Set the availability of the threads' callstacks depending on + // whether the thread is stopped or not + for (let ref in this.threads) { + if (this.threads.hasOwnProperty(ref)) { + 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].stopped = true; } this.emit(debug.ModelEvents.CALLSTACK_UPDATED); diff --git a/src/vs/workbench/parts/debug/common/debugSource.ts b/src/vs/workbench/parts/debug/common/debugSource.ts index f948d90b1f1a9..2fc3eaef6b69c 100644 --- a/src/vs/workbench/parts/debug/common/debugSource.ts +++ b/src/vs/workbench/parts/debug/common/debugSource.ts @@ -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), const threads = model.getThreads(); for (let threadId in threads) { - if (threads.hasOwnProperty(threadId) && threads[threadId].callStack) { - const found = threads[threadId].callStack.filter(sf => sf.source.uri.toString() === uri.toString()).pop(); + if (threads.hasOwnProperty(threadId) && threads[threadId].getCachedCallStack()) { + const found = threads[threadId].getCachedCallStack().filter(sf => sf.source.uri.toString() === uri.toString()).pop(); if (found) { return found.source.raw; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index c577486f74f30..8a68ba37435c1 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -240,13 +240,17 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService this.setStateAndEmit(debug.State.Stopped); const threadId = event.body.threadId; - this.getThreadData(threadId).then(() => { - this.session.stackTrace({ threadId: threadId, levels: 20 }).done((result) => { + this.getThreadData(threadId).done(() => { + let thread = this.model.getThreads()[threadId]; - this.model.rawUpdate({ threadId: threadId, callStack: result.body.stackFrames, stoppedDetails: event.body }); - this.windowService.getWindow().focus(); - const callStack = this.model.getThreads()[threadId].callStack; + this.model.rawUpdate({ + threadId: threadId, + stoppedDetails: event.body, + allThreadsStopped: event.body.allThreadsStopped + }); + thread.getCallStack(this).then(callStack => { + this.windowService.getWindow().focus(); if (callStack.length > 0) { // focus first stack frame from top that has source location const stackFrameToFocus = arrays.first(callStack, sf => !!sf.source, callStack[0]); @@ -263,7 +267,7 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService this.toDisposeOnSessionEnd.push(this.session.addListener2(debug.SessionEvents.CONTINUED, () => { aria.status(nls.localize('debuggingContinued', "Debugging continued.")); - this.model.clearThreads(false); + this.model.continueThreads(); this.setFocusedStackFrameAndEvaluate(null); this.setStateAndEmit(this.configurationManager.getConfiguration().noDebug ? debug.State.RunningNoDebug : debug.State.Running); })); diff --git a/src/vs/workbench/parts/debug/test/common/debugModel.test.ts b/src/vs/workbench/parts/debug/test/common/debugModel.test.ts index 6e3b0877baa93..581f8eb9702d8 100644 --- a/src/vs/workbench/parts/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/parts/debug/test/common/debugModel.test.ts @@ -7,6 +7,11 @@ import assert = require('assert'); import uri from 'vs/base/common/uri'; import severity from 'vs/base/common/severity'; 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', () => { var model: debugmodel.Model; @@ -91,6 +96,183 @@ suite('Debug - Model', () => { 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 function assertWatchExpressions(watchExpressions: debugmodel.Expression[], expectedName: string) { diff --git a/src/vs/workbench/parts/debug/test/common/mockDebugService.ts b/src/vs/workbench/parts/debug/test/common/mockDebugService.ts new file mode 100644 index 0000000000000..18b7180b66dcb --- /dev/null +++ b/src/vs/workbench/parts/debug/test/common/mockDebugService.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { + return TPromise.as(null); + } + + public openConfigFile(sideBySide: boolean): TPromise { + return TPromise.as(false); + } + + public loadLaunchConfig(): TPromise { + return TPromise.as(null); + } + + public setFocusedStackFrameAndEvaluate(focusedStackFrame: debug.IStackFrame): void {} + + public setBreakpointsForModel(modelUri: uri, rawData: debug.IRawBreakpoint[]): void {} + + public toggleBreakpoint(IRawBreakpoint): TPromise { + return TPromise.as(null); + } + + public enableOrDisableAllBreakpoints(enabled: boolean): TPromise { + return TPromise.as(null); + } + + public toggleEnablement(element: debug.IEnablement): TPromise { + return TPromise.as(null); + } + + public toggleBreakpointsActivated(): TPromise { + return TPromise.as(null); + } + + public removeAllBreakpoints(): TPromise { + return TPromise.as(null); + } + + public sendAllBreakpoints(): TPromise { + return TPromise.as(null); + } + + public editBreakpoint(editor: editorbrowser.ICodeEditor, lineNumber: number): TPromise { + return TPromise.as(null); + } + + public addFunctionBreakpoint(): void {} + + public renameFunctionBreakpoint(id: string, newFunctionName: string): TPromise { + return TPromise.as(null); + } + + public removeFunctionBreakpoints(id?: string): TPromise { + return TPromise.as(null); + } + + public addReplExpression(name: string): TPromise { + 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 { + return TPromise.as(null); + } + + public renameWatchExpression(id: string, newName: string): TPromise { + return TPromise.as(null); + } + + public clearWatchExpressions(id?: string): void {} + + public createSession(noDebug: boolean): TPromise { + return TPromise.as(null); + } + + public restartSession(): TPromise { + 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 { + return TPromise.as(null); + } + + public revealRepl(focus?: boolean): TPromise { + 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 { + return TPromise.as(null); + } + + public next(args: DebugProtocol.NextArguments): TPromise { + return TPromise.as(null); + } + + public stepIn(args: DebugProtocol.StepInArguments): TPromise { + return TPromise.as(null); + } + + public stepOut(args: DebugProtocol.StepOutArguments): TPromise { + return TPromise.as(null); + } + + public continue(args: DebugProtocol.ContinueArguments): TPromise { + return TPromise.as(null); + } + + public pause(args: DebugProtocol.PauseArguments): TPromise { + return TPromise.as(null); + } + + public stackTrace(args: DebugProtocol.StackTraceArguments): TPromise { + return TPromise.as({ + body: { + stackFrames: [] + } + }); + } + + public scopes(args: DebugProtocol.ScopesArguments): TPromise { + return TPromise.as(null); + } + + public variables(args: DebugProtocol.VariablesArguments): TPromise { + return TPromise.as(null); + } + + evaluate(args: DebugProtocol.EvaluateArguments): TPromise { + return TPromise.as(null); + } +}