diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 9ffa5e8600f..0fad52f9739 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -120,7 +120,7 @@ export class DebugService implements IDebugService { this.disposables.add(this.adapterManager); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this.adapterManager); this.disposables.add(this.configurationManager); - this.debugStorage = this.instantiationService.createInstance(DebugStorage); + this.debugStorage = this.disposables.add(this.instantiationService.createInstance(DebugStorage)); this.chosenEnvironments = this.debugStorage.loadChosenEnvironments(); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 13decde4682..2e2b758fc43 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -40,7 +40,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -export class DebugSession implements IDebugSession { +export class DebugSession implements IDebugSession, IDisposable { parentSession: IDebugSession | undefined; private _subId: string | undefined; @@ -52,7 +52,7 @@ export class DebugSession implements IDebugSession { private threads = new Map(); private threadIds: number[] = []; private cancellationMap = new Map(); - private rawListeners: IDisposable[] = []; + private rawListeners = new DisposableStore(); private fetchThreadsScheduler: RunOnceScheduler | undefined; private passFocusScheduler: RunOnceScheduler; private lastContinuedThreadId: number | undefined; @@ -103,14 +103,11 @@ export class DebugSession implements IDebugSession { this.repl = (this.parentSession as DebugSession).repl; } - const toDispose = new DisposableStore(); + const toDispose = this.rawListeners.add(new DisposableStore()); const replListener = toDispose.add(new MutableDisposable()); replListener.value = this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire()); if (lifecycleService) { - toDispose.add(lifecycleService.onWillShutdown(() => { - this.shutdown(); - dispose(toDispose); - })); + toDispose.add(lifecycleService.onWillShutdown(() => this.shutdown())); } const compoundRoot = this._options.compoundRoot; @@ -950,7 +947,7 @@ export class DebugSession implements IDebugSession { return; } - this.rawListeners.push(this.raw.onDidInitialize(async () => { + this.rawListeners.add(this.raw.onDidInitialize(async () => { aria.status(localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = async () => { if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) { @@ -976,7 +973,7 @@ export class DebugSession implements IDebugSession { })); const statusQueue = new Queue(); - this.rawListeners.push(this.raw.onDidStop(async event => { + this.rawListeners.add(this.raw.onDidStop(async event => { statusQueue.queue(async () => { this.passFocusScheduler.cancel(); this.stoppedDetails.push(event.body); @@ -1026,7 +1023,7 @@ export class DebugSession implements IDebugSession { }); })); - this.rawListeners.push(this.raw.onDidThread(event => { + this.rawListeners.add(this.raw.onDidThread(event => { statusQueue.queue(async () => { if (event.body.reason === 'started') { // debounce to reduce threadsRequest frequency and improve performance @@ -1034,7 +1031,7 @@ export class DebugSession implements IDebugSession { this.fetchThreadsScheduler = new RunOnceScheduler(() => { this.fetchThreads(); }, 100); - this.rawListeners.push(this.fetchThreadsScheduler); + this.rawListeners.add(this.fetchThreadsScheduler); } if (!this.fetchThreadsScheduler.isScheduled()) { this.fetchThreadsScheduler.schedule(); @@ -1052,7 +1049,7 @@ export class DebugSession implements IDebugSession { }); })); - this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => { + this.rawListeners.add(this.raw.onDidTerminateDebugee(async event => { aria.status(localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { await this.debugService.restartSession(this, event.body.restart); @@ -1061,14 +1058,14 @@ export class DebugSession implements IDebugSession { } })); - this.rawListeners.push(this.raw.onDidContinued(event => { + this.rawListeners.add(this.raw.onDidContinued(event => { statusQueue.queue(async () => { const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId; if (typeof threadId === 'number') { this.stoppedDetails = this.stoppedDetails.filter(sd => sd.threadId !== threadId); const tokens = this.cancellationMap.get(threadId); this.cancellationMap.delete(threadId); - tokens?.forEach(t => t.cancel()); + tokens?.forEach(t => t.dispose(true)); } else { this.stoppedDetails = []; this.cancelAllRequests(); @@ -1082,7 +1079,7 @@ export class DebugSession implements IDebugSession { })); const outputQueue = new Queue(); - this.rawListeners.push(this.raw.onDidOutput(async event => { + this.rawListeners.add(this.raw.onDidOutput(async event => { const outputSeverity = event.body.category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info; // When a variables event is received, execute immediately to obtain the variables value #126967 @@ -1161,7 +1158,7 @@ export class DebugSession implements IDebugSession { }); })); - this.rawListeners.push(this.raw.onDidBreakpoint(event => { + this.rawListeners.add(this.raw.onDidBreakpoint(event => { const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined; const breakpoint = this.model.getBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id); const functionBreakpoint = this.model.getFunctionBreakpoints().find(bp => bp.getIdFromAdapter(this.getId()) === id); @@ -1216,30 +1213,30 @@ export class DebugSession implements IDebugSession { } })); - this.rawListeners.push(this.raw.onDidLoadedSource(event => { + this.rawListeners.add(this.raw.onDidLoadedSource(event => { this._onDidLoadedSource.fire({ reason: event.body.reason, source: this.getSource(event.body.source) }); })); - this.rawListeners.push(this.raw.onDidCustomEvent(event => { + this.rawListeners.add(this.raw.onDidCustomEvent(event => { this._onDidCustomEvent.fire(event); })); - this.rawListeners.push(this.raw.onDidProgressStart(event => { + this.rawListeners.add(this.raw.onDidProgressStart(event => { this._onDidProgressStart.fire(event); })); - this.rawListeners.push(this.raw.onDidProgressUpdate(event => { + this.rawListeners.add(this.raw.onDidProgressUpdate(event => { this._onDidProgressUpdate.fire(event); })); - this.rawListeners.push(this.raw.onDidProgressEnd(event => { + this.rawListeners.add(this.raw.onDidProgressEnd(event => { this._onDidProgressEnd.fire(event); })); - this.rawListeners.push(this.raw.onDidInvalidateMemory(event => { + this.rawListeners.add(this.raw.onDidInvalidateMemory(event => { this._onDidInvalidMemory.fire(event); })); - this.rawListeners.push(this.raw.onDidInvalidated(async event => { + this.rawListeners.add(this.raw.onDidInvalidated(async event => { if (!(event.body.areas && event.body.areas.length === 1 && (event.body.areas[0] === 'variables' || event.body.areas[0] === 'watch'))) { // If invalidated event only requires to update variables or watch, do that, otherwise refatch threads https://github.com/microsoft/vscode/issues/106745 this.cancelAllRequests(); @@ -1253,7 +1250,7 @@ export class DebugSession implements IDebugSession { } })); - this.rawListeners.push(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event))); + this.rawListeners.add(this.raw.onDidExitAdapter(event => this.onDidExitAdapter(event))); } private onDidExitAdapter(event?: AdapterEndEvent): void { @@ -1280,6 +1277,11 @@ export class DebugSession implements IDebugSession { this._onDidChangeState.fire(); } + public dispose() { + this.cancelAllRequests(); + dispose(this.rawListeners); + } + //---- sources getSourceForUri(uri: URI): Source | undefined { @@ -1325,7 +1327,7 @@ export class DebugSession implements IDebugSession { } private cancelAllRequests(): void { - this.cancellationMap.forEach(tokens => tokens.forEach(t => t.cancel())); + this.cancellationMap.forEach(tokens => tokens.forEach(t => t.dispose(true))); this.cancellationMap.clear(); } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 98bae97b4e1..50640fcf7e5 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -26,6 +26,7 @@ import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassem import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILogService } from 'vs/platform/log/common/log'; +import { autorun } from 'vs/base/common/observable'; interface IDebugProtocolVariableWithContext extends DebugProtocol.Variable { __vscodeVariableMenuContext?: string; @@ -1173,19 +1174,19 @@ export class ThreadAndSessionIds implements ITreeElement { } } -export class DebugModel implements IDebugModel { +export class DebugModel extends Disposable implements IDebugModel { private sessions: IDebugSession[]; private schedulers = new Map }>(); private breakpointsActivated = true; - private readonly _onDidChangeBreakpoints = new Emitter(); - private readonly _onDidChangeCallStack = new Emitter(); - private readonly _onDidChangeWatchExpressions = new Emitter(); - private breakpoints: Breakpoint[]; - private functionBreakpoints: FunctionBreakpoint[]; - private exceptionBreakpoints: ExceptionBreakpoint[]; - private dataBreakpoints: DataBreakpoint[]; - private watchExpressions: Expression[]; + private readonly _onDidChangeBreakpoints = this._register(new Emitter()); + private readonly _onDidChangeCallStack = this._register(new Emitter()); + private readonly _onDidChangeWatchExpressions = this._register(new Emitter()); + private breakpoints!: Breakpoint[]; + private functionBreakpoints!: FunctionBreakpoint[]; + private exceptionBreakpoints!: ExceptionBreakpoint[]; + private dataBreakpoints!: DataBreakpoint[]; + private watchExpressions!: Expression[]; private instructionBreakpoints: InstructionBreakpoint[]; constructor( @@ -1194,11 +1195,21 @@ export class DebugModel implements IDebugModel { @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService ) { - this.breakpoints = debugStorage.loadBreakpoints(); - this.functionBreakpoints = debugStorage.loadFunctionBreakpoints(); - this.exceptionBreakpoints = debugStorage.loadExceptionBreakpoints(); - this.dataBreakpoints = debugStorage.loadDataBreakpoints(); - this.watchExpressions = debugStorage.loadWatchExpressions(); + super(); + + this._register(autorun(reader => { + this.breakpoints = debugStorage.breakpoints.read(reader); + this.functionBreakpoints = debugStorage.functionBreakpoints.read(reader); + this.exceptionBreakpoints = debugStorage.exceptionBreakpoints.read(reader); + this.dataBreakpoints = debugStorage.dataBreakpoints.read(reader); + this._onDidChangeBreakpoints.fire(undefined); + })); + + this._register(autorun(reader => { + this.watchExpressions = debugStorage.watchExpressions.read(reader); + this._onDidChangeWatchExpressions.fire(undefined); + })); + this.instructionBreakpoints = []; this.sessions = []; } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index 1265ad5fa13..1f11c827a16 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -10,6 +10,8 @@ import { IEvaluate, IExpression, IDebugModel } from 'vs/workbench/contrib/debug/ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ILogService } from 'vs/platform/log/common/log'; +import { observableValue } from 'vs/base/common/observable'; +import { Disposable } from 'vs/base/common/lifecycle'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -19,13 +21,38 @@ const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions'; const DEBUG_CHOSEN_ENVIRONMENTS_KEY = 'debug.chosenenvironment'; const DEBUG_UX_STATE_KEY = 'debug.uxstate'; -export class DebugStorage { +export class DebugStorage extends Disposable { + public readonly breakpoints = observableValue('debugBreakpoints', this.loadBreakpoints()); + public readonly functionBreakpoints = observableValue('debugFunctionBreakpoints', this.loadFunctionBreakpoints()); + public readonly exceptionBreakpoints = observableValue('debugExceptionBreakpoints', this.loadExceptionBreakpoints()); + public readonly dataBreakpoints = observableValue('debugDataBreakpoints', this.loadDataBreakpoints()); + public readonly watchExpressions = observableValue('debugWatchExpressions', this.loadWatchExpressions()); + constructor( @IStorageService private readonly storageService: IStorageService, @ITextFileService private readonly textFileService: ITextFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService - ) { } + ) { + super(); + + this._register(storageService.onDidChangeValue(StorageScope.WORKSPACE, undefined, this._store)(e => { + if (e.external) { + switch (e.key) { + case DEBUG_BREAKPOINTS_KEY: + return this.breakpoints.set(this.loadBreakpoints(), undefined); + case DEBUG_FUNCTION_BREAKPOINTS_KEY: + return this.functionBreakpoints.set(this.loadFunctionBreakpoints(), undefined); + case DEBUG_EXCEPTION_BREAKPOINTS_KEY: + return this.exceptionBreakpoints.set(this.loadExceptionBreakpoints(), undefined); + case DEBUG_DATA_BREAKPOINTS_KEY: + return this.dataBreakpoints.set(this.loadDataBreakpoints(), undefined); + case DEBUG_WATCH_EXPRESSIONS_KEY: + return this.watchExpressions.set(this.loadWatchExpressions(), undefined); + } + } + })); + } loadDebugUxState(): 'simple' | 'default' { return this.storageService.get(DEBUG_UX_STATE_KEY, StorageScope.WORKSPACE, 'default') as 'simple' | 'default'; @@ -35,7 +62,7 @@ export class DebugStorage { this.storageService.store(DEBUG_UX_STATE_KEY, value, StorageScope.WORKSPACE, StorageTarget.MACHINE); } - loadBreakpoints(): Breakpoint[] { + private loadBreakpoints(): Breakpoint[] { let result: Breakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { @@ -46,7 +73,7 @@ export class DebugStorage { return result || []; } - loadFunctionBreakpoints(): FunctionBreakpoint[] { + private loadFunctionBreakpoints(): FunctionBreakpoint[] { let result: FunctionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => { @@ -57,7 +84,7 @@ export class DebugStorage { return result || []; } - loadExceptionBreakpoints(): ExceptionBreakpoint[] { + private loadExceptionBreakpoints(): ExceptionBreakpoint[] { let result: ExceptionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { @@ -68,7 +95,7 @@ export class DebugStorage { return result || []; } - loadDataBreakpoints(): DataBreakpoint[] { + private loadDataBreakpoints(): DataBreakpoint[] { let result: DataBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { @@ -79,7 +106,7 @@ export class DebugStorage { return result || []; } - loadWatchExpressions(): Expression[] { + private loadWatchExpressions(): Expression[] { let result: Expression[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE, '[]')).map((watchStoredData: { name: string; id: string }) => { diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index 10161571d0a..9164b7458a8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import * as dom from 'vs/base/browser/dom'; -import { Expression, Variable, Scope, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isWindows } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; +import { renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { isStatusbarInDebugMode } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { State } from 'vs/workbench/contrib/debug/common/debug'; -import { isWindows } from 'vs/base/common/platform'; +import { Expression, Scope, StackFrame, Thread, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; const $ = dom.$; suite('Debug - Base Debug View', () => { @@ -37,6 +38,8 @@ suite('Debug - Base Debug View', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('render view tree', () => { const container = $('.container'); const treeContainer = renderViewTree(container); @@ -132,9 +135,9 @@ suite('Debug - Base Debug View', () => { }); test('statusbar in debug mode', () => { - const model = createMockDebugModel(); - const session = createTestSession(model); - const session2 = createTestSession(model, undefined, { suppressDebugStatusbar: true }); + const model = createMockDebugModel(disposables); + const session = disposables.add(createTestSession(model)); + const session2 = disposables.add(createTestSession(model, undefined, { suppressDebugStatusbar: true })); assert.strictEqual(isStatusbarInDebugMode(State.Inactive, []), false); assert.strictEqual(isStatusbarInDebugMode(State.Initializing, [session]), false); assert.strictEqual(isStatusbarInDebugMode(State.Running, [session]), true); diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index e5db72a8eaa..db19f575765 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -4,24 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { URI as uri } from 'vs/base/common/uri'; -import { DebugModel, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; -import { getExpandedBodySize, getBreakpointMessageAndIcon } from 'vs/workbench/contrib/debug/browser/breakpointsView'; -import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { Range } from 'vs/editor/common/core/range'; -import { IBreakpointData, IBreakpointUpdateData, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; -import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; -import { OverviewRulerLane } from 'vs/editor/common/model'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { URI as uri } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { OverviewRulerLane } from 'vs/editor/common/model'; import { LanguageService } from 'vs/editor/common/services/languageService'; -import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; -import { MockDebugService } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; +import { getBreakpointMessageAndIcon, getExpandedBodySize } from 'vs/workbench/contrib/debug/browser/breakpointsView'; +import { IBreakpointData, IBreakpointUpdateData, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; +import { Breakpoint, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; +import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; +import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; +import { MockDebugService, MockDebugStorage } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void { +function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]) { let eventCount = 0; const toDispose = model.onDidChangeBreakpoints(e => { assert.strictEqual(e?.sessionOnly, false); @@ -37,22 +41,26 @@ function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakp assert.strictEqual((e!.added![i] as Breakpoint).lineNumber, data[i].lineNumber); } }); - model.addBreakpoints(uri, data); + const bps = model.addBreakpoints(uri, data); assert.strictEqual(eventCount, 1); + return bps; } suite('Debug - Breakpoints', () => { let model: DebugModel; - const disposables = new DisposableStore(); + let disposables: DisposableStore; setup(() => { - model = createMockDebugModel(); + disposables = new DisposableStore(); + model = createMockDebugModel(disposables); }); teardown(() => { - disposables.clear(); + disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + // Breakpoints test('simple', () => { @@ -175,7 +183,7 @@ suite('Debug - Breakpoints', () => { const modelUri = uri.file('/myfolder/myfile.js'); addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]); const breakpoints = model.getBreakpoints(); - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); const data = new Map(); assert.strictEqual(breakpoints[0].lineNumber, 5); @@ -187,7 +195,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(breakpoints[0].lineNumber, 5); assert.strictEqual(breakpoints[1].lineNumber, 50); - const session2 = createTestSession(model); + const session2 = disposables.add(createTestSession(model)); const data2 = new Map(); data2.set(breakpoints[0].getId(), { verified: true, line: 100 }); data2.set(breakpoints[1].getId(), { verified: true, line: 500 }); @@ -212,7 +220,7 @@ suite('Debug - Breakpoints', () => { test('exception breakpoints', () => { let eventCount = 0; - model.onDidChangeBreakpoints(() => eventCount++); + disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); model.setExceptionBreakpointsForSession("session-id-1", [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]); assert.strictEqual(eventCount, 1); let exceptionBreakpoints = model.getExceptionBreakpointsForSession("session-id-1"); @@ -245,7 +253,7 @@ suite('Debug - Breakpoints', () => { test('exception breakpoints multiple sessions', () => { let eventCount = 0; - model.onDidChangeBreakpoints(() => eventCount++); + disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); model.setExceptionBreakpointsForSession("session-id-4", [{ filter: 'uncaught', label: 'UNCAUGHT', default: true }, { filter: 'caught', label: 'CAUGHT' }]); model.setExceptionBreakpointFallbackSession("session-id-4"); @@ -286,7 +294,7 @@ suite('Debug - Breakpoints', () => { test('instruction breakpoints', () => { let eventCount = 0; - model.onDidChangeBreakpoints(() => eventCount++); + disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); //address: string, offset: number, condition?: string, hitCondition?: string model.addInstructionBreakpoint('0xCCCCFFFF', 0); @@ -308,7 +316,7 @@ suite('Debug - Breakpoints', () => { test('data breakpoints', () => { let eventCount = 0; - model.onDidChangeBreakpoints(() => eventCount++); + disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); model.addDataBreakpoint('second', 'secondId', false, ['readWrite'], 'readWrite'); @@ -432,4 +440,31 @@ suite('Debug - Breakpoints', () => { textModel.dispose(); instantiationService.dispose(); }); + + test('updates when storage changes', () => { + const storage1 = disposables.add(new TestStorageService()); + const debugStorage1 = disposables.add(new MockDebugStorage(storage1)); + const model1 = disposables.add(new DebugModel(debugStorage1, { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); + + // 1. create breakpoints in the first model + const modelUri = uri.file('/myfolder/my file first.js'); + const first = [ + { lineNumber: 1, enabled: true, condition: 'x > 5' }, + { lineNumber: 2, column: 4, enabled: false }, + ]; + + addBreakpointsAndCheckEvents(model1, modelUri, first); + debugStorage1.storeBreakpoints(model1); + const stored = storage1.get('debug.breakpoint', StorageScope.WORKSPACE); + + // 2. hydrate a new model and ensure external breakpoints get applied + const storage2 = disposables.add(new TestStorageService()); + const model2 = disposables.add(new DebugModel(disposables.add(new MockDebugStorage(storage2)), { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); + storage2.store('debug.breakpoint', stored, StorageScope.WORKSPACE, StorageTarget.USER, /* external= */ true); + assert.deepStrictEqual(model2.getBreakpoints().map(b => b.getId()), model1.getBreakpoints().map(b => b.getId())); + + // 3. ensure non-external changes are ignored + storage2.store('debug.breakpoint', '[]', StorageScope.WORKSPACE, StorageTarget.USER, /* external= */ false); + assert.deepStrictEqual(model2.getBreakpoints().map(b => b.getId()), model1.getBreakpoints().map(b => b.getId())); + }); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index c6158426173..9a9f2a09d63 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -4,24 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import * as sinon from 'sinon'; -import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; -import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; -import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; -import { Range } from 'vs/editor/common/core/range'; -import { IDebugSessionOptions, State, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; -import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; -import { Constants } from 'vs/base/common/uint'; -import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView'; -import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; +import { Constants } from 'vs/base/common/uint'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { NullLogService } from 'vs/platform/log/common/log'; +import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; +import { getContext, getContextForContributedActions, getSpecificSourceName } from 'vs/workbench/contrib/debug/browser/callStackView'; +import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService'; +import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; +import { IDebugService, IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/debug'; +import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; +import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; const mockWorkspaceContextService = { getWorkspace: () => { @@ -70,22 +72,28 @@ function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFr suite('Debug - CallStack', () => { let model: DebugModel; let mockRawSession: MockRawSession; + let disposables: DisposableStore; setup(() => { - model = createMockDebugModel(); + disposables = new DisposableStore(); + model = createMockDebugModel(disposables); mockRawSession = new MockRawSession(); }); teardown(() => { + disposables.dispose(); sinon.restore(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + // Threads test('threads simple', () => { const threadId = 1; const threadName = 'firstThread'; const session = createTestSession(model); + disposables.add(session); model.addSession(session); assert.strictEqual(model.getSessions(true).length, 1); @@ -113,6 +121,7 @@ suite('Debug - CallStack', () => { // Add the threads const session = createTestSession(model); + disposables.add(session); model.addSession(session); session['raw'] = mockRawSession; @@ -193,6 +202,7 @@ suite('Debug - CallStack', () => { // Add the threads const session = createTestSession(model); + disposables.add(session); model.addSession(session); session['raw'] = mockRawSession; @@ -246,6 +256,7 @@ suite('Debug - CallStack', () => { const runningThreadName = 'runningThread'; const stoppedReason = 'breakpoint'; const session = createTestSession(model); + disposables.add(session); model.addSession(session); session['raw'] = mockRawSession; @@ -318,6 +329,7 @@ suite('Debug - CallStack', () => { test('stack frame get specific source name', () => { const session = createTestSession(model); + disposables.add(session); model.addSession(session); const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session); @@ -327,6 +339,7 @@ suite('Debug - CallStack', () => { test('stack frame toString()', () => { const session = createTestSession(model); + disposables.add(session); const thread = new Thread(session, 'mockthread', 1); const firstSource = new Source({ name: 'internalModule.js', @@ -342,17 +355,17 @@ suite('Debug - CallStack', () => { }); test('debug child sessions are added in correct order', () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); model.addSession(session); - const secondSession = createTestSession(model, 'mockSession2'); + const secondSession = disposables.add(createTestSession(model, 'mockSession2')); model.addSession(secondSession); - const firstChild = createTestSession(model, 'firstChild', { parentSession: session }); + const firstChild = disposables.add(createTestSession(model, 'firstChild', { parentSession: session })); model.addSession(firstChild); - const secondChild = createTestSession(model, 'secondChild', { parentSession: session }); + const secondChild = disposables.add(createTestSession(model, 'secondChild', { parentSession: session })); model.addSession(secondChild); - const thirdSession = createTestSession(model, 'mockSession3'); + const thirdSession = disposables.add(createTestSession(model, 'mockSession3')); model.addSession(thirdSession); - const anotherChild = createTestSession(model, 'secondChild', { parentSession: secondSession }); + const anotherChild = disposables.add(createTestSession(model, 'secondChild', { parentSession: secondSession })); model.addSession(anotherChild); const sessions = model.getSessions(); @@ -366,6 +379,7 @@ suite('Debug - CallStack', () => { test('decorations', () => { const session = createTestSession(model); + disposables.add(session); model.addSession(session); const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session); let decorations = createDecorationsForStackFrame(firstStackFrame, true, false); @@ -398,6 +412,7 @@ suite('Debug - CallStack', () => { test('contexts', () => { const session = createTestSession(model); + disposables.add(session); model.addSession(session); const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session); let context = getContext(firstStackFrame); @@ -436,8 +451,10 @@ suite('Debug - CallStack', () => { return State.Stopped; } }(generateUuid(), { resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, undefined!, mockUriIdentityService, new TestInstantiationService(), undefined!, undefined!, new NullLogService()); + disposables.add(session); const runningSession = createTestSession(model); + disposables.add(runningSession); model.addSession(runningSession); model.addSession(session); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index c3b5d88bf80..baade0af6d2 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { generateUuid } from 'vs/base/common/uuid'; -import { appendStylizedStringToContainer, handleANSIOutput, calcANSI8bitColor } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { Color, RGBA } from 'vs/base/common/color'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { TestThemeService, TestColorTheme } from 'vs/platform/theme/test/common/testThemeService'; -import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; -import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; -import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { appendStylizedStringToContainer, calcANSI8bitColor, handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; +import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; +import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; +import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Debug - ANSI Handling', () => { @@ -32,7 +33,7 @@ suite('Debug - ANSI Handling', () => { */ setup(() => { disposables = new DisposableStore(); - model = createMockDebugModel(); + model = createMockDebugModel(disposables); session = createTestSession(model); const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposables); @@ -50,6 +51,8 @@ suite('Debug - ANSI Handling', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('appendStylizedStringToContainer', () => { const root: HTMLSpanElement = document.createElement('span'); let child: Node; diff --git a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts index 287c489a781..a63840db4e6 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts @@ -5,27 +5,28 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { FileService } from 'vs/platform/files/common/fileService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; import { DebugConfigurationProviderTriggerKind, IAdapterManager, IConfig, IDebugAdapterExecutable, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { TestHistoryService, TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { URI } from 'vs/base/common/uri'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; suite('debugConfigurationManager', () => { const configurationProviderType = 'custom-type'; let _debugConfigurationManager: ConfigurationManager; - const disposables = new DisposableStore(); + let disposables: DisposableStore; const adapterManager = { getDebugAdapterDescriptor(session: IDebugSession, config: IConfig): Promise { @@ -47,6 +48,7 @@ suite('debugConfigurationManager', () => { const configurationService = new TestConfigurationService(); setup(() => { + disposables = new DisposableStore(); const fileService = disposables.add(new FileService(new NullLogService())); const instantiationService = disposables.add(new TestInstantiationService(new ServiceCollection([IPreferencesService, preferencesService], [IConfigurationService, configurationService]))); _debugConfigurationManager = new ConfigurationManager( @@ -62,6 +64,10 @@ suite('debugConfigurationManager', () => { new ContextKeyService(configurationService)); }); + teardown(() => disposables.dispose()); + + ensureNoDisposablesAreLeakedInTestSuite(); + test('resolves configuration based on type', async () => { disposables.add(_debugConfigurationManager.registerDebugConfigurationProvider({ type: configurationProviderType, diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index 62e17b6dcc2..f71a916f0c0 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover'; import type { IExpression, IScope } from 'vs/workbench/contrib/debug/common/debug'; @@ -13,9 +15,20 @@ import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callS import { createMockDebugModel, mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; suite('Debug - Hover', () => { + let disposables: DisposableStore; + setup(() => { + disposables = new DisposableStore(); + }); + + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + test('find expression in stack frame', async () => { - const model = createMockDebugModel(); - const session = createTestSession(model); + const model = createMockDebugModel(disposables); + const session = disposables.add(createTestSession(model)); const thread = new class extends Thread { public override getCallStack(): StackFrame[] { diff --git a/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts index 499580f8416..7647820f3d0 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; import { mockObject, MockObject } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MemoryRangeType } from 'vs/workbench/contrib/debug/common/debug'; import { MemoryRegion } from 'vs/workbench/contrib/debug/common/debugModel'; import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; @@ -20,6 +21,8 @@ suite('Debug - Memory', () => { success: true, }; + ensureNoDisposablesAreLeakedInTestSuite(); + suite('MemoryRegion', () => { let memory: VSBuffer; let unreadable: number; diff --git a/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts index be735728c90..672ae3c3061 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugSource.test.ts @@ -4,14 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { URI as uri } from 'vs/base/common/uri'; -import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { isWindows } from 'vs/base/common/platform'; -import { mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; +import { URI as uri } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; suite('Debug - Source', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('from raw source', () => { const source = new Source({ name: 'zz', diff --git a/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts index 3b2b6003495..585497db087 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugUtils.test.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { formatPII, getExactExpressionStartAndEnd, getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfig } from 'vs/workbench/contrib/debug/common/debug'; +import { formatPII, getExactExpressionStartAndEnd, getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; suite('Debug - Utils', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('formatPII', () => { assert.strictEqual(formatPII('Foo Bar', false, {}), 'Foo Bar'); assert.strictEqual(formatPII('Foo {key} Bar', false, {}), 'Foo {key} Bar'); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts index 3b232aab21f..d42de786cb5 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugViewModel.test.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; -import { StackFrame, Expression, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; -import { mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; -import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { NullLogService } from 'vs/platform/log/common/log'; +import { Expression, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; +import { mockUriIdentityService } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; +import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; suite('Debug - View Model', () => { let model: ViewModel; @@ -19,6 +20,8 @@ suite('Debug - View Model', () => { model = new ViewModel(new MockContextKeyService()); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('focused stack frame', () => { assert.strictEqual(model.focusedStackFrame, undefined); assert.strictEqual(model.focusedThread, undefined); diff --git a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index 3e7ed14e6f8..a452e1a0e97 100644 --- a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { isWindows } from 'vs/base/common/platform'; -import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { URI } from 'vs/base/common/uri'; -import { ITunnelService } from 'vs/platform/tunnel/common/tunnel'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isWindows } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ITunnelService } from 'vs/platform/tunnel/common/tunnel'; +import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Debug - Link Detector', () => { @@ -32,6 +33,8 @@ suite('Debug - Link Detector', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + /** * Assert that a given Element is an anchor element. * diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts index 29c7e61f148..9e736c215b1 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebugModel.ts @@ -3,15 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DisposableStore } from 'vs/base/common/lifecycle'; import { NullLogService } from 'vs/platform/log/common/log'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { MockDebugStorage } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; const fileService = new TestFileService(); export const mockUriIdentityService = new UriIdentityService(fileService); -export function createMockDebugModel(): DebugModel { - return new DebugModel(new MockDebugStorage(), { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService()); +export function createMockDebugModel(disposable: DisposableStore): DebugModel { + const storage = disposable.add(new TestStorageService()); + const debugStorage = disposable.add(new MockDebugStorage(storage)); + return disposable.add(new DebugModel(debugStorage, { isDirty: (e: any) => false }, mockUriIdentityService, new NullLogService())); } diff --git a/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts b/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts index 1ccdc5bcefe..465438ad2c5 100644 --- a/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock, mockObject } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -14,13 +16,24 @@ import { IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; suite('RawDebugSession', () => { + let disposables: DisposableStore; + setup(() => { + disposables = new DisposableStore(); + }); + + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + function createTestObjects() { const debugAdapter = new MockDebugAdapter(); const dbgr = mockObject()({ type: 'mock-debug' }); - new RawDebugSession( + const session = new RawDebugSession( debugAdapter, dbgr as any as IDebugger, 'sessionId', @@ -29,6 +42,9 @@ suite('RawDebugSession', () => { new (mock()), new (mock()), new (mock())); + disposables.add(session); + disposables.add(debugAdapter); + return { debugAdapter, dbgr }; } diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index a04f2ed1963..b1dbc030a48 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -5,30 +5,41 @@ import * as assert from 'assert'; -import severity from 'vs/base/common/severity'; -import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; -import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; -import { ReplOutputElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult, ReplGroup, ReplVariableElement } from 'vs/workbench/contrib/debug/common/replModel'; -import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; -import { timeout } from 'vs/base/common/async'; -import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; -import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter'; import { TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { timeout } from 'vs/base/common/async'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import severity from 'vs/base/common/severity'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; +import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter'; +import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; +import { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, ReplModel, ReplOutputElement, ReplVariableElement } from 'vs/workbench/contrib/debug/common/replModel'; +import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; +import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; import { MockDebugAdapter, MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; suite('Debug - REPL', () => { let model: DebugModel; let rawSession: MockRawSession; const configurationService = new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }); + let disposables: DisposableStore; + setup(() => { - model = createMockDebugModel(); + disposables = new DisposableStore(); + model = createMockDebugModel(disposables); rawSession = new MockRawSession(); }); + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + test('repl output', () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); const repl = new ReplModel(configurationService); repl.appendToRepl(session, { output: 'first line\n', sev: severity.Error }); repl.appendToRepl(session, { output: 'second line ', sev: severity.Error }); @@ -86,7 +97,7 @@ suite('Debug - REPL', () => { }); test('repl output count', () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); const repl = new ReplModel(configurationService); repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info }); repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info }); @@ -108,14 +119,14 @@ suite('Debug - REPL', () => { test('repl merging', () => { // 'mergeWithParent' should be ignored when there is no parent. - const parent = createTestSession(model, 'parent', { repl: 'mergeWithParent' }); - const child1 = createTestSession(model, 'child1', { parentSession: parent, repl: 'separate' }); - const child2 = createTestSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' }); - const grandChild = createTestSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' }); - const child3 = createTestSession(model, 'child3', { parentSession: parent }); + const parent = disposables.add(createTestSession(model, 'parent', { repl: 'mergeWithParent' })); + const child1 = disposables.add(createTestSession(model, 'child1', { parentSession: parent, repl: 'separate' })); + const child2 = disposables.add(createTestSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' })); + const grandChild = disposables.add(createTestSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' })); + const child3 = disposables.add(createTestSession(model, 'child3', { parentSession: parent })); let parentChanges = 0; - parent.onDidChangeReplElements(() => ++parentChanges); + disposables.add(parent.onDidChangeReplElements(() => ++parentChanges)); parent.appendToRepl({ output: '1\n', sev: severity.Info }); assert.strictEqual(parentChanges, 1); @@ -151,7 +162,7 @@ suite('Debug - REPL', () => { }); test('repl expressions', () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); assert.strictEqual(session.getReplElements().length, 0); model.addSession(session); @@ -173,11 +184,11 @@ suite('Debug - REPL', () => { }); test('repl ordering', async () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); model.addSession(session); const adapter = new MockDebugAdapter(); - const raw = new RawDebugSession(adapter, undefined!, '', '', undefined!, undefined!, undefined!, undefined!,); + const raw = disposables.add(new RawDebugSession(adapter, undefined!, '', '', undefined!, undefined!, undefined!, undefined!,)); session.initializeForTest(raw); await session.addReplExpression(undefined, 'before.1'); @@ -195,7 +206,7 @@ suite('Debug - REPL', () => { }); test('repl groups', async () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); const repl = new ReplModel(configurationService); repl.appendToRepl(session, { output: 'first global line', sev: severity.Info }); @@ -233,7 +244,7 @@ suite('Debug - REPL', () => { }); test('repl filter', async () => { - const session = createTestSession(model); + const session = disposables.add(createTestSession(model)); const repl = new ReplModel(configurationService); const replFilter = new ReplFilter(); diff --git a/src/vs/workbench/contrib/debug/test/browser/watch.test.ts b/src/vs/workbench/contrib/debug/test/browser/watch.test.ts index 5ae664ed05d..17fbff3be13 100644 --- a/src/vs/workbench/contrib/debug/test/browser/watch.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/watch.test.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Expression, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { DebugModel, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; // Expressions @@ -19,13 +21,20 @@ function assertWatchExpressions(watchExpressions: Expression[], expectedName: st } suite('Debug - Watch', () => { - let model: DebugModel; + let disposables: DisposableStore; setup(() => { - model = createMockDebugModel(); + disposables = new DisposableStore(); + model = createMockDebugModel(disposables); }); + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + test('watch expressions', () => { assert.strictEqual(model.getWatchExpressions().length, 0); model.addWatchExpression('console'); diff --git a/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts b/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts index f4e048c7b1e..2246b7fa248 100644 --- a/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/abstractDebugAdapter.test.ts @@ -5,9 +5,12 @@ import * as assert from 'assert'; import { timeout } from 'vs/base/common/async'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; suite('Debug - AbstractDebugAdapter', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + suite('event ordering', () => { let adapter: MockDebugAdapter; let output: string[]; diff --git a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index 779f301a4eb..a39f590b616 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -5,12 +5,17 @@ import * as assert from 'assert'; import { DeferredPromise } from 'vs/base/common/async'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { mockObject } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { MockDebugStorage } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('DebugModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + suite('FunctionBreakpoint', () => { test('Id is saved', () => { const fbp = new FunctionBreakpoint('function', true, 'hit condition', 'condition', 'log message'); @@ -42,7 +47,10 @@ suite('DebugModel', () => { }); fakeThread.getId.returns(1); - const model = new DebugModel(new MockDebugStorage(), { isDirty: (e: any) => false }, undefined!, new NullLogService()); + const disposable = new DisposableStore(); + const storage = disposable.add(new TestStorageService()); + const model = new DebugModel(disposable.add(new MockDebugStorage(storage)), { isDirty: (e: any) => false }, undefined!, new NullLogService()); + disposable.add(model); let top1Resolved = false; let whole1Resolved = false; @@ -70,6 +78,8 @@ suite('DebugModel', () => { await wholeStackDeferred.complete(); await result1.wholeCallStack; await result2.wholeCallStack; + + disposable.dispose(); }); }); }); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 5e8430947cf..30353a78a35 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -10,11 +10,11 @@ import { URI as uri } from 'vs/base/common/uri'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { NullLogService } from 'vs/platform/log/common/log'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugger, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEvaluate, IExceptionBreakpoint, IExceptionInfo, IExpression, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; +import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; -import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, Expression, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; @@ -666,34 +666,7 @@ export class MockDebugAdapter extends AbstractDebugAdapter { export class MockDebugStorage extends DebugStorage { - constructor() { - super(undefined as any, undefined as any, undefined as any, new NullLogService()); + constructor(storageService: IStorageService) { + super(storageService, undefined as any, undefined as any, new NullLogService()); } - - override loadBreakpoints(): Breakpoint[] { - return []; - } - - override loadFunctionBreakpoints(): FunctionBreakpoint[] { - return []; - } - - override loadExceptionBreakpoints(): ExceptionBreakpoint[] { - return []; - - } - - override loadDataBreakpoints(): DataBreakpoint[] { - return []; - - } - - override loadWatchExpressions(): Expression[] { - return []; - - } - - override storeWatchExpressions(_watchExpressions: (IExpression & IEvaluate)[]): void { } - - override storeBreakpoints(_debugModel: IDebugModel): void { } }